How big is an nvarchar(max) as far as ADO is concerned? - delphi

i am trying to use a parameterized query against ADO:
INSERT INTO Foo (Name, Value) VALUES(#name, #value)
In SQL Server the Name column is a varchar type. The Value column is an nvarchar(max).
What size do i pass when creating a parameter when i don't know, or want to specify, the size?
procedure SaveTheThing(Connection: TADOConnection);
var
sql: WideString;
cmd: _Command;
begin
sql := 'INSERT INTO Foo (Name, Value) VALUES(#name, #value)';
cmd := CoCommand.Create;
cmd.Set_ActiveConnection(Connection.ConnectionObject);
cmd.Set_CommandType(adCmdText);
cmd.Set_CommandText(sql);
//and now add the parameters
cmd.Parameters.Append(
cmd.CreateParameter('#name', adVarChar, adParamInput, -1, filename)
);
cmd.Parameters.Append(
cmd.CreateParameter('#value', adVarWChar, adParamInput, -1, GetXmlToWideString)
);
cmd.Execute({out}recordsAffected, EmptyParam, adCmdSomeThatDoesntCauseAnExcetpion or adExecuteNoRecords);
end;
The simple alternative was going to be:
sql := 'INSERT INTO Foo (Name, Value)'#13#10+
'VALUES (+QuotedStr(filename)+', '+QuotedStrW(GetXmlToWideString)+')';
and be done already. But i thought i'd burn a few days trying to make parameterized queries a viable solution, and avoid having to write a QuotedStrW.

You can use the -1 value in the size of a ADO parameter without problems.
Try this sample code , which insert a 2MB string in the Value column
var
sql: WideString;
cmd: _Command;
recordsAffected : OleVariant;
begin
sql := 'INSERT INTO Foo (Name, Value) VALUES(?, ?)';
cmd := CoCommand.Create;
cmd.Set_ActiveConnection(Connection.ConnectionObject);
cmd.Set_CommandType(adCmdText);
cmd.Set_CommandText(sql);
//and now add the parameters
cmd.Parameters.Append(cmd.CreateParameter('#name', adVarChar, adParamInput, -1, 'AfileName'));
cmd.Parameters.Append(cmd.CreateParameter('#value', adVarWChar, adParamInput, -1, StringOfChar('#', 2*1024*1024)));
cmd.Execute({out}recordsAffected, EmptyParam, adExecuteNoRecords);
end;

You can use my answer on your other post:
"Must declare the variable #myvariable" error with ADO parameterized query
to use parameters and parameter by name. Try to avoid using _Command, but use TADOCommand because it is more friendly (and simpler to code too). You can assign parameter to a value by using:
Parameters.ParamByName('xxxx').value := someValue.
Of course someValue data type must correspond to your SQL server column data type definition.

Related

Assigning default value to snowflake stored procedure arguments

Is it possible to have default values in arguments of Stored procedures of Snowflake. For the below example, I am getting error. Please help
syntax error line 1 at position 53 unexpected ''test''.
create or replace procedure test(arg1 string default 'test')
returns string not null
language sql
as
$$
begin
return arg1;
end;
$$
;
Snowflake's procedures applies polymorphism instead of using default value. This solution is when you do not want to call sp like func1(Null)
For example (sql scripting):
create or replace procedure func1(arg1 varchar, arg2 varchar)
...
create or replace procedure func1(arg1 varchar)
...
call func1(arg1 , 'some-default-value')
...
One option could be providing NULL as value and handle it at the begining of the stored procedure with COALSESCE:
create or replace procedure test(arg1 string)
returns string not null
language sql
as
$$
begin
arg1 := COALESCE(arg1, 'test');
return arg1;
end;
$$;
CALL test(NULL);
-- test
Setting a default value/values as arguments directly in Stored procedures is not available in Snowflake currently
The below link can be referred for the allowed syntax in Stored Procedures
https://docs.snowflake.com/en/sql-reference/sql/create-procedure.html#syntax

Pass NULL parameter to TFDConnection.ExecSQLScalar

Is there a possibility to pass NULL value to some parameter of this FireDAC query?:
conn: TFDConnection;
fPar1, fPar2, fPar3: OleVariant;
cnt := conn.ExecSQLScalar(
'SELECT COUNT(*) FROM my_table WHERE par1=:p1 AND par2=:p2 AND par3=:p3',
[fPar1, fPar2, fPar3]
);
Is it possible without intermediate TFDQuery using TFDConnection object only?
Yes, you can do this, despite the fact that the parameters TFDConnection uses
for ExecSQLScalar are not directly accessible from your calling code, but it may not produce the result you are expecting unless you modify your SQL - see below.
Presumably, you have had an error message like "[FireDAC] parameter type [fPar2 ] is unknown ..." if you set fPar2 to Null beforehand.
You can avoid that by using the override of ExecSQLScalar that allows you to specify
the field types of the parameters in an open array following the parameter
which lists the variants, as in e.g.
cnt := conn.ExecSQLScalar(
'SELECT COUNT(*) FROM my_table WHERE par1=:p1 AND par2=:p2 AND par3=:p3',
[fPar1, fPar2, fPar3],
[ftString, ftString, ftString] // or whatever
);
See
function TFDCustomConnection.ExecSQLScalar(const ASQL: String;
const AParams: array of Variant; const ATypes: array of TFieldType): Variant;
in FireDAC.Comp.Client.Pas
BUT, on my data here, this does NOT produce the correct count value (using Seattle and SS2014) presumably because of Uwe Raabe's good point about par1 = Null versus par1 is Null. To get the correct answer, I had to modify the SQL as per Keith Miller's comment to include set ansi_nulls off before SELECT ...

JDBC - Retrieve Boolean output from Oracle Procedure

I need to invoke an Oracle procedure with one IN parameter with VARCHAR2 and OUT parameter as BOOLEAN data type.
Below is my code using SimpleJdbcCall
SimpleJdbcCall jdbcCall = new SimpleJdbcCall(getTemplate())
.withCatalogName("package_name")
.withProcedureName("proc_name")
.withoutProcedureColumnMetaDataAccess()
.declareParameters(
new SqlParameter ("userName", Types.VARCHAR),
new SqlOutParameter("status", Types.BOOLEAN)
);
Map<String, Object> inParams = new HashMap<String, Object>();
inParams .put("userName", userInput);
Map<String, Object> outputValue= jdbcCall.execute(inParams);
Exception : CallableStatementCallback; uncategorized SQLException for
SQL [{call PACKAGE_NAME.PROC_NAME(?, ?)}]; SQL state [99999];
error code [17004]; Invalid column type: 16; nested exception is
java.sql.SQLException: Invalid column type: 16
After doing a research i found that "JDBC drivers do not support the passing of Boolean parameters to PL/SQL stored procedures"
It has been suggested to wrap the PL/SQL procedure with a second PL/SQL procedure. The main problem is that i am restricted for write access in the db as that is client data. please help me to fix this issue.
Some of the links i referred
https://docs.oracle.com/cd/F49540_01/DOC/java.815/a64685/tips3.htm
https://community.oracle.com/thread/2139408?tstart=0
https://community.oracle.com/thread/887712?tstart=0
https://community.oracle.com/thread/975159?tstart=0
Stored Function - Sending/Receiving Boolean - BD
http://docs.oracle.com/cd/B28359_01/java.111/b31224/apxtblsh.htm#i1005380
From the official Oracle JDBC documentation:
It is not feasible for Oracle JDBC drivers to support calling
arguments or return values of the PL/SQL RECORD, BOOLEAN, or table
with non-scalar element types. [...] As a workaround to PL/SQL RECORD,
BOOLEAN, or non-scalar table types, create container procedures that
handle the data as types supported by JDBC. For example, to wrap a
stored procedure that uses PL/SQL boolean, create a stored procedure
that takes a character or number from JDBC and passes it to the
original procedure as BOOLEAN or, for an output parameter, accepts a
BOOLEAN argument from the original procedure and passes it as a CHAR
or NUMBER to JDBC. Similarly, to wrap a stored procedure that uses
PL/SQL records, create a stored procedure that handles a record in its
individual components, such as CHAR and NUMBER, or in a structured
object type.
This is exactly what you did in your answer, but I wanted to add the documentation as reference.
I fixed the issue by writing a wrapper procedure to process the result of the actual procedure and sending back the result as varchar data type .
If any of you find it as wrong approach or if you a have any easy way to fix this, please do share your comments.
Below is the procedure:
DECLARE
userName VARCHAR2(13);
status BOOLEAN;
result VARCHAR2(13);
BEGIN
userName := ?;
status := NULL;
package_name.proc_name ( userName, status);
BEGIN
IF status THEN result := 'Yes';
ELSIF NOT status THEN result := 'No';
ELSE result := 'NULL';
END IF;
END;
COMMIT;
? := result;
END ;
Also instead of simpleJdbcCall, i used CallableStatement for processing this. Refer below:
try{
String wrapperProc= "DECLARE userName VARCHAR2(13); status BOOLEAN; result VARCHAR2(13); BEGIN userName := ?; status := NULL; "+
"package_name.proc_name ( userName, status ); BEGIN IF status THEN result := 'Yes'; ELSIF NOT status THEN " +
"result := 'No'; ELSE result := 'NULL'; END IF; END; COMMIT; ? := result;END ;";
CallableStatement proc_stmt= null;
proc_stmt = getTemplate().getDataSource().getConnection().prepareCall(wrapperProc);
proc_stmt.setString(1, "userName");
proc_stmt.registerOutParameter(2, Types.VARCHAR);
proc_stmt.execute();
System.out.println("Final Result : "+proc_stmt.getString(2));
} catch(SQLException e){
System.out.println("SQL Exception : "+e.getMessage());
e.printStackTrace();
} catch (Exception e) {
System.out.println("Exception : "+e.getMessage());
e.printStackTrace();
}
~~Suriya

How to parameterize widestrings using TADOCommand parameterized query?

i am trying to use a parameterized query with Delphi TADOCommand:
var
s: WideString;
cmd: TADOCommand;
recordsAffected: OleVariant;
begin
cmd := TADOCommand.Create(nil);
cmd.Connection := Connection;
cmd.CommandText := 'INSERT INTO Sqm(Filename) VALUES(:filename)';
s := AFilename;
cmd.Parameters.ParamByName('filename').Value := s;
cmd.Execute();
The resulting data in the database is complete mangled:
C?:\U?s?er?s?\i??n?.A?V`A?T?O?P?I?A?\A?p?p?D??t??\L?o???l?\A?v?at??r? S?o?f?t?w?är¨? C?r??t?i??n?s?\S?o°f?t?w?r?? Q?u??li?t?y? M??t?r?i?cs?\C??S?-s?q?m?00.x?m?l
i can use a native parameterized ADO Command object. It saves the data correctly:
C̬:\Ȗŝḙr͇s̶\i̜ẵn̥.ÀV̹AͧT̶O̠P̩I̿Ȁ\A͜p̥p̔D͑ẫt̒ā\L̫o͋ɕălͭ\A̼v̼ẵt͈ấr̄ S̫o̖f͎t̻w̵ạr͂ẽ C̾r̮ḛẵt͘iͩo̳n̬s̨\S̪ōf̒t͘w̚âr̿ɇ Qͬüẳlͮi̫tͥy̽ M͘ȇt̨r̟i̻çš\C͍MͥS̚-s̞q̕m͜00.xͤm̧l̝
but it's very fragile and not suitable for production use.
How can i use unicode/WideStrings with TADOCommand in Delphi?
Bonus Chatter
In SQL Server Profiler you can see the SQL being executed:
exec sp_executesql N'INSERT INTO Sqm(Filename) VALUES(#P1)', N'#P1 char(300),#P2 text', 'C?:\Us?er?s?\i?än?.A?V?A?T?O?P?I?À\A?p?p?D?ât?a\L?o?çal¯\A?v?at??r? So?f?t?w?ar?? C?r??á?i?o?n?s?\So¸f"t?w?ar?? Q?u??l?i?ty? M??t?r?i¸?s`\C?M°S?-s?q?m?00.?m¨´l¯ '
Which points out the problem - it's building the WideString parameter as a char(300) value. Make it not broke.
The last i see of my WideString before it goes down the parameter hole is:
ParameterObject.Value := NewValue;
where
NewValue is a variant of type VT_BSTR (aka varOleStr) with the proper value
ParameterObject is a native ADO _Parameter object, with a .Type of 129 (adChar)
Even trying to force the parameter type:
cmd.Parameters.ParamByName('filename').DataType := ftWideString;
cmd.Parameters.ParamByName('filename').Value := s;
doesn't help.
Note: This question is part of a series on how to paramaterize INSERT INTO foo (value) VALUES (%s)
https://stackoverflow.com/questions/10726212/using-wrong-type-with-parameterized-query-causes-error
How big is an nvarchar(max) as far as ADO is concerned?
How to parameterize widestrings using TADOCommand parameterized query?
"Must declare the variable #myvariable" error with ADO parameterized query
How about using
cmd.Parameters.ParamByName('filename').Value := WideStringToUCS4String(s);
By the way, s is declared as widestring. is it necessary to have s as a widestring? How about just
var
s : String;
in System.pas, UCS4String (UCS-4 byte or UTF-32 bits) is declared as:
...
...
UCS4Char = type LongWord;
...
UCS4String = array of UCS4Char;
...
function WideStringToUCS4String(const S: WideString): UCS4String;
...
function UCS4StringToWidestring(const S: UCS4String): WideString;
What data type you stored the filename column as ? Can sql server 2000 handle UTF-32 string?
The answer is that it cannot be done in Delphi (5).
It might be fixed in newer versions of Delphi; but without anyone to test it we won't know.
Q.: How to parameterize widestrings using TADOCommand parameterized query?
A.: You can't. Sorry for the inconvience.

How to pass NULL value from Unidac Query component in Delphi?

When I am using StoredProc component in Delphi using
ParamByname('ParamName').Clear I'm able to send NULL value.
But how can I pass NULL value when using a Query component?
with Query do
begin
SQL.ADD('exec d_upd_calc'+Quoted(EditCalc.Text));
end
In the above scenario I want to send NULL if the edit box is blank.
I am using Delphi 2010, Unidac with Sybase.
Even in Queries you can work with parameters:
Query.SQL.Text := 'exec d_upd_calc :myparam';
Query.Prepare;
Query.ParamByName('myparam').Clear;
And it's better to use parameters than to build the complete string, because you must not handle quotes and avoid security leaks via SQL-injection.
With Advantage DB I would do something along these lines:
var
sqlText: string;
with Query do
begin
if EditCalc.Text = '' then
sqlText := 'exec d_upd_calc NULL' else
sqlText := 'exec d_upd_calc '+Quoted(EditCalc.Text);
SQL.ADD(sqlText);
end;
If the keyword is also NULL then this should work.
Does Quoted remove/escape any dangerous user input to prevent SQL injection? If yes then it's good. If not then it should.

Resources