/** * @file impexp.c * SQLite extension module for importing/exporting * database information from/to SQL source text and * export to CSV text. * * 2007 January 27 * * The author disclaims copyright to this source code. In place of * a legal notice, here is a blessing: * * May you do good and not evil. * May you find forgiveness for yourself and forgive others. * May you share freely, never taking more than you give. * ******************************************************************** * *
 * Usage:
 *
 *  SQLite function:
 *       SELECT import_sql(filename);
 *
 *  C function:
 *       int impexp_import_sql(sqlite3 *db,
 *                             char *filename);
 *
 *       Reads SQL commands from filename and executes them
 *       against the current database. Returns the number
 *       of changes to the current database.
 *
 *
 *  SQLite function:
 *       SELECT export_sql(filename, [mode, tablename, ...]);
 *
 *  C function:
 *       int impexp_export_sql(sqlite3 *db, char *filename, int mode, ...);
 *
 *       Writes SQL to filename similar to SQLite's shell
 *       ".dump" meta command. Mode selects the output format:
 *       Mode 0 (default): dump schema and data using the
 *       optional table names following the mode argument.
 *       Mode 1: dump data only using the optional table
 *       names following the mode argument.
 *       Mode 2: dump schema and data using the optional
 *       table names following the mode argument; each
 *       table name is followed by a WHERE clause, i.e.
 *       "mode, table1, where1, table2, where2, ..."
 *       Mode 3: dump data only, same rules as in mode 2.
 *       Returns approximate number of lines written or
 *       -1 when an error occurred.
 *
 *       Bit 1 of mode:      when 1 dump data only
 *       Bits 8..9 of mode:  blob quoting mode
 *           0   default
 *         256   ORACLE
 *         512   SQL Server
 *         768   MySQL
 *
 *
 *  SQLite function:
 *       SELECT export_csv(filename, hdr, prefix1, tablename1, schema1, ...]);
 *
 *  C function:
 *       int impexp_export_csv(sqlite3 *db, char *filename, int hdr, ...);
 *                             [char *prefix1, char *tablename1,
 *                             char *schema1, ...]
 *
 *       Writes entire tables as CSV to provided filename. A header
 *       row is written when the hdr parameter is true. The
 *       rows are optionally introduced with a column made up of
 *       the prefix (non-empty string) for the respective table.
 *       If "schema" is NULL, "sqlite_master" is used, otherwise
 *       specify e.g. "sqlite_temp_master" for temporary tables or 
 *       "att.sqlite_master" for the attached database "att".
 *
 *          CREATE TABLE A(a,b);
 *          INSERT INTO A VALUES(1,2);
 *          INSERT INTO A VALUES(3,'foo');
 *          CREATE TABLE B(c);
 *          INSERT INTO B VALUES('hello');
 *          SELECT export_csv('out.csv', 0, 'aa', 'A', NULL, 'bb', 'B', NULL);
 *          -- CSV output
 *          "aa",1,2
 *          "aa",3,"foo"
 *          "bb","hello"
 *          SELECT export_csv('out.csv', 1, 'aa', 'A', NULL, 'bb', 'B', NULL);
 *          -- CSV output
 *          "aa","a","b"
 *          "aa",1,2
 *          "aa",3,"foo"
 *          "bb","c"
 *          "bb","hello"
 *
 *
 *  SQLite function:
 *       SELECT export_xml(filename, appendflag, indent,
 *                         [root, item, tablename, schema]+);
 *
 *  C function:
 *       int impexp_export_xml(sqlite3 *db, char *filename,
 *                             int append, int indent, char *root,
 *                             char *item, char *tablename, char *schema);
 *
 *       Writes a table as simple XML to provided filename. The
 *       rows are optionally enclosed with the "root" tag,
 *       the row data is enclosed in "item" tags. If "schema"
 *       is NULL, "sqlite_master" is used, otherwise specify
 *       e.g. "sqlite_temp_master" for temporary tables or 
 *       "att.sqlite_master" for the attached database "att".
 *          
 *          
 *           value
 *           ...
 *          
 *
 *       e.g.
 *
 *          CREATE TABLE A(a,b);
 *          INSERT INTO A VALUES(1,2.1);
 *          INSERT INTO A VALUES(3,'foo');
 *          INSERT INTO A VALUES('',NULL);
 *          INSERT INTO A VALUES(X'010203','');
 *          SELECT export_xml('out.xml', 0, 2, 'TBL_A', 'ROW', 'A');
 *          -- XML output
 *            
 *              
 *                <a TYPE="INTEGER">1</a>
 *                <b TYPE="REAL">2.1</b>
 *              
 *              
 *                <a TYPE="INTEGER">3</a>
 *                <b TYPE="TEXT">foo</b>
 *              
 *              
 *                <a TYPE="TEXT"></a>
 *                <b TYPE="NULL"></b>
 *              
 *              
 *                <a TYPE="BLOB">&x03;</a>
 *                <b TYPE="TEXT">&lt;blob&gt;</b>
 *              
 *            
 *
 *       Quoting of XML entities is performed only on the data,
 *       not on column names and root/item tags.
 *
 *
 *  SQLite function:
 *       SELECT export_json(filename, sql);
 *
 *  C function:
 *       int impexp_export_json(sqlite3 *db, char *sql,
 *                              impexp_putc pfunc, void *parg);
 *
 *       Executes arbitrary SQL statements and formats
 *       the result in JavaScript Object Notation (JSON).
 *       The layout of the result is:
 *
 *        object {results, sql}
 *         results[] object {columns, rows, changes, last_insert_rowid, error}
 *          columns[]
 *           object {name, decltype, type }     (sqlite3_column_*)
 *          rows[][]                            (sqlite3_column_*)
 *          changes                             (sqlite3_changes)
 *          last_insert_rowid                   (sqlite3_last_insert_rowid)
 *          error                               (sqlite3_errmsg)
 *         sql                                  (SQL text)
 *
 *       For each single SQL statement in "sql" an object in the
 *       "results" array is produced.
 *
 *       The function pointer for the output function to
 *       "impexp_export_json" has a signature compatible
 *       with fputc(3).
 *
 *
 * On Win32 the filename argument may be specified as NULL in order
 * to open a system file dialog for interactive filename selection.
 * 
*/ #ifdef STANDALONE #include #define sqlite3_api_routines void #else #include static SQLITE_EXTENSION_INIT1 #endif #include #include #include #include #ifdef _WIN32 #include #define strcasecmp _stricmp #define strncasecmp _strnicmp #else #include #endif #include "impexp.h" /** * @typedef struct json_pfs * @struct json_pfs * JSON output helper structure */ typedef struct { impexp_putc pfunc; /**< function like fputc() */ void *parg; /**< argument to function */ } json_pfs; static const char space_chars[] = " \f\n\r\t\v"; #define ISSPACE(c) ((c) && (strchr(space_chars, (c)) != 0)) /** * Read one line of input into dynamically allocated buffer * which the caller must free with sqlite3_free() * @param fin FILE pointer * @result dynamically allocated input line */ static char * one_input_line(FILE *fin) { char *line, *tmp; int nline; int n; int eol; nline = 256; line = sqlite3_malloc(nline); if (!line) { return 0; } n = 0; eol = 0; while (!eol) { if (n + 256 > nline) { nline = nline * 2 + 256; tmp = sqlite3_realloc(line, nline); if (!tmp) { sqlite3_free(line); return 0; } line = tmp; } if (!fgets(line + n, nline - n, fin)) { if (n == 0) { sqlite3_free(line); return 0; } line[n] = 0; eol = 1; break; } while (line[n]) { n++; } if ((n > 0) && (line[n-1] == '\n')) { n--; line[n] = 0; eol = 1; } } tmp = sqlite3_realloc(line, n + 1); if (!tmp) { sqlite3_free(line); } return tmp; } /** * Test if string ends with a semicolon * @param str string to be tested * @param n length of string * @result true or false */ static int ends_with_semicolon(const char *str, int n) { while ((n > 0) && ISSPACE(str[n - 1])) { n--; } return (n > 0) && (str[n - 1] == ';'); } /** * Test if string contains entirely whitespace or SQL comment * @param str string to be tested * @result true or false */ static int all_whitespace(const char *str) { for (; str[0]; str++) { if (ISSPACE(str[0])) { continue; } if ((str[0] == '/') && (str[1] == '*')) { str += 2; while (str[0] && ((str[0] != '*') || (str[1] != '/'))) { str++; } if (!str[0]) { return 0; } str++; continue; } if ((str[0] == '-') && (str[1] == '-')) { str += 2; while (str[0] && (str[0] != '\n')) { str++; } if (!str[0]) { return 1; } continue; } return 0; } return 1; } /** * Process contents of FILE pointer as SQL commands * @param db SQLite database to work on * @param fin input FILE pointer * @result number of errors */ static int process_input(sqlite3 *db, FILE *fin) { char *line = 0; char *sql = 0; int nsql = 0; int rc; int errors = 0; while (1) { line = one_input_line(fin); if (!line) { break; } if ((!sql || !sql[0]) && all_whitespace(line)) { continue; } if (!sql) { int i; for (i = 0; line[i] && ISSPACE(line[i]); i++) { /* empty loop body */ } if (line[i]) { nsql = strlen(line); sql = sqlite3_malloc(nsql + 1); if (!sql) { errors++; break; } strcpy(sql, line); } } else { int len = strlen(line); char *tmp; tmp = sqlite3_realloc(sql, nsql + len + 2); if (!tmp) { errors++; break; } sql = tmp; strcpy(sql + nsql, "\n"); nsql++; strcpy(sql + nsql, line); nsql += len; } sqlite3_free(line); line = 0; if (sql && ends_with_semicolon(sql, nsql) && sqlite3_complete(sql)) { rc = sqlite3_exec(db, sql, 0, 0, 0); if (rc != SQLITE_OK) { errors++; } sqlite3_free(sql); sql = 0; nsql = 0; } } if (sql) { sqlite3_free(sql); } if (line) { sqlite3_free(line); } return errors; } /** * SQLite function to quote SQLite value depending on optional quote mode * @param context SQLite function context * @param argc number of arguments * @param argv argument vector * * Layout of arguments: * * argv[0] - value to be quoted
* argv[1] - value of quote mode (optional)
*/ static void quote_func(sqlite3_context *context, int argc, sqlite3_value **argv) { int mode = 0; if (argc < 1) { return; } if (argc > 1) { mode = sqlite3_value_int(argv[1]); } switch (sqlite3_value_type(argv[0])) { case SQLITE_NULL: { sqlite3_result_text(context, "NULL", 4, SQLITE_STATIC); break; } case SQLITE_INTEGER: case SQLITE_FLOAT: { sqlite3_result_value(context, argv[0]); break; } case SQLITE_BLOB: { char *text = 0; unsigned char *blob = (unsigned char *) sqlite3_value_blob(argv[0]); int nblob = sqlite3_value_bytes(argv[0]); if (2 * nblob + 4 > 1000000000) { sqlite3_result_error(context, "value too large", -1); return; } text = (char *) sqlite3_malloc((2 * nblob) + 4); if (!text) { sqlite3_result_error(context, "out of memory", -1); } else { int i, k = 0; static const char xdigits[] = "0123456789ABCDEF"; if (mode == 1) { /* ORACLE enclosed in '' */ text[k++] = '\''; } else if (mode == 2) { /* SQL Server 0x prefix */ text[k++] = '0'; text[k++] = 'x'; } else if (mode == 3) { /* MySQL x'..' */ text[k++] = 'x'; text[k++] = '\''; } else { /* default */ text[k++] = 'X'; text[k++] = '\''; } for (i = 0; i < nblob; i++) { text[k++] = xdigits[(blob[i] >> 4 ) & 0x0F]; text[k++] = xdigits[blob[i] & 0x0F]; } if (mode == 1) { /* ORACLE enclosed in '' */ text[k++] = '\''; } else if (mode == 2) { /* SQL Server 0x prefix */ } else if (mode == 3) { /* MySQL x'..' */ text[k++] = '\''; } else { /* default */ text[k++] = '\''; } text[k] = '\0'; sqlite3_result_text(context, text, k, SQLITE_TRANSIENT); sqlite3_free(text); } break; } case SQLITE_TEXT: { int i, n; const unsigned char *arg = sqlite3_value_text(argv[0]); char *p; if (!arg) { return; } for (i = 0, n = 0; arg[i]; i++) { if (arg[i] == '\'') { n++; } } if (i + n + 3 > 1000000000) { sqlite3_result_error(context, "value too large", -1); return; } p = sqlite3_malloc(i + n + 3); if (!p) { sqlite3_result_error(context, "out of memory", -1); return; } p[0] = '\''; for (i = 0, n = 1; arg[i]; i++) { p[n++] = arg[i]; if (arg[i] == '\'') { p[n++] = '\''; } } p[n++] = '\''; p[n] = 0; sqlite3_result_text(context, p, n, SQLITE_TRANSIENT); sqlite3_free(p); break; } } } /** * SQLite function to quote an SQLite value in CSV format * @param context SQLite function context * @param argc number of arguments * @param argv argument vector */ static void quote_csv_func(sqlite3_context *context, int argc, sqlite3_value **argv) { if (argc < 1) { return; } switch (sqlite3_value_type(argv[0])) { case SQLITE_NULL: { sqlite3_result_text(context, "", 0, SQLITE_STATIC); break; } case SQLITE_INTEGER: case SQLITE_FLOAT: { sqlite3_result_value(context, argv[0]); break; } case SQLITE_BLOB: { char *text = 0; unsigned char *blob = (unsigned char *) sqlite3_value_blob(argv[0]); int nblob = sqlite3_value_bytes(argv[0]); if (2 * nblob + 4 > 1000000000) { sqlite3_result_error(context, "value too large", -1); return; } text = (char *) sqlite3_malloc((2 * nblob) + 4); if (!text) { sqlite3_result_error(context, "out of memory", -1); } else { int i, k = 0; static const char xdigits[] = "0123456789ABCDEF"; text[k++] = '"'; for (i = 0; i < nblob; i++) { text[k++] = xdigits[(blob[i] >> 4 ) & 0x0F]; text[k++] = xdigits[blob[i] & 0x0F]; } text[k++] = '"'; text[k] = '\0'; sqlite3_result_text(context, text, k, SQLITE_TRANSIENT); sqlite3_free(text); } break; } case SQLITE_TEXT: { int i, n; const unsigned char *arg = sqlite3_value_text(argv[0]); char *p; if (!arg) { return; } for (i = 0, n = 0; arg[i]; i++) { if (arg[i] == '"') { n++; } } if (i + n + 3 > 1000000000) { sqlite3_result_error(context, "value too large", -1); return; } p = sqlite3_malloc(i + n + 3); if (!p) { sqlite3_result_error(context, "out of memory", -1); return; } p[0] = '"'; for (i = 0, n = 1; arg[i]; i++) { p[n++] = arg[i]; if (arg[i] == '"') { p[n++] = '"'; } } p[n++] = '"'; p[n] = 0; sqlite3_result_text(context, p, n, SQLITE_TRANSIENT); sqlite3_free(p); break; } } } /** * SQLite function to make XML indentation * @param context SQLite function context * @param argc number of arguments * @param argv argument vector */ static void indent_xml_func(sqlite3_context *context, int argc, sqlite3_value **argv) { static const char spaces[] = " "; int n = 0; if (argc > 0) { n = sqlite3_value_int(argv[0]); if (n > 32) { n = 32; } else if (n < 0) { n = 0; } } sqlite3_result_text(context, spaces, n, SQLITE_STATIC); } /** * SQLite function to quote a string for XML * @param context SQLite function context * @param argc number of arguments * @param argv argument vector */ static void quote_xml_func(sqlite3_context *context, int argc, sqlite3_value **argv) { static const char xdigits[] = "0123456789ABCDEF"; int type, addtype = 0; if (argc < 1) { return; } if (argc > 1) { addtype = sqlite3_value_int(argv[1]); } type = sqlite3_value_type(argv[0]); switch (type) { case SQLITE_NULL: { if (addtype > 0) { sqlite3_result_text(context, " TYPE=\"NULL\">", -1, SQLITE_STATIC); } else { sqlite3_result_text(context, "", 0, SQLITE_STATIC); } break; } case SQLITE_INTEGER: case SQLITE_FLOAT: { if (addtype > 0) { char *text = (char *) sqlite3_malloc(128); int k; if (!text) { sqlite3_result_error(context, "out of memory", -1); return; } strcpy(text, (type == SQLITE_FLOAT) ? " TYPE=\"REAL\">" : " TYPE=\"INTEGER\">"); k = strlen(text); strcpy(text + k, (char *) sqlite3_value_text(argv[0])); k = strlen(text); sqlite3_result_text(context, text, k, SQLITE_TRANSIENT); sqlite3_free(text); } else { sqlite3_result_value(context, argv[0]); } break; } case SQLITE_BLOB: { char *text = 0; unsigned char *blob = (unsigned char *) sqlite3_value_blob(argv[0]); int nblob = sqlite3_value_bytes(argv[0]); int i, k = 0; if (6 * nblob + 34 > 1000000000) { sqlite3_result_error(context, "value too large", -1); return; } text = (char *) sqlite3_malloc((6 * nblob) + 34); if (!text) { sqlite3_result_error(context, "out of memory", -1); return; } if (addtype > 0) { strcpy(text, " TYPE=\"BLOB\">"); k = strlen(text); } for (i = 0; i < nblob; i++) { text[k++] = '&'; text[k++] = '#'; text[k++] = 'x'; text[k++] = xdigits[(blob[i] >> 4 ) & 0x0F]; text[k++] = xdigits[blob[i] & 0x0F]; text[k++] = ';'; } text[k] = '\0'; sqlite3_result_text(context, text, k, SQLITE_TRANSIENT); sqlite3_free(text); break; } case SQLITE_TEXT: { int i, n; const unsigned char *arg = sqlite3_value_text(argv[0]); char *p; if (!arg) { return; } for (i = 0, n = 0; arg[i]; i++) { if ((arg[i] == '"') || (arg[i] == '\'') || (arg[i] == '<') || (arg[i] == '>') || (arg[i] == '&') || (arg[i] < ' ')) { n += 5; } } if (i + n + 32 > 1000000000) { sqlite3_result_error(context, "value too large", -1); return; } p = sqlite3_malloc(i + n + 32); if (!p) { sqlite3_result_error(context, "out of memory", -1); return; } n = 0; if (addtype > 0) { strcpy(p, " TYPE=\"TEXT\">"); n = strlen(p); } for (i = 0; arg[i]; i++) { if (arg[i] == '"') { p[n++] = '&'; p[n++] = 'q'; p[n++] = 'u'; p[n++] = 'o'; p[n++] = 't'; p[n++] = ';'; } else if (arg[i] == '\'') { p[n++] = '&'; p[n++] = 'a'; p[n++] = 'p'; p[n++] = 'o'; p[n++] = 's'; p[n++] = ';'; } else if (arg[i] == '<') { p[n++] = '&'; p[n++] = 'l'; p[n++] = 't'; p[n++] = ';'; } else if (arg[i] == '>') { p[n++] = '&'; p[n++] = 'g'; p[n++] = 't'; p[n++] = ';'; } else if (arg[i] == '&') { p[n++] = '&'; p[n++] = 'a'; p[n++] = 'm'; p[n++] = 'p'; p[n++] = ';'; } else if (arg[i] < ' ') { p[n++] = '&'; p[n++] = '#'; p[n++] = 'x'; p[n++] = xdigits[(arg[i] >> 4 ) & 0x0F]; p[n++] = xdigits[arg[i] & 0x0F]; p[n++] = ';'; } else if (addtype < 0 && (arg[i] == ' ')) { p[n++] = '&'; p[n++] = '#'; p[n++] = 'x'; p[n++] = xdigits[(arg[i] >> 4 ) & 0x0F]; p[n++] = xdigits[arg[i] & 0x0F]; p[n++] = ';'; } else { p[n++] = arg[i]; } } p[n] = '\0'; sqlite3_result_text(context, p, n, SQLITE_TRANSIENT); sqlite3_free(p); break; } } } /** * SQLite function to read and process SQL commands from a file * @param ctx SQLite function context * @param nargs number of arguments * @param args argument vector */ static void import_func(sqlite3_context *ctx, int nargs, sqlite3_value **args) { sqlite3 *db = (sqlite3 *) sqlite3_user_data(ctx); int changes0 = sqlite3_changes(db); char *filename = 0; FILE *fin; #ifdef _WIN32 char fnbuf[MAX_PATH]; #endif if (nargs > 0) { if (sqlite3_value_type(args[0]) != SQLITE_NULL) { filename = (char *) sqlite3_value_text(args[0]); } } #ifdef _WIN32 if (!filename) { OPENFILENAME ofn; memset(&ofn, 0, sizeof (ofn)); memset(fnbuf, 0, sizeof (fnbuf)); ofn.lStructSize = sizeof (ofn); ofn.lpstrFile = fnbuf; ofn.nMaxFile = MAX_PATH; ofn.Flags = OFN_HIDEREADONLY | OFN_NOCHANGEDIR | OFN_FILEMUSTEXIST | OFN_EXPLORER | OFN_PATHMUSTEXIST; if (GetOpenFileName(&ofn)) { filename = fnbuf; } } #endif if (!filename) { goto done; } fin = fopen(filename, "r"); if (!fin) { goto done; } process_input(db, fin); fclose(fin); done: sqlite3_result_int(ctx, sqlite3_changes(db) - changes0); } /* see doc in impexp.h */ int impexp_import_sql(sqlite3 *db, char *filename) { int changes0; FILE *fin; #ifdef _WIN32 char fnbuf[MAX_PATH]; #endif if (!db) { return 0; } changes0 = sqlite3_changes(db); #ifdef _WIN32 if (!filename) { OPENFILENAME ofn; memset(&ofn, 0, sizeof (ofn)); memset(fnbuf, 0, sizeof (fnbuf)); ofn.lStructSize = sizeof (ofn); ofn.lpstrFile = fnbuf; ofn.nMaxFile = MAX_PATH; ofn.Flags = OFN_HIDEREADONLY | OFN_NOCHANGEDIR | OFN_FILEMUSTEXIST | OFN_EXPLORER | OFN_PATHMUSTEXIST; if (GetOpenFileName(&ofn)) { filename = fnbuf; } } #endif if (!filename) { goto done; } fin = fopen(filename, "r"); if (!fin) { goto done; } process_input(db, fin); fclose(fin); done: return sqlite3_changes(db) - changes0; } /** * @typedef DUMP_DATA * @struct DUMP_DATA * Structure for dump callback */ typedef struct { sqlite3 *db; /**< SQLite database pointer */ int with_schema; /**< if true, output schema */ int quote_mode; /**< mode for quoting data */ char *where; /**< optional where clause of dump */ int nlines; /**< counter for output lines */ int indent; /**< current indent level */ FILE *out; /**< output file pointer */ } DUMP_DATA; /** * Write indentation to dump * @param dd information structure for dump */ static void indent(DUMP_DATA *dd) { int i; for (i = 0; i < dd->indent; i++) { fputc(' ', dd->out); } } /** * Execute SQL to dump contents of one table * @param dd information structure for dump * @param errp pointer receiving error message * @param fmt if true, use sqlite3_*printf() on SQL * @param query SQL text to perform dump of table * @param ... optional arguments * @result SQLite error code */ static int table_dump(DUMP_DATA *dd, char **errp, int fmt, const char *query, ...) { sqlite3_stmt *select = 0; int rc; const char *rest, *q = query; va_list ap; if (errp && *errp) { sqlite3_free(*errp); *errp = 0; } if (fmt) { va_start(ap, query); q = sqlite3_vmprintf(query, ap); va_end(ap); if (!q) { return SQLITE_NOMEM; } } #if defined(HAVE_SQLITE3PREPAREV2) && HAVE_SQLITE3PREPAREV2 rc = sqlite3_prepare_v2(dd->db, q, -1, &select, &rest); #else rc = sqlite3_prepare(dd->db, q, -1, &select, &rest); #endif if (fmt) { sqlite3_free((char *) q); } if ((rc != SQLITE_OK) || !select) { return rc; } rc = sqlite3_step(select); while (rc == SQLITE_ROW) { if (fputs((char *) sqlite3_column_text(select, 0), dd->out) > 0) { dd->nlines++; } if (dd->quote_mode >= 0) { fputc(';', dd->out); } if (dd->quote_mode == -1) { fputc('\r', dd->out); } if (dd->quote_mode >= -1) { fputc('\n', dd->out); } rc = sqlite3_step(select); } rc = sqlite3_finalize(select); if (rc != SQLITE_OK) { if (errp) { *errp = sqlite3_mprintf("%s", sqlite3_errmsg(dd->db)); } } return rc; } /** * Free dynamically allocated string buffer * @param in input string pointer */ static void append_free(char **in) { long *p = (long *) *in; if (p) { p -= 2; sqlite3_free(p); *in = 0; } } /** * Append a string to dynamically allocated string buffer * with optional quoting * @param in input string pointer * @param append string to append * @param quote quote character or NUL * @result new string to be free'd with append_free() */ static char * append(char **in, char const *append, char quote) { long *p = (long *) *in; long len, maxlen, actlen; int i; char *pp; int nappend = append ? strlen(append) : 0; if (p) { p -= 2; maxlen = p[0]; actlen = p[1]; } else { maxlen = actlen = 0; } len = nappend + actlen; if (quote) { len += 2; for (i = 0; i < nappend; i++) { if (append[i] == quote) { len++; } } } else if (!nappend) { return *in; } if (len >= maxlen - 1) { long *q; maxlen = (len + 0x03ff) & (~0x3ff); q = (long *) sqlite3_realloc(p, maxlen + 1 + 2 * sizeof (long)); if (!q) { return 0; } if (!p) { q[1] = 0; } p = q; p[0] = maxlen; *in = (char *) (p + 2); } pp = *in + actlen; if (quote) { *pp++ = quote; for (i = 0; i < nappend; i++) { *pp++ = append[i]; if (append[i] == quote) { *pp++ = quote; } } *pp++ = quote; *pp = '\0'; } else { if (nappend) { memcpy(pp, append, nappend); pp += nappend; *pp = '\0'; } } p[1] = pp - *in; return *in; } /** * Quote string for XML output during dump * @param dd information structure for dump * @param str string to be output */ static void quote_xml_str(DUMP_DATA *dd, char *str) { static const char xdigits[] = "0123456789ABCDEF"; int i; if (!str) { return; } for (i = 0; str[i]; i++) { if (str[i] == '"') { fputs(""", dd->out); } else if (str[i] == '\'') { fputs("'", dd->out); } else if (str[i] == '<') { fputs("<", dd->out); } else if (str[i] == '>') { fputs(">", dd->out); } else if (str[i] == '&') { fputs("&", dd->out); } else if ((unsigned char) str[i] <= ' ') { char buf[8]; buf[0] = '&'; buf[1] = '&'; buf[2] = '#'; buf[3] = 'x'; buf[4] = xdigits[(str[i] >> 4 ) & 0x0F]; buf[5] = xdigits[str[i] & 0x0F]; buf[6] = ';'; buf[7] = '\0'; fputs(buf, dd->out); } else { fputc(str[i], dd->out); } } } /** * Callback for sqlite3_exec() to dump one data row * @param udata information structure for dump * @param nargs number of columns * @param args column data * @param cols column labels * @result 0 to continue, 1 to abort */ static int dump_cb(void *udata, int nargs, char **args, char **cols) { int rc; const char *table, *type, *sql; DUMP_DATA *dd = (DUMP_DATA *) udata; if ((nargs != 3) || (args == NULL)) { return 1; } table = args[0]; type = args[1]; sql = args[2]; if (strcmp(table, "sqlite_sequence") == 0) { if (dd->with_schema) { if (fputs("DELETE FROM sqlite_sequence;\n", dd->out) >= 0) { dd->nlines++; } } } else if (strcmp(table, "sqlite_stat1") == 0) { if (dd->with_schema) { if (fputs("ANALYZE sqlite_master;\n", dd->out) >= 0) { dd->nlines++; } } } else if (strncmp(table, "sqlite_", 7) == 0) { return 0; } else if (strncmp(sql, "CREATE VIRTUAL TABLE", 20) == 0) { if (dd->with_schema) { sqlite3_stmt *stmt = 0; char *creat = 0, *table_info = 0; append(&table_info, "PRAGMA table_info(", 0); append(&table_info, table, '"'); append(&table_info, ")", 0); #if defined(HAVE_SQLITE3PREPAREV2) && HAVE_SQLITE3PREPAREV2 rc = sqlite3_prepare_v2(dd->db, table_info, -1, &stmt, 0); #else rc = sqlite3_prepare(dd->db, table_info, -1, &stmt, 0); #endif append_free(&table_info); if ((rc != SQLITE_OK) || !stmt) { bailout0: if (stmt) { sqlite3_finalize(stmt); } append_free(&creat); return 1; } append(&creat, table, '"'); append(&creat, "(", 0); rc = sqlite3_step(stmt); while (rc == SQLITE_ROW) { const char *p; p = (const char *) sqlite3_column_text(stmt, 1); append(&creat, p, '"'); append(&creat, " ", 0); p = (const char *) sqlite3_column_text(stmt, 2); if (p && p[0]) { append(&creat, p, 0); } if (sqlite3_column_int(stmt, 5)) { append(&creat, " PRIMARY KEY", 0); } if (sqlite3_column_int(stmt, 3)) { append(&creat, " NOT NULL", 0); } p = (const char *) sqlite3_column_text(stmt, 4); if (p && p[0]) { append(&creat, " DEFAULT ", 0); append(&creat, p, 0); } rc = sqlite3_step(stmt); if (rc == SQLITE_ROW) { append(&creat, ",", 0); } } if (rc != SQLITE_DONE) { goto bailout0; } sqlite3_finalize(stmt); append(&creat, ")", 0); if (creat && fprintf(dd->out, "CREATE TABLE %s;\n", creat) > 0) { dd->nlines++; } append_free(&creat); } } else { if (dd->with_schema) { if (fprintf(dd->out, "%s;\n", sql) > 0) { dd->nlines++; } } } if ((strcmp(type, "table") == 0) || ((dd->quote_mode < 0) && (strcmp(type, "view") == 0))) { sqlite3_stmt *stmt = 0; char *select = 0, *hdr = 0, *table_info = 0; char buffer[256]; append(&table_info, "PRAGMA table_info(", 0); append(&table_info, table, '"'); append(&table_info, ")", 0); #if defined(HAVE_SQLITE3PREPAREV2) && HAVE_SQLITE3PREPAREV2 rc = sqlite3_prepare_v2(dd->db, table_info, -1, &stmt, 0); #else rc = sqlite3_prepare(dd->db, table_info, -1, &stmt, 0); #endif append_free(&table_info); if ((rc != SQLITE_OK) || !stmt) { bailout1: if (stmt) { sqlite3_finalize(stmt); } append_free(&hdr); append_free(&select); return 1; } if (dd->quote_mode < -1) { if (dd->where) { append(&select, "SELECT ", 0); sprintf(buffer, "indent_xml(%d)", dd->indent); append(&select, buffer, 0); append(&select, " || '<' || quote_xml(", 0); append(&select, dd->where, '"'); append(&select, ",-1) || '>\n' || ", 0); } else { append(&select, "SELECT ", 0); } } else if (dd->quote_mode < 0) { if (dd->where) { append(&select, "SELECT quote_csv(", 0); append(&select, dd->where, '"'); append(&select, ") || ',' || ", 0); } else { append(&select, "SELECT ", 0); } if (dd->indent) { append(&hdr, select, 0); } } else { char *tmp = 0; if (dd->with_schema) { append(&select, "SELECT 'INSERT INTO ' || ", 0); } else { append(&select, "SELECT 'INSERT OR REPLACE INTO ' || ", 0); } append(&tmp, table, '"'); if (tmp) { append(&select, tmp, '\''); append_free(&tmp); } } if ((dd->quote_mode >= 0) && !dd->with_schema) { char *tmp = 0; append(&select, " || ' (' || ", 0); rc = sqlite3_step(stmt); while (rc == SQLITE_ROW) { const char *text = (const char *) sqlite3_column_text(stmt, 1); append(&tmp, text, '"'); if (tmp) { append(&select, tmp, '\''); append_free(&tmp); } rc = sqlite3_step(stmt); if (rc == SQLITE_ROW) { append(&select, " || ',' || ", 0); } } if (rc != SQLITE_DONE) { goto bailout1; } sqlite3_reset(stmt); append(&select, "|| ')'", 0); } if ((dd->quote_mode == -1) && dd->indent) { rc = sqlite3_step(stmt); while (rc == SQLITE_ROW) { const char *text = (const char *) sqlite3_column_text(stmt, 1); append(&hdr, "quote_csv(", 0); append(&hdr, text, '"'); rc = sqlite3_step(stmt); if (rc == SQLITE_ROW) { append(&hdr, ") || ',' || ", 0); } else { append(&hdr, ")", 0); } } if (rc != SQLITE_DONE) { goto bailout1; } sqlite3_reset(stmt); } if (dd->quote_mode >= 0) { append(&select, " || ' VALUES(' || ", 0); } rc = sqlite3_step(stmt); while (rc == SQLITE_ROW) { const char *text = (const char *) sqlite3_column_text(stmt, 1); const char *type = (const char *) sqlite3_column_text(stmt, 2); int tlen = strlen(type ? type : ""); if (dd->quote_mode < -1) { sprintf(buffer, "indent_xml(%d)", dd->indent + 1); append(&select, buffer, 0); append(&select, "|| '<' || quote_xml(", 0); append(&select, text, '\''); append(&select, ",-1) || quote_xml(", 0); append(&select, text, '"'); append(&select, ",1) || '\n'", 0); } else if (dd->quote_mode < 0) { /* leave out BLOB columns */ if (((tlen >= 4) && (strncasecmp(type, "BLOB", 4) == 0)) || ((tlen >= 6) && (strncasecmp(type, "BINARY", 6) == 0))) { rc = sqlite3_step(stmt); if (rc != SQLITE_ROW) { tlen = strlen(select); if (tlen > 10) { select[tlen - 10] = '\0'; } } continue; } append(&select, "quote_csv(", 0); append(&select, text, '"'); } else { append(&select, "quote_sql(", 0); append(&select, text, '"'); if (dd->quote_mode) { char mbuf[32]; sprintf(mbuf, ",%d", dd->quote_mode); append(&select, mbuf, 0); } } rc = sqlite3_step(stmt); if (rc == SQLITE_ROW) { if (dd->quote_mode >= -1) { append(&select, ") || ',' || ", 0); } else { append(&select, " || ", 0); } } else { if (dd->quote_mode >= -1) { append(&select, ") ", 0); } else { append(&select, " ", 0); } } } if (rc != SQLITE_DONE) { goto bailout1; } sqlite3_finalize(stmt); stmt = 0; if (dd->quote_mode >= 0) { append(&select, "|| ')' FROM ", 0); } else { if ((dd->quote_mode < -1) && dd->where) { sprintf(buffer, " || indent_xml(%d)", dd->indent); append(&select, buffer, 0); append(&select, " || 'where, '"'); append(&select, ",-1) || '>\n' FROM ", 0); } else { append(&select, "FROM ", 0); } } append(&select, table, '"'); if ((dd->quote_mode >= 0) && dd->where) { append(&select, " ", 0); append(&select, dd->where, 0); } if (hdr) { rc = table_dump(dd, 0, 0, hdr); append_free(&hdr); hdr = 0; } rc = table_dump(dd, 0, 0, select); if (rc == SQLITE_CORRUPT) { append(&select, " ORDER BY rowid DESC", 0); rc = table_dump(dd, 0, 0, select); } append_free(&select); } return 0; } /** * Execute SQL on sqlite_master table in order to dump data. * @param dd information structure for dump * @param errp pointer receiving error message * @param query SQL for sqlite3_*printf() * @param ... argument list * @result SQLite error code */ static int schema_dump(DUMP_DATA *dd, char **errp, const char *query, ...) { int rc; char *q; va_list ap; if (errp) { sqlite3_free(*errp); *errp = 0; } va_start(ap, query); q = sqlite3_vmprintf(query, ap); va_end(ap); if (!q) { return SQLITE_NOMEM; } rc = sqlite3_exec(dd->db, q, dump_cb, dd, errp); if (rc == SQLITE_CORRUPT) { char *tmp; tmp = sqlite3_mprintf("%s ORDER BY rowid DESC", q); sqlite3_free(q); if (!tmp) { return rc; } q = tmp; if (errp) { sqlite3_free(*errp); *errp = 0; } rc = sqlite3_exec(dd->db, q, dump_cb, dd, errp); } sqlite3_free(q); return rc; } /** * SQLite function for SQL output, see impexp_export_sql * @param ctx SQLite function context * @param nargs number of arguments * @param args argument vector */ static void export_func(sqlite3_context *ctx, int nargs, sqlite3_value **args) { DUMP_DATA dd0, *dd = &dd0; sqlite3 *db = (sqlite3 *) sqlite3_user_data(ctx); int i, mode = 0; char *filename = 0; #ifdef _WIN32 char fnbuf[MAX_PATH]; #endif dd->db = db; dd->where = 0; dd->nlines = -1; dd->indent = 0; if (nargs > 0) { if (sqlite3_value_type(args[0]) != SQLITE_NULL) { filename = (char *) sqlite3_value_text(args[0]); } } #ifdef _WIN32 if (!filename) { OPENFILENAME ofn; memset(&ofn, 0, sizeof (ofn)); memset(fnbuf, 0, sizeof (fnbuf)); ofn.lStructSize = sizeof (ofn); ofn.lpstrFile = fnbuf; ofn.nMaxFile = MAX_PATH; ofn.Flags = OFN_HIDEREADONLY | OFN_NOCHANGEDIR | OFN_EXPLORER | OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST; if (GetSaveFileName(&ofn)) { filename = fnbuf; } } #endif if (!filename) { goto done; } dd->out = fopen(filename, "w"); if (!dd->out) { goto done; } if (nargs > 1) { mode = sqlite3_value_int(args[1]); } dd->with_schema = !(mode & 1); dd->quote_mode = (mode >> 8) & 3; dd->nlines = 0; if (fputs("BEGIN TRANSACTION;\n", dd->out) >= 0) { dd->nlines++; } if (nargs <= 2) { schema_dump(dd, 0, "SELECT name, type, sql FROM sqlite_master" " WHERE sql NOT NULL AND type = 'table'"); if (dd->with_schema) { table_dump(dd, 0, 0, "SELECT sql FROM sqlite_master WHERE" " sql NOT NULL AND type IN ('index','trigger','view')"); } } else { for (i = 2; i < nargs; i += (mode & 2) ? 2 : 1) { dd->where = 0; if ((mode & 2) && (i + 1 < nargs)) { dd->where = (char *) sqlite3_value_text(args[i + 1]); } schema_dump(dd, 0, "SELECT name, type, sql FROM sqlite_master" " WHERE tbl_name LIKE %Q AND type = 'table'" " AND sql NOT NULL", sqlite3_value_text(args[i])); if (dd->with_schema) { table_dump(dd, 0, 1, "SELECT sql FROM sqlite_master" " WHERE sql NOT NULL" " AND type IN ('index','trigger','view')" " AND tbl_name LIKE %Q", sqlite3_value_text(args[i])); } } } if (fputs("COMMIT;\n", dd->out) >= 0) { dd->nlines++; } fclose(dd->out); done: sqlite3_result_int(ctx, dd->nlines); } /** * SQLite function for CSV output, see impexp_export_csv * @param ctx SQLite function context * @param nargs number of arguments * @param args argument vector */ static void export_csv_func(sqlite3_context *ctx, int nargs, sqlite3_value **args) { DUMP_DATA dd0, *dd = &dd0; sqlite3 *db = (sqlite3 *) sqlite3_user_data(ctx); int i; char *filename = 0; #ifdef _WIN32 char fnbuf[MAX_PATH]; #endif dd->db = db; dd->where = 0; dd->nlines = -1; dd->indent = 0; dd->with_schema = 0; dd->quote_mode = -1; if (nargs > 0) { if (sqlite3_value_type(args[0]) != SQLITE_NULL) { filename = (char *) sqlite3_value_text(args[0]); } } #ifdef _WIN32 if (!filename) { OPENFILENAME ofn; memset(&ofn, 0, sizeof (ofn)); memset(fnbuf, 0, sizeof (fnbuf)); ofn.lStructSize = sizeof (ofn); ofn.lpstrFile = fnbuf; ofn.nMaxFile = MAX_PATH; ofn.Flags = OFN_HIDEREADONLY | OFN_NOCHANGEDIR | OFN_EXPLORER | OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST; if (GetSaveFileName(&ofn)) { filename = fnbuf; } } #endif if (!filename) { goto done; } #ifdef _WIN32 dd->out = fopen(filename, "wb"); #else dd->out = fopen(filename, "w"); #endif if (!dd->out) { goto done; } dd->nlines = 0; if (nargs > 1) { if (sqlite3_value_type(args[1]) != SQLITE_NULL) { if (sqlite3_value_int(args[1])) { dd->indent = 1; } } } for (i = 2; i <= nargs - 3; i += 3) { char *schema = 0, *sql; dd->where = 0; if (sqlite3_value_type(args[i]) != SQLITE_NULL) { dd->where = (char *) sqlite3_value_text(args[i]); if (dd->where && !dd->where[0]) { dd->where = 0; } } if (sqlite3_value_type(args[i + 2]) != SQLITE_NULL) { schema = (char *) sqlite3_value_text(args[i + 2]); } if (!schema || (schema[0] == '\0')) { schema = "sqlite_master"; } sql = sqlite3_mprintf("SELECT name, type, sql FROM %s" " WHERE tbl_name LIKE %%Q AND " " (type = 'table' OR type = 'view')" " AND sql NOT NULL", schema); if (sql) { schema_dump(dd, 0, sql, sqlite3_value_text(args[i + 1])); sqlite3_free(sql); } } fclose(dd->out); done: sqlite3_result_int(ctx, dd->nlines); } /** * SQLite function for XML output, see impexp_export_xml * @param ctx SQLite function context * @param nargs number of arguments * @param args argument vector */ static void export_xml_func(sqlite3_context *ctx, int nargs, sqlite3_value **args) { DUMP_DATA dd0, *dd = &dd0; sqlite3 *db = (sqlite3 *) sqlite3_user_data(ctx); int i; char *filename = 0; char *openmode = "w"; #ifdef _WIN32 char fnbuf[MAX_PATH]; #endif dd->db = db; dd->where = 0; dd->nlines = -1; dd->indent = 0; dd->with_schema = 0; dd->quote_mode = -2; if (nargs > 0) { if (sqlite3_value_type(args[0]) != SQLITE_NULL) { filename = (char *) sqlite3_value_text(args[0]); } } #ifdef _WIN32 if (!filename) { OPENFILENAME ofn; memset(&ofn, 0, sizeof (ofn)); memset(fnbuf, 0, sizeof (fnbuf)); ofn.lStructSize = sizeof (ofn); ofn.lpstrFile = fnbuf; ofn.nMaxFile = MAX_PATH; ofn.Flags = OFN_HIDEREADONLY | OFN_NOCHANGEDIR | OFN_EXPLORER | OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST; if (GetSaveFileName(&ofn)) { filename = fnbuf; } } #endif if (!filename) { goto done; } if (nargs > 1) { if (sqlite3_value_type(args[1]) != SQLITE_NULL) { if (sqlite3_value_int(args[1])) { openmode = "a"; } } } if (nargs > 2) { if (sqlite3_value_type(args[2]) != SQLITE_NULL) { dd->indent = sqlite3_value_int(args[2]); if (dd->indent < 0) { dd->indent = 0; } } } dd->out = fopen(filename, openmode); if (!dd->out) { goto done; } dd->nlines = 0; for (i = 3; i <= nargs - 4; i += 4) { char *root = 0, *schema = 0, *sql; if (sqlite3_value_type(args[i]) != SQLITE_NULL) { root = (char *) sqlite3_value_text(args[i]); if (root && !root[0]) { root = 0; } } dd->where = 0; if (sqlite3_value_type(args[i + 1]) != SQLITE_NULL) { dd->where = (char *) sqlite3_value_text(args[i + 1]); if (dd->where && !dd->where[0]) { dd->where = 0; } } if (root) { indent(dd); dd->indent++; fputs("<", dd->out); quote_xml_str(dd, root); fputs(">\n", dd->out); } if (sqlite3_value_type(args[i + 3]) != SQLITE_NULL) { schema = (char *) sqlite3_value_text(args[i + 3]); } if (!schema || (schema[0] == '\0')) { schema = "sqlite_master"; } sql = sqlite3_mprintf("SELECT name, type, sql FROM %s" " WHERE tbl_name LIKE %%Q AND" " (type = 'table' OR type = 'view')" " AND sql NOT NULL", schema); if (sql) { schema_dump(dd, 0, sql, sqlite3_value_text(args[i + 2])); sqlite3_free(sql); } if (root) { dd->indent--; indent(dd); fputs("out); quote_xml_str(dd, root); fputs(">\n", dd->out); } } fclose(dd->out); done: sqlite3_result_int(ctx, dd->nlines); } /* see doc in impexp.h */ int impexp_export_sql(sqlite3 *db, char *filename, int mode, ...) { DUMP_DATA dd0, *dd = &dd0; va_list ap; char *table; #ifdef _WIN32 char fnbuf[MAX_PATH]; #endif if (!db) { return 0; } dd->db = db; dd->where = 0; dd->nlines = -1; #ifdef _WIN32 if (!filename) { OPENFILENAME ofn; memset(&ofn, 0, sizeof (ofn)); memset(fnbuf, 0, sizeof (fnbuf)); ofn.lStructSize = sizeof (ofn); ofn.lpstrFile = fnbuf; ofn.nMaxFile = MAX_PATH; ofn.Flags = OFN_HIDEREADONLY | OFN_NOCHANGEDIR | OFN_EXPLORER | OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST; if (GetSaveFileName(&ofn)) { filename = fnbuf; } } #endif if (!filename) { goto done; } dd->out = fopen(filename, "w"); if (!dd->out) { goto done; } dd->with_schema = !(mode & 1); dd->nlines = 0; if (fputs("BEGIN TRANSACTION;\n", dd->out) >= 0) { dd->nlines++; } va_start(ap, mode); table = va_arg(ap, char *); if (!table) { schema_dump(dd, 0, "SELECT name, type, sql FROM sqlite_master" " WHERE sql NOT NULL AND type = 'table'"); if (dd->with_schema) { table_dump(dd, 0, 0, "SELECT sql FROM sqlite_master WHERE" " sql NOT NULL AND type IN ('index','trigger','view')"); } } else { while (table) { dd->where = 0; if ((mode & 2)) { dd->where = va_arg(ap, char *); } schema_dump(dd, 0, "SELECT name, type, sql FROM sqlite_master" " WHERE tbl_name LIKE %Q AND type = 'table'" " AND sql NOT NULL", table); if (dd->with_schema) { table_dump(dd, 0, 1, "SELECT sql FROM sqlite_master" " WHERE sql NOT NULL" " AND type IN ('index','trigger','view')" " AND tbl_name LIKE %Q", table); } table = va_arg(ap, char *); } } va_end(ap); if (fputs("COMMIT;\n", dd->out) >= 0) { dd->nlines++; } fclose(dd->out); done: return dd->nlines; } /* see doc in impexp.h */ int impexp_export_csv(sqlite3 *db, char *filename, int hdr, ...) { DUMP_DATA dd0, *dd = &dd0; va_list ap; char *prefix, *table, *schema; #ifdef _WIN32 char fnbuf[MAX_PATH]; #endif if (!db) { return 0; } dd->db = db; dd->where = 0; dd->nlines = -1; dd->indent = 0; dd->with_schema = 0; dd->quote_mode = -1; dd->indent = hdr != 0; #ifdef _WIN32 if (!filename) { OPENFILENAME ofn; memset(&ofn, 0, sizeof (ofn)); memset(fnbuf, 0, sizeof (fnbuf)); ofn.lStructSize = sizeof (ofn); ofn.lpstrFile = fnbuf; ofn.nMaxFile = MAX_PATH; ofn.Flags = OFN_HIDEREADONLY | OFN_NOCHANGEDIR | OFN_EXPLORER | OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST; if (GetSaveFileName(&ofn)) { filename = fnbuf; } } #endif if (!filename) { goto done; } #ifdef _WIN32 dd->out = fopen(filename, "wb"); #else if ((hdr < 0) && access(filename, W_OK) == 0) { dd->out = fopen(filename, "a"); dd->indent = 0; } else { dd->out = fopen(filename, "w"); } #endif if (!dd->out) { goto done; } dd->nlines = 0; va_start(ap, hdr); prefix = va_arg(ap, char *); table = va_arg(ap, char *); schema = va_arg(ap, char *); while (table != NULL) { char *sql; dd->where = (prefix && prefix[0]) ? prefix : 0; if (!schema || (schema[0] == '\0')) { schema = "sqlite_master"; } sql = sqlite3_mprintf("SELECT name, type, sql FROM %s" " WHERE tbl_name LIKE %%Q AND" " (type = 'table' OR type = 'view')" " AND sql NOT NULL", schema); if (sql) { schema_dump(dd, 0, sql, table); sqlite3_free(sql); } prefix = va_arg(ap, char *); table = va_arg(ap, char *); schema = va_arg(ap, char *); } va_end(ap); fclose(dd->out); done: return dd->nlines; } /* see doc in impexp.h */ int impexp_export_xml(sqlite3 *db, char *filename, int append, int indnt, char *root, char *item, char *tablename, char *schema) { DUMP_DATA dd0, *dd = &dd0; char *sql; #ifdef _WIN32 char fnbuf[MAX_PATH]; #endif if (!db) { return 0; } dd->db = db; dd->where = item; dd->nlines = -1; dd->indent = (indnt > 0) ? indnt : 0; dd->with_schema = 0; dd->quote_mode = -2; #ifdef _WIN32 if (!filename) { OPENFILENAME ofn; memset(&ofn, 0, sizeof (ofn)); memset(fnbuf, 0, sizeof (fnbuf)); ofn.lStructSize = sizeof (ofn); ofn.lpstrFile = fnbuf; ofn.nMaxFile = MAX_PATH; ofn.Flags = OFN_HIDEREADONLY | OFN_NOCHANGEDIR | OFN_EXPLORER | OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST; if (GetSaveFileName(&ofn)) { filename = fnbuf; } } #endif if (!filename) { goto done; } dd->out = fopen(filename, append ? "a" : "w"); if (!dd->out) { goto done; } dd->nlines = 0; if (root) { indent(dd); dd->indent++; fputs("<", dd->out); quote_xml_str(dd, root); fputs(">\n", dd->out); } if (!schema || (schema[0] == '\0')) { schema = "sqlite_master"; } sql = sqlite3_mprintf("SELECT name, type, sql FROM %s" " WHERE tbl_name LIKE %%Q AND" " (type = 'table' OR type = 'view')" " AND sql NOT NULL", schema); if (sql) { schema_dump(dd, 0, sql, tablename); sqlite3_free(sql); } if (root) { dd->indent--; indent(dd); fputs("out); quote_xml_str(dd, root); fputs(">\n", dd->out); } fclose(dd->out); done: return dd->nlines; } /** * Write string using JSON output function * @param string string to be written * @param pfs JSON output function */ static void json_pstr(const char *string, json_pfs *pfs) { while (*string) { pfs->pfunc(*string, pfs->parg); string++; } } /** * Quote and write string using JSON output function * @param string string to be written * @param pfs JSON output function */ static void json_pstrq(const char *string, json_pfs *pfs) { impexp_putc pfunc = pfs->pfunc; void *parg = pfs->parg; char buf[64]; if (!string) { json_pstr("null", pfs); return; } pfunc('"', parg); while (*string) { switch (*string) { case '"': case '\\': pfunc('\\', parg); pfunc(*string, parg); break; case '\b': pfunc('\\', parg); pfunc('b', parg); break; case '\f': pfunc('\\', parg); pfunc('f', parg); break; case '\n': pfunc('\\', parg); pfunc('n', parg); break; case '\r': pfunc('\\', parg); pfunc('r', parg); break; case '\t': pfunc('\\', parg); pfunc('t', parg); break; default: if (((*string < ' ') && (*string > 0)) || (*string == 0x7f)) { sprintf(buf, "\\u%04x", *string); json_pstr(buf, pfs); } else if (*string < 0) { unsigned char c = string[0]; unsigned long uc = 0; if (c < 0xc0) { uc = c; } else if (c < 0xe0) { if ((string[1] & 0xc0) == 0x80) { uc = ((c & 0x1f) << 6) | (string[1] & 0x3f); ++string; } else { uc = c; } } else if (c < 0xf0) { if (((string[1] & 0xc0) == 0x80) && ((string[2] & 0xc0) == 0x80)) { uc = ((c & 0x0f) << 12) | ((string[1] & 0x3f) << 6) | (string[2] & 0x3f); string += 2; } else { uc = c; } } else if (c < 0xf8) { if (((string[1] & 0xc0) == 0x80) && ((string[2] & 0xc0) == 0x80) && ((string[3] & 0xc0) == 0x80)) { uc = ((c & 0x03) << 18) | ((string[1] & 0x3f) << 12) | ((string[2] & 0x3f) << 6) | (string[4] & 0x3f); string += 3; } else { uc = c; } } else if (c < 0xfc) { if (((string[1] & 0xc0) == 0x80) && ((string[2] & 0xc0) == 0x80) && ((string[3] & 0xc0) == 0x80) && ((string[4] & 0xc0) == 0x80)) { uc = ((c & 0x01) << 24) | ((string[1] & 0x3f) << 18) | ((string[2] & 0x3f) << 12) | ((string[4] & 0x3f) << 6) | (string[5] & 0x3f); string += 4; } else { uc = c; } } else { /* ignore */ ++string; } if (uc < 0x10000) { sprintf(buf, "\\u%04lx", uc); } else if (uc < 0x100000) { uc -= 0x10000; sprintf(buf, "\\u%04lx", 0xd800 | ((uc >> 10) & 0x3ff)); json_pstr(buf, pfs); sprintf(buf, "\\u%04lx", 0xdc00 | (uc & 0x3ff)); } else { strcpy(buf, "\\ufffd"); } json_pstr(buf, pfs); } else { pfunc(*string, parg); } break; } ++string; } pfunc('"', parg); } /** * Conditionally quote and write string using JSON output function * @param string string to be written * @param pfs JSON output function */ static void json_pstrc(const char *string, json_pfs *pfs) { if (*string && strchr(".0123456789-+", *string)) { json_pstr(string, pfs); } else { json_pstrq(string, pfs); } } /** * Write a blob as base64 string using JSON output function * @param blk pointer to blob * @param len length of blob * @param pfs JSON output function */ static void json_pb64(const unsigned char *blk, int len, json_pfs *pfs) { impexp_putc pfunc = pfs->pfunc; void *parg = pfs->parg; int i, reg[5]; char buf[16]; static const char *b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; if (!blk) { json_pstr("null", pfs); return; } buf[4] = '\0'; pfunc('"', parg); for (i = 0; i < len; i += 3) { reg[1] = reg[2] = reg[3] = reg[4] = 0; reg[0] = blk[i]; if (i + 1 < len) { reg[1] = blk[i + 1]; reg[3] = 1; } if (i + 2 < len) { reg[2] = blk[i + 2]; reg[4] = 1; } buf[0] = b64[reg[0] >> 2]; buf[1] = b64[((reg[0] << 4) & 0x30) | (reg[1] >> 4)]; if (reg[3]) { buf[2] = b64[((reg[1] << 2) & 0x3c) | (reg[2] >> 6)]; } else { buf[2] = '='; } if (reg[4]) { buf[3] = b64[reg[2] & 0x3f]; } else { buf[3] = '='; } json_pstr(buf, pfs); } pfunc('"', parg); } /** * Execute SQL and write output as JSON * @param db SQLite database pointer * @param sql SQL text * @param pfunc JSON output function * @param parg argument for output function * @result SQLite error code */ static int json_output(sqlite3 *db, char *sql, impexp_putc pfunc, void *parg) { json_pfs pfs0, *pfs = &pfs0; const char *tail = sql; int i, nresults = 0, result = SQLITE_ERROR; pfs->pfunc = pfunc; pfs->parg = parg; json_pstr("{\"sql\":", pfs); json_pstrq(sql, pfs); json_pstr(",\"results\":[", pfs); do { sqlite3_stmt *stmt; int firstrow = 1, nrows = 0; char buf[256]; ++nresults; json_pstr((nresults == 1) ? "{" : ",{", pfs); result = sqlite3_prepare(db, tail, -1, &stmt, &tail); if (result != SQLITE_OK) { doerr: if (nrows == 0) { json_pstr("\"columns\":null,\"rows\":null,\"changes\":0," "\"last_insert_rowid\":null,", pfs); } json_pstr("\"error:\"", pfs); json_pstrq(sqlite3_errmsg(db), pfs); pfunc('}', parg); break; } result = sqlite3_step(stmt); while ((result == SQLITE_ROW) || (result == SQLITE_DONE)) { if (firstrow) { for (i = 0; i < sqlite3_column_count(stmt); i++) { char *type; json_pstr((i == 0) ? "\"columns\":[" : ",", pfs); json_pstr("{\"name\":", pfs); json_pstrq(sqlite3_column_name(stmt, i), pfs); json_pstr(",\"decltype\":", pfs); json_pstrq(sqlite3_column_decltype(stmt, i), pfs); json_pstr(",\"type\":", pfs); switch (sqlite3_column_type(stmt, i)) { case SQLITE_INTEGER: type = "integer"; break; case SQLITE_FLOAT: type = "float"; break; case SQLITE_BLOB: type = "blob"; break; case SQLITE_TEXT: type = "text"; break; case SQLITE_NULL: type = "null"; break; default: type = "unknown"; break; } json_pstrq(type, pfs); pfunc('}', parg); } if (i) { pfunc(']', parg); } firstrow = 0; } if (result == SQLITE_DONE) { break; } ++nrows; json_pstr((nrows == 1) ? ",\"rows\":[" : ",", pfs); for (i = 0; i < sqlite3_column_count(stmt); i++) { pfunc((i == 0) ? '[' : ',', parg); switch (sqlite3_column_type(stmt, i)) { case SQLITE_INTEGER: json_pstr((char *) sqlite3_column_text(stmt, i), pfs); break; case SQLITE_FLOAT: json_pstrc((char *) sqlite3_column_text(stmt, i), pfs); break; case SQLITE_BLOB: json_pb64((unsigned char *) sqlite3_column_blob(stmt, i), sqlite3_column_bytes(stmt, i), pfs); break; case SQLITE_TEXT: json_pstrq((char *) sqlite3_column_text(stmt, i), pfs); break; case SQLITE_NULL: default: json_pstr("null", pfs); break; } } json_pstr((i == 0) ? "null]" : "]", pfs); result = sqlite3_step(stmt); } if (nrows > 0) { pfunc(']', parg); } result = sqlite3_finalize(stmt); if (result != SQLITE_OK) { if (nrows > 0) { sprintf(buf, #ifdef _WIN32 ",\"changes\":%d,\"last_insert_rowid\":%I64d", #else ",\"changes\":%d,\"last_insert_rowid\":%lld", #endif sqlite3_changes(db), sqlite3_last_insert_rowid(db)); json_pstr(buf, pfs); } goto doerr; } if (nrows == 0) { json_pstr("\"columns\":null,\"rows\":null", pfs); } sprintf(buf, #ifdef _WIN32 ",\"changes\":%d,\"last_insert_rowid\":%I64d", #else ",\"changes\":%d,\"last_insert_rowid\":%lld", #endif sqlite3_changes(db), sqlite3_last_insert_rowid(db)); json_pstr(buf, pfs); json_pstr(",\"error\":null}", pfs); } while (tail && *tail); json_pstr("]}", pfs); return result; } /** * SQLite function for JSON output, see impexp_export_json * @param ctx SQLite function context * @param nargs number of arguments * @param args argument vector */ static void export_json_func(sqlite3_context *ctx, int nargs, sqlite3_value **args) { sqlite3 *db = (sqlite3 *) sqlite3_user_data(ctx); int result = -1; char *filename = 0; char *sql = 0; FILE *out = 0; #ifdef _WIN32 char fnbuf[MAX_PATH]; #endif if (nargs > 0) { if (sqlite3_value_type(args[0]) != SQLITE_NULL) { filename = (char *) sqlite3_value_text(args[0]); } } #ifdef _WIN32 if (!filename) { OPENFILENAME ofn; memset(&ofn, 0, sizeof (ofn)); memset(fnbuf, 0, sizeof (fnbuf)); ofn.lStructSize = sizeof (ofn); ofn.lpstrFile = fnbuf; ofn.nMaxFile = MAX_PATH; ofn.Flags = OFN_HIDEREADONLY | OFN_NOCHANGEDIR | OFN_EXPLORER | OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST; if (GetSaveFileName(&ofn)) { filename = fnbuf; } } #endif if (!filename) { goto done; } out = fopen(filename, "w"); if (!out) { goto done; } if (nargs > 1) { sql = (char *) sqlite3_value_text(args[1]); } if (sql) { result = json_output(db, sql, (impexp_putc) fputc, out); } fclose(out); done: sqlite3_result_int(ctx, result); } /* see doc in impexp.h */ int impexp_export_json(sqlite3 *db, char *sql, impexp_putc pfunc, void *parg) { return json_output(db, sql, pfunc, parg); } /** * Initializer for SQLite extension load mechanism. * @param db SQLite database pointer * @param errmsg pointer receiving error message * @param api SQLite API routines * @result SQLite error code */ #ifdef STANDALONE static int #else int #endif sqlite3_extension_init(sqlite3 *db, char **errmsg, const sqlite3_api_routines *api) { int rc, i; static const struct { const char *name; void (*func)(sqlite3_context *, int, sqlite3_value **); int nargs; int textrep; } ftab[] = { { "quote_sql", quote_func, -1, SQLITE_UTF8 }, { "import_sql", import_func, -1, SQLITE_UTF8 }, { "export_sql", export_func, -1, SQLITE_UTF8 }, { "quote_csv", quote_csv_func, -1, SQLITE_UTF8 }, { "export_csv", export_csv_func, -1, SQLITE_UTF8 }, { "indent_xml", indent_xml_func, 1, SQLITE_UTF8 }, { "quote_xml", quote_xml_func, -1, SQLITE_UTF8 }, { "export_xml", export_xml_func, -1, SQLITE_UTF8 }, { "export_json", export_json_func, -1, SQLITE_UTF8 } }; #ifndef STANDALONE if (api != NULL) { SQLITE_EXTENSION_INIT2(api); } #endif for (i = 0; i < sizeof (ftab) / sizeof (ftab[0]); i++) { rc = sqlite3_create_function(db, ftab[i].name, ftab[i].nargs, ftab[i].textrep, db, ftab[i].func, 0, 0); if (rc != SQLITE_OK) { for (--i; i >= 0; --i) { sqlite3_create_function(db, ftab[i].name, ftab[i].nargs, ftab[i].textrep, 0, 0, 0, 0); } break; } } return rc; } /* see doc in impexp.h */ int impexp_init(sqlite3 *db) { return sqlite3_extension_init(db, NULL, NULL); }