libdb/lang/sql/odbc/blobtoxy.c
2011-09-13 13:44:24 -04:00

1569 lines
41 KiB
C

/*
* @file blobtoxy.c
* SQLite extension module for read-only BLOB to X/Y mapping
* using SQLite 3.3.x virtual table API plus some useful
* scalar and aggregate functions.
*
* $Id: blobtoxy.c,v 1.15 2007/09/03 12:22:37 chw Exp chw $
*
* Copyright (c) 2007 Christian Werner <chw@ch-werner.de>
*
* See the file "license.terms" for information on usage
* and redistribution of this file and for a
* DISCLAIMER OF ALL WARRANTIES.
*
* Usage:
*
* Master (non-virtual) table:
*
* CREATE TABLE t(
* key INTEGER PRIMARY KEY,
* data BLOB,
* scale DOUBLE,
* offset DOUBLE,
* foo TEXT,
* bar TEXT
* );
*
* BLOB to X/Y mapping:
*
* CREATE VIRTUAL TABLE t1
* USING blobtoxy (t, key, data, short_le, x_scale, x_offset,
* y_scale, y_offset, "foo, bar");
* CREATE VIRTUAL TABLE t2
* USING blobtoxy (t, key, data, uchar, null, null, null, null, 'bar');
* CREATE VIRTUAL TABLE t3
* USING blobtoxy (t, key, data, int_be, 10.0, null, 10.0, null, "foo");
* CREATE VIRTUAL TABLE t4
* USING blobtoxy (t, key, data, float, null, -10, null, 10);
*
* Arguments to "blobtoxy" module:
*
* 0. master table name (required)
* 1. key column in master table (required)
* 2. blob column in master table (required)
* 3. type code (optional), defaults to "char"
* 4. X scale column in master table (optional),
* may be specified as integer or float constant, too,
* to explicitely omit scale, use an empty string ('')
* or 'null'
* 5. X offset column in master table (optional),
* may be specified as integer or float constant, too,
* to explicitely omit offset, use an empty string ('')
* or 'null'
* 6. Y scale column in master table (optional), see point 4.
* 7. Y offset column in master table (optional), see point 5.
* 8. other columns of the master table to appear in the
* result set (optional), must be specified as a
* single or double quoted string with comma
* separated column names as a sequence of named
* columns as it would be written in a SELECT
* statement
*
* Supported data types:
*
* "char" -> BLOB is a signed char array
* "uchar" -> BLOB is an unsigned char array
* "short_le" -> BLOB is a short array little endian
* "short_be" -> BLOB is a short array big endian
* "ushort_le" -> BLOB is an unsigned short array little endian
* "ushort_be" -> BLOB is an unsigned short array big endian
* "int_le" -> BLOB is a int array little endian
* "int_be" -> BLOB is a int array big endian
* "uint_le" -> BLOB is an unsigned int array little endian
* "uint_be" -> BLOB is an unsigned int array big endian
* "float" -> BLOB is a float array
* "double" -> BLOB is a double array
*
* Columns of "blobtoxy" mapped virtual table:
*
* "key" Key column for JOINing with master table
* "x" index within BLOB.
* This value is optionally translated by
* the "x_scale" and "x_offset" columns
* i.e. x' = x * x_scale + x_offset, yielding
* a floating point result.
* "y" BLOB's value at "x"-unscaled-index
* This value is optionally translated by
* the "y_scale" and "y_offset" columns
* i.e. y' = y * y_scale + y_offset, yielding
* a floating point result.
* ... Other columns, see above
*
* If the "key" field of the master table is an integer data
* type, it is used as the ROWID of the mapped virtual table.
* Otherwise the ROWID is a 0-based counter of the output rows.
*
*
* Exported SQLite functions (svg|tk)_path[_from_blob], blt_vec_(x|y)
*
* Scalar context:
*
* svg_path_from_blob(data, type, x_scale, x_offset, y_scale, y_offset)
* tk_path_from_blob(data, type, x_scale, x_offset, y_scale, y_offset)
* blt_vec_(x|y)(data, type, x_scale, x_offset, y_scale, y_offset)
* tk3d_path_from_blob(data, type, x_scale, x_offset, y_scale, y_offset,
* z_value, z_scale, z_offset)
*
* Like BLOB to X/Y mapping but produces SVG or Tk Canvas
* path/polyline as a string, e.g.
*
* SVG: "M 1 1 L 2 2 L 3 7 L 4 1
* Tk Canvas: "1 1 2 2 3 7 4 1"
* BLT Vector X: "1 2 3 4"
* BLT Vector Y: "1 2 7 1"
* Tk 3D Canvas: "1 1 0 2 2 0 3 7 0 4 1 0"
*
* Arguments:
*
* 0. blob data (required); this parameter is always
* interpreted as blob. It must contain at least
* two elements, otherwise the function's result
* is NULL to indicate that nothing need be drawn
* 1. type code (optional), defaults to "char"
* 2. X scale (optional), see above
* 3. X offset (optional), see above
* 4. Y scale (optional), see above
* 5. Y offset (optional), see above
* 6. Z value (optional)
* 8. Z scale (optional)
* 9. Z offset (optional)
*
* Aggregate context:
*
* svg_path(xdata, ydata, x_scale, x_offset, y_scale, y_offset)
* tk_path(xdata, ydata, x_scale, x_offset, y_scale, y_offset)
* blt_vec(data, scale, offset)
* tk3d_path(xdata, ydata, x_scale, x_offset, y_scale, y_offset,
* zdata, z_scale, z_offset)
*
* Same behaviour except that xdata/ydata/data/zdata are interpreted
* directly as numeric values.
*
*
* Exported SQLite function subblob
*
* subblob(data, start, length, size, skip)
*
* Works somewhat like substr, e.g.
*
* select quote(subblob(X'0A0B0C0D0E0F0001',2,2,1,3))
* -> X'0B0F'
*
* Arguments:
*
* 0. blob data (required); this parameter is always
* interpreted as blob.
* 1. start offset (required) in bytes as in substr
* function (1-based, negative offsets count from end)
* 2. length (required) in bytes to be copied
* 3. size (optional) of items in bytes to be copied
* in combination with skip argument
* 4. skip (optional) in bytes after one item of
* size argument has been copied
*
*
* Exported SQLite function rownumber
*
* rownumber(any)
*
* Returns the row number counting from 0 in simple
* selects. An arbitrary dummy but constant argument
* must be provided to this function in order to satisfy
* some needs of the SQLite3 C API, e.g.
*
* rownumber(0), rownumber('foo') right
* rownumber(column_name) wrong, will yield always 0
*
*/
#ifdef STANDALONE
#include <sqlite3.h>
#else
#include <sqlite3ext.h>
SQLITE_EXTENSION_INIT1
#endif
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#ifdef _WIN32
#define strcasecmp _stricmp
#define strncasecmp _strnicmp
#define vsnprintf _vsnprintf
#endif
#define TYPE_CODE(num, type) (((num) << 8) | (sizeof (type)))
#define TYPE_SIZE(code) ((code) & 0xFF)
#define TYPE_CHAR TYPE_CODE( 0, char)
#define TYPE_UCHAR TYPE_CODE( 1, short)
#define TYPE_SHORT_LE TYPE_CODE( 2, short)
#define TYPE_USHORT_LE TYPE_CODE( 3, short)
#define TYPE_SHORT_BE TYPE_CODE( 4, short)
#define TYPE_USHORT_BE TYPE_CODE( 5, short)
#define TYPE_INT_LE TYPE_CODE( 6, int)
#define TYPE_UINT_LE TYPE_CODE( 7, int)
#define TYPE_INT_BE TYPE_CODE( 8, int)
#define TYPE_UINT_BE TYPE_CODE( 9, int)
#define TYPE_FLOAT TYPE_CODE(10, float)
#define TYPE_DOUBLE TYPE_CODE(11, double)
typedef struct b2xy_table {
sqlite3_vtab base; /* SQLite's base virtual table struct */
sqlite3 *db; /* Open database */
char *master_table; /* Table where to fetch BLOB from */
char *fq_master_table; /* Fully qualified master_table */
char *key_column; /* Name of key column */
char *blob_column; /* Name of BLOB column */
char *x_scale_column; /* Name of column giving X scale or NULL */
char *x_offset_column; /* Name of column giving X offset or NULL */
char *y_scale_column; /* Name of column giving Y scale or NULL */
char *y_offset_column; /* Name of column giving Y offset or NULL */
char *other_columns; /* Other columns or empty string */
int type; /* Data type of BLOB */
int argc; /* Number arguments from b2xy_create() call */
char **argv; /* Argument vector from b2xy_create() call */
} b2xy_table;
typedef struct b2xy_cursor {
sqlite3_vtab_cursor base; /* SQLite's base cursor struct */
b2xy_table *table; /* Link to table struct */
sqlite3_stmt *select; /* Prepared SELECT statement or NULL */
sqlite3_value *key; /* Value of current key */
int fix_cols; /* Fixed number of columns of result set */
int num_cols; /* Total number of columns of result set */
char *val; /* Value of current BLOB */
int val_len; /* Length of current BLOB */
int x_scale_col; /* Column number of X scale or 0 */
int x_offset_col; /* Column number of X offset or 0 */
double x_scale, x_offset; /* Current X scale and offset */
int y_scale_col; /* Column number of Y scale or 0 */
int y_offset_col; /* Column number of Y offset or 0 */
double y_scale, y_offset; /* Current X scale and offset */
int do_x_scale; /* If true, use X scale and offset */
int do_y_scale; /* If true, use Y scale and offset */
int type; /* Data type of BLOB */
int index; /* Current index in BLOB */
int rowid_from_key; /* When true, ROWID used from key column */
sqlite_int64 rowid; /* Current ROWID */
} b2xy_cursor;
static int
string_to_type(const char *str)
{
if (strcasecmp(str, "char") == 0) {
return TYPE_CHAR;
}
if (strcasecmp(str, "uchar") == 0) {
return TYPE_UCHAR;
}
if (strcasecmp(str, "short_le") == 0) {
return TYPE_SHORT_LE;
}
if (strcasecmp(str, "ushort_le") == 0) {
return TYPE_USHORT_LE;
}
if (strcasecmp(str, "short_be") == 0) {
return TYPE_SHORT_BE;
}
if (strcasecmp(str, "ushort_be") == 0) {
return TYPE_USHORT_BE;
}
if (strcasecmp(str, "int_le") == 0) {
return TYPE_INT_LE;
}
if (strcasecmp(str, "uint_le") == 0) {
return TYPE_UINT_LE;
}
if (strcasecmp(str, "int_be") == 0) {
return TYPE_INT_BE;
}
if (strcasecmp(str, "uint_be") == 0) {
return TYPE_UINT_BE;
}
if (strcasecmp(str, "float") == 0) {
return TYPE_FLOAT;
}
if (strcasecmp(str, "double") == 0) {
return TYPE_DOUBLE;
}
return 0;
}
static int
b2xy_destroy(sqlite3_vtab *vtab)
{
b2xy_table *bt = (b2xy_table *) vtab;
sqlite3_free(bt);
return SQLITE_OK;
}
static int
b2xy_create(sqlite3 *db, void *userdata, int argc,
const char *const*argv, sqlite3_vtab **vtabret, char **errp)
{
int rc = SQLITE_NOMEM;
b2xy_table *bt;
int i, size, type = TYPE_CHAR;
/*
* argv[0] - module name
* argv[1] - database name
* argv[2] - table name (virtual table)
* argv[3] - master table name (required)
* argv[4] - key column (required)
* argv[5] - blob column (required)
* argv[6] - type code (optional)
* argv[7] - X scale column (optional)
* argv[8] - X offset column (optional)
* argv[9] - Y scale column (optional)
* argv[10] - Y offset column (optional)
* argv[11] - other columns (optional)
*/
if (argc < 6) {
*errp = sqlite3_mprintf("need at least 3 arguments");
return SQLITE_ERROR;
}
if (argc > 6) {
type = string_to_type(argv[6]);
if (!type) {
*errp = sqlite3_mprintf("unsupported type %Q", argv[6]);
return SQLITE_ERROR;
}
}
if (argc > 11) {
if (argv[11][0] != '"' && argv[11][0] != '\'') {
*errp = sqlite3_mprintf("other columns must be quoted");
return SQLITE_ERROR;
}
}
size = sizeof (char *) * argc;
for (i = 0; i < argc; i++) {
size += argv[i] ? (strlen(argv[i]) + 1) : 0;
}
/* additional space for '"' + argv[1] '"."' + argv[3] + '"' */
size += argv[1] ? (strlen(argv[1]) + 3) : 3;
size += argv[3] ? (strlen(argv[3]) + 3) : 0;
bt = sqlite3_malloc(sizeof (b2xy_table) + size);
if (bt) {
char *p, *key_type = 0, *x_type, *y_type, *other_types = 0;
memset(bt, 0, sizeof (b2xy_table) + size);
bt->db = db;
bt->type = type;
bt->argc = argc;
bt->argv = (char **) (bt + 1);
p = (char *) (bt->argv + argc);
for (i = 0; i < argc; i++) {
if (argv[i]) {
bt->argv[i] = p;
strcpy(p, argv[i]);
p += strlen(p) + 1;
}
}
bt->master_table = bt->argv[3];
bt->fq_master_table = p;
p[0] = '\0';
if (bt->argv[1]) {
strcat(p, "\"");
strcat(p, bt->argv[1]);
strcat(p, "\".");
}
if (bt->argv[3]) {
strcat(p, "\"");
strcat(p, bt->argv[3]);
strcat(p, "\"");
}
bt->key_column = bt->argv[4];
bt->blob_column = bt->argv[5];
if (bt->argc > 7 && bt->argv[7][0]) {
bt->x_scale_column = bt->argv[7];
if (strcasecmp(bt->x_scale_column, "null") == 0) {
bt->x_scale_column = 0;
}
}
if (bt->argc > 8 && bt->argv[8][0]) {
bt->x_offset_column = bt->argv[8];
if (strcasecmp(bt->x_offset_column, "null") == 0) {
bt->x_offset_column = 0;
}
}
if (bt->argc > 9 && bt->argv[9][0]) {
bt->y_scale_column = bt->argv[9];
if (strcasecmp(bt->y_scale_column, "null") == 0) {
bt->y_scale_column = 0;
}
}
if (bt->argc > 10 && bt->argv[10][0]) {
bt->y_offset_column = bt->argv[10];
if (strcasecmp(bt->y_offset_column, "null") == 0) {
bt->y_offset_column = 0;
}
}
if (bt->argc > 11) {
p = bt->argv[11];
p[0] = ',';
bt->other_columns = p;
p += strlen(p) - 1;
if (*p == '"' || *p == '\'') {
*p = '\0';
}
} else {
bt->other_columns = "";
}
/* find out types of key and x/y columns */
if (bt->x_scale_column || bt->x_offset_column ||
bt->type == TYPE_FLOAT || bt->type == TYPE_DOUBLE) {
x_type = " DOUBLE";
} else {
x_type = " INTEGER";
}
if (bt->y_scale_column || bt->y_offset_column ||
bt->type == TYPE_FLOAT || bt->type == TYPE_DOUBLE) {
y_type = " DOUBLE";
} else {
y_type = " INTEGER";
}
p = sqlite3_mprintf("PRAGMA %Q.table_info(%Q)",
bt->argv[1] ? bt->argv[1] : "MAIN",
bt->master_table);
if (p) {
int nrows = 0, ncols = 0;
char **rows = 0;
rc = sqlite3_get_table(db, p, &rows, &nrows, &ncols, 0);
sqlite3_free(p);
if (rc == SQLITE_OK) {
for (i = 1; ncols >= 3 && i <= nrows; i++) {
p = rows[i * ncols + 1];
if (p && strcasecmp(bt->key_column, p) == 0) {
key_type = sqlite3_mprintf(" %s", rows[i * ncols + 2]);
break;
}
}
}
if (rows) {
sqlite3_free_table(rows);
}
}
/* find out types of other columns */
p = 0;
if (bt->other_columns[0]) {
p = sqlite3_mprintf("SELECT %s FROM %s WHERE 0",
bt->other_columns + 1, bt->fq_master_table);
}
if (p) {
sqlite3_stmt *stmt = 0;
#if defined(HAVE_SQLITE3PREPAREV2) && HAVE_SQLITE3PREPAREV2
rc = sqlite3_prepare_v2(db, p, -1, &stmt, 0);
#else
rc = sqlite3_prepare(db, p, -1, &stmt, 0);
#endif
sqlite3_free(p);
if (rc == SQLITE_OK && stmt) {
sqlite3_step(stmt);
for (i = 0; i < sqlite3_column_count(stmt); i++) {
p = sqlite3_mprintf("%s%s\"%s\" %s",
other_types ? other_types : "",
other_types ? "," : "",
sqlite3_column_name(stmt, i),
sqlite3_column_decltype(stmt, i));
sqlite3_free(other_types);
other_types = 0;
if (p) {
other_types = p;
} else {
break;
}
}
sqlite3_finalize(stmt);
if (other_types) {
p = sqlite3_mprintf(",%s", other_types);
sqlite3_free(other_types);
other_types = p;
}
}
}
p = sqlite3_mprintf("CREATE TABLE \"%s\"(key%s CONSTRAINT fk "
"REFERENCES \"%s\"(\"%s\"),x%s,y%s%s)",
argv[2], key_type ? key_type : "",
bt->master_table, bt->key_column,
x_type, y_type,
other_types ? other_types : bt->other_columns);
if (key_type) {
sqlite3_free(key_type);
}
if (other_types) {
sqlite3_free(other_types);
}
if (p) {
rc = sqlite3_declare_vtab(db, p);
sqlite3_free(p);
}
if (rc != SQLITE_OK) {
b2xy_destroy((sqlite3_vtab *) bt);
bt = 0;
}
}
*vtabret = (sqlite3_vtab *) bt;
return rc;
}
static int
b2xy_open(sqlite3_vtab *vtab, sqlite3_vtab_cursor **curret)
{
int rc = SQLITE_NOMEM;
b2xy_table *bt = (b2xy_table *) vtab;
b2xy_cursor *bc;
bc = sqlite3_malloc(sizeof (b2xy_cursor));
if (bc) {
memset(bc, 0, sizeof(b2xy_cursor));
bc->table = bt;
bc->type = bt->type;
*curret = (sqlite3_vtab_cursor *) bc;
rc = SQLITE_OK;
}
return rc;
}
static int
b2xy_close(sqlite3_vtab_cursor *cur)
{
b2xy_cursor *bc = (b2xy_cursor *) cur;
sqlite3_finalize(bc->select);
sqlite3_free(bc);
return SQLITE_OK;
}
static int
b2xy_column(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i)
{
b2xy_cursor *bc = (b2xy_cursor *) cur;
char *p;
double v;
switch (i) {
case 0:
sqlite3_result_value(ctx, bc->key);
break;
case 1:
if (bc->do_x_scale) {
v = bc->index * bc->x_scale + bc->x_offset;
sqlite3_result_double(ctx, v);
} else {
sqlite3_result_int(ctx, bc->index);
}
break;
case 2:
if (!bc->val || bc->index * TYPE_SIZE(bc->type) >= bc->val_len) {
goto put_null;
}
p = bc->val + bc->index * TYPE_SIZE(bc->type);
switch (bc->type) {
case TYPE_CHAR:
if (bc->do_y_scale) {
v = p[0];
goto scale_it;
}
sqlite3_result_int(ctx, p[0]);
break;
case TYPE_UCHAR:
if (bc->do_y_scale) {
v = p[0] & 0xFF;
goto scale_it;
}
sqlite3_result_int(ctx, p[0] & 0xFF);
break;
case TYPE_SHORT_LE:
if (bc->do_y_scale) {
v = (p[0] & 0xFF) | (p[1] << 8);
goto scale_it;
}
sqlite3_result_int(ctx, (p[0] & 0xFF) | (p[1] << 8));
break;
case TYPE_USHORT_LE:
if (bc->do_y_scale) {
v = (p[0] & 0xFF) | ((p[1] & 0xFF) << 8);
goto scale_it;
}
sqlite3_result_int(ctx, (p[0] & 0xFF) | ((p[1] & 0xFF) << 8));
break;
case TYPE_SHORT_BE:
if (bc->do_y_scale) {
v = (p[1] & 0xFF) | (p[0] << 8);
goto scale_it;
}
sqlite3_result_int(ctx, (p[1] & 0xFF) | (p[0] << 8));
break;
case TYPE_USHORT_BE:
if (bc->do_y_scale) {
v = (p[1] & 0xFF) | ((p[0] & 0xFF) << 8);
goto scale_it;
}
sqlite3_result_int(ctx, (p[1] & 0xFF) | ((p[0] & 0xFF) << 8));
break;
case TYPE_INT_LE:
if (bc->do_y_scale) {
v = (p[0] & 0xFF) | ((p[1] & 0xFF) << 8) |
((p[2] & 0xFF) << 16) | (p[3] << 24);
goto scale_it;
}
sqlite3_result_int64(ctx, (p[0] & 0xFF) |
((p[1] & 0xFF) << 8) |
((p[2] & 0xFF) << 16) |
(p[3] << 24));
break;
case TYPE_UINT_LE:
if (bc->do_y_scale) {
v = (p[0] & 0xFF) | ((p[1] & 0xFF) << 8) |
((p[2] & 0xFF) << 16) | ((p[3] & 0xFF) << 24);
goto scale_it;
}
sqlite3_result_int64(ctx, (p[0] & 0xFF) |
((p[1] & 0xFF) << 8) |
((p[2] & 0xFF) << 16) |
((p[3] & 0xFF) << 24));
break;
case TYPE_INT_BE:
if (bc->do_y_scale) {
v = (p[3] & 0xFF) | ((p[2] & 0xFF) << 8) |
((p[1] & 0xFF) << 16) | (p[0] << 24);
goto scale_it;
}
sqlite3_result_int64(ctx, (p[3] & 0xFF) |
((p[2] & 0xFF) << 8) |
((p[1] & 0xFF) << 16) |
(p[0] << 24));
break;
case TYPE_UINT_BE:
if (bc->do_y_scale) {
v = (p[3] & 0xFF) | ((p[2] & 0xFF) << 8) |
((p[1] & 0xFF) << 16) | ((p[0] & 0xFF) << 24);
goto scale_it;
}
sqlite3_result_int64(ctx, (p[3] & 0xFF) |
((p[2] & 0xFF) << 8) |
((p[1] & 0xFF) << 16) |
((p[0] & 0xFF) << 24));
break;
case TYPE_FLOAT:
v = ((float *) p)[0];
goto scale_it;
case TYPE_DOUBLE:
v = ((double *) p)[0];
if (bc->do_y_scale) {
scale_it:
v = v * bc->y_scale + bc->y_offset;
}
sqlite3_result_double(ctx, v);
break;
default:
put_null:
sqlite3_result_null(ctx);
break;
}
break;
default:
i += bc->fix_cols - 3;
if (i < 0 || i >= bc->num_cols) {
sqlite3_result_null(ctx);
} else {
sqlite3_result_value(ctx, sqlite3_column_value(bc->select, i));
}
break;
}
return SQLITE_OK;
}
static int
b2xy_rowid(sqlite3_vtab_cursor *cur, sqlite_int64 *rowidp)
{
b2xy_cursor *bc = (b2xy_cursor *) cur;
*rowidp = bc->rowid;
return SQLITE_OK;
}
static int
b2xy_eof(sqlite3_vtab_cursor *cur)
{
b2xy_cursor *bc = (b2xy_cursor *) cur;
return bc->select ? 0 : 1;
}
static int
b2xy_next(sqlite3_vtab_cursor *cur)
{
b2xy_cursor *bc = (b2xy_cursor *) cur;
b2xy_table *bt = bc->table;
if (!bc->select) {
return SQLITE_OK;
}
if (bc->val) {
bc->index += 1;
}
refetch:
if (!bc->val || bc->index * TYPE_SIZE(bc->type) >= bc->val_len) {
int rc = sqlite3_step(bc->select);
if (rc == SQLITE_SCHEMA) {
rc = sqlite3_step(bc->select);
}
if (rc != SQLITE_ROW) {
sqlite3_finalize(bc->select);
bc->select = 0;
return SQLITE_OK;
}
bc->rowid_from_key = 0;
bc->index = 0;
bc->val = (char *) sqlite3_column_blob(bc->select, 1);
bc->val_len = sqlite3_column_bytes(bc->select, 1);
if (!bc->val || bc->val_len <= 0) {
goto refetch;
}
bc->key = sqlite3_column_value(bc->select, 0);
if (sqlite3_column_type(bc->select, 0) == SQLITE_INTEGER) {
bc->rowid_from_key = 1;
bc->rowid = sqlite3_column_int64(bc->select, 0);
}
bc->do_x_scale = 0;
bc->x_scale = 1.0;
bc->x_offset = 0.0;
if (bt->x_scale_column) {
bc->x_scale = sqlite3_column_double(bc->select, bc->x_scale_col);
bc->do_x_scale++;
}
if (bt->x_offset_column) {
bc->x_offset = sqlite3_column_double(bc->select, bc->x_offset_col);
bc->do_x_scale++;
}
bc->do_y_scale = 0;
bc->y_scale = 1.0;
bc->y_offset = 0.0;
if (bt->y_scale_column) {
bc->y_scale = sqlite3_column_double(bc->select, bc->y_scale_col);
bc->do_y_scale++;
}
if (bt->y_offset_column) {
bc->y_offset = sqlite3_column_double(bc->select, bc->y_offset_col);
bc->do_y_scale++;
}
}
if (!bc->rowid_from_key) {
bc->rowid++;
}
return SQLITE_OK;
}
static int
b2xy_filter(sqlite3_vtab_cursor *cur, int idxNum, const char *idxStr,
int argc, sqlite3_value **argv)
{
b2xy_cursor *bc = (b2xy_cursor *) cur;
b2xy_table *bt = bc->table;
char *query, *tmp, *op = 0;
int rc;
bc->rowid_from_key = 0;
bc->rowid = 0;
if (bc->select) {
sqlite3_finalize(bc->select);
bc->select = 0;
}
bc->fix_cols = 2;
query = sqlite3_mprintf("select \"%s\",\"%s\"", bt->key_column,
bt->blob_column);
if (!query) {
return SQLITE_NOMEM;
}
if (bt->x_scale_column) {
tmp = sqlite3_mprintf("%s,\"%s\"", query, bt->x_scale_column);
sqlite3_free(query);
if (!tmp) {
return SQLITE_NOMEM;
}
query = tmp;
bc->x_scale_col = bc->fix_cols;
bc->fix_cols++;
}
if (bt->x_offset_column) {
tmp = sqlite3_mprintf("%s,\"%s\"", query, bt->x_offset_column);
sqlite3_free(query);
if (!tmp) {
return SQLITE_NOMEM;
}
query = tmp;
bc->x_offset_col = bc->fix_cols;
bc->fix_cols++;
}
if (bt->y_scale_column) {
tmp = sqlite3_mprintf("%s,\"%s\"", query, bt->y_scale_column);
sqlite3_free(query);
if (!tmp) {
return SQLITE_NOMEM;
}
query = tmp;
bc->y_scale_col = bc->fix_cols;
bc->fix_cols++;
}
if (bt->y_offset_column) {
tmp = sqlite3_mprintf("%s,\"%s\"", query, bt->y_offset_column);
sqlite3_free(query);
if (!tmp) {
return SQLITE_NOMEM;
}
query = tmp;
bc->y_offset_col = bc->fix_cols;
bc->fix_cols++;
}
tmp = sqlite3_mprintf("%s%s from %s", query, bt->other_columns,
bt->fq_master_table);
sqlite3_free(query);
if (!tmp) {
return SQLITE_NOMEM;
}
query = tmp;
if (idxNum && argc > 0) {
switch (idxNum) {
case SQLITE_INDEX_CONSTRAINT_EQ:
op = "=";
break;
case SQLITE_INDEX_CONSTRAINT_GT:
op = ">";
break;
case SQLITE_INDEX_CONSTRAINT_LE:
op = "<=";
break;
case SQLITE_INDEX_CONSTRAINT_LT:
op = "<";
break;
case SQLITE_INDEX_CONSTRAINT_GE:
op = ">=";
break;
case SQLITE_INDEX_CONSTRAINT_MATCH:
op = "like";
break;
}
if (op) {
tmp = sqlite3_mprintf("%s where \"%s\" %s ?",
query, bt->key_column, op);
sqlite3_free(query);
if (!tmp) {
return SQLITE_NOMEM;
}
query = tmp;
}
}
if (idxStr) {
tmp = sqlite3_mprintf("%s %s", query, idxStr);
sqlite3_free(query);
if (!tmp) {
return SQLITE_NOMEM;
}
query = tmp;
}
bc->num_cols = bc->fix_cols;
#if defined(HAVE_SQLITE3PREPAREV2) && HAVE_SQLITE3PREPAREV2
rc = sqlite3_prepare_v2(bt->db, query, -1, &bc->select, 0);
#else
rc = sqlite3_prepare(bt->db, query, -1, &bc->select, 0);
if (rc == SQLITE_SCHEMA) {
rc = sqlite3_prepare(bt->db, query, -1, &bc->select, 0);
}
#endif
sqlite3_free(query);
if (rc == SQLITE_OK) {
bc->num_cols = sqlite3_column_count(bc->select);
if (op) {
sqlite3_bind_value(bc->select, 1, argv[0]);
}
}
return (rc == SQLITE_OK) ? b2xy_next(cur) : rc;
}
static int
b2xy_bestindex(sqlite3_vtab *tab, sqlite3_index_info *info)
{
b2xy_table *bt = (b2xy_table *) tab;
int i, key_order = 0, consumed = 0;
/* preset to not using index */
info->idxNum = 0;
/*
* Only when the key column of the master table
* (0th column in virtual table) is used in a
* constraint, a WHERE condition in the xFilter
* function can be coded. This is indicated by
* setting "idxNum" to the "op" value of that
* constraint.
*/
for (i = 0; i < info->nConstraint; ++i) {
if (info->aConstraint[i].usable) {
if (info->aConstraint[i].iColumn == 0 &&
info->aConstraint[i].op != 0) {
info->idxNum = info->aConstraint[i].op;
info->aConstraintUsage[i].argvIndex = 1;
info->aConstraintUsage[i].omit = 1;
info->estimatedCost = 1.0;
break;
}
}
}
/*
* ORDER BY can be optimized, when our X column
* is not present or to be sorted ascending.
* Additionally when the key column is to be sorted
* an ORDER BY is sent to the xFilter function.
*/
for (i = 0; i < info->nOrderBy; i++) {
if (info->aOrderBy[i].iColumn == 0) {
key_order = info->aOrderBy[i].desc ? -1 : 1;
consumed++;
} else if (info->aOrderBy[i].iColumn == 1 &&
!info->aOrderBy[i].desc) {
consumed++;
}
}
if (consumed) {
/* check for other ORDER BY columns */
for (i = 0; i < info->nOrderBy; i++) {
if (info->aOrderBy[i].iColumn == 1 &&
info->aOrderBy[i].desc) {
consumed = 0;
} else if (info->aOrderBy[i].iColumn > 1) {
consumed = 0;
}
}
}
if (consumed && key_order) {
info->idxStr = sqlite3_mprintf("ORDER BY \"%s\" %s",
bt->key_column,
key_order < 0 ? "DESC" : "ASC");
info->needToFreeIdxStr = 1;
}
info->orderByConsumed = consumed;
return SQLITE_OK;
}
static const sqlite3_module b2xy_module = {
0, /* iVersion */
b2xy_create, /* xCreate */
b2xy_create, /* xConnect */
b2xy_bestindex, /* xBestIndex */
b2xy_destroy, /* xDisconnect */
b2xy_destroy, /* xDestroy */
b2xy_open, /* xOpen */
b2xy_close, /* xClose */
b2xy_filter, /* xFilter */
b2xy_next, /* xNext */
b2xy_eof, /* xEof */
b2xy_column, /* xColumn */
b2xy_rowid, /* xRowid */
0, /* xUpdate */
0, /* xBegin */
0, /* xSync */
0, /* xCommit */
0, /* xRollback */
0, /* xFindMethod */
};
typedef struct {
int max, idx;
char *str;
} strbuf;
static int
init_strbuf(strbuf *sb)
{
int n = 1024;
if (sb->max <= 0 || !sb->str) {
sb->str = sqlite3_malloc(n);
if (!sb->str) {
return SQLITE_NOMEM;
}
sb->max = n;
}
sb->idx = 0;
return SQLITE_OK;
}
static int
expand_strbuf(strbuf *sb)
{
int n;
char *str;
if (sb->max <= 0 || !sb->str) {
return init_strbuf(sb);
}
n = sb->max * 2;
str = sqlite3_realloc(sb->str, n);
if (!str) {
return SQLITE_NOMEM;
}
sb->max = n;
sb->str = str;
return SQLITE_OK;
}
static void
drop_strbuf(strbuf *sb)
{
if (sb->str) {
sqlite3_free(sb->str);
sb->str = 0;
}
sb->max = 0;
}
static int
print_strbuf(strbuf *sb, const char *fmt, ...)
{
int i, n, rc;
va_list ap;
va_start(ap, fmt);
for (i = 0; i < 2; i++) {
if (sb->max - (sb->idx + 1) < 256) {
rc = expand_strbuf(sb);
if (rc != SQLITE_OK) {
return rc;
}
}
rc = SQLITE_NOMEM;
n = vsnprintf(sb->str + sb->idx, sb->max - sb->idx, fmt, ap);
if (n >= 0 && (sb->idx + n) < (sb->max - 1)) {
sb->idx += n;
rc = SQLITE_OK;
break;
}
}
va_end(ap);
return rc;
}
#define PATH_MODE_TK ((void *) 0)
#define PATH_MODE_SVG ((void *) 1)
#define PATH_MODE_BLT_X ((void *) 2)
#define PATH_MODE_BLT_Y ((void *) 3)
#define PATH_MODE_BLT ((void *) 4)
#define PATH_MODE_TK3D ((void *) 5)
static void
common_path_func(sqlite3_context *ctx, int nargs, sqlite3_value **args)
{
void *mode = sqlite3_user_data(ctx);
char *data;
int i, linebreak, size, type = TYPE_CHAR;
int do_x_scale = 0, do_y_scale = 0, do_z_scale = 0;
double x_scale, x_offset, y_scale, y_offset, z0, z_scale, z_offset;
strbuf sb;
/*
* args[0] - blob data (required)
* args[1] - type (optional)
* args[2] - X scale (optional)
* args[3] - X offset (optional)
* args[4] - Y scale (optional)
* args[5] - Y offset (optional)
* args[6] - Z value (optional)
* args[7] - Z scale (optional)
* args[8] - Z offset (optional)
*/
if (nargs < 1) {
sqlite3_result_error(ctx, "need at least 1 argument", -1);
return;
}
if (nargs > 1) {
type = string_to_type((const char *) sqlite3_value_text(args[1]));
if (!type) {
sqlite3_result_error(ctx, "bad type name", -1);
return;
}
}
data = (char *) sqlite3_value_blob(args[0]);
size = sqlite3_value_bytes(args[0]) / TYPE_SIZE(type);
if (!data ||
(mode != PATH_MODE_BLT_X && mode != PATH_MODE_BLT_Y && size < 2) ||
size < 1) {
goto nullorempty;
}
x_scale = 1;
x_offset = 0;
if (nargs > 2) {
x_scale = sqlite3_value_double(args[2]);
do_x_scale++;
}
if (nargs > 3) {
x_offset = sqlite3_value_double(args[3]);
do_x_scale++;
}
y_scale = 1;
y_offset = 0;
if (nargs > 4) {
y_scale = sqlite3_value_double(args[4]);
do_y_scale++;
}
if (nargs > 5) {
y_offset = sqlite3_value_double(args[5]);
do_y_scale++;
}
z0 = 0;
z_scale = 1;
z_offset = 0;
if (mode == PATH_MODE_TK3D && nargs > 6) {
z0 = sqlite3_value_double(args[6]);
}
if (mode == PATH_MODE_TK3D && nargs > 7) {
z_scale = sqlite3_value_double(args[7]);
do_z_scale++;
}
if (mode == PATH_MODE_TK3D && nargs > 8) {
z_offset = sqlite3_value_double(args[8]);
do_z_scale++;
}
memset(&sb, 0, sizeof (sb));
if (init_strbuf(&sb) != SQLITE_OK) {
goto nullorempty;
}
linebreak = 100;
for (i = 0; i < size; i++, data += TYPE_SIZE(type)) {
double x, y = 0, z = z0;
char *fmt;
if (do_z_scale) {
z = z0 * z_scale + z_offset;
}
if (do_x_scale) {
x = i * x_scale + x_offset;
} else {
x = i;
}
switch (type) {
case TYPE_CHAR:
y = data[0];
break;
case TYPE_UCHAR:
y = data[0] & 0xFF;
break;
case TYPE_SHORT_LE:
y = (data[0] & 0xFF) | (data[1] << 8);
break;
case TYPE_USHORT_LE:
y = (data[0] & 0xFF) | ((data[1] & 0xFF) << 8);
break;
case TYPE_SHORT_BE:
y = (data[1] & 0xFF) | (data[0] << 8);
break;
case TYPE_USHORT_BE:
y = (data[1] & 0xFF) | ((data[0] & 0xFF) << 8);
break;
case TYPE_INT_LE:
y = (data[0] & 0xFF) | ((data[1] & 0xFF) << 8) |
((data[2] & 0xFF) << 16) | (data[3] << 24);
break;
case TYPE_UINT_LE:
y = (data[0] & 0xFF) | ((data[1] & 0xFF) << 8) |
((data[2] & 0xFF) << 16) | ((data[3] & 0xFF) << 24);
break;
case TYPE_INT_BE:
y = (data[3] & 0xFF) | ((data[2] & 0xFF) << 8) |
((data[1] & 0xFF) << 16) | (data[0] << 24);
break;
case TYPE_UINT_BE:
y = (data[3] & 0xFF) | ((data[2] & 0xFF) << 8) |
((data[1] & 0xFF) << 16) | ((data[0] & 0xFF) << 24);
break;
case TYPE_FLOAT:
y = ((float *) data)[0];
break;
case TYPE_DOUBLE:
y = ((double *) data)[0];
break;
}
if (do_y_scale) {
y = y * y_scale + y_offset;
}
if (mode == PATH_MODE_BLT_X || mode == PATH_MODE_BLT_Y) {
double v = (mode == PATH_MODE_BLT_X) ? x : y;
if (print_strbuf(&sb, (i == 0) ? "%g" : " %g", v) != SQLITE_OK) {
drop_strbuf(&sb);
break;
}
continue;
}
if (mode == PATH_MODE_SVG && i == 0) {
fmt = "M %g %g";
} else if (mode == PATH_MODE_SVG && i == 1) {
fmt = " L %g %g";
} else if (mode == PATH_MODE_SVG && sb.idx >= linebreak) {
fmt = "\nL %g %g";
linebreak = sb.idx + 100;
} else if (i == 0) {
fmt = (mode == PATH_MODE_TK3D) ? "%g %g %g" : "%g %g";
} else {
fmt = (mode == PATH_MODE_TK3D) ? " %g %g %g" : " %g %g";
}
if (print_strbuf(&sb, fmt, x, y, z) != SQLITE_OK) {
drop_strbuf(&sb);
break;
}
}
if (sb.str) {
sqlite3_result_text(ctx, sb.str, sb.idx, sqlite3_free);
sb.str = 0;
return;
}
nullorempty:
if (mode == PATH_MODE_BLT_X || mode == PATH_MODE_BLT_Y) {
sqlite3_result_text(ctx, "", 0, SQLITE_STATIC);
} else {
sqlite3_result_null(ctx);
}
}
typedef struct {
int init, count, linebreak;
void *mode;
strbuf sb;
} path_aggctx;
static void
common_path_step(sqlite3_context *ctx, int nargs, sqlite3_value **args)
{
path_aggctx *pag;
int type;
char *fmt;
double x, y, z = 0;
double x_scale, y_scale, x_offset, y_offset, z_scale, z_offset;
/*
* args[0] - X value (required)
* args[1] - Y value (required)
* args[2] - X scale (optional)
* args[3] - X offset (optional)
* args[4] - Y scale (optional)
* args[5] - Y offset (optional)
* args[6] - Z value (optional)
* args[7] - Z scale (optional)
* args[8] - Z offset (optional)
*/
if (nargs < 2) {
return;
}
pag = sqlite3_aggregate_context(ctx, sizeof (*pag));
if (!pag->init) {
if (init_strbuf(&pag->sb) != SQLITE_OK) {
return;
}
pag->linebreak = 100;
pag->count = 0;
pag->mode = sqlite3_user_data(ctx);
pag->init = 1;
}
type = sqlite3_value_type(args[0]);
if (type != SQLITE_INTEGER && type != SQLITE_FLOAT) {
return;
}
type = sqlite3_value_type(args[1]);
if (type != SQLITE_INTEGER && type != SQLITE_FLOAT) {
return;
}
x = sqlite3_value_double(args[0]);
y = sqlite3_value_double(args[1]);
x_scale = 1;
x_offset = 0;
if (nargs > 2) {
type = sqlite3_value_type(args[2]);
if (type == SQLITE_INTEGER || type == SQLITE_FLOAT) {
x_scale = sqlite3_value_double(args[2]);
}
}
if (nargs > 3) {
type = sqlite3_value_type(args[3]);
if (type == SQLITE_INTEGER || type == SQLITE_FLOAT) {
x_offset = sqlite3_value_double(args[3]);
}
}
y_scale = 1;
y_offset = 0;
if (nargs > 4) {
type = sqlite3_value_type(args[4]);
if (type == SQLITE_INTEGER || type == SQLITE_FLOAT) {
y_scale = sqlite3_value_double(args[4]);
}
}
if (nargs > 5) {
type = sqlite3_value_type(args[5]);
if (type == SQLITE_INTEGER || type == SQLITE_FLOAT) {
y_offset = sqlite3_value_double(args[5]);
}
}
z_scale = 1;
z_offset = 0;
if (pag->mode == PATH_MODE_TK3D && nargs > 6) {
z = sqlite3_value_double(args[6]);
if (nargs > 7) {
type = sqlite3_value_type(args[7]);
if (type == SQLITE_INTEGER || type == SQLITE_FLOAT) {
z_scale = sqlite3_value_double(args[7]);
}
}
if (nargs > 8) {
type = sqlite3_value_type(args[8]);
if (type == SQLITE_INTEGER || type == SQLITE_FLOAT) {
z_offset = sqlite3_value_double(args[8]);
}
}
z = z * z_scale + z_offset;
}
x = x * x_scale + x_offset;
y = y * y_scale + y_offset;
if (pag->mode == PATH_MODE_SVG && pag->count == 0) {
fmt = "M %g %g";
} else if (pag->mode == PATH_MODE_SVG && pag->count == 1) {
fmt = " L %g %g";
} else if (pag->mode == PATH_MODE_SVG && pag->sb.idx >= pag->linebreak) {
fmt = "\nL %g %g";
pag->linebreak = pag->sb.idx + 100;
} else if (pag->count == 0) {
fmt = (pag->mode == PATH_MODE_TK3D) ? "%g %g %g" : "%g %g";
} else {
fmt = (pag->mode == PATH_MODE_TK3D) ? " %g %g %g" : " %g %g";
}
if (print_strbuf(&pag->sb, fmt, x, y, z) != SQLITE_OK) {
drop_strbuf(&pag->sb);
pag->init = 0;
} else {
pag->count++;
}
}
static void
common_path_finalize(sqlite3_context *ctx)
{
path_aggctx *pag = sqlite3_aggregate_context(ctx, sizeof (*pag));
if (pag->init) {
if (pag->count > 1 || pag->mode == PATH_MODE_BLT) {
sqlite3_result_text(ctx, pag->sb.str, pag->sb.idx, sqlite3_free);
pag->sb.str = 0;
pag->init = 0;
return;
}
drop_strbuf(&pag->sb);
}
if (pag->mode == PATH_MODE_BLT) {
sqlite3_result_text(ctx, "", 0, SQLITE_STATIC);
} else {
sqlite3_result_null(ctx);
}
}
static void
blt_vec_step(sqlite3_context *ctx, int nargs, sqlite3_value **args)
{
path_aggctx *pag;
int type;
double v, scale, offset;
/*
* args[0] - value (required)
* args[1] - scale (optional)
* args[2] - offset (optional)
*/
if (nargs < 1) {
return;
}
pag = sqlite3_aggregate_context(ctx, sizeof (*pag));
if (!pag->init) {
if (init_strbuf(&pag->sb) != SQLITE_OK) {
return;
}
pag->count = 0;
pag->mode = PATH_MODE_BLT;
pag->init = 1;
}
type = sqlite3_value_type(args[0]);
if (type != SQLITE_INTEGER && type != SQLITE_FLOAT) {
return;
}
v = sqlite3_value_double(args[0]);
scale = 1;
offset = 0;
if (nargs > 1) {
type = sqlite3_value_type(args[1]);
if (type == SQLITE_INTEGER || type == SQLITE_FLOAT) {
scale = sqlite3_value_double(args[2]);
}
}
if (nargs > 2) {
type = sqlite3_value_type(args[2]);
if (type == SQLITE_INTEGER || type == SQLITE_FLOAT) {
offset = sqlite3_value_double(args[3]);
}
}
v = v * scale + offset;
if (print_strbuf(&pag->sb, (pag->count == 0) ? "%g" : " %g", v)
!= SQLITE_OK) {
drop_strbuf(&pag->sb);
pag->init = 0;
} else {
pag->count++;
}
}
static void
subblob_func(sqlite3_context *ctx, int nargs, sqlite3_value **args)
{
int insize, outsize, start, itemsize = 1, itemskip = 0;
int i, k, n;
char *indata, *outdata;
if (nargs < 3) {
sqlite3_result_error(ctx, "need at least 1 argument", -1);
return;
}
indata = (char *) sqlite3_value_blob(args[0]);
insize = sqlite3_value_bytes(args[0]);
if (!indata || insize <= 0) {
isnull:
sqlite3_result_null(ctx);
return;
}
start = sqlite3_value_int(args[1]);
if (start < 0) {
start = insize - start;
if (start < 0) {
start = 0;
}
} else if (start > 0) {
start--;
}
if (start >= insize) {
goto isnull;
}
outsize = sqlite3_value_int(args[2]);
if (outsize > insize - start) {
outsize = insize - start;
}
if (outsize <= 0) {
goto isnull;
}
if (nargs > 3) {
itemsize = sqlite3_value_int(args[3]);
if (itemsize <= 0 || itemsize > outsize) {
goto isnull;
}
}
if (nargs > 4) {
itemskip = sqlite3_value_int(args[4]);
if (itemskip < 0) {
goto isnull;
}
}
outdata = sqlite3_malloc(outsize);
if (!outdata) {
sqlite3_result_error(ctx, "out of memory", -1);
return;
}
for (i = n = 0; i < outsize; i++) {
for (k = 0; k < itemsize; k++) {
outdata[i + k] = indata[start];
n++;
start++;
if (start >= insize) {
break;
}
}
start += itemskip;
if (start >= insize) {
break;
}
}
if (n > 0) {
sqlite3_result_blob(ctx, outdata, n, sqlite3_free);
return;
}
sqlite3_result_null(ctx);
sqlite3_free(outdata);
}
typedef struct {
sqlite3_context *ctx;
sqlite3_value *value;
sqlite_int64 count;
} rownumber_ctx;
static void
rownumber_func(sqlite3_context *ctx, int nargs, sqlite3_value **args)
{
rownumber_ctx *rn = sqlite3_get_auxdata(ctx, 0);
if (!rn || rn->ctx != ctx || rn->value != args[0]) {
rn = sqlite3_malloc(sizeof (*rn));
if (rn) {
rn->ctx = ctx;
rn->value = args[0];
rn->count = 0;
}
sqlite3_set_auxdata(ctx, 0, rn, sqlite3_free);
} else {
rn->count++;
}
sqlite3_result_int64(ctx, rn ? rn->count : 0);
}
#ifndef STANDALONE
static
#endif
int
b2xy_init(sqlite3 *db)
{
sqlite3_create_function(db, "subblob", -1, SQLITE_ANY, (void *) 0,
subblob_func, 0, 0);
sqlite3_create_function(db, "tk_path_from_blob", -1, SQLITE_UTF8,
PATH_MODE_TK, common_path_func, 0, 0);
sqlite3_create_function(db, "svg_path_from_blob", -1, SQLITE_UTF8,
PATH_MODE_SVG, common_path_func, 0, 0);
sqlite3_create_function(db, "blt_vec_x", -1, SQLITE_UTF8,
PATH_MODE_BLT_X, common_path_func, 0, 0);
sqlite3_create_function(db, "blt_vec_y", -1, SQLITE_UTF8,
PATH_MODE_BLT_Y, common_path_func, 0, 0);
sqlite3_create_function(db, "tk3d_path_from_blob", -1, SQLITE_UTF8,
PATH_MODE_TK3D, common_path_func, 0, 0);
sqlite3_create_function(db, "tk_path", -1, SQLITE_ANY, PATH_MODE_TK,
0, common_path_step, common_path_finalize);
sqlite3_create_function(db, "svg_path", -1, SQLITE_ANY, PATH_MODE_SVG,
0, common_path_step, common_path_finalize);
sqlite3_create_function(db, "blt_vec", -1, SQLITE_ANY, PATH_MODE_BLT,
0, blt_vec_step, common_path_finalize);
sqlite3_create_function(db, "tk3d_path", -1, SQLITE_ANY, PATH_MODE_TK3D,
0, common_path_step, common_path_finalize);
sqlite3_create_function(db, "rownumber", 1, SQLITE_ANY, 0,
rownumber_func, 0, 0);
return sqlite3_create_module(db, "blobtoxy", &b2xy_module, 0);
}
#ifndef STANDALONE
int
sqlite3_extension_init(sqlite3 *db, char **errmsg,
const sqlite3_api_routines *api)
{
SQLITE_EXTENSION_INIT2(api);
return b2xy_init(db);
}
#endif