gambas-source-code/gb.db.sqlite3/src/helper.c
gambas c1bc03ccfa Multiple statements in the same query does not leak memory anymore.
[GB.DB.SQLITE3]
* BUG: Multiple statements in the same query does not leak memory anymore. In that case, the result of the last statement is returned.
2019-05-14 01:25:51 +02:00

533 lines
12 KiB
C

/***************************************************************************
helper.c
(c) 2000-2017 Benoît Minisini <g4mba5@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
MA 02110-1301, USA.
***************************************************************************/
#define __HELPER_C
#include "helper.h"
#include "gb_buffer.h"
//--------------------------------------------------------------------------
static int _last_error = SQLITE_OK;
SQLITE_DATABASE *sqlite_open_database(const char *path, const char *host)
{
SQLITE_DATABASE *db;
sqlite3 *handle;
if (!path)
path = ":memory:";
_last_error = sqlite3_open(path, &handle);
if (_last_error == SQLITE_OK)
{
GB.Alloc(POINTER(&db), sizeof(SQLITE_DATABASE));
db->handle = handle;
db->path = GB.NewZeroString(path);
db->host = GB.NewZeroString(host);
db->error = SQLITE_OK;
return db;
}
else
return NULL;
}
void sqlite_close_database(SQLITE_DATABASE *db)
{
sqlite3_close(db->handle);
GB.FreeString(&db->path);
GB.FreeString(&db->host);
GB.Free(POINTER(&db));
}
const char *sqlite_get_error_message(SQLITE_DATABASE *db)
{
const char *error;
int err;
err = db ? db->error : _last_error;
switch (err)
{
case SQLITE_OK:
error = "Successful result";
break;
case SQLITE_ERROR:
error = sqlite3_errmsg(db->handle);
break;
case SQLITE_INTERNAL:
error = "Internal logic error - Report this error on the mailing-list at sqlite.org";
break;
case SQLITE_PERM:
error = "Access permission denied";
break;
case SQLITE_ABORT:
error = "Callback routine requested an abort";
break;
case SQLITE_BUSY:
error = "The database file is locked";
break;
case SQLITE_LOCKED:
error = "A table in the database is locked";
break;
case SQLITE_NOMEM:
error = "Out of memory";
break;
case SQLITE_READONLY:
error = "Attempt to write a readonly database";
break;
case SQLITE_INTERRUPT:
error = "Operation terminated by sqlite_interrupt()";
break;
case SQLITE_IOERR:
error = "Some kind of disk I/O error occurred";
break;
case SQLITE_CORRUPT:
error = "The database disk image is malformed";
break;
case SQLITE_NOTFOUND:
error = "(Internal Only) Table or record not found";
break;
case SQLITE_FULL:
error = "Insertion failed because database is full";
break;
case SQLITE_CANTOPEN:
error = "Unable to open the database file";
break;
case SQLITE_PROTOCOL:
error = "Database lock protocol error";
break;
case SQLITE_EMPTY:
error = "(Internal Only) Database table is empty";
break;
case SQLITE_SCHEMA:
error = "The database schema changed";
break;
case SQLITE_TOOBIG:
error = "Too much data for one row of a table";
break;
case SQLITE_CONSTRAINT:
error = "Abort due to constraint violation";
break;
case SQLITE_MISMATCH:
error = "Data type mismatch";
break;
default:
error = "Undefined SQLite error";
}
return error;
}
//--------------------------------------------------------------------------
GB_TYPE sqlite_get_type(const char *type, int *length)
{
int i;
char *upper;
GB_TYPE gtype;
char *left, *right;
if (length)
*length = 0;
if (!type || !*type)
return GB_T_STRING;
upper = GB.NewZeroString(type);
for (i = 0; i < GB.StringLength(upper); i++)
upper[i] = toupper(upper[i]);
type = upper;
if (strstr(type, "CHAR(") /* note the opening bracket */
|| strstr(type, "CLOB") || strstr(type, "TEXT") /* also catches TINYTEXT */
|| strstr(type, "VARCHAR") || strstr(type, "VARYING CHAR")
|| strstr(type, "ENUM") || strstr(type, "SET") || strstr(type, "YEAR"))
{ /* MySQL 2 or 4 digit year (string) */
gtype = GB_T_STRING;
}
else if (strstr(type, "CHAR") /* this is a 1-byte value */
|| strstr(type, "TINYINT")
|| strstr(type, "INT1") || strstr(type, "BOOL"))
{
gtype = GB_T_BOOLEAN;
}
else if (strstr(type, "SMALLINT") || strstr(type, "INT2"))
{
gtype = GB_T_INTEGER;
}
else if (strstr(type, "MEDIUMINT"))
{
gtype = GB_T_INTEGER;
}
else if (strstr(type, "BIGINT") || strstr(type, "INT8"))
{
gtype = GB_T_LONG;
}
else if (strstr(type, "INTEGER")
|| strstr(type, "INT") || strstr(type, "INT4"))
{
gtype = GB_T_INTEGER;
}
else if (strstr(type, "DECIMAL") || strstr(type, "NUMERIC"))
{
gtype = GB_T_FLOAT;
}
else if (strstr(type, "TIMESTAMP") || strstr(type, "DATETIME"))
{
gtype = GB_T_DATE;
}
else if (strstr(type, "DATE"))
{
gtype = GB_T_DATE;
}
else if (strstr(type, "TIME"))
{
gtype = GB_T_DATE;
}
else if (strstr(type, "DOUBLE") || strstr(type, "FLOAT8"))
{
gtype = GB_T_FLOAT;
}
else if (strstr(type, "REAL") /* this is PostgreSQL "real", not
MySQL "real" which is a
synonym of "double" */
|| strstr(type, "FLOAT")
|| strstr(type, "FLOAT4"))
{
gtype = GB_T_FLOAT;
}
else if (strstr(type, "BLOB")) // BM
{
gtype = DB_T_BLOB;;
}
else
gtype = GB_T_STRING;
if (gtype == GB_T_STRING && type && length != NULL)
{
/* if a length has been defined it will be between () */
right = (char *)rindex(type, ')');
left = (char *)index(type, '(');
if (left && right)
{
*right = 0;
*length = atoi(left + 1);
}
}
GB.FreeString(&upper);
return gtype;
}
//--------------------------------------------------------------------------
static void clear_query(SQLITE_RESULT *result)
{
int i;
if (!result->buffer)
return;
for (i = 0; i < result->ncol; i++)
GB.FreeString(&result->names[i]);
GB.Free(POINTER(&result->names));
GB.Free(POINTER(&result->types));
GB.Free(POINTER(&result->lengths));
GB.FreeArray(&result->values);
BUFFER_delete(&result->buffer);
}
void sqlite_query_free(SQLITE_RESULT *result)
{
clear_query(result);
GB.Free(POINTER(&result));
}
static int my_sqlite3_exec(
sqlite3 *db, /* The database on which the SQL executes */
const char *zSql, /* The SQL to be executed */
SQLITE_RESULT *result,
bool need_types
)
{
int rc = SQLITE_OK;
const char *zLeftover;
sqlite3_stmt *pStmt = 0;
int ncol;
int nRetry = 0;
int nCallback;
int i;
const char *decltype;
int n_unknown = 0;
int type;
//fprintf(stderr, "my_sqlite3_exec: %s\n", zSql);
if( zSql==0 ) return SQLITE_OK;
while( (rc==SQLITE_OK || (rc==SQLITE_SCHEMA && (++nRetry)<2)) && zSql[0] )
{
pStmt = 0;
//fprintf(stderr, "my_sqlite3_exec: sqlite3_prepare_v2 ?\n");
rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover);
if( rc!=SQLITE_OK ){
//fprintf(stderr, "my_sqlite3_exec: sqlite3_prepare_v2 failed: %s\n", sqlite3_errmsg(db));
if( pStmt ) sqlite3_finalize(pStmt);
continue;
}
if( !pStmt ){
/* this happens for a comment or white-space */
zSql = zLeftover;
continue;
}
nCallback = 0;
clear_query(result);
BUFFER_create(&result->buffer);
result->ncol = ncol = sqlite3_column_count(pStmt);
if (ncol > 0)
{
GB.AllocZero(POINTER(&result->names), ncol * sizeof(const char *));
if (need_types)
{
GB.Alloc(POINTER(&result->types), ncol * sizeof(int));
GB.Alloc(POINTER(&result->lengths), ncol * sizeof(int));
}
}
GB.NewArray(POINTER(&result->values), sizeof(int), 0);
for(;;)
{
rc = sqlite3_step(pStmt);
/* Invoke the callback function if required */
if((SQLITE_ROW==rc ||
//(SQLITE_DONE==rc && !nCallback && db->flags&SQLITE_NullCallback)) ){
(SQLITE_DONE==rc && !nCallback && 1)) )
{
if (0 == nCallback)
{
for (i = 0; i < ncol; i++)
result->names[i] = GB.NewZeroString(sqlite3_column_name(pStmt, i));
if (need_types)
{
for (i = 0; i < ncol; i++)
{
decltype = sqlite3_column_decltype(pStmt, i);
if (!decltype)
{
result->types[i] = 0;
result->lengths[i] = 0;
n_unknown++;
}
else
{
result->types[i] = (int)sqlite_get_type(decltype, &result->lengths[i]);
}
}
}
nCallback++;
}
if (rc == SQLITE_ROW)
{
int *addr = GB.Insert(&result->values, -1, ncol * 2);
char *value;
int len;
result->nrow++;
for (i = 0; i < ncol; i++)
{
type = sqlite3_column_type(pStmt, i);
if (n_unknown && result->types[i] == 0)
{
switch(type)
{
case SQLITE_INTEGER: result->types[i] = GB_T_LONG; break;
case SQLITE_FLOAT: result->types[i] = GB_T_FLOAT; break;
case SQLITE_BLOB: result->types[i] = (int)DB_T_BLOB; break;
default: result->types[i] = GB_T_STRING;
}
n_unknown--;
}
if (type == SQLITE_BLOB)
value = (char *)sqlite3_column_blob(pStmt, i);
else
value = (char *)sqlite3_column_text(pStmt, i);
len = sqlite3_column_bytes(pStmt, i);
if (len == 0)
addr[i * 2] = BUFFER_length(result->buffer) - 1;
else
{
addr[i * 2] = BUFFER_add(&result->buffer, value, len + 1);
result->buffer[BUFFER_length(result->buffer) - 1] = 0;
}
addr[i * 2 + 1] = len;
}
}
}
if (rc != SQLITE_ROW)
{
rc = sqlite3_finalize(pStmt);
pStmt = 0;
if (rc != SQLITE_SCHEMA)
{
nRetry = 0;
zSql = zLeftover;
while( isspace((unsigned char)zSql[0]) ) zSql++;
}
break;
}
}
}
if (pStmt)
sqlite3_finalize(pStmt);
return rc;
}
SQLITE_RESULT *sqlite_query_exec(SQLITE_DATABASE *db, const char *query, bool need_types)
{
SQLITE_RESULT *result;
int retry;
int res;
GB.AllocZero(POINTER(&result), sizeof(SQLITE_RESULT));
for (retry = 1; retry <= 2; retry++)
{
res = my_sqlite3_exec(db->handle, query, result, need_types);
if (res != SQLITE_SCHEMA)
break;
}
if (res != SQLITE_OK)
{
db->error = res;
sqlite_query_free(result);
return NULL;
}
else
return result;
}
void sqlite_query_get(SQLITE_RESULT *result, int pos, int col, char **value, int *length)
{
int i;
if (pos < 0 || pos >= result->nrow || col < 0 || col >= result->ncol)
{
*value = NULL;
if (length)
*length = 0;
return;
}
i = pos * result->ncol * 2 + col * 2;
*value = result->buffer + result->values[i];
if (length)
*length = result->values[i + 1];
}
char *sqlite_query_get_string(SQLITE_RESULT *result, int pos, int col)
{
char *value;
sqlite_query_get(result, pos, col, &value, NULL);
return value ? value : "";
}
int sqlite_query_get_int(SQLITE_RESULT *result, int pos, int col)
{
char *value;
sqlite_query_get(result, pos, col, &value, NULL);
if (!value)
return 0;
else
return atoi(value);
}
int sqlite_query_find_field(SQLITE_RESULT *result, const char *name)
{
int i;
char *field;
char *p;
/*fprintf(stderr, "sqlite_query_find_field: %s\n", name);
for (i = 0; i < result->ncol; i++)
fprintf(stderr, "'%s' ", result->names[i]);
fprintf(stderr, "\n");*/
if (strchr(name, '.'))
{
for (i = 0; i < result->ncol; i++)
{
if (strcmp(result->names[i], name) == 0)
return i;
}
}
else
{
for (i = 0; i < result->ncol; i++)
{
field = result->names[i];
p = strchr(field, '.');
if (p)
field = p + 1;
if (strcmp(field, name) == 0)
return i;
}
}
return -1;
}