Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Execute Stored Procedures Directly #2154

Merged
merged 52 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from 51 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
740b25f
Initial implementation for exec cstmt directly
tkyc Jun 28, 2023
40095d5
Regression fix, continue to do extra metadata lookup call to fetch SP…
tkyc Jun 28, 2023
8b129c8
Fixes p1
tkyc Aug 28, 2023
d841377
Fixes p2
tkyc Aug 31, 2023
0aa1d33
Fixes p3
tkyc Aug 31, 2023
caafb05
Fixes p4
tkyc Sep 7, 2023
01491d5
Updated test to test erroring out on retrieving out param value
tkyc Sep 8, 2023
6593b34
Revert "Fixes p4"
tkyc Sep 17, 2023
d03b520
UDF fix
tkyc Sep 17, 2023
e34350d
Accounted for premature closing of statements
tkyc Oct 17, 2023
8f7d40a
Datetime out of range fix
tkyc Oct 17, 2023
1114154
Cstmts executed internally by the driver for XA transactions send out…
tkyc Oct 23, 2023
34f647b
Accounted for zero param sproc
tkyc Oct 31, 2023
dd390e2
Fixed setting/registering of out of order params
tkyc Oct 31, 2023
b99521c
Datetime fractional seconds correction
tkyc Nov 1, 2023
55a589c
DTC tests
tkyc Nov 2, 2023
fbe237b
Zero param sproc test; Error test for casting of sproc return value
tkyc Nov 2, 2023
9c68564
Test shuffled ordering of registering and setting cstmt params
tkyc Nov 2, 2023
4d3e2df
UDF test
tkyc Nov 2, 2023
e477535
Skip return values from RPC on statement close if there are any
tkyc Nov 2, 2023
b4e4030
Test mix of index a param names for cstmt
tkyc Nov 2, 2023
9646312
Removed static callRpcDirectly variable
tkyc Nov 2, 2023
f0d832c
Test cleanup
tkyc Nov 2, 2023
82c26c7
Fixed invalid param test
tkyc Nov 2, 2023
5b62c1f
Fixed test for JDK 8
tkyc Nov 2, 2023
12dd981
Formatting; Removed comma counting
tkyc Nov 6, 2023
2b35e53
Removed getter/setter enum
tkyc Nov 6, 2023
ce4972b
Removed comments regarding Yukon in IOBuffer
tkyc Nov 6, 2023
8b12fc1
Changed returnValueIsAccessed to isReturnValueAccessed
tkyc Nov 6, 2023
e2e0e3f
Fixed intellij wildcard imports
tkyc Nov 6, 2023
5630abd
Renamed DTC test class and test
tkyc Nov 6, 2023
70bc076
Revert "Removed getter/setter enum"
tkyc Nov 6, 2023
bf45de2
New connection string property to toggle sp_sproc_columns calls
tkyc Nov 6, 2023
6c86610
Deleted DTC test
tkyc Nov 6, 2023
f616f72
Removed non-driver error from TestResource
tkyc Nov 6, 2023
c87fc06
Fixed switch case for date/time types in dtv class
tkyc Nov 6, 2023
0ea9e60
SQLServerConnTest and RequestBoundaryMethodTest update
tkyc Nov 7, 2023
dfceeff
Removed DTC test group
tkyc Nov 7, 2023
e3b2aa8
Fixed docs/comments for new CS prop
tkyc Nov 7, 2023
245d173
Fixed setting/registering of when using only named parameters
tkyc Nov 7, 2023
9c09781
RPC call check considers whether the call is an internal encryption q…
tkyc Nov 8, 2023
4099b84
Switched from useFastCallableStatements to useFlexibleCallableStatements
tkyc Nov 8, 2023
80a35d2
Fixed typo in CS prop setter/getter
tkyc Nov 8, 2023
f8b0dc0
Include enclave package in RPC call
tkyc Nov 8, 2023
1e6ee48
Code Review Changes; Fixed threading issue
tkyc Nov 9, 2023
eb69fe1
Merge branch 'main' into exec-cstmt-directly
tkyc Nov 15, 2023
d41e1f8
Merge conflict fix p2.
tkyc Nov 15, 2023
ef0869e
Update src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSourc…
tkyc Nov 15, 2023
224d03a
Update src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableSt…
tkyc Nov 15, 2023
3af17db
Update src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.…
tkyc Nov 15, 2023
e771daf
Update src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedSt…
tkyc Nov 15, 2023
2e03f12
PR review; initial named parameter and index parameter restrictions
tkyc Nov 16, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
xAzureSQLDW - - - - For tests not compatible with Azure Data Warehouse -
xAzureSQLMI - - - - For tests not compatible with Azure SQL Managed Instance
NTLM - - - - - - - For tests using NTLM Authentication mode (excluded by default)
kerberos - - - - - For tests using Kerberos authentication (excluded by default)
kerberos - - - - - For tests using Kerberos authentication (excluded by default)
reqExternalSetup - For tests requiring external setup (excluded by default)
clientCertAuth - - For tests requiring client certificate authentication
setup (excluded by default) - - - - - - - - - - - - - - - - - - - - - - -
Expand Down
50 changes: 37 additions & 13 deletions src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,7 @@ static final String getEncryptionLevel(int level) {
final static int COLINFO_STATUS_DIFFERENT_NAME = 0x20;

final static int MAX_FRACTIONAL_SECONDS_SCALE = 7;
final static int DEFAULT_FRACTIONAL_SECONDS_SCALE = 3;

final static Timestamp MAX_TIMESTAMP = Timestamp.valueOf("2079-06-06 23:59:59");
final static Timestamp MIN_TIMESTAMP = Timestamp.valueOf("1900-01-01 00:00:00");
Expand Down Expand Up @@ -4786,7 +4787,7 @@ void writeVMaxHeader(long headerLength, boolean isNull, SQLCollation collation)
* Utility for internal writeRPCString calls
*/
void writeRPCStringUnicode(String sValue) throws SQLServerException {
writeRPCStringUnicode(null, sValue, false, null);
writeRPCStringUnicode(null, sValue, false, null, false);
}

/**
Expand All @@ -4801,8 +4802,8 @@ void writeRPCStringUnicode(String sValue) throws SQLServerException {
* @param collation
* the collation of the data value
*/
void writeRPCStringUnicode(String sName, String sValue, boolean bOut,
SQLCollation collation) throws SQLServerException {
void writeRPCStringUnicode(String sName, String sValue, boolean bOut, SQLCollation collation,
boolean isNonPLP) throws SQLServerException {
boolean bValueNull = (sValue == null);
int nValueLen = bValueNull ? 0 : (2 * sValue.length());
// Textual RPC requires a collation. If none is provided, as is the case when
Expand All @@ -4814,10 +4815,9 @@ void writeRPCStringUnicode(String sName, String sValue, boolean bOut,
* Use PLP encoding if either OUT params were specified or if the user query exceeds
* DataTypes.SHORT_VARTYPE_MAX_BYTES
*/
if (nValueLen > DataTypes.SHORT_VARTYPE_MAX_BYTES || bOut) {
if ((nValueLen > DataTypes.SHORT_VARTYPE_MAX_BYTES || bOut) && !isNonPLP) {
writeRPCNameValType(sName, bOut, TDSType.NVARCHAR);

// Handle Yukon v*max type header here.
writeVMaxHeader(nValueLen, // Length
bValueNull, // Is null?
collation);
Expand Down Expand Up @@ -5416,7 +5416,6 @@ void writeTVPColumnMetaData(TVP value) throws SQLServerException {
// Use PLP encoding on Yukon and later with long values
if (!isShortValue) // PLP
{
// Handle Yukon v*max type header here.
writeShort((short) 0xFFFF);
con.getDatabaseCollation().writeCollation(this);
} else // non PLP
Expand All @@ -5434,7 +5433,6 @@ void writeTVPColumnMetaData(TVP value) throws SQLServerException {
isShortValue = pair.getValue().precision <= DataTypes.SHORT_VARTYPE_MAX_BYTES;
// Use PLP encoding on Yukon and later with long values
if (!isShortValue) // PLP
// Handle Yukon v*max type header here.
writeShort((short) 0xFFFF);
else // non PLP
writeShort((short) DataTypes.SHORT_VARTYPE_MAX_BYTES);
Expand Down Expand Up @@ -5569,8 +5567,8 @@ void writeCryptoMetaData() throws SQLServerException {
writeByte(cryptoMeta.normalizationRuleVersion);
}

void writeRPCByteArray(String sName, byte[] bValue, boolean bOut, JDBCType jdbcType,
SQLCollation collation) throws SQLServerException {
void writeRPCByteArray(String sName, byte[] bValue, boolean bOut, JDBCType jdbcType, SQLCollation collation,
boolean isNonPLP) throws SQLServerException {
boolean bValueNull = (bValue == null);
int nValueLen = bValueNull ? 0 : bValue.length;
boolean isShortValue = (nValueLen <= DataTypes.SHORT_VARTYPE_MAX_BYTES);
Expand Down Expand Up @@ -5616,8 +5614,7 @@ void writeRPCByteArray(String sName, byte[] bValue, boolean bOut, JDBCType jdbcT

writeRPCNameValType(sName, bOut, tdsType);

if (usePLP) {
// Handle Yukon v*max type header here.
if (usePLP && !isNonPLP) {
writeVMaxHeader(nValueLen, bValueNull, collation);
tkyc marked this conversation as resolved.
Show resolved Hide resolved

// Send the data.
Expand Down Expand Up @@ -6398,7 +6395,6 @@ void writeRPCInputStream(String sName, InputStream stream, long streamLength, bo

writeRPCNameValType(sName, bOut, jdbcType.isTextual() ? TDSType.BIGVARCHAR : TDSType.BIGVARBINARY);

// Handle Yukon v*max type header here.
writeVMaxHeader(streamLength, false, jdbcType.isTextual() ? collation : null);
}

Expand Down Expand Up @@ -6538,7 +6534,6 @@ void writeRPCReaderUnicode(String sName, Reader re, long reLength, boolean bOut,

writeRPCNameValType(sName, bOut, TDSType.NVARCHAR);

// Handle Yukon v*max type header here.
writeVMaxHeader(
(DataTypes.UNKNOWN_STREAM_LENGTH == reLength) ? DataTypes.UNKNOWN_STREAM_LENGTH : 2 * reLength, // Length
// (in
Expand Down Expand Up @@ -6993,6 +6988,35 @@ final short peekStatusFlag() {
return 0;
}

final int peekReturnValueStatus() throws SQLServerException {
// Ensure that we have a packet to read from.
if (!ensurePayload()) {
throwInvalidTDS();
}

// In order to parse the 'status' value, we need to skip over the following properties in the TDS packet
// payload: TDS token type (1 byte value), ordinal/length (2 byte value), parameter name length value (1 byte value) and
// the number of bytes that make the parameter name (need to be calculated).
//
// 'offset' starts at 4 because tdsTokenType + ordinal/length + parameter name length value is 4 bytes. So, we
// skip 4 bytes immediateley.
int offset = 4;
int paramNameLength = currentPacket.payload[payloadOffset + 3];

// Check if parameter name is set. If it's set, it should be > 0. In which case, we add the
// additional bytes to skip.
if (paramNameLength > 0) {
// Each character in unicode is 2 bytes
offset += 2 * paramNameLength;
}

if (payloadOffset + offset <= currentPacket.payloadLength) {
return currentPacket.payload[payloadOffset + offset] & 0xFF;
}

return -1;
}

final int readUnsignedByte() throws SQLServerException {
// Ensure that we have a packet to read from.
if (!ensurePayload())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,25 @@ public interface ISQLServerDataSource extends javax.sql.CommonDataSource {
*/
boolean getUseDefaultGSSCredential();

/**
* Sets whether or not sp_sproc_columns will be used for parameter name lookup.
*
* @param useFlexibleCallableStatements
* When set to false, sp_sproc_columns is not used for parameter name lookup
* in callable statements. This eliminates a round trip to the server but imposes limitations
* on how parameters are set. When set to false, applications must either reference
* parameters by name or by index, not both. Parameters must also be set in the same
* order as the stored procedure definition.
*/
void setUseFlexibleCallableStatements(boolean useFlexibleCallableStatements);

/**
* Returns whether or not sp_sproc_columns is being used for parameter name lookup.
*
* @return useFlexibleCallableStatements
*/
boolean getUseFlexibleCallableStatements();

/**
* Sets the GSSCredential.
*
Expand Down
75 changes: 69 additions & 6 deletions src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ final class Parameter {
// For unencrypted parameters cryptometa will be null. For encrypted parameters it will hold encryption metadata.
CryptoMetadata cryptoMeta = null;

boolean isNonPLP = false;

TypeInfo getTypeInfo() {
return typeInfo;
}
Expand All @@ -48,6 +50,7 @@ final CryptoMetadata getCryptoMetadata() {
private boolean shouldHonorAEForParameter = false;
private boolean userProvidesPrecision = false;
private boolean userProvidesScale = false;
private boolean isReturnValue = false;

// The parameter type definition
private String typeDefinition = null;
Expand All @@ -70,6 +73,60 @@ boolean isOutput() {
return null != registeredOutDTV;
}

/**
* Returns true/false if the parameter is of return type
*
* @return isReturnValue
*/
boolean isReturnValue() {
return isReturnValue;
}

/**
* Sets the parameter to be of return type
*
* @param isReturnValue
*/
void setReturnValue(boolean isReturnValue) {
this.isReturnValue = isReturnValue;
}

/**
* Sets the name of the parameter
*
* @param name
*/
void setName(String name) {
this.name = name;
}

/**
* Retrieve the name of the parameter
*
* @return
*/
String getName() {
return this.name;
}

/**
* Returns the `registeredOutDTV` instance of the parameter
*
* @return registeredOutDTV
*/
DTV getRegisteredOutDTV() {
return this.registeredOutDTV;
}

/**
* Returns the `inputDTV` instance of the parameter
*
* @return inputDTV
*/
DTV getInputDTV() {
return this.inputDTV;
}

// Since a parameter can have only one type definition for both sending its value to the server (IN)
// and getting its value from the server (OUT), we use the JDBC type of the IN parameter value if there
// is one; otherwise we use the registered OUT param JDBC type.
Expand Down Expand Up @@ -246,7 +303,7 @@ void setFromReturnStatus(int returnStatus, SQLServerConnection con) throws SQLSe
if (null == getterDTV)
getterDTV = new DTV();

getterDTV.setValue(null, JDBCType.INTEGER, returnStatus, JavaType.INTEGER, null, null, null, con,
getterDTV.setValue(null, this.getJdbcType(), returnStatus, JavaType.INTEGER, null, null, null, con,
getForceEncryption());
}

Expand Down Expand Up @@ -387,10 +444,14 @@ boolean isValueGotten() {

Object getValue(JDBCType jdbcType, InputStreamGetterArgs getterArgs, Calendar cal, TDSReader tdsReader,
SQLServerStatement statement) throws SQLServerException {
if (null == getterDTV)
if (null == getterDTV) {
getterDTV = new DTV();
}

if (null != tdsReader) {
deriveTypeInfo(tdsReader);
}

deriveTypeInfo(tdsReader);
// If the parameter is not encrypted or column encryption is turned off (either at connection or
// statement level), cryptoMeta would be null.
return getterDTV.getValue(jdbcType, outScale, getterArgs, cal, typeInfo, cryptoMeta, tdsReader, statement);
Expand Down Expand Up @@ -1206,15 +1267,17 @@ String getTypeDefinition(SQLServerConnection con, TDSReader tdsReader) throws SQ
return typeDefinition;
}

void sendByRPC(TDSWriter tdsWriter, SQLServerStatement statement) throws SQLServerException {
void sendByRPC(TDSWriter tdsWriter, boolean callRPCDirectly,
SQLServerStatement statement) throws SQLServerException {
assert null != inputDTV : "Parameter was neither set nor registered";
SQLServerConnection conn = statement.connection;

try {
inputDTV.isNonPLP = isNonPLP;
inputDTV.sendCryptoMetaData(this.cryptoMeta, tdsWriter);
inputDTV.setJdbcTypeSetByUser(getJdbcTypeSetByUser(), getValueLength());
inputDTV.sendByRPC(name, null, conn.getDatabaseCollation(), valueLength, isOutput() ? outScale : scale,
isOutput(), tdsWriter, statement);
inputDTV.sendByRPC(callRPCDirectly ? name : null, null, conn.getDatabaseCollation(), valueLength,
isOutput() ? outScale : scale, isOutput(), tdsWriter, statement);
} finally {
// reset the cryptoMeta in IOBuffer
inputDTV.sendCryptoMetaData(null, tdsWriter);
Expand Down
Loading