JPA - Error preparing CallableStatement - Index 4 out of range but only have 3 params - sql-server

I'm using Spring Data JPA 1.10.2 with com.microsoft.sqlserver's sqljdbc 4.2. I get the following error:
o.h.engine.jdbc.spi.SqlExceptionHelper : Error preparing CallableStatement [User.pTest]
com.microsoft.sqlserver.jdbc.SQLServerException: The index 4 is out of range.
My entity class is:
#Entity
#NamedStoredProcedureQuery(name = "User.getUser", procedureName = "User.pTest", parameters = {
#StoredProcedureParameter(mode = ParameterMode.OUT, name = "session", type = byte[].class),
#StoredProcedureParameter(mode = ParameterMode.IN, name = "name", type = String.class),
#StoredProcedureParameter(mode = ParameterMode.IN, name = "date", type = Date.class)
})
#Data //lombok
public class User {
// serves no purpose other than to meet
// JPA requirement
#Id
private Long id;
}
The repository code is
public interface UserRepository extends Repository<User, Long> {
#Procedure("User.pTest")
byte[] getUserSession(#Param("name") String name,
#Param("date") Date date
);
}
My test code is as follows and when I run it I get the error:
#Test
public void testGettingApxSession() {
Calendar cal = new GregorianCalendar(2016,6,5);
byte[] b = userRepository.getUserSession("myName", cal.getTime());
}
How do I resolve the error?
Update
Forgot to include the relevant part of the SQL Server stored proc:
ALTER procedure [User].[pTest]
#session varbinary(max) out
,#name nvarchar(max) = null
,#opt nvarchar(max) = null
,#date datetime
as
begin
set #session = CAST(N'<?xml version="1.0" encoding="UTF-16" ?><Session session = 45"'/>' as varbinary(max))
end

If you specify the nth parameter, Microsoft SQL Server driver requires that you also include the other parameters up to n. For example, let's say your procedure has five parameters (in this order) each having a default value of null:
param1 = null
param2 = null
param3 = null
param4 = null
param5 = null
You may only want to specify a value for param4. But if you do, you also must at least specify param1, param2, and param3 in the call to the stored procedure.
That may not seem too much of an issue. But if you have many default params and only need to specify say the 15th parameter, it will be quite tedious to also have to specify the other 14.
At least that's not fatal. But it is fatal in combination with the following Hibernate rule: Hibernate requires that each specified param is not null. But what if the procedure requires that only param3 or param4 have a value (but not both)? If you specify param4 to have a value, then you must include param3 (per MS driver) and you must give it a value (per Hibernate). So, you're sunk.
The only solutions are
Use a different driver such as jTDS (which has an issue on truncating >8000 bytes) OR
Write a wrapper procedure to just have only the needed params (e.g. param4) and let it call the procedure with five parameters.
Details of the exception in the OP
The Microsoft SQL Server driver has found (in the db metadata) four stored procedure parameters and returns the name and index of each. Then, the Hibernate / JPA code is building the CallableStatement.Via #NamedStoredProcedureonly three parameters have been defined. So, it builds something like pTest(?,?,?). But then Hibernate uses the metadata to say that Date should be in the fourth position which is out of range. The built pTest only has three parameters.

Related

Having an Issue with too many parameters being passed to stored procedures in ColdFusion 2016

I have several stored procedures in an application that were functioning perfectly (for years) until our recent upgrade from ColdFusion 2010 to ColdFusion 2016. Now, I am getting error messages of either too many parameters or a certain parameter is not a parameter is not contained in the procedure that is being called.
I have opted to upload some code so people can better understand what is actually happening. Still learning how to format code here so please forgive me if it is still lacking.
In both cases I have double checked the parameter lists in the stored procedure in the procedure calls and have found that they are all indeed correct. In fact, nothing has changed in this code for over 5 years. This behavior has only begun since the upgrade has taken place.
Below is the first example. I will list the procedure call (in cfscript)
first then the parameter list from the stored procedure and then the error message it produced:
public query function readStorage(numeric group1=0,numeric group2=0) {
local.group1Value = arguments.group1?arguments.group1:"";
local.group2Value = arguments.group2?arguments.group2:"";
spService = new storedproc();
spService.setDatasource(variables.dsn);
spService.setUsername(variables.userName);
spService.setPassword(variables.password);
spService.setProcedure("usp_readCompatibilityStorage");
spService.addParam(dbvarname="#group1Id",cfsqltype="cf_sql_integer"
, type="in",value=local.group1Value,null=!arguments.group1);
spService.addParam(dbvarname="#group2Id",cfsqltype="cf_sql_integer"
,type="in",value=local.group2Value,null=!arguments.group2);
spService.addProcResult(name="rs1",resultset=1);
local.result = spService.execute();
return local.result.getProcResultSets().rs1;
}
Below is the parameter list from the stored procedure:
#groupId1 int = NULL
,#groupId2 int = NULL
Below is the error message I get:
[Macromedia][SQLServer JDBC Driver][SQLServer]#group1Id is not a
parameter for procedure usp_readCompatibilityStorage.
Second Example:
public query function read(string cribIdList="",
numeric cribNumber=0,
string isAnnex="",
numeric siteId=0,
string parentCribIdList="",
numeric supervisorId=0,
numeric statusId=0,
string orderBy="cribNumber ASC") {
local.cribNumberValue = arguments.cribNumber?arguments.cribNumber:"";
local.siteIdValue = arguments.siteId?arguments.siteId:"";
local.superIdValue = arguments.supervisorId ? arguments.supervisorId:"";
local.statusIdValue = arguments.statusId ? arguments.statusId:"";
spService = new storedproc();
spService.setDatasource(variables.dsn);
spService.setUsername(variables.userName);
spService.setPassword(variables.password);
spService.setProcedure("usp_readCrib");
spService.addParam(dbvarname="#cribIdList",cfsqltype="cf_sql_varchar"
,type="in",value=arguments.cribIdList
,null=!len(arguments.cribIdList));
spService.addParam(dbvarname="#cribNumber",cfsqltype="cf_sql_integer"
,type="in",value=local.cribNumberValue
,null=!arguments.cribNumber);
spService.addParam(dbvarname="#isAnnex",cfsqltype="cf_sql_varchar"
,type="in",value=arguments.isAnnex,null=!len(arguments.isAnnex));
spService.addParam(dbvarname="#siteId",cfsqltype="cf_sql_integer"
,type="in",value=local.siteIdValue,null=!arguments.siteId);
spService.addParam(dbvarname="#parentCribIdList"
, cfsqltype="cf_sql_varchar", type="in"
, value=arguments.parentCribIdList
, null=!len(arguments.parentCribIdList));
spService.addParam(dbvarname="#supervisorId",
cfsqltype="cf_sql_integer", type="in",value=local.superIdValue
, null=!arguments.supervisorId);
spService.addParam(dbvarname="#statusId"
, cfsqltype="cf_sql_integer", type="in"
, value=local.statusIdValue, null=!arguments.statusId);
spService.addParam(dbvarname="#orderBy",cfsqltype="cf_sql_varchar"
, type="in",value=arguments.orderBy);
spService.addProcResult(name="rs1",resultset=1);
local.result = spService.execute();
return local.result.getProcResultSets().rs1;
}
Below is the parameter list from the stored procedure:
#cribIdList varchar(500) = NULL
,#cribNumber int = NULL
,#isAnnex varchar(3) = NULL
,#siteId int = NULL
,#parentCribIdList varchar(500) = NULL
,#supervisorId int = NULL
,#statusId int = NULL
,#orderBy varchar(50)
Below is the message returned from the server:
[Macromedia][SQLServer JDBC Driver][SQLServer]Procedure or function
usp_readCrib has too many arguments specified.
In the case of both errors, they seem to be occurring at the following path:
Error Details - struct
COLUMN 0
ID CFSTOREDPROC
LINE 489
RAW_TRACE at cfbase2ecfc235349229$funcINVOKETAG.runFunction(E:\ColdFusion2016\cfusion\CustomTags\com\adobe\coldfusion\base.cfc:489)
TEMPLATE E: \ColdFusion2016\cfusion\CustomTags\com\adobe\coldfusion\base.cfc
TYPE CFML````
ColdFusion 10 and greater limit the amount of parameters in a request to 100 by default. Fortunately this can be updated and changed to reflect the required amount of parameters you need for your stored procedures.

Call stored procedure specifying only parameters with a value

In an instance of SQL Server 2016 I have a stored procedure with dozens of parameters. For example:
CREATE PROCEDURE spName (
#par1 INT = NULL,
#par2 VARCHAR(10) = NULL,
....
....
#par98 INT = NULL,
#par99 INT = NULL,
) AS
BEGIN
....
....
END
I have a client written in C# that calls the stored procedure specifying only the parameters with a value. Ex:
SqlCommand cmd = new SqlCommand();
cmd.CommandText = "spName";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Connection = dbConn;
cmd.Parameters.Add(new SqlParameter("par1", "val1"));
cmd.Parameters.Add(new SqlParameter("par47", "val47"));
...
cmd.ExecuteNonQuery();
It works perfectly! So, the procedure is executed and only the 2 parameters (par1 and par47) have a value. Other parameters maintain the default value (NULL).
I would do the same from a Java client using Microsoft JDBC driver 6.2.
I specify the parameters with List<Map<String, Object>>, so a list of couple parameterName-->parameterValue. The following method builds the PreparedStatement object:
private CallableStatement prepareStatement(String spName, Map<String, ?> parameters) throws SQLException {
setupConnection();
CallableStatement stmt = null;
try {
stmt = conn.prepareCall(getSpCallString(spName, parameters));
if (parameters != null) {
for (String parName : parameters.keySet())
stmt.setObject(parName, parameters.get(parName));
}
} catch (SQLException e) {
ApplicationLogging.severe("Cannot prepare callable statement", e);
throw e;
}
return stmt;
}
The method getSpCallString() generates a string of the type { call spName ?,?, ... , ? } with a number of ? as the number of parameters with a value passed to the procedure, so not all 99 parameters. If I have 2 parameter it generates the string { call spName ?,? }.
By passing for example par15=val15 and par47=val47 it raises the following exception:
com.microsoft.sqlserver.jdbc.SQLServerException: The index 2 is out of range.
I could resolve this putting in the call command the same number of ? as the number of parameter of the stored procedure but... I don't know the number of parameters for each stored procedure (and their position)!
In C# this is simply resolved because the parameters are assigned only with their name, so the number and the order of parameters can be really a black box.
Can I do this in some way in Java?
This is a confirmed deficiency in the current implementation of named parameter support for CallableStatement in the mssql-jdbc driver. Despite section 13.3.2 of the JDBC 4.2 specification stating ...
Named parameters can be used to specify only the values that have no default value.
... we seem to be required to provide a parameter placeholder for every possible parameter, and there doesn't appear to be a way to specify DEFAULT for the parameters we might otherwise simply omit.
As a workaround we could use code like this
public static ResultSet executeStoredProcedureQuery(
Connection conn, String spName, Map<String, Object> paramItems)
throws SQLException {
StringBuffer sqlBuf = new StringBuffer("EXEC ");
sqlBuf.append(spName);
int paramCount = 1;
for (String paramName : paramItems.keySet()) {
sqlBuf.append(
(paramCount++ > 1 ? ", " : " ") +
(paramName.startsWith("#") ? "" : "#") + paramName + "=?");
}
String sql = sqlBuf.toString();
myLogger.log(Level.INFO, sql);
// e.g., EXEC dbo.BreakfastSP #helpings=?, #person=?, #food=?
PreparedStatement ps = conn.prepareStatement(sql);
paramCount = 1;
for (String paramName : paramItems.keySet()) {
ps.setObject(paramCount++, paramItems.get(paramName));
}
return ps.executeQuery();
}
which we could call like this
// test data
Map<String, Object> paramItems = new HashMap<>();
paramItems.put("#person", "Gord");
paramItems.put("#food", "bacon");
paramItems.put("#helpings", 3);
//
ResultSet rs = executeStoredProcedureQuery(conn, "dbo.BreakfastSP", paramItems);
If using a third party library to facilitate calling such procedures is an option for you, then jOOQ certainly helps via its code generator for stored procedures, which generates stubs for each of your procedures, making such calls type safe. It includes support for:
Table valued functions
Table valued parameters
Defaulted parameters
In / Out parameters
Optional return value of procedures
Fetching undeclared update counts and result sets
Much more
In your case, you could write:
Spname sp = new Spname();
sp.setPar1("val1");
sp.setPar47("val47");
sp.execute(configuration); // The object containing your JDBC connection
sp.getResults(); // The result set(s) and update counts, if any
Behind the scenes, a JDBC CallableStatement is created, just like you would do manually:
try (CallableStatement s = c.prepareCall(
"{ ? = call [dbo].[spName] (#par1 = ?, #par47 = ?) }"
)) {
// Get the optional procedure return value that all procedures might return
s.registerOutParameter(1, Types.INTEGER);
s.setString(2, "val1");
s.setString(3, "val47");
s.execute();
// Lengthy procedure to fetch update counts and result set(s)
}
See this article if you want to generically fetch update counts and result set(s) with JDBC.
Disclaimer: I work for the company behind jOOQ.

Why does Entity Framework throw an exception when changing SqlParameter order?

Im using entity framework 4.3 code first for calling stored procedure the way i call the stored procedure is like this:
var parameters = new[]
{
new SqlParameter("member", 1),
**new SqlParameter("Code","0165210662660001"),**
new SqlParameter("PageSize", 1),
new SqlParameter("PageNumber",1)
};
var result = context.Database.SqlQuery<resultClass>(
"mySpName #member, #Code, #PageSize,#PageNumber" parameters).ToList();
It gets executed on the SqlServer and I get the result.
But if I change the order of the paramaters like this:
var result = context.Database.SqlQuery<resultClass>("mySpName #Code, #member,#PageSize,#PageNumber" parameters).ToList();
var parameters = new[]
{
**new SqlParameter("Code","0165210662660001"),**
new SqlParameter("Member", 1),
new SqlParameter("PageSize", 1),
new SqlParameter("PageNumber",1)
};
I got an error like this :
Error converting data type nvarchar to int
The stored procedure is like this :
ALTER PROCEDURE [c].[mySpName]
#Member INT ,
#Code VARCHAR (50) ,
#PageSize INT ,
#PageNumber INT
AS
Why do i get this order?
Is it important to keep parameters order?
What can i do so that I can call a stored procedure without being concerned about parameters order?
============ i find a workaround and it works perfectly ============
public class blahContext<T>
{
int i = 0;
public IEnumerable<T> ExecuteStoreQuery(string SPname, SqlParameter[] parameters)
{
using (var context = new CADAContext())
{
string para = string.Join(", ", (from p in parameters
where !"NULL".Equals(p.Value)
select string.Concat(new object[] { "#", p.ParameterName, "={", this.i++, "}" })).ToList<string>());
object[] x = (from p in parameters
where !"NULL".Equals(p.Value)
select p.Value).ToArray<object>();
return context.Database.SqlQuery<T>(SPname + " " + para, x).ToList();
}
}
It's not because of the parameter order in your parameters object - it's because in your second code snippet you're explicitly passing the #Code value as the first parameter when the SP is expecting a Member INT value.
var result = context.Database.SqlQuery<resultClass>("mySpName #Code, #member,#PageSize,#PageNumber" parameters).ToList();
...you're passing in "0165210662660001" as the first parameter and the conversion to INT is failing.
The order of your parameters in your parameters object is irrelevant as EF (ADO.NET actually) will map those parameters to the #parametername values in your query string. So the new SqlParameter("Code","0165210662660001") will be mapped into the #Code position in your query - which int the second code snipped is actually the position for the Member value as expected by the SP.
However... you can execute a SP using named parameters as well and in that case you can pass the parameters to the SP in any order as below:
db.Database.SqlQuery<resultClass>("mySpName PageNumber=#PageNumber,Code=#Code,PageSize=#PageSize,Member=#member", parameters).ToList();
You see that I'm not passing the params to the SP in the order they were defined [by the SP] but because they're named I don't have to care.
For different ways of passing params see: This Answer for some good examples.

How to connect two sets of db components using master / detail relationship?

This is how my db components are connected in program:
The datatypes of components are (first row, from left to right): TDBGrid, TDataSource, TClientDataSet, TDataSetProvider, TSQLQuery, TSQLConnection. Second row is analogous to first row.
Client data set Query_LimitDetail is linked to master data source through properties MasterSource and MasterFields. Their values are as follows:
Query_LimitDetail->MasterSource = DataSource_Limit;
Query_LimitDetail->MasterFields = L"ID";
SQL command assigned to client data sets are:
select * from LIMIT order by NAME
select * from LIMITDETAIL where LIMIT_ID = :ID order by ACCUR
This is how i open queries:
Query_Limit->Open();
Query_LimitDetail->Open();
When trying to open detail query, program throws following error:
dbExpress driver does not support the TDBXTypes.UNKNOWN data type. Vendor error message: unknown ISC error 0
Is there something I didnt make correctly?
The solution is to set SQL parameter type just before opening the query:
Query_Limit->Close();
Query_Limit->Open();
// ID param
TParam *param = Query_LimitDetail->Params->Items[0];
param->DataType = ftInteger;
Query_LimitDetail->Close();
Query_LimitDetail->Open();
Alternative and more universal solution is to set parameter type in OnBeforeOpen event handler like this:
__fastcall MyDataModule::MyDataModule(TComponent *Owner) :
TDataModule(Owner)
{
...
Query_LimitDetail->BeforeOpen = Query_LimitDetail_OnBeforeOpen;
...
}
void __fastcall MyDataModule::Query_LimitDetailBeforeOpen(TDataSet *DataSet)
{
if (Query_LimitDetail->Params->Count == 0)
{
return;
}
// ID param
TParam *param = Query_LimitDetail->Params->Items[0];
param->DataType = ftInteger;
}
It is also neccessary to link column in master table (specified in MasterFields property) to column in detail table:
Query_LimitDetail->IndexFieldNames = L"LIMIT_ID";

Getting String from SQLCLR

I need to call an external DLL that resides in the PC that hosts the SQL Server from various clients in order to get digital signatures.
I thought using SQLCLR is a good idea.I have issues though...
I wrote this in order to call the external dll function inside the C# dll
[Microsoft.SqlServer.Server.SqlProcedure]
public static void Sign( SqlString receipt, out SqlString signature)
{
short result=0;
string arg = "";
string commPort = "1";
signature = "";
bool btrue = true, bfalse = false;
string CommPort = commPort.ToString();
string Receipt = receipt.ToString();
string Signature = signature.ToString();
ExtDll.Box ExtBox = new Box();
ExtBox.Sign_(ref CommPort,Receipt,ref result,ref Signature,ref bfalse,ref arg,ref btrue);
signature = (SqlString)Signature;
}
I wrote the asembly for the SQL Server
CREATE ASSEMBLY Mybox
FROM 'C:\Users\Panos\Documents\Visual Studio 2010\Projects\DigitalSignature\Extbox\bin\Debug\Extbox.dll'
WITH PERMISSION_SET = UNSAFE
I wrote a stored procedure
CREATE PROC sp_extbox (#receipt char(2048), #signature char(100) out)
AS
EXTERNAL NAME Mybox.Signatures.Sign
But when I execute the above script I get
CREATE PROCEDURE for "sp_extbox" failed because T-SQL and CLR types for parameter "#receipt" do not match.
I believe an SqlString corresponds to nvarchar.
So, try this instead of your current last script:
CREATE PROC sp_extbox (#receipt nvarchar(4000), #signature nvarchar(4000) out)
AS
EXTERNAL NAME Mybox.Signatures.Sign

Resources