I have some problems about ODBC programming with C/C++, PostgreSQL connector.
I just want to put some BLOB data in my PostgreSQL with C/C++, ODBC
here's my DB Table Create Query
CREATE TABLE BLOB_TEST
(
id number(20),
data BYTEA
);
And, here's my code
.
.
.
int retcode = 0;
SQLCHAR sql[1024] =
"BEGIN \n"
"insert into BLOB_TEST "
"values(9, ?); \n"
"EXCEPTION \n"
"when DUP_VAL_ON_INDEX then \n"
"dbms_output.put_line(1); \n"
"END; ";
char * temp_str = "this is a BLOB input TEST";
retcode = SQLPrepareA(hstmt, sql, SQL_NTS);
.
.
.
SQLBindParameter(hstmt,
1, /* Parameter number, starting at 1 */
SQL_PARAM_INPUT, /* in, out, inout */
SQL_C_BINARY, /* C data type of the parameter */
SQL_LONGVARBINARY, /* SQL data type of the parameter : char(8)*/
0, /* size of the column or expression, precision */
0, /* The decimal digits, scale */
temp_str, /* A pointer to a buffer for the parameter’s data */
0, /* Length of the ParameterValuePtr buffer in bytes */
NULL /* indicator */
);
.
.
.
retcode = SQLExecute(hstmt);
if (retcode == SQL_SUCCESS)
{
printf("Query Execute Success\n");
}
else
{
SQLGetDiagRecA(SQL_HANDLE_STMT, hstmt, ++rec, state, &native, message, sizeof(message), &length);
printf("%s : %ld : %ld : %s\n", state, rec, native, message);
printf("Query Execute ERROR : %d\n", retcode);
}
SQLExecute return -1(SQL_ERROR) and ERROR Message says:
SQLSTATE 42601, Missing ";" at the end of Expression
I know that PostgreSQL BLOB(BYTEA) type matched SQL_LONGVARBINARY Option when using SQLBindParameter, But that makes ERROR...
Is there any odd expression in my prepared query?
Or, Is there any way to check value-combind Query that SQLExcute function made?
I'm very confused, Cause the query that I prepared works well when using PgAdmin Querying tools...
So, I Want to check value-combind Query that SQLExcute function made.
You are trying to run a PL/SQL block on a database that is not Oracle. How is that supposed to work?
PostgreSQL has a DO statement that serves a similar purpose, but you cannot use parameters with it. You should send only the INSERT statement and do the exception handling in your C client code.
Related
I do not know how to implement prepared statements in my Sqlite3 code
#include <iostream>
#include <sqlite3.h>
#include <stdio.h>
static int callback (void* NotUsed, int argc, char** argv, char** azColName) {
int i;
for (i = 0; i < argc; i++) {
std::cout << ("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
}
std::cout << ("\n");
return 0;
}
int main (int argc, char* argv[]) {
sqlite3* db;
char* zErrMsg = 0;
int rc;
char* sql;
/* Open database */
rc = sqlite3_open ("test.db", &db);
if (rc) {
std::cerr << "Can't open database: \n" << sqlite3_errmsg (db);
return (0);
}
else {
std::cout << "Opened database successfully\n";
}
std::string newName;
std::cin >> newName;
/* Create SQL statement */
sql = "UPDATE company SET name = newName WHERE id = 1";
/* Execute SQL statement */
rc = sqlite3_exec (db, sql, callback, 0, &zErrMsg);
if (rc != SQLITE_OK) {
std::cout << "SQL error: \n" << zErrMsg;
sqlite3_free (zErrMsg);
}
else {
std::cout << "Records created successfully\n";
}
sqlite3_close (db);
return 0;
}
The user has to input newName and this variable should be used to Update a field in the Database. This way it does not work, because the Sql script is searching for a column. In the internet I found, that I had to use a prepared statement, but I do not know how to implement it.
You start with an sql statement that has placeholders for the parameters that you wish to bind later. Here, I use a single question mark for the placeholder, but there are other options described in the documentation.
std::string sql = "UPDATE company SET name = ? WHERE id = 1";
Then you construct a prepared statement (or "compile", as they say it in sqlite documentation). You'll normally use sqlite_prepare_v2 function, but there are others (for when your statement is encoded in something else than utf-8, for example).
sqlite3_stmt* stmt; // will point to prepared stamement object
sqlite3_prepare_v2(
db, // the handle to your (opened and ready) database
sql.c_str(), // the sql statement, utf-8 encoded
sql.length(), // max length of sql statement
&stmt, // this is an "out" parameter, the compiled statement goes here
nullptr); // pointer to the tail end of sql statement (when there are
// multiple statements inside the string; can be null)
Then you bind the parameter(s). There's a whole bunch of avaliable functions. Which one exactly you use depends on the type
of data that you're binding to the parameter. Here, we bind text, so we use sqlite3_bind_text:
std::string newName = /* get name from user */;
sqlite3_bind_text(
stmt, // previously compiled prepared statement object
1, // parameter index, 1-based
newName.c_str(), // the data
newName.length(), // length of data
SQLITE_STATIC); // this parameter is a little tricky - it's a pointer to the callback
// function that frees the data after the call to this function.
// It can be null if the data doesn't need to be freed, or like in this case,
// special value SQLITE_STATIC (the data is managed by the std::string
// object and will be freed automatically).
So, the prepared statement is ready to go. Now you execute it by passing it to sqlite3_step:
sqlite3_step(stmt); // you'll want to check the return value, read on...
Now, when you step through a statement that's supposed to return rows of a result table, this function will keep returning SQLITE_ROW as long as there are result rows to process, and SQLITE_DONE when there are none left. You can use sqlite3_column_* family of functions to get the single columns from a result row. I'll let you figure this out on your own.
For a simple update statements that you have, sqlite3_step will return SQLITE_DONE on the first call. More info and possible error codes are here.
When it's all done, you finish by destructing the prepared statement.
sqlite3_finalize(stmt);
I hope this should get you started.
I'm currently working on a project where I want to use SQLite to store some data. Everything is working well except when I want to insert new data into the table. When I run the application, I get segmentation fault, but I can't find the problem.
void sqlite(char *id, char *sensorname, char *sensorvalue){
sqlite3 *db;
char *zErrMsg = 0;
int rc;
char *sql;
const char* data = "Callback function called";
/* Open database */
rc = sqlite3_open("/home/macho/Documents/sensor_database.db", &db);
if( rc ){
fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
exit(0);
}else{
fprintf(stderr, "Opened database successfully\n");
}
sql = "INSERT INTO sensors (id,sensorname,sensorvalue) VALUES(";
char* split = ",";
strcat(sql, id);
strcat(sql, ",");
strcat(sql, sensorname);
strcat(sql, ",");
strcat(sql, sensorvalue);
strcat(sql, ");");
rc = sqlite3_exec(db, sql, callback, (void*)data, &zErrMsg);
if( rc != SQLITE_OK ){
fprintf(stderr, "SQL error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
}else{
fprintf(stdout, "Operation done successfully\n");
}
sqlite3_close(db);
}
And in the main, I'm calling the sqlite() function:
sqlite("1","sensor","sensor1");
Any idea what the problem can be?
Thanks!
You assign sql a static (read-only) string and then attempt to append to it. Instead, create a large writeable array either on the stack or use malloc and then assemble your query in that. So
char sql[4096];
strcpy(sql, "INSERT INTO sensors ...
...
Note that you should check for overflow of the buffer based on the lengths of the values.
BTW, the code as written is just asking for an SQL injection attack if accessible to users. Look up Bobby Tables.
I am trying to merge two SQL databases with same schema using C programming. As I am new to SQL, I tried the below code to merge using C programming.
But I could not merge it as I could not pass the file path in the sql statement in runtime. How to pass the file path in the below sql statement so that I can merge.
/Create SQL statement to merge two DBs/
rc = sqlite3_open(argv[1], &db); /*-----> argv[1] is old_student.db*/
if( rc )
{
printf("Can't open database: %s\n", sqlite3_errmsg(db));
exit(0);
}
else
{
printf("Opened database successfully\n");
}
/*Create SQL statement to merge two DBs*/
sql = "attach '<file_path_name>' as toMerge;\ /*--------> how to pass file path here in runtime */
BEGIN;\
insert or ignore into student_table (Name, AGE, Address)\
select Name, AGE, Address from toMerge.student_table;\
COMMIT;";
/* Execute SQL statement */
rc = sqlite3_exec(db, sql, callback, (void*)data, &zErrMsg);
if( rc != SQLITE_OK )
{
printf("SQL error: %s\n", zErrMsg);
sqlite3_free(zErrMsg);
}
else
{
printf("%s merged New_student.db successfully with old_student.db\n");
}
sqlite3_close(db);
Please let me know how should i pass the parameter in SQL statement in runtime.
Your help is highly appreciated.
Thanks in advance.
This can be accomplished by declaring the sql array as a VLA, and then using sprintf to construct the query as shown below.
static const char *prefix = "attach '";
static const char *suffix =
"' as toMerge;"
" BEGIN;"
" insert or ignore into student_table (Name, AGE, Address)"
" select Name, AGE, Address from toMerge.student_table;"
" COMMIT;";
int length = strlen(prefix) + strlen(argv[1]) + strlen(suffix) + 1;
char sql[length];
sprintf( sql, "%s%s%s", prefix, argv[1], suffix );
Summary: I'm trying to write a text string to a column of type varchar(max) using ODBC and SQL Server 2005. It fails if the length of the string is greater than 8000. Help!
I have some C++ code that uses ODBC (SQL Native Client) to write a text string to a table. If I change the column from, say, varchar(100) to varchar(max) and try to write a string with length greater than 8000, the write fails with the following error
[Microsoft][ODBC SQL Server
Driver]String data, right truncation
So, can anyone advise me on if this can be done, and how?
Some example (not production) code that shows what I'm trying to do:
SQLHENV hEnv = NULL;
SQLRETURN iError = SQLAllocEnv(&hEnv);
HDBC hDbc = NULL;
SQLAllocConnect(hEnv, &hDbc);
const char* pszConnStr = "Driver={SQL Server};Server=127.0.0.1;Database=MyTestDB";
UCHAR szConnectOut[SQL_MAX_MESSAGE_LENGTH];
SWORD iConnectOutLen = 0;
iError = SQLDriverConnect(hDbc, NULL, (unsigned char*)pszConnStr,
SQL_NTS, szConnectOut,
(SQL_MAX_MESSAGE_LENGTH-1), &iConnectOutLen,
SQL_DRIVER_COMPLETE);
HSTMT hStmt = NULL;
iError = SQLAllocStmt(hDbc, &hStmt);
const char* pszSQL = "INSERT INTO MyTestTable (LongStr) VALUES (?)";
iError = SQLPrepare(hStmt, (SQLCHAR*)pszSQL, SQL_NTS);
char* pszBigString = AllocBigString(8001);
iError = SQLSetParam(hStmt, 1, SQL_C_CHAR, SQL_VARCHAR, 0, 0, (SQLPOINTER)pszBigString, NULL);
iError = SQLExecute(hStmt); // Returns SQL_ERROR if pszBigString len > 8000
The table MyTestTable contains a single colum defined as varchar(max). The function AllocBigString (not shown) creates a string of arbitrary length.
I understand that previous versions of SQL Server had an 8000 character limit to varchars, but not why is this happening in SQL 2005?
Thanks,
Andy
You sure you load the SQL Native Driver for 2005, not the old driver for 2000? The native driver name is {SQL Server Native Client 10.0} for 2k8 or {SQL Native Client} for 2k5
The error message ODBC SQL Server Driver seem to indicate the old 2k driver (I may be wrong, haven't touch ODBC in like 10 years now).
Turns out that although the fix works for SQLSetParam, it does not work for SQLBindParameter.
For example:
int iLength = 18001;
char* pszBigString = new char[iLength + 1];
memset(pszBigString, 'a', iLength);
pszBigString[iLength] = 0;
LONG_PTR lLength = SQL_NTS;
::SQLBindParameter(hStmt, 1, SQL_PARAM_INPUT,
SQL_C_CHAR,
SQL_VARCHAR,
iLength, 0, pszBigString, iLength * sizeof(TCHAR),
&lLength);
will result in the same 22001 "String data, right truncation" error, regardless of which driver is used.
In fact, my experiments have shown that you do not actually need to install version 10 of the client driver. Instead you should use SQL_LONGVARCHAR instead of SQL_VARCHAR if you expect the lengths of your strings to exceed 8000 characters. You could potentially perform a mass find-and-replace, but it's possible that using SQL_LONGVARCHAR might incur some sort of penalty (although that's pure speculation; it's an 'extended datatype').
I have tested this successfully with both drivers on Windows XP:
{SQL Server} 2000.85.1117.00 (04/08/2004)
{SQL Server Native Client 10.0} 2007.100.1600.22 (10/07/2008)
I'm trying to use SQLBindParameter to prepare my driver for input via SQLPutData. The field in the database is a TEXT field. My function is crafted based on MS's example here:
http://msdn.microsoft.com/en-us/library/ms713824(VS.85).aspx.
I've setup the environment, made the connection, and prepared my statement successfully but when I call SQLBindParam (using code below) it consistently fails reporting: [Microsoft][SQL Native Client]Invalid precision value
int col_num = 1;
SQLINTEGER length = very_long_string.length( );
retcode = SQLBindParameter( StatementHandle,
col_num,
SQL_PARAM_INPUT,
SQL_C_BINARY,
SQL_LONGVARBINARY,
NULL,
NULL,
(SQLPOINTER) col_num,
NULL,
&length );
The above relies on the driver in use returning "N" for the SQL_NEED_LONG_DATA_LEN information type in SQLGetInfo. My driver returns "Y". How do I bind so that I can use SQLPutData?
Though it doesn't look just like the documentation's example code, I found the following solution to work for what I'm trying to accomplish. Thanks gbjbaanb for making me retest my input combinations to SQLBindParameter.
SQLINTEGER length;
RETCODE retcode = SQLBindParameter( StatementHandle,
col_num, // position of the parameter in the query
SQL_PARAM_INPUT,
SQL_C_CHAR,
SQL_VARCHAR,
data_length, // size of our data
NULL, // decimal precision: not used our data types
&my_string, // SQLParamData will return this value later to indicate what data it's looking for so let's pass in the address of our std::string
data_length,
&length ); // it needs a length buffer
// length in the following operation must still exist when SQLExecDirect or SQLExecute is called
// in my code, I used a pointer on the heap for this.
length = SQL_LEN_DATA_AT_EXEC( data_length );
After a statement is executed, you can use SQLParamData to determine what data SQL wants you to send it as follows:
std::string* my_string;
// set string pointer to value given to SQLBindParameter
retcode = SQLParamData( StatementHandle, (SQLPOINTER*) &my_string );
Finally, use SQLPutData to send the contents of your string to SQL:
// send data in chunks until everything is sent
SQLINTEGER len;
for ( int i(0); i < my_string->length( ); i += CHUNK_SIZE )
{
std::string substr = my_string->substr( i, CHUNK_SIZE );
len = substr.length( );
retcode = SQLPutData( StatementHandle, (SQLPOINTER) substr.c_str( ), len );
}
you're passing NULL as the buffer length, this is an in/out param that shoudl be the size of the col_num parameter. Also, you should pass a value for the ColumnSize or DecimalDigits parameters.
http://msdn.microsoft.com/en-us/library/ms710963(VS.85).aspx