mirror of
https://github.com/berkeleydb/libdb.git
synced 2024-11-16 17:16:25 +00:00
1569 lines
41 KiB
C
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
|