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

Changed CallableStatement to allow you to not provide a named paramet… #2616

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 13 additions & 0 deletions src/main/java/com/microsoft/sqlserver/jdbc/Parameter.java
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ void setOutScale(int outScale) {
private String name;
private String schemaName;


// The parameter name from sp_sproc_columns. Used for building the CALL string with @<inOutParameterName>x=@...
private String inOutParameterName;

/*
* The different DTVs representing the parameter's value: getterDTV - The OUT value, if set, of the parameter after
* execution. This is the value retrieved by CallableStatement getter methods. registeredOutDTV - The "IN" value
Expand Down Expand Up @@ -201,6 +205,7 @@ final Parameter cloneForBatch() {
clonedParam.valueLength = valueLength;
clonedParam.userProvidesPrecision = userProvidesPrecision;
clonedParam.userProvidesScale = userProvidesScale;
clonedParam.inOutParameterName = inOutParameterName;
return clonedParam;
}

Expand Down Expand Up @@ -1271,4 +1276,12 @@ boolean getForceEncryption() {
void setForceEncryption(boolean forceEncryption) {
this.forceEncryption = forceEncryption;
}

public String getInOutParameterName() {
return inOutParameterName == null ? "" : inOutParameterName;
}

public void setInOutParameterName(String inOutParameterName) {
this.inOutParameterName = inOutParameterName;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.io.Reader;
import java.math.BigDecimal;
import java.net.URL;
import java.sql.BatchUpdateException;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Date;
Expand All @@ -19,6 +20,7 @@
import java.sql.ResultSet;
import java.sql.RowId;
import java.sql.SQLException;
import java.sql.SQLTimeoutException;
import java.sql.SQLType;
import java.sql.SQLXML;
import java.sql.Time;
Expand Down Expand Up @@ -73,11 +75,29 @@ public class SQLServerCallableStatement extends SQLServerPreparedStatement imple
/** Currently active Stream Note only one stream can be active at a time */
private transient Closeable activeStream;

/** map */
private Map<String, Integer> map = new ConcurrentHashMap<>();
/**
* Does the number of parameters in CALL(?,?,?) match the number of parameters
* in the proc (from exec sp_sproc_columns ). If true we expect the proc to
* provide defaults for the unspecified parameters and that all parameters must
* be named not ordinal
*/
private boolean parameterCountMissmatch = false;

/**
* When paramCountMissmatch is true, this generates the parameters index into inOutParams
**/
private AtomicInteger registeredNamedParameterIndex = new AtomicInteger(0);

/** atomic integer */
AtomicInteger ai = new AtomicInteger(0);
/**
* True if any parameter has been set by name
*/
private boolean hasNamedParameters;

/**
* revised indexes mapping column name to index into inOutParam[] to allow for
* default values
*/
private Map<String, Integer> calculatedParameterIndex;

/**
* Create a new callable statement.
Expand Down Expand Up @@ -1357,23 +1377,72 @@ private int findColumn(String columnName) throws SQLServerException {
insensitiveParameterNames.put(p, columnIndex++);
}
}
calculatedParameterIndex = new ConcurrentHashMap<String, Integer>();
// ParameterNames will always contain @RETURN_VALUE, but inOutParam only will if
// ?=CALL, so add 1 to
// if no return requested to balance the parameter counts
parameterCountMissmatch = parameterNames.size() != inOutParam.length + (bReturnValueSyntax ? 0 : 1);
hasNamedParameters = true;

if (parameterCountMissmatch)
validateParameters();

} catch (SQLException e) {
SQLServerException.makeFromDriverError(connection, this, e.toString(), null, false);
}

}

// If the server didn't return anything (eg. the param names for the sp_sproc_columns), user might not
// have required permissions to view all the parameterNames. And, there's also the case depending on the permissions,
// @RETURN_VALUE may or may not be present. So, the parameterNames list might have an additional +1 parameter.
if (null != parameterNames && parameterNames.size() <= 1) {
return map.computeIfAbsent(columnName, ifAbsent -> ai.incrementAndGet());
}

// handle `@name` as well as `name`, since `@name` is what's returned
// by DatabaseMetaData#getProcedureColumns
String columnNameWithSign = columnName.startsWith("@") ? columnName : "@" + columnName;

int index = 0;
// If proc has the same number of parameters as the CALL(?,?,?) declared, and the server successfully returned
// a list of the procs parameters then fix the column names to the order they are returned from sp_sproc_columns
if (!parameterCountMissmatch && null != parameterNames && parameterNames.size() > 1) {
Integer matchPos = checkParameterNameExists(columnName, columnNameWithSign);

// @RETURN_VALUE is always in the list.
// If the user uses return value ?=call(@p1) syntax then @p1 is index 2 otherwise its index 1.
index = matchPos + iReturnValueOffset;
calculatedParameterIndex.put(columnNameWithSign, index);

} else {
// The proc defines more parameters than the CALL(?,?,?) provided or the server didn't return
// the param names from the sp_sproc_columns as user might not have required permissions
// to view all the parameterNames. And, there's also the case depending on the permissions,
// @RETURN_VALUE may or may not be present. So, the parameterNames list might
// have an additional +1 parameter.

// Return_value will always be index1
if (bReturnValueSyntax && "@RETURN_VALUE".equals(columnNameWithSign)) {
index = 1;
} else {
Integer storedIndex = calculatedParameterIndex.get(columnNameWithSign);
if (storedIndex != null) {
return storedIndex;
}
if (null == parameterNames || parameterNames.size() > 1) {
checkParameterNameExists(columnName, columnNameWithSign);
}
index = calculatedParameterIndex.computeIfAbsent(columnNameWithSign,
idx -> registeredNamedParameterIndex.incrementAndGet() + iReturnValueOffset);

inOutParam[index - 1].setInOutParameterName(columnNameWithSign);
}
}
return index;
}

/**
* Check the provided parameter name against the names returned from
* sp_sproc_columns
*
* @param columnName
* @param columnNameWithSign
* @return
* @throws SQLServerException
*/
private Integer checkParameterNameExists(String columnName, String columnNameWithSign) throws SQLServerException {
// In order to be as accurate as possible when locating parameter name
// indexes, as well as be deterministic when running on various client
// locales, we search for parameter names using the following scheme:
Expand All @@ -1390,13 +1459,7 @@ private int findColumn(String columnName) throws SQLServerException {
Object[] msgArgs = {columnName, procedureName};
SQLServerException.makeFromDriverError(connection, this, form.format(msgArgs), SQLSTATE_07009, false);
}

// @RETURN_VALUE is always in the list. If the user uses return value ?=call(@p1) syntax then
// @p1 is index 2 otherwise its index 1.
if (bReturnValueSyntax) // 3.2717
return matchPos + 1;
else
return matchPos;
return matchPos;
}

@Override
Expand Down Expand Up @@ -2444,4 +2507,59 @@ public void registerOutParameter(String parameterName, SQLType sqlType) throws S
registerOutParameter(parameterName, sqlType.getVendorTypeNumber());
loggerExternal.exiting(getClassNameLogging(), "registerOutParameter");
}

@Override
public ResultSet executeQuery() throws SQLServerException, SQLTimeoutException {
validateParameters();
return super.executeQuery();
}

@Override
public int executeUpdate() throws SQLServerException, SQLTimeoutException {
validateParameters();
return super.executeUpdate();
}

@Override
public long executeLargeUpdate() throws SQLServerException, SQLTimeoutException {
validateParameters();
return super.executeLargeUpdate();
}

@Override
public boolean execute() throws SQLServerException, SQLTimeoutException {
validateParameters();
return super.execute();
}

@Override
public int[] executeBatch() throws SQLServerException, BatchUpdateException, SQLTimeoutException {
validateParameters();
return super.executeBatch();
}

@Override
public long[] executeLargeBatch() throws SQLServerException, BatchUpdateException, SQLTimeoutException {
validateParameters();
return super.executeLargeBatch();
}

private void validateParameters() throws SQLServerException {
if (parameterCountMissmatch) {
boolean hasIndexedParameters = false;
// Check for un-named parameters excluding return value if expected
for (int x = (bReturnValueSyntax ? 1 : 0); x < inOutParam.length; x++) {
Parameter p = inOutParam[x];
if ("".equals(p.getInOutParameterName()) && p.getJdbcType() != JDBCType.UNKNOWN) {
hasIndexedParameters = true;
break;
}
}
if (hasNamedParameters && hasIndexedParameters) {
throw new SQLServerException(SQLServerException.getErrString("R_InvalidMixedParameters"),
SQLState.DATA_EXCEPTION_NOT_SPECIFIC, DriverError.NOT_SET, null);
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8037,8 +8037,14 @@ void endRequestInternal() throws SQLException {
String replaceParameterMarkers(String sqlSrc, int[] paramPositions, Parameter[] params,
boolean isReturnValueSyntax) {
final int MAX_PARAM_NAME_LEN = 6;

int paramNameLen = 0;
for(Parameter p : params) {
paramNameLen+=p.getInOutParameterName().length();
}

char[] sqlDst = new char[sqlSrc.length() + (params.length * (MAX_PARAM_NAME_LEN + OUT.length))
+ (params.length * 2)];
+ (params.length * 2) + paramNameLen];
int dstBegin = 0;
int srcBegin = 0;
int nParam = 0;
Expand All @@ -8052,9 +8058,18 @@ String replaceParameterMarkers(String sqlSrc, int[] paramPositions, Parameter[]
if (sqlSrc.length() == srcEnd)
break;

String paramName = params[paramIndex].getInOutParameterName();
if (paramName.length()>0) {
paramName.getChars(0, paramName.length(), sqlDst, dstBegin);
dstBegin += paramName.length();
sqlDst[dstBegin++] = '=';
}

dstBegin += makeParamName(nParam++, sqlDst, dstBegin, true);
srcBegin = srcEnd + 1 <= sqlSrc.length() - 1 && sqlSrc.charAt(srcEnd + 1) == ' ' ? srcEnd + 2 : srcEnd + 1;


System.arraycopy(OUT, 0, sqlDst, dstBegin, OUT.length);

if (params[paramIndex++].isOutput() && (!isReturnValueSyntax || paramIndex > 1)) {
System.arraycopy(OUT, 0, sqlDst, dstBegin, OUT.length);
dstBegin += OUT.length;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ public class SQLServerPreparedStatement extends SQLServerStatement implements IS

/** Set to true if the statement is a stored procedure call that expects a return value */
final boolean bReturnValueSyntax;
final int iReturnValueOffset;

/** user FMTOnly flag */
private boolean useFmtOnly = this.connection.getUseFmtOnly();
Expand Down Expand Up @@ -281,6 +282,7 @@ private boolean resetPrepStmtHandle(boolean discardCurrentCacheItem) {
// Retrieve meta data from cache item.
procedureName = parsedSQL.procedureName;
bReturnValueSyntax = parsedSQL.bReturnValueSyntax;
iReturnValueOffset = parsedSQL.bReturnValueSyntax ? 1 : 0;
userSQL = parsedSQL.processedSQL;
userSQLParamPositions = parsedSQL.parameterPositions;
initParams(userSQLParamPositions.length);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,7 @@ protected Object[][] getContents() {
{"R_UnableToFindClass", "Unable to locate specified class: {0}"},
{"R_ibmModuleNotFound", "com.ibm.security.auth.module.Krb5LoginModule module was not found."},
{"R_moduleNotFound", "Neither com.sun.security.auth.module.Krb5LoginModule nor com.ibm.security.auth.module.Krb5LoginModule was found."},
{"R_InvalidMixedParameters", "Cannot mix named and indexed parameters unless all parameters are provided."},
};
}
// @formatter:on
2 changes: 2 additions & 0 deletions src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ protected Object[][] getContents() {
{"R_deadConnection", "Dead connection should be invalid"},
{"R_wrongExceptionMessage", "Wrong exception message"}, {"R_wrongSqlState", "Wrong sql state"},
{"R_parameterNotDefined", "Parameter {0} was not defined"},
{"R_parameterNotSet", "The value is not set for the parameter number {0}."},
{"R_parameterNotProvided", "expects parameter {0}, which was not supplied"},
{"R_unexpectedExceptionContent", "Unexpected content in exception message"},
{"R_connectionClosed", "The connection has been closed"},
{"R_conversionFailed", "Conversion failed when converting {0} to {1} data type"},
Expand Down
Loading