Skip to content

Commit 11680a6

Browse files
tkycDavid-Engel
andauthored
Execute Stored Procedures Directly (#2154)
* Initial implementation for exec cstmt directly * Regression fix, continue to do extra metadata lookup call to fetch SP param name info * Fixes p1 * Fixes p2 * Fixes p3 * Fixes p4 * Updated test to test erroring out on retrieving out param value * Revert "Fixes p4" This reverts commit b19dfb0. * UDF fix * Accounted for premature closing of statements * Datetime out of range fix * Cstmts executed internally by the driver for XA transactions send out params as non-PLP over the wire for nvarchar and binary types * Accounted for zero param sproc * Fixed setting/registering of out of order params * Datetime fractional seconds correction * DTC tests * Zero param sproc test; Error test for casting of sproc return value * Test shuffled ordering of registering and setting cstmt params * UDF test * Skip return values from RPC on statement close if there are any * Test mix of index a param names for cstmt * Removed static callRpcDirectly variable * Test cleanup * Fixed invalid param test * Fixed test for JDK 8 * Formatting; Removed comma counting * Removed getter/setter enum * Removed comments regarding Yukon in IOBuffer * Changed returnValueIsAccessed to isReturnValueAccessed * Fixed intellij wildcard imports * Renamed DTC test class and test * Revert "Removed getter/setter enum" This reverts commit 2b35e53. * New connection string property to toggle sp_sproc_columns calls * Deleted DTC test * Removed non-driver error from TestResource * Fixed switch case for date/time types in dtv class * SQLServerConnTest and RequestBoundaryMethodTest update * Removed DTC test group * Fixed docs/comments for new CS prop * Fixed setting/registering of when using only named parameters * RPC call check considers whether the call is an internal encryption query call * Switched from useFastCallableStatements to useFlexibleCallableStatements * Fixed typo in CS prop setter/getter * Include enclave package in RPC call * Code Review Changes; Fixed threading issue * Merge conflict fix p2. * Update src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java Co-authored-by: David Engel <[email protected]> * Update src/main/java/com/microsoft/sqlserver/jdbc/SQLServerCallableStatement.java Co-authored-by: David Engel <[email protected]> * Update src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java Co-authored-by: David Engel <[email protected]> * Update src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java Co-authored-by: David Engel <[email protected]> * PR review; initial named parameter and index parameter restrictions --------- Co-authored-by: David Engel <[email protected]>
1 parent 1fed717 commit 11680a6

21 files changed

+1739
-398
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
xAzureSQLDW - - - - For tests not compatible with Azure Data Warehouse -
4444
xAzureSQLMI - - - - For tests not compatible with Azure SQL Managed Instance
4545
NTLM - - - - - - - For tests using NTLM Authentication mode (excluded by default)
46-
kerberos - - - - - For tests using Kerberos authentication (excluded by default)
46+
kerberos - - - - - For tests using Kerberos authentication (excluded by default)
4747
reqExternalSetup - For tests requiring external setup (excluded by default)
4848
clientCertAuth - - For tests requiring client certificate authentication
4949
setup (excluded by default) - - - - - - - - - - - - - - - - - - - - - - -

src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,7 @@ static final String getEncryptionLevel(int level) {
466466
final static int COLINFO_STATUS_DIFFERENT_NAME = 0x20;
467467

468468
final static int MAX_FRACTIONAL_SECONDS_SCALE = 7;
469+
final static int DEFAULT_FRACTIONAL_SECONDS_SCALE = 3;
469470

470471
final static Timestamp MAX_TIMESTAMP = Timestamp.valueOf("2079-06-06 23:59:59");
471472
final static Timestamp MIN_TIMESTAMP = Timestamp.valueOf("1900-01-01 00:00:00");
@@ -4788,7 +4789,7 @@ void writeVMaxHeader(long headerLength, boolean isNull, SQLCollation collation)
47884789
* Utility for internal writeRPCString calls
47894790
*/
47904791
void writeRPCStringUnicode(String sValue) throws SQLServerException {
4791-
writeRPCStringUnicode(null, sValue, false, null);
4792+
writeRPCStringUnicode(null, sValue, false, null, false);
47924793
}
47934794

47944795
/**
@@ -4803,8 +4804,8 @@ void writeRPCStringUnicode(String sValue) throws SQLServerException {
48034804
* @param collation
48044805
* the collation of the data value
48054806
*/
4806-
void writeRPCStringUnicode(String sName, String sValue, boolean bOut,
4807-
SQLCollation collation) throws SQLServerException {
4807+
void writeRPCStringUnicode(String sName, String sValue, boolean bOut, SQLCollation collation,
4808+
boolean isNonPLP) throws SQLServerException {
48084809
boolean bValueNull = (sValue == null);
48094810
int nValueLen = bValueNull ? 0 : (2 * sValue.length());
48104811
// Textual RPC requires a collation. If none is provided, as is the case when
@@ -4816,10 +4817,9 @@ void writeRPCStringUnicode(String sName, String sValue, boolean bOut,
48164817
* Use PLP encoding if either OUT params were specified or if the user query exceeds
48174818
* DataTypes.SHORT_VARTYPE_MAX_BYTES
48184819
*/
4819-
if (nValueLen > DataTypes.SHORT_VARTYPE_MAX_BYTES || bOut) {
4820+
if ((nValueLen > DataTypes.SHORT_VARTYPE_MAX_BYTES || bOut) && !isNonPLP) {
48204821
writeRPCNameValType(sName, bOut, TDSType.NVARCHAR);
48214822

4822-
// Handle Yukon v*max type header here.
48234823
writeVMaxHeader(nValueLen, // Length
48244824
bValueNull, // Is null?
48254825
collation);
@@ -5418,7 +5418,6 @@ void writeTVPColumnMetaData(TVP value) throws SQLServerException {
54185418
// Use PLP encoding on Yukon and later with long values
54195419
if (!isShortValue) // PLP
54205420
{
5421-
// Handle Yukon v*max type header here.
54225421
writeShort((short) 0xFFFF);
54235422
con.getDatabaseCollation().writeCollation(this);
54245423
} else // non PLP
@@ -5436,7 +5435,6 @@ void writeTVPColumnMetaData(TVP value) throws SQLServerException {
54365435
isShortValue = pair.getValue().precision <= DataTypes.SHORT_VARTYPE_MAX_BYTES;
54375436
// Use PLP encoding on Yukon and later with long values
54385437
if (!isShortValue) // PLP
5439-
// Handle Yukon v*max type header here.
54405438
writeShort((short) 0xFFFF);
54415439
else // non PLP
54425440
writeShort((short) DataTypes.SHORT_VARTYPE_MAX_BYTES);
@@ -5571,8 +5569,8 @@ void writeCryptoMetaData() throws SQLServerException {
55715569
writeByte(cryptoMeta.normalizationRuleVersion);
55725570
}
55735571

5574-
void writeRPCByteArray(String sName, byte[] bValue, boolean bOut, JDBCType jdbcType,
5575-
SQLCollation collation) throws SQLServerException {
5572+
void writeRPCByteArray(String sName, byte[] bValue, boolean bOut, JDBCType jdbcType, SQLCollation collation,
5573+
boolean isNonPLP) throws SQLServerException {
55765574
boolean bValueNull = (bValue == null);
55775575
int nValueLen = bValueNull ? 0 : bValue.length;
55785576
boolean isShortValue = (nValueLen <= DataTypes.SHORT_VARTYPE_MAX_BYTES);
@@ -5618,8 +5616,7 @@ void writeRPCByteArray(String sName, byte[] bValue, boolean bOut, JDBCType jdbcT
56185616

56195617
writeRPCNameValType(sName, bOut, tdsType);
56205618

5621-
if (usePLP) {
5622-
// Handle Yukon v*max type header here.
5619+
if (usePLP && !isNonPLP) {
56235620
writeVMaxHeader(nValueLen, bValueNull, collation);
56245621

56255622
// Send the data.
@@ -6400,7 +6397,6 @@ void writeRPCInputStream(String sName, InputStream stream, long streamLength, bo
64006397

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

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

@@ -6540,7 +6536,6 @@ void writeRPCReaderUnicode(String sName, Reader re, long reLength, boolean bOut,
65406536

65416537
writeRPCNameValType(sName, bOut, TDSType.NVARCHAR);
65426538

6543-
// Handle Yukon v*max type header here.
65446539
writeVMaxHeader(
65456540
(DataTypes.UNKNOWN_STREAM_LENGTH == reLength) ? DataTypes.UNKNOWN_STREAM_LENGTH : 2 * reLength, // Length
65466541
// (in
@@ -6995,6 +6990,35 @@ final short peekStatusFlag() {
69956990
return 0;
69966991
}
69976992

6993+
final int peekReturnValueStatus() throws SQLServerException {
6994+
// Ensure that we have a packet to read from.
6995+
if (!ensurePayload()) {
6996+
throwInvalidTDS();
6997+
}
6998+
6999+
// In order to parse the 'status' value, we need to skip over the following properties in the TDS packet
7000+
// payload: TDS token type (1 byte value), ordinal/length (2 byte value), parameter name length value (1 byte value) and
7001+
// the number of bytes that make the parameter name (need to be calculated).
7002+
//
7003+
// 'offset' starts at 4 because tdsTokenType + ordinal/length + parameter name length value is 4 bytes. So, we
7004+
// skip 4 bytes immediateley.
7005+
int offset = 4;
7006+
int paramNameLength = currentPacket.payload[payloadOffset + 3];
7007+
7008+
// Check if parameter name is set. If it's set, it should be > 0. In which case, we add the
7009+
// additional bytes to skip.
7010+
if (paramNameLength > 0) {
7011+
// Each character in unicode is 2 bytes
7012+
offset += 2 * paramNameLength;
7013+
}
7014+
7015+
if (payloadOffset + offset <= currentPacket.payloadLength) {
7016+
return currentPacket.payload[payloadOffset + offset] & 0xFF;
7017+
}
7018+
7019+
return -1;
7020+
}
7021+
69987022
final int readUnsignedByte() throws SQLServerException {
69997023
// Ensure that we have a packet to read from.
70007024
if (!ensurePayload())

src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -496,5 +496,5 @@ CallableStatement prepareCall(String sql, int nType, int nConcur, int nHold,
496496
* @param calcBigDecimalScale
497497
* A boolean that indicates if the driver should calculate scale from inputted big decimal values.
498498
*/
499-
void setCalcBigDecimalScale(boolean computeBigDecimal);
499+
void setCalcBigDecimalScale(boolean calcBigDecimalScale);
500500
}

src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,25 @@ public interface ISQLServerDataSource extends javax.sql.CommonDataSource {
609609
*/
610610
boolean getUseDefaultGSSCredential();
611611

612+
/**
613+
* Sets whether or not sp_sproc_columns will be used for parameter name lookup.
614+
*
615+
* @param useFlexibleCallableStatements
616+
* When set to false, sp_sproc_columns is not used for parameter name lookup
617+
* in callable statements. This eliminates a round trip to the server but imposes limitations
618+
* on how parameters are set. When set to false, applications must either reference
619+
* parameters by name or by index, not both. Parameters must also be set in the same
620+
* order as the stored procedure definition.
621+
*/
622+
void setUseFlexibleCallableStatements(boolean useFlexibleCallableStatements);
623+
624+
/**
625+
* Returns whether or not sp_sproc_columns is being used for parameter name lookup.
626+
*
627+
* @return useFlexibleCallableStatements
628+
*/
629+
boolean getUseFlexibleCallableStatements();
630+
612631
/**
613632
* Sets the GSSCredential.
614633
*

src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ final class Parameter {
3737
// For unencrypted parameters cryptometa will be null. For encrypted parameters it will hold encryption metadata.
3838
CryptoMetadata cryptoMeta = null;
3939

40+
boolean isNonPLP = false;
41+
4042
TypeInfo getTypeInfo() {
4143
return typeInfo;
4244
}
@@ -48,6 +50,7 @@ final CryptoMetadata getCryptoMetadata() {
4850
private boolean shouldHonorAEForParameter = false;
4951
private boolean userProvidesPrecision = false;
5052
private boolean userProvidesScale = false;
53+
private boolean isReturnValue = false;
5154

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

76+
/**
77+
* Returns true/false if the parameter is of return type
78+
*
79+
* @return isReturnValue
80+
*/
81+
boolean isReturnValue() {
82+
return isReturnValue;
83+
}
84+
85+
/**
86+
* Sets the parameter to be of return type
87+
*
88+
* @param isReturnValue
89+
*/
90+
void setReturnValue(boolean isReturnValue) {
91+
this.isReturnValue = isReturnValue;
92+
}
93+
94+
/**
95+
* Sets the name of the parameter
96+
*
97+
* @param name
98+
*/
99+
void setName(String name) {
100+
this.name = name;
101+
}
102+
103+
/**
104+
* Retrieve the name of the parameter
105+
*
106+
* @return
107+
*/
108+
String getName() {
109+
return this.name;
110+
}
111+
112+
/**
113+
* Returns the `registeredOutDTV` instance of the parameter
114+
*
115+
* @return registeredOutDTV
116+
*/
117+
DTV getRegisteredOutDTV() {
118+
return this.registeredOutDTV;
119+
}
120+
121+
/**
122+
* Returns the `inputDTV` instance of the parameter
123+
*
124+
* @return inputDTV
125+
*/
126+
DTV getInputDTV() {
127+
return this.inputDTV;
128+
}
129+
73130
// Since a parameter can have only one type definition for both sending its value to the server (IN)
74131
// and getting its value from the server (OUT), we use the JDBC type of the IN parameter value if there
75132
// is one; otherwise we use the registered OUT param JDBC type.
@@ -246,7 +303,7 @@ void setFromReturnStatus(int returnStatus, SQLServerConnection con) throws SQLSe
246303
if (null == getterDTV)
247304
getterDTV = new DTV();
248305

249-
getterDTV.setValue(null, JDBCType.INTEGER, returnStatus, JavaType.INTEGER, null, null, null, con,
306+
getterDTV.setValue(null, this.getJdbcType(), returnStatus, JavaType.INTEGER, null, null, null, con,
250307
getForceEncryption());
251308
}
252309

@@ -387,10 +444,14 @@ boolean isValueGotten() {
387444

388445
Object getValue(JDBCType jdbcType, InputStreamGetterArgs getterArgs, Calendar cal, TDSReader tdsReader,
389446
SQLServerStatement statement) throws SQLServerException {
390-
if (null == getterDTV)
447+
if (null == getterDTV) {
391448
getterDTV = new DTV();
449+
}
450+
451+
if (null != tdsReader) {
452+
deriveTypeInfo(tdsReader);
453+
}
392454

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

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

12131275
try {
1276+
inputDTV.isNonPLP = isNonPLP;
12141277
inputDTV.sendCryptoMetaData(this.cryptoMeta, tdsWriter);
12151278
inputDTV.setJdbcTypeSetByUser(getJdbcTypeSetByUser(), getValueLength());
1216-
inputDTV.sendByRPC(name, null, conn.getDatabaseCollation(), valueLength, isOutput() ? outScale : scale,
1217-
isOutput(), tdsWriter, statement);
1279+
inputDTV.sendByRPC(callRPCDirectly ? name : null, null, conn.getDatabaseCollation(), valueLength,
1280+
isOutput() ? outScale : scale, isOutput(), tdsWriter, statement);
12181281
} finally {
12191282
// reset the cryptoMeta in IOBuffer
12201283
inputDTV.sendCryptoMetaData(null, tdsWriter);

0 commit comments

Comments
 (0)