libDatabase
/* $Id$ */
/* Copyright (c) 2012-2019 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS Database libDatabase */
/* 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, version 3 of the License.
*
* 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, see <http://www.gnu.org/licenses/>. */
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <sqlite3.h>
#include <System.h>
#include "Database/engine.h"
/* SQLite3 */
/* private */
/* types */
typedef struct _DatabaseEngine
{
sqlite3 * handle;
} SQLite3;
typedef struct _DatabaseEngineStatement
{
sqlite3_stmt * stmt;
} SQLite3Statement;
/* protected */
/* prototypes */
/* plug-in */
static SQLite3 * _sqlite3_init(Config * config, char const * section);
static void _sqlite3_destroy(SQLite3 * sqlite3);
static int64_t _sqlite3_get_last_id(SQLite3 * sqlite3);
static int _sqlite3_query(SQLite3 * sqlite3, char const * query,
DatabaseCallback callback, void * data);
static SQLite3Statement * _sqlite3_statement_new(SQLite3 * sqlite3,
char const * query);
static void _sqlite3_statement_delete(SQLite3 * sqlite3,
SQLite3Statement * statement);
static int _sqlite3_statement_query(SQLite3 * sqlite3,
SQLite3Statement * statement, DatabaseCallback callback,
void * data, va_list args);
/* public */
/* variables */
DatabaseEngineDefinition database =
{
"SQLite3",
NULL,
_sqlite3_init,
_sqlite3_destroy,
_sqlite3_get_last_id,
_sqlite3_query,
_sqlite3_statement_new,
_sqlite3_statement_delete,
_sqlite3_statement_query
};
/* private */
/* functions */
/* _sqlite3_init */
static SQLite3 * _sqlite3_init(Config * config, char const * section)
{
SQLite3 * sqlite3;
char const * name;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, section);
#endif
if((sqlite3 = object_new(sizeof(*sqlite3))) == NULL)
return NULL;
sqlite3->handle = NULL;
if((name = config_get(config, section, "filename")) != NULL
&& sqlite3_open(name, &sqlite3->handle) != SQLITE_OK)
error_set_code(1, "%s: %s", name, (sqlite3->handle != NULL)
? sqlite3_errmsg(sqlite3->handle)
: "Unknown error");
if(sqlite3->handle == NULL)
{
_sqlite3_destroy(sqlite3);
return NULL;
}
return sqlite3;
}
/* _sqlite3_destroy */
static void _sqlite3_destroy(SQLite3 * sqlite3)
{
if(sqlite3->handle != NULL)
sqlite3_close(sqlite3->handle);
object_delete(sqlite3);
}
/* accessors */
/* _sqlite3_get_last_id */
static int64_t _sqlite3_get_last_id(SQLite3 * sqlite3)
{
/* XXX returns an int64_t */
return sqlite3_last_insert_rowid(sqlite3->handle);
}
/* useful */
/* _sqlite3_statement_new */
static SQLite3Statement * _sqlite3_statement_new(SQLite3 * sqlite3,
char const * query)
{
SQLite3Statement * statement;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, query);
#endif
if((statement = object_new(sizeof(*statement))) == NULL)
return NULL;
if(sqlite3_prepare_v2(sqlite3->handle, query, -1, &statement->stmt,
NULL) != SQLITE_OK)
{
error_set_code(1, "%s", sqlite3_errmsg(sqlite3->handle));
object_delete(statement);
return NULL;
}
return statement;
}
/* _sqlite3_statement_delete */
static void _sqlite3_statement_delete(SQLite3 * sqlite3,
SQLite3Statement * statement)
{
(void) sqlite3;
/* XXX ignore errors */
sqlite3_finalize(statement->stmt);
object_delete(statement);
}
/* _sqlite3_statement_query */
static int _sqlite3_statement_query(SQLite3 * sqlite3,
SQLite3Statement * statement, DatabaseCallback callback,
void * data, va_list args)
{
int ret = 0;
int type;
char const * name;
int i;
int cnt;
int argc;
char ** argv;
char ** columns;
char ** p;
int l;
time_t t;
struct tm tm;
char buf[32];
char const * s;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
sqlite3_reset(statement->stmt);
while(ret == 0 && (type = va_arg(args, int)) != -1)
{
name = va_arg(args, char const *);
if((i = sqlite3_bind_parameter_index(statement->stmt, name))
== 0)
{
ret = -error_set_code(1, "%s", "Unknown parameter");
break;
}
switch(type)
{
case DT_NULL:
if((s = va_arg(args, void *)) != NULL)
ret = -error_set_code(1, "%s",
strerror(EINVAL));
else
sqlite3_bind_null(statement->stmt, i);
break;
case DT_INTEGER:
l = va_arg(args, int);
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s() %s=\"%ld\"\n",
__func__, name, l);
#endif
if(sqlite3_bind_int(statement->stmt, i, l)
== SQLITE_OK)
break;
ret = -error_set_code(1, "%s", sqlite3_errmsg(
sqlite3->handle));
break;
case DT_TIMESTAMP:
t = va_arg(args, time_t);
if(gmtime_r(&t, &tm) == NULL)
break;
if(strftime(buf, sizeof(buf), "%Y-%m-%d"
" %H:%M:%S", &tm) == 0)
{
ret = -error_set_code(1, "%s", strerror(
errno));
break;
}
if(sqlite3_bind_text(statement->stmt, i, buf,
-1, SQLITE_TRANSIENT)
== SQLITE_OK)
break;
ret = -error_set_code(1, "%s", sqlite3_errmsg(
sqlite3->handle));
break;
case DT_VARCHAR:
if((s = va_arg(args, char const *)) == NULL)
{
ret = -error_set_code(1, "%s",
strerror(EINVAL));
break;
}
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s() %s=\"%s\" (%d)\n",
__func__, name, s, i);
#endif
if(sqlite3_bind_text(statement->stmt, i, s, -1,
SQLITE_STATIC)
== SQLITE_OK)
break;
ret = -error_set_code(1, "%s", sqlite3_errmsg(
sqlite3->handle));
break;
default:
ret = -error_set_code(1, "%s (%d)",
"Unsupported type", type);
break;
}
}
if(ret != 0)
return ret;
/* return directly if there are no results */
if((argc = sqlite3_column_count(statement->stmt)) == 0)
{
/* FIXME should really execute until done */
if(sqlite3_step(statement->stmt) != SQLITE_DONE)
ret = -error_set_code(1, "%s", sqlite3_errmsg(
sqlite3->handle));
return ret;
}
/* pre-allocate the results */
argv = malloc(sizeof(*argv) * argc);
columns = malloc(sizeof(*columns) * argc);
if(argv == NULL || columns == NULL)
{
free(argv);
free(columns);
return -error_set_code(1, "%s", strerror(errno));
}
/* obtain the column names */
for(i = 0; i < argc; i++)
/* XXX may fail */
columns[i] = strdup(sqlite3_column_name(statement->stmt, i));
for(cnt = 0; (i = sqlite3_step(statement->stmt)) == SQLITE_ROW; cnt++)
{
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s() cnt=%d\n", __func__, cnt);
#endif
/* reset the result line */
memset(argv, 0, sizeof(*argv) * argc);
for(i = 0; (type = sqlite3_column_type(statement->stmt, i))
!= SQLITE_NULL; i++)
{
switch(type)
{
case SQLITE_INTEGER:
l = sqlite3_column_int(statement->stmt,
i);
snprintf(buf, sizeof(buf), "%d", l);
s = buf;
break;
case SQLITE_TEXT:
s = (char const *)sqlite3_column_text(
statement->stmt, i);
break;
case SQLITE_BLOB:
case SQLITE_FLOAT:
default:
/* FIXME really implement */
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s() i=%d\n",
__func__, i);
#endif
continue;
}
p = &argv[i];
/* XXX check errors */
*p = strdup(s);
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s() argv[%d]=\"%s\"\n",
__func__, i, argv[i]);
#endif
}
/* call the callback */
if(ret == 0 && callback != NULL)
callback(data, argc, argv, columns);
for(i = 0; i < argc; i++)
free(argv[i]);
}
free(argv);
/* call the callback with no values */
if(cnt == 0 && callback != NULL)
callback(data, argc, NULL, columns);
for(l = 0; l < argc; l++)
free(columns[l]);
free(columns);
if(ret == 0 && i != SQLITE_DONE)
ret = -error_set_code(1, "%s", sqlite3_errmsg(sqlite3->handle));
return ret;
}
/* _sqlite3_query */
int _sqlite3_query(SQLite3 * sqlite3, char const * query,
DatabaseCallback callback, void * data)
{
int ret;
char * error = NULL;
#ifdef DEBUG
fprintf(stderr, "DEBUG: %s(\"%s\")\n", __func__, query);
#endif
ret = (sqlite3_exec(sqlite3->handle, query, callback, data, &error)
== SQLITE_OK) ? 0 : -error_set_code(1, "%s",
(error != NULL) ? error : "Unknown error");
if(error != NULL)
sqlite3_free(error);
return ret;
}