I'm trying to execute an SQL statement on a C core-function on Postgres. I'm using the SPI (Server Programming Interface) to do this.
The code is partially shown below.
However, when I execute the SPI_execute_plan, the argument $1 should be substituted by the parameter received on the function, but it is not being done correctly. The result is always the SPI_ERROR_ARGUMENT case.
I would like to know if I can print the SQL statement with the argument $1 substituted by the command and what is the problem with my code.
I'm using Postgres version 13.2 on CentOS 7.
Thanks in advance!
PG_FUNCTION_INFO_V1(print_statistics);
Datum print_statistics(PG_FUNCTION_ARGS) {
if (PG_ARGISNULL(0) || PG_ARGISNULL(1)|| PG_ARGISNULL(2) || PG_ARGISNULL(3)) {
ereport(ERROR, (errmsg("Null parameters are not accepted.")));
}
text *arg1 = (text *)PG_GETARG_TEXT_P(0);
char * str1 = (char *)palloc(VARSIZE_ANY_EXHDR(arg1) + VARHDRSZ);
SET_VARSIZE(str1, VARSIZE_ANY_EXHDR(arg1) + VARHDRSZ);
memcpy(str1, VARDATA_ANY(arg1), VARSIZE_ANY_EXHDR(arg1));
Oid argtypes[1] = {25};
char nulls[1] = {' '};
Datum values[1];
values[0] = CStringGetDatum(str1);
char* query = "SELECT * FROM table1 WHERE name = '$1'";
SPIPlanPtr plan = SPI_prepare(query, 1, argtypes);
elog(INFO, "arg1 (%s): %s", str1, DatumGetCString(values[0]));
SPI_connect();
int result = SPI_execute_plan(plan, values, nulls, false, 0);
switch(result){
case SPI_OK_SELECT: {elog(INFO, "SPI_OK_SELECT! Result: %d\n", result); break;}
case SPI_ERROR_ARGUMENT: {elog(INFO, "SPI_ERROR_ARGUMENT. Result: %d\n", result); break;}
case SPI_ERROR_COPY: {elog(INFO, "SPI_ERROR_COPY. Result: %d\n", result); break;}
case SPI_ERROR_OPUNKNOWN: {elog(INFO, "SPI_ERROR_OPUNKNOWN. Result: %d\n", result); break;}
case SPI_ERROR_UNCONNECTED: {elog(INFO, "SPI_ERROR_UNCONNECTED. Result: %d\n", result); break;}
}
SPI_finish();
The problem happened because I put the command to prepare the query before openning the connection with SPI.
The correct here is put the SPI_prepare(query, 1, argtypes); after the SPI_connect() command.
Related
$params = [
':x1' => $locationBox['leftLongitude'],
':y1' => $locationBox['topLatitude'],
':x2' => $locationBox['rightLongitude'],
':y2' => $locationBox['topLatitude'],
':x3' => $locationBox['rightLongitude'],
':y3' => $locationBox['bottomLatitude'],
':x4' => $locationBox['leftLongitude'],
':y4' => $locationBox['bottomLatitude'],
':x5' => $locationBox['leftLongitude'],
':y5' => $locationBox['topLatitude']
];
$sql = "
....
INNER JOIN tag_geo T3 ON (T3.id = T2.tag_id_b AND ST_Covers(ST_GeogFromText('POLYGON((:x1 :y1, :x2 :y2, :x3 :y3, :x4 :y4, :x5 :y5))'), T3.geo_location));
";
$connection = \Yii::$app->getDb();
$command = $connection->createCommand($sql);
$command->bindValues($params);
$result = $command->queryAll();
I get an error:
SQLSTATE[HY093]: Invalid parameter number: :x1 Failed to prepare SQL
Notice single tick ('POLYGON), if I remove the ticks that wrap the POLYGON function the parameters get evaluated but another error occurred, since this POLYGON must be in single quotes.
Because of the single quotes around POLYGON function, the polygon part is recognized by the db engine as it is, e.g. as the string POLYGON((:x1 :y1, :x2 :y2, :x3 :y3, :x4 :y4, :x5 :y5)). So you should implement only one marker (:polygon) in the sql statement istead:
<?php
$sql = "
....
INNER JOIN tag_geo T3 ON (T3.id = T2.tag_id_b AND ST_Covers(ST_GeogFromText(:polygon), T3.geo_location));
";
$params = [
":poligon" => sprintf( // Output: POLYGON((x1-value y1-value, x2-value y2-value, ...))
"POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))"
, $locationBox['leftLongitude']
, $locationBox['topLatitude']
, $locationBox['rightLongitude']
, $locationBox['topLatitude']
, $locationBox['rightLongitude']
, $locationBox['bottomLatitude']
, $locationBox['leftLongitude']
, $locationBox['bottomLatitude']
, $locationBox['leftLongitude']
, $locationBox['topLatitude']
)
];
//...
Of course, if it still doesn't work use question mark markers (?) instead.
This situation is similar with the one where one tries to prepare an sql statement which uses the LIKE keyword. An example here: Syntax of LIKE in PreparedStatement.
I have tried both a prepared statement and a regular statement to retrieve a record from my table using a long iPhone simulator path.
I simply cannot get a SELECT statement to work which uses field Path in the WHERE statement. If I remove the WHERE statement, it returns the record.
e.g.
Last Added[/Users/admin/Library/Developer/CoreSimulator/Devices/1546523B-D9E4-45F6-9ACA-ADA5CF73DFE4/data/Containers/Data/Application/9187662E-16F3-4573-B486-256BDA00CBD1/Documents/wf_mages-3.lha]
SQL[SELECT ID FROM Tune WHERE Path="/Users/admin/Library/Developer/CoreSimulator/Devices/1546523B-D9E4-45F6-9ACA-ADA5CF73DFE4/data/Containers/Data/Application/9187662E-16F3-4573-B486-256BDA00CBD1/Documents/wf_mages-3.lha"]
I have tried both double and single quotes in the query. I have even tried the same query in SQLiteBrowser and it works fine.
SELECT ID FROM Tune WHERE Path LIKE '%mages%' works fine.
Is the problem with the forward slashes or the size of the path value? Do forward slashes need to be escaped somehow?
The only other post I could find about forward slashes recommended prepared statements being used but that's what I started with originally and they wouldn't work. Look-ups work fine on any other fields in my tables - just not paths.
int GetTuneIDWithPath( char *pszPath )
{
// TEST
char txTmp [ 1024 ];
sprintf( g_txSQL, "SELECT %s", fldTune_ID );
sprintf( txTmp, " FROM %s ", tbTune );
strcat( g_txSQL, txTmp );
//sprintf( txTmp, " WHERE %s=\"%s\"", fldTune_Path, pszPath );
//strcat( g_txSQL, txTmp );
// sprintf( txTmp, " WHERE %s='%s'", fldTune_Path, pszPath );
//strcat( g_txSQL, txTmp );
//sprintf( txTmp, " WHERE %s LIKE '%%mages%%'", fldTune_Path );
//strcat( g_txSQL, txTmp );
LogDebugf( "SQL[%s]", g_txSQL );
char *zErrMsg = 0;
char **pszResult;
int nRows;
int nCols;
int rc;
rc = sqlite3_get_table_wrapper( g_MainDB,
g_txSQL,
&pszResult,
&nRows,
&nCols,
&zErrMsg );
if( nRows != 1 )
{
LogDebugf( "FAILED nRows: %d", nRows );
return FALSE;
}
// First row is field names, so start at 1
int nCol = 0;
int nID = Safe_atoi( pszResult[ nCols + nCol ] ); nCol ++;
LogDebugf( "PDS> nID: %d Find[%s]", nID, pszPath );
return nID;
}
Prior to this I did try using prepared statements like I do for other queries:
char txTmp [ 1024 ];
sprintf( g_txSQL, "SELECT %s", fldTune_ID );
sprintf( txTmp, " FROM %s WHERE %s=?", tbTune, fldTune_Path );
strcat( g_txSQL, txTmp );
sqlite3_stmt *stmt = NULL;
sqlite3_prepare( g_MainDB, g_txSQL, -1, &stmt, NULL );
sqlite3_reset( stmt );
sqlite3_bind_text( stmt, 1, pszPath, strlen( pszPath ), SQLITE_STATIC );
rc = sqlite3_step( stmt );
if( rc != SQLITE_ROW )
{
LogDebugf( "PDS> Lookup failed [%s] rc: %d", pszPath, rc );
return FALSE;
}
int nID = sqlite3_column_int( stmt, 0 );
LogDebugf( "PDS> nID: %d Find[%s]", nID, pszPath );
You need to ignore the actual location of the file and only record and search for files within the app documents directory, so you need to store/search for 'wf_mages-3.lha' and not '/Users/admin/Library/Developer/CoreSimulator/Devices/1546523B-D9E4-45F6-9ACA-ADA5CF73DFE4/data/Containers/Data/Application/9187662E-16F3-4573-B486-256BDA00CBD1/Documents/wf_mages-3.lha'.
Only when actually reading/writing the file do you need to prepend the documents path, which you get via NSSearchPathForDirectoriesInDomains().
The reason for this is that the location will change during an app update and the documents directory contents will be migrated but the database rows will not, and you will have lost the connection between the two.
There should be no issue using '/' in the statement as it's not special as far as SQL is concerned, and so it should be possible to create a hierarchy within the documents folder without problem.
To comment on your code, I've never seen that method used before and I would advocate the more conventional sqlite3_prepare(), sqlite3_step(), and sqlite3_finalize() functions. Also, most crucially, you need to report any errors using sqlite3_errmsg(), otherwise you'll make no progress at all.
I would like to query a column name's inputdate, type : date. When I query:
where (inputdate>='$VEStart' AND inputdate<='$VEEnd')
I got:
error : warning pg_query() query failed error invalid input syntax for date where (inputdate>='' AND inputdate<='').
But when I try to replace it:
where (inputdate>='2015-12-01' AND inputdate<='2015-12-31')
It works. I thought it was a problem with variables. so I tried to echo both variables, but they display the right values. Anything wrong here?
Just to give you an example beyond the comment, use something like this and ensure that you add improvements to the below code before putting it into production use; also test it well.
<?php
$VEStart = '2015-01-01';
$VEEnd = '2015-2-28';
// validate the dates
if (!isDateValid($VEStart)) {
print "Invalid start date\n";
return;
}
if (!isDateValid($VEEnd)) {
print "Invalid end date\n";
return;
}
// format the dates
$VEStart = formattedDate($VEStart);
$VEEnd = formattedDate($VEEnd);
echo sprintf ("all good - %s and %s\n", $VEStart, $VEEnd);
// see http://php.net/manual/en/function.pg-query-params.php
$sql = 'select ... where inputdate between $1 and $2';
$connection = pg_connect('...');
$result = pg_query_params($connection, $sql, array($VEStart, $VEEnd));
...more code...
// ----
// add good phpdoc
// see how others add doc - http://stackoverflow.com/questions/1904214/what-is-the-proper-php-function-documentation-format
function formattedDate($date) {
list($year, $month, $day) = explode('-', $date);
return date('Y-m-d', mktime(0, 0, 0, $month, $day, $year));
}
// add good phpdoc
function isDateValid($date) {
list($year, $month, $day) = explode('-', $date);
return checkdate($month, $day, $year);
}
?>
This works,
print map { $_." x" => $_ } 1..5;
print map { ("$_ x" => $_) } 1..5;
print map { ("$_ x") => $_ } 1..5;
but this throws syntax error,
print map { "$_ x" => $_ } 1..5;
Is this documented bug, undocumented bug, or I can't see why this should not compile?
Why perl thinks this should be map EXPR, LIST instead of map BLOCK LIST
From perlref
Because curly brackets (braces) are used for several other things including BLOCKs, you may occasionally have to disambiguate braces at the beginning of a statement by putting a + or a return in front so that Perl realizes the opening brace isn't starting a BLOCK. The economy and mnemonic value of using curlies is deemed worth this occasional extra hassle.
To make your intentions clearer and to help the parser,
Say +{...} to unambiguously specify a hash reference
#list_of_hashrefs = map +{ "$_ x" => $_ }, 1..5;
Say {; ...} to unambiguously specify a code block
%mappings = map {; "$_ x" => $_ } 1..5;
Why perl thinks this should be map EXPR, LIST instead of map BLOCK LIST?
The relevant section of code is in toke.c, Perl's lexer (the below is from Perl 5.22.0):
/* This hack serves to disambiguate a pair of curlies
* as being a block or an anon hash. Normally, expectation
* determines that, but in cases where we're not in a
* position to expect anything in particular (like inside
* eval"") we have to resolve the ambiguity. This code
* covers the case where the first term in the curlies is a
* quoted string. Most other cases need to be explicitly
* disambiguated by prepending a "+" before the opening
* curly in order to force resolution as an anon hash.
*
* XXX should probably propagate the outer expectation
* into eval"" to rely less on this hack, but that could
* potentially break current behavior of eval"".
* GSAR 97-07-21
*/
t = s;
if (*s == '\'' || *s == '"' || *s == '`') {
/* common case: get past first string, handling escapes */
for (t++; t < PL_bufend && *t != *s;)
if (*t++ == '\\')
t++;
t++;
}
else if (*s == 'q') {
if (++t < PL_bufend
&& (!isWORDCHAR(*t)
|| ((*t == 'q' || *t == 'x') && ++t < PL_bufend
&& !isWORDCHAR(*t))))
{
/* skip q//-like construct */
const char *tmps;
char open, close, term;
I32 brackets = 1;
while (t < PL_bufend && isSPACE(*t))
t++;
/* check for q => */
if (t+1 < PL_bufend && t[0] == '=' && t[1] == '>') {
OPERATOR(HASHBRACK);
}
term = *t;
open = term;
if (term && (tmps = strchr("([{< )]}> )]}>",term)))
term = tmps[5];
close = term;
if (open == close)
for (t++; t < PL_bufend; t++) {
if (*t == '\\' && t+1 < PL_bufend && open != '\\')
t++;
else if (*t == open)
break;
}
else {
for (t++; t < PL_bufend; t++) {
if (*t == '\\' && t+1 < PL_bufend)
t++;
else if (*t == close && --brackets <= 0)
break;
else if (*t == open)
brackets++;
}
}
t++;
}
else
/* skip plain q word */
while (t < PL_bufend && isWORDCHAR_lazy_if(t,UTF))
t += UTF8SKIP(t);
}
else if (isWORDCHAR_lazy_if(t,UTF)) {
t += UTF8SKIP(t);
while (t < PL_bufend && isWORDCHAR_lazy_if(t,UTF))
t += UTF8SKIP(t);
}
while (t < PL_bufend && isSPACE(*t))
t++;
/* if comma follows first term, call it an anon hash */
/* XXX it could be a comma expression with loop modifiers */
if (t < PL_bufend && ((*t == ',' && (*s == 'q' || !isLOWER(*s)))
|| (*t == '=' && t[1] == '>')))
OPERATOR(HASHBRACK);
if (PL_expect == XREF)
{
block_expectation:
/* If there is an opening brace or 'sub:', treat it
as a term to make ${{...}}{k} and &{sub:attr...}
dwim. Otherwise, treat it as a statement, so
map {no strict; ...} works.
*/
s = skipspace(s);
if (*s == '{') {
PL_expect = XTERM;
break;
}
if (strnEQ(s, "sub", 3)) {
d = s + 3;
d = skipspace(d);
if (*d == ':') {
PL_expect = XTERM;
break;
}
}
PL_expect = XSTATE;
}
else {
PL_lex_brackstack[PL_lex_brackets-1] = XSTATE;
PL_expect = XSTATE;
}
Explanation
If the first term after the opening curly is a string (delimited by ', ", or `) or a bareword beginning with a capital letter, and the following term is , or =>, the curly is treated as the beginning of an anonymous hash (that's what OPERATOR(HASHBRACK); means).
The other cases are a little harder for me to understand. I ran the following program through gdb:
{ (x => 1) }
and ended up in the final else block:
else {
PL_lex_brackstack[PL_lex_brackets-1] = XSTATE;
PL_expect = XSTATE;
}
Suffice it to say, the execution path is clearly different; it ends up being parsed as a block.
I have a problem with a prepared statement, here is my code:
function query_array($table, $data) {
foreach ($data as $column => $value) {
$columns[] = sprintf("`%s` = '%s'", $column, $this->db->real_escape_string($value));
}
$column_list = join(',', $columns);
// Prepare the statement
$stmt = $this->db->prepare("UPDATE `?` SET ?");
$stmt->bind_param('ss', $table, $column_list);
// Execute the statement
$stmt->execute();
// Save the affected rows
$affected = $stmt->affected_rows;
// Close the statement
$stmt->close();
// ...
}
$this->db returns an object;
$table = 'settings'; (string)
$column_list: (string)
`title` = 'Socialsd',`captcha` = '0',`public` = '',`private` = '',`time` = '1',`perpage` = '10',`message` = '140',`mail` = '1',`inter` = '10000',`size` = '1048576',`format` = 'png,jpg,gif',`sizeMsg` = '1048576',`formatMsg` = 'png,jpg,gif,bmp',`censor` = '',`ad1` = '',`ad2` = ''
The error I'm getting is:
Fatal error: Uncaught exception 'ErrorException' with message 'You
have an error in your SQL syntax; check the manual that corresponds to
your MySQL server version for the right syntax to use near '?' at line
1' in C:\xampp\htdocs\new\includes\classes.php:256 Stack trace: #0
C:\xampp\htdocs\new\sources\admin.php(225):
updateSettings->query_array('settings', Array) #1
C:\xampp\htdocs\new\index.php(42): PageMain() #2 {main} thrown in
C:\xampp\htdocs\new\includes\classes.php on line 256
I can't figure out what causes this, because trying the following works just fine:
$query = sprintf("UPDATE `%s` SET %s", $table, $column_list);
$result = $this->db->query($query);
Any help is appreciated.
Update 1: May I know why this has been down-voted? It would be nice to know.
Update 2: So I've removed the last bind ($column_list) and put in the statement the entire output of $column_list, so basically I was binding only the table name, and now I get another error:
Can't find file: '.\diary\#003f.frm' (errno: 22)
Now I'm really confused.
I have found the answer here: Use one bind_param() with variable number of input vars and also as #Jocelyn linked me, I've found that table names can't be binded. Can be closed.