How to call a function with void return using Firedac FDConnection Component in Delphi XE5? - database

I recently started using the [ExecSQLScalar]1 and [ExecSQL]2 methods of the FDConnection component in Delphi XE5. It's very handy not to need to build a Dataset object, like FDQuery just for simple queries or executions.
However I had a curious problem when executing a function with void return that has internal validations where it can generate exceptions. I'm using a Postgres database.
CREATE FUNCTION can_be_exception()
RETURNS void AS
$$
BEGIN
RAISE EXCEPTION E'fail';
END;
$$
LANGUAGE plpgsql STABLE;
In delphi, I call the ExecSQLScalar function ...
FDConnection1.ExecSQLScalar('select 1');
FDConnection1.ExecSQLScalar('select can_be_exception()');
On first run, I get the following error:
Project TFDConnectionDEMO.exe raised exception class
EPgNativeException with message '[FireDAC][Phys][PG][libpq] ERROR:
fail'.
On the second run, I get a Violation Access error:
Project TFDConnectionDEMO.exe raised exception class $C0000005 with
message 'access violation at 0x00000000: read of address 0x00000000'.
Apparently the error occurs in the line below in unit FireDAC.Comp.Client
function TFDCustomConnection.ExecSQLScalar(const ASQL: String;
const AParams: array of Variant; const ATypes: array of TFieldType): Variant;
var
oCmd: IFDPhysCommand;
begin
oCmd := BaseCreateSQL;
try
if BasePrepareSQL(oCmd, ASQL, AParams, ATypes) or (FExecSQLTab = nil) then begin
FDFree(FExecSQLTab);
...
ignoring the previous error and trying again, another error is displayed...
Project TZConnectionDEMO.exe raised exception class EFDException with
message '[FireDAC][DatS]-24. Row is not nested'.
Searching, I found no response to this error. I figured my mistake would be to call the bank raise_exception function using the ExecSQLScalar function of the FDConnection component. So I tried using FDConnection.ExecSQL and as I imagined, you can not use this if there is a SELECT clause in the parameter.
Is there a better way to call function with void return using FDConnection.ExecSQL? would a BUG be in the component? or would not it be correct to make that kind of call?

Using ExecSQLScalar is fine in this case. This is certainly a bug (which was already fixed, at least in Delphi 10.2.3). As you've correctly pointed out, the problem is in releasing a table storage object instance held by the FExecSQLTab field by using FDFree procedure.
I don't have Delphi XE5 source code but maybe you can see something like this inside (comments about what happened are added by me):
if BasePrepareSQL(oCmd, ASQL, AParams, ATypes) or (FExecSQLTab = nil) then
begin
FDFree(FExecSQLTab); { ← directly calls destructor if object is not nil }
FExecSQLTab := oCmd.Define; { ← no assignment if command execution raises exception }
end;
Problem was that when a SQL command execution raised exception during storage table definition stage (oCmd.Define), reference to previously destroyed storage table object instance (by FDFree) remained stored in the FExecSQLTab field (as a dangling pointer).
Then when a different command was executed that way, FDFree procedure was called just for that dangling pointer. Hence the access violation.
Way to correct this is replacing line e.g. by:
FDFree(FExecSQLTab);
by:
FDFreeAndNil(FExecSQLTab);
which was done in some later Delphi release.

Related

SQL Server 2012 assembly execution failure - System.NullReferenceException: Object reference not set to an instance of an object

I am trying to execute a UDF which uses a CLR assembly. Each time I try to run it I get the below error:
Msg 6522, Level 16, State 1, Line 45
A .NET Framework error occurred during execution of user-defined routine or aggregate "Geocode":
System.NullReferenceException: Object reference not set to an instance of an object.
at ProSpatial.UserDefinedFunctions.GeocodeUDF(SqlString countryRegion, SqlString adminDistrict, SqlString locality, SqlString postalCode, SqlString addressLine)
It is a geocoding assembly, which is based on the below blog:
https://alastaira.wordpress.com/2012/05/04/geocoding-in-sql-server-with-the-bing-maps-locations-api/
Is there anything I can do to fix it? It was working fine, but just stopped working recently. I changed the Bing Maps key to a new one, but that hasn't resolved the issue. Anyone have any ideas?
Cheers
Edit: I entered a formatted URL into Chrome, to see if I can get the error. I entered the below URL format:
http://dev.virtualearth.net/REST/v1/Locations?countryRegion={0}&adminDistrict={1}&locality={2}&postalCode={3}&addressLine={4}&key={5}&output=xml
All I did was to replace items 0 to 5 with their respective entries. I left the curly brackets in there, and then also tried it without the curly brackets. Without the curly brackets, the URL returned a result with no issues.
In the browser, I am getting geocoded results, but not in SQL any more
Just worked it out. One of the address fields that was being geocoded was null in the database, and the API did not like that. So I removed that from the geocoding list
Looking at the code in that blog, if you are passing in a NULL, then it's not the API that's throwing the error. The error is happening because NULL values are not being handled when the method is first called. The code just casts the SqlString input parameters to string without first checking to see if there is a value in the SqlString variable. SqlString is very similar to a nullable string.
Fortunately, it is rather easy to adjust the code to properly handle NULL values in the input parameters. Starting with a redacted snippet of the original code below, I will show how to do this for one parameter and you can repeat that modification for the others (well, the ones that might actually be NULL in the DB; no need to do this for parameters that are guaranteed to be there due to being in a NOT NULL column).
public static SqlGeography GeocodeUDF(
SqlString countryRegion,
SqlString adminDistrict,
SqlString locality,
SqlString postalCode,
SqlString addressLine
)
{
...
string localAdminDistrict = string.Empty;
if (!adminDistrict.IsNull)
{
localAdminDistrict = adminDistrict.Value;
}
// Attempt to geocode the requested address
try
{
geocodeResponse = Geocode(
(string)countryRegion,
localAdminDistrict,
(string)locality,
(string)postalCode,
(string)addressLine
);
}
All I did was:
Added a local variable for localAdminDistrict, defaulted to an empty string.
Added an if block to set localAdminDistrict if adminDistrict is not null.
Updated the call to Geocode() to use localAdminDistrict instead of (string)adminDistrict.
This should work as long as the Bing Maps API is ok with an empty value for adminDistrict (i.e. ...&adminDistrict=&locality=... ), as opposed to adminDistrict not being present in the GET request (which is easy enough, it just requires an additional modification).
ALSO: As long as you are going to be in there updating the code, you really should move the calls to the Close() and Dispose() methods into a finally block for that try / catch.
For more info on working with SQLCLR in general, please visit: SQLCLR Info

Simplejdbccall Stored Procedure withNamedBinding true

I want to execute stored procedure with dynamic parameters with SimpleJdbcCall. In total I have 6 optional parameters in SQL server SP, out of them I must be able to pass any or none. My SP executes fine as expected in MS Studio. But not by SimpleJdbcCall. I tried in many ways and one of them I tried is withNamedBinding. But it gives Input Syntax error near "=" as below.
this.simpleJdbcCall = new SimpleJdbcCall(jdbcTemplateObject)
.withNamedBinding()
.withSchemaName("dbo")
.withProcedureName("EmployeeDetails")
.useInParameterNames(
paramNameArray)
.returningResultSet("detailReportData", BeanPropertyRowMapper.newInstance(Employee.class));
Map<String,Object> out = this.simpleJdbcCall.execute(sqlSource);
Log:
2019-01-31 18:14:49 DEBUG SimpleJdbcCall:405 - The following
parameters are used for call {call dbo.EmployeeDetails(empCode => ?,
empName => ?, empLoc => ?)} with {empCode=0, empName='hgkghdkgf',
empLoc='kjhjk'} 2019-01-31 18:14:49 DEBUG DispatcherServlet:993 -
Could not complete request
org.springframework.jdbc.UncategorizedSQLException:
CallableStatementCallback; uncategorized SQLException for SQL [{call
dbo.EmployeeDetails(empCode => ?, empName => ?, empLoc => ?)}]; SQL
state [S0001]; error code [102]; Incorrect syntax near '='.; nested
exception is com.microsoft.sqlserver.jdbc.SQLServerException:
Incorrect syntax near '='.
I had faced similar type of problem and found few solutions in this stack overflow question. But in my case I was using MS-SQL server and it was giving me this same syntax error. While searching for solutions I came across this example of Spring (See Section 11.5.6. Declaring parameters to use for a SimpleJdbcCall).
Just in case if the link becomes unavailable, here is what it says
We can opt to declare one, some or all of the parameters explicitly. The parameter metadata is still being used. By calling the method withoutProcedureColumnMetaDataAccess we can specify that we would like to bypass any processing of the metadata lookups for potential parameters and only use the declared ones. Another situation that can arise is that one or more in parameters have default values and we would like to leave them out of the call. To do that we will just call the useInParameterNames to specify the list of in parameter names to include.
And here is the sample code
public class JdbcActorDao implements ActorDao {
private SimpleJdbcCall procReadActor;
public void setDataSource(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.procReadActor =
new SimpleJdbcCall(jdbcTemplate)
.withProcedureName("read_actor")
.withoutProcedureColumnMetaDataAccess()
.useInParameterNames("in_id")
.declareParameters(
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),
new SqlOutParameter("out_last_name", Types.VARCHAR),
new SqlOutParameter("out_birth_date", Types.DATE)
);
}
// ... additional methods
}
I am not sure why but the withNamedBinding() method seems to not work properly with ms-sql server and creates the syntax error.
So in the above solution the work around is to
not to use the .withNamedBinding() method.
use the .withoutProcedureColumnMetaDataAccess() method.
while passing your parameter array to the .useInParameterNames() method, make sure you pass it in the sequence it is declared in the proc (as we are not using name binding). My out parameter was declared few parameters before the last one and so it was giving me error while converting var to int as my out param was int.
So now your solution should look something like this :
this.simpleJdbcCall = new SimpleJdbcCall(jdbcTemplateObject)
.withSchemaName("dbo")
.withProcedureName("EmployeeDetails")
.withoutProcedureColumnMetaDataAccess()
.useInParameterNames(paramNameArray)
.returningResultSet("detailReportData", BeanPropertyRowMapper.newInstance(Employee.class));
Map<String,Object> out = this.simpleJdbcCall.execute(sqlSource);
Give it a try and see if this works for you.

Delphi : incorrect parameter message error with AdoQuery using SQL aggregate function

I use delphi XE5, I have a database with 7 tables, and my problem: I click on a button which runs this code:
dbgridappr2.Enabled:=false;
adoquery4.Parameters.Clear;
datasource6.DataSet:=adoquery4;
ADOQuery4:=TADOQuery.Create(Application);
adoquery4.Active:=False;
adoquery4.Connection:=ADOConnection1;
adoquery4.SQL.Clear;
adoquery4.SQL.Add('select SPEC.ISp AS ''Spécialité'',COUNT(APPR.NValAp) AS ''Nombre dApprentis de même Spécialité'' ');
adoquery4.SQL.Add('FROM APPR,SPEC ');
adoquery4.SQL.Add('where SPEC.CSp=APPR.CSp ');
adoquery4.SQL.Add('GROUP BY SPEC.ISp ');
adoquery4.SQL.Add('ORDER BY COUNT(APPR.NValAp) desc ');
ADOQuery4.Prepared := True;
ADOQuery4.ExecSQL;
//adoquery4.Open;
adoquery4.Active:=true;
dbgridappr2.Visible:=true;
dbgridappr2.DataSource:=datasource6;
dbgridappr2.Enabled:=true;
I have the result in a DbGrid but the message error show incorrect parameter ??
I changed the button code but I always get the same error message. I have 10 buttons that all run similar code with different AdoQuery and I have the result but I have always the same message error
The code you've posted is an absolute mess. Here's why:
You clear the adoquery4.Parameters here, and assign adoquery4 to datasource6.DataSet:
dbgridappr2.Enabled:=false;
adoquery4.Parameters.Clear;
datasource6.DataSet:=adoquery4;
You then immediately throw away the existing adoquery4 (leaking memory in the process), and replace it with a new instance of TADOQuery:
ADOQuery4:=TADOQuery.Create(Application);
You then close the newly created ADOQuery4 (which could not possibly be Active at this point), assign a connection (which would be fine), and clear the SQL (which could not possibly have any content here):
adoquery4.Active:=False;
adoquery4.Connection:=ADOConnection1;
adoquery4.SQL.Clear;
So far, about 90% of what you've done is meaningless.
Then you make the mistake of calling ADOQuery4.ExecSQL;, which is used to execute queries that return no rowset, like INSERT, DELETE, and so forth. You need to use ADOQuery4.Open or ADOQuery4.Active := True instead for a SELECT. This is the actual cause of the error you're getting; you're calling ExecSQL with an SQL statement that returns a rowset, and that's invalid.
Let's try again, and throw in a slight improvement in the SQL in the process. Ignore everything you've posted here, and start over:
ADOQuery4.DisableControls;
try
// If the query is
if ADOQuery4.Active then
ADOQuery4.Close;
ADOQuery4.Parameters.Clear;
ADOQuery4.SQL.Clear;
AdoQuery4.SQL.Add('select SPEC.ISp AS ''Spécialité'',');
AdoQuery4.SQL.Add('COUNT(APPR.NValAp) AS ''Nombre dApprentis de même Spécialité''');
AdoQuery4.SQL.Add('FROM APPR INNER JOIN SPEC');
AdoQuery4.SQL.Add('ON SPEC.CSp = APPR.CSp');
AdoQuery4.SQL.Add('GROUP BY SPEC.ISp ');
AdoQuery4.SQL.Add('ORDER BY COUNT(APPR.NValAp) desc');
ADOQuery4.Open;
finally
ADOQuery4.EnableControls;
end;
(I don't know what all of the juggling of the dbgridappr2.DataSource is about, but unless you're changing the datasource from a different query none of that is necessary. The calls to DisableControls and EnableControls stops any UI components from being updated while the new query is executed.)

How to show useful error messages from a database error callback in Phonegap?

Using Phonegap you can set a function to be called back if the whole database transaction or the individual SQL statement errors. I'd like to know how to get more information about the error.
I have one generic error-handling function, and lots of different SELECTs or INSERTs that may trigger it. How can I tell which one was at fault? It is not always obvious from the error message.
My code so far is...
function get_rows(tx) {
tx.executeSql("SELECT * FROM Blah", [], lovely_success, statement_error);
}
function add_row(tx) {
tx.executeSql("INSERT INTO Blah (1, 2, 3)", [], carry_on, statement_error);
}
function statement_error(tx, error) {
alert(error.code + ' / ' + error.message);
}
From various examples I see the error callback will be passed a transaction object and an error object. I read that .code can have the following values:
UNKNOWN_ERR = 0
DATABASE_ERR = 1
VERSION_ERR = 2
TOO_LARGE_ERR = 3
QUOTA_ERR = 4
SYNTAX_ERR = 5
CONSTRAINT_ERR = 6
TIMEOUT_ERR = 7
Are there any other properties/methods of the error object?
What are the properties/methods of the transaction object at this point?
I can't seem to find a good online reference for this. Certainly not on the Phonegap website!
http://docs.phonegap.com/en/2.2.0/cordova_storage_storage.md.html#SQLError
The SQLError object is thrown when an error occurs when manipulating a database.
This object has two properties:
code (one of your described constants)
message: A description of the error.
So in your code error.message will give you a nice description of what went wrong in which transaction.
I think that's exactly what you want, isn't it?
Best regards, F481
Transaction object
The only thing you can do with the transaction object is call its .executeSql() method, as far as I can ascertain. I cannot find any properties of this object.
Error object
The error object has a .code property which contains a number. You can either check the numerical value (see my original question above) or use something like:
if (error.code == error.DATABASE_ERR) alert('nasty database error')
The .message property is a string and may return something like this:
could not prepare statement (1 near "wibble": syntax error)
could not prepare statement (1 no such table: MyyTable)
could not prepare statement (1 table MyTable has no column named MyColunm)
could not execute statement (19 constraint failed)
Other messages are possible! This is just the few I spotted when debugging in Chrome. I notice in Phonegap the messages are briefer: "no such table: MyyTable"
There are two sets of success/error callbacks
Also note that there is another database error callback on the initial call to .transaction(). Your function will only be returned an error object (no transaction object).
The error's .code will be zero and the .message will be "the statement callback raised an exception or statement error callback did not return false".
So remember to have your statement callbacks (function mentioned inside .executeSql such as my statement_error in the code example of my original question) return true or false depending on whether you want your transaction error callback (second function mentioned inside .transaction) to be hit. The 'success' callback you specified (third one inside .transaction) will be run if you return true (or don't return anything).

Ada and SPARK identifier `State` is either undeclared or not visible at this point

I am doing an automatic train protection on Ada with SPARK approach. This is my spec in SPARK:
package Sensors
--# own State,Pointer,State1,State2;
--# initializes State,Pointer,State1,State2;
is
type Sensor_Type is (Proceed, Caution, Danger, Undef);
subtype Sensor_Index_Type is Integer range 1..3;
procedure Write_Sensors(Value_1, Value_2, Value_3: in Sensor_Type);
--# global in out State,Pointer;
--# derives State from State,Value_1, Value_2, Value_3,Pointer &
--# Pointer from Pointer;
function Read_Sensor(Sensor_Index: in Sensor_Index_Type) return Sensor_Type;
function Read_Sensor_Majority return Sensor_Type;
end Sensors;
and this is my Ada:
package body Sensors is
type Vector is array(Sensor_Index_Type) of Sensor_Type;
State: Vector;
Pointer:Integer;
State1:Sensor_Type;
State2:Sensor_Type;
procedure Write_Sensors(Value_1, Value_2, Value_3: in Sensor_Type) is
begin
State(Pointer):=Value_1;
Pointer:= Pointer + 1;
State(Pointer):=Value_2;
Pointer:= Pointer + 1;
State(Pointer):=Value_3;
end Write_Sensors;
function Read_Sensor (Sensor_Index: in Sensor_Index_Type) return Sensor_Type
is
State1:Sensor_Type;
begin
State1:=Proceed;
if Sensor_Index=1 then
State1:=Proceed;
elsif Sensor_Index=2 then
State1:=Caution;
elsif Sensor_Index=3 then
State1:=Danger;
end if;
return State1;
end Read_Sensor;
function Read_Sensor_Majority return Sensor_Type is
State2:Sensor_Type;
begin
State2 := state(1);
return State2;
end Read_Sensor_Majority;
begin -- initialization
State:=Vector'(Sensor_Index_Type =>Proceed);
pointer:= 0;
State1:=Proceed;
State2:=Proceed;
end Sensors;
I want to know why in the function Read_Sensor_Majority I can't use the State(1) or any of the State() array values. If there is a way to use them, should I put anything in the specs of SPARK to make it happen?
The errors it's showing are:
1)Expression contains referenced to variable state which has an undefined value flow error 20
2)the variable state is nether imported nor defined flow error 32
3)the undefined initial value of state maybe used in the derivation of the function value flow error 602
You need to change the spec to read
function Read_Sensor_Majority return Sensor_Type;
--# global in State;
As I said in the comments above, I was puzzled by
State := Vector'(Sensor_Index_Type => Proceed);
but the compiler accepts it so it must be OK. And a little test shows that it has the same effect as
State := Vector'(others => Proceed);
Also pleased to report that the SPARK GPL 2011 toolset is now available for Mac OS X!
Heh. Well, those are definitely SPARK errors, rather than "garden variety" compiler errors.
It would be nice to see an actual cut-and-paste version of the errors (along with an indication of which lines they are referring to) rather than just an imperfect transcription. However, I do realise that isn't always possible for security/connectivity reasons.
It looks like all three are complaining about the flow of data through your system. Without knowing which lines they refer to, the best I can suggest is to try to manually trace your flow of data through your system to try to see what their problem is.
If I had to take a wild guess with the info I have here, I'd say it perhaps has a problem with your reading of a value from State(1) in the routine Read_Sensor_Majority, because it has no way of knowing that you've previously placed a value into that array location.
The code you have in the package's begin...end body area should take care of that, except it appears to have a compile error itself, as Simon pointed out in the comments. Perhaps if you fix that problem, SPARK will understand what is going on and quit complaining about your control flows.
If SPARK likes to spit out "I'm confused" errors on code that doesn't even get past the Ada compiler, it might be wise to make sure the Ada compiler likes the pure Ada part of your code before asking SPARK to look it over.

Resources