/* * @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 * * 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 #else #include SQLITE_EXTENSION_INIT1 #endif #include #include #include #include #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