diff options
author | Paolo Bonzini <pbonzini@redhat.com> | 2012-12-07 18:06:22 +0100 |
---|---|---|
committer | Paolo Bonzini <pbonzini@redhat.com> | 2012-12-07 22:42:01 +0100 |
commit | 7c5a82bf6a39ddbf9702692373b71eee2feb972b (patch) | |
tree | 412e5ae1d8a25513192de8836a366695eb4d6563 | |
parent | 94e6b12fbea34f1b0f503955dace3ef69d3ec1f4 (diff) | |
download | msitools-7c5a82bf6a39ddbf9702692373b71eee2feb972b.tar.gz msitools-7c5a82bf6a39ddbf9702692373b71eee2feb972b.tar.xz msitools-7c5a82bf6a39ddbf9702692373b71eee2feb972b.zip |
msibuild: add options to execute arbitrary queries
-rw-r--r-- | Makefile.am | 4 | ||||
-rw-r--r-- | tools/msibuild.c | 36 | ||||
-rw-r--r-- | tools/sqldelim.c | 253 | ||||
-rw-r--r-- | tools/sqldelim.h | 21 |
4 files changed, 312 insertions, 2 deletions
diff --git a/Makefile.am b/Makefile.am index bb2516e..f89a867 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,7 +1,7 @@ ACLOCAL_AMFLAGS = -I m4 SUBDIRS = libmsi tests . -noinst_HEADERS = include/debug.h +noinst_HEADERS = include/debug.h tools/sqldelim.h dist_include_HEADERS = include/libmsi.h AM_CPPFLAGS = -Iinclude -I$(srcdir)/include $(GLIB_CFLAGS) $(GSF_CFLAGS) $(UUID_CFLAGS) @@ -10,7 +10,7 @@ AM_LDFLAGS = -Llibmsi bin_PROGRAMS = msibuild msiinfo -msibuild_SOURCES = tools/msibuild.c +msibuild_SOURCES = tools/msibuild.c tools/sqldelim.c msibuild_LDADD = -lmsi $(GLIB_LIBS) $(GSF_LIBS) $(UUID_LIBS) msibuild_DEPENDENCIES = libmsi/libmsi.la diff --git a/tools/msibuild.c b/tools/msibuild.c index 66af31c..b996ba5 100644 --- a/tools/msibuild.c +++ b/tools/msibuild.c @@ -29,6 +29,8 @@ #include <uuid.h> #endif +#include "sqldelim.h" + static void init_suminfo(LibmsiSummaryInfo *si) { libmsi_summary_info_set_property(si, MSI_PID_TITLE, @@ -186,12 +188,33 @@ static int add_stream(const char *stream, const char *file) return r; } +static int do_query(const char *sql, void *opaque) +{ + LibmsiResult r; + LibmsiQuery *query; + + r = libmsi_database_open_query(db, sql, &query); + if (r != LIBMSI_RESULT_SUCCESS) { + fprintf(stderr, "failed to open query (%u)\n", r); + return r; + } + + r = libmsi_query_execute(query, NULL); + if (r != LIBMSI_RESULT_SUCCESS) + fprintf(stderr, "failed to execute query (%u)\n", r); + + libmsi_query_close(query); + libmsi_unref(query); + return r; +} + static void show_usage(void) { printf( "Usage: msibuild MSIFILE [OPTION]...\n" "Options:\n" " -s name [author] [template] [uuid] Set summary information.\n" + " -q query Execute SQL query/queries.\n" " -i table1.idt Import one table into the database.\n" " -a stream file Add 'stream' to storage with contents of 'file'.\n" "\nExisting tables or streams will be overwritten. If package.msi does not exist a new file\n" @@ -251,6 +274,19 @@ int main(int argc, char *argv[]) } while (argv[1] && argv[1][0] != '-'); argc--, argv++; break; + case 'q': + do { + ret = sql_get_statement(argv[1], do_query, NULL); + if (ret == 0) { + ret = sql_get_statement("", do_query, NULL); + } + if (ret) { + break; + } + argc--, argv++; + } while (argv[1] && argv[1][0] != '-'); + argc--, argv++; + break; case 'a': if (argc < 3) break; ret = add_stream(argv[1], argv[2]); diff --git a/tools/sqldelim.c b/tools/sqldelim.c new file mode 100644 index 0000000..1eebf09 --- /dev/null +++ b/tools/sqldelim.c @@ -0,0 +1,253 @@ +/* +** 2001 September 15 +** +** 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. +** +************************************************************************* +** A tokenizer for SQL +** +** This file contains C code that splits an SQL input string up into +** individual tokens, groups them back into statements, and passes the +** statements up to a user-defined callback. +*/ + +#include <ctype.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <glib.h> + +/* +** All the keywords of the SQL language are stored as in a hash +** table composed of instances of the following structure. +*/ +typedef struct Keyword Keyword; +struct Keyword { + const uint8_t *zName; /* The keyword name */ +}; + +#define MAX_TOKEN_LEN 11 + +/* +** These are the keywords that begin a new SQL statement. +** They MUST be in alphabetical order +*/ +static const Keyword aKeywordTable[] = { + { "ALTER" }, + { "CREATE" }, + { "DELETE" }, + { "DROP" }, + { "INSERT" }, + { "SELECT" }, + { "UPDATE" }, +}; + +#define KEYWORD_COUNT (sizeof aKeywordTable / sizeof (Keyword)) + +/* +** Comparison function for binary search. +*/ +static int sql_compare_keyword(const void *m1, const void *m2){ + const uint8_t *p = m1; + const Keyword *k = m2; + const uint8_t *q = k->zName; + + for (; *p; p++, q++) { + uint8_t c; + if ((uint16_t) *p > 127) + return 1; + c = *p; + if (c >= 'a' && c <= 'z') + c ^= 'A' ^ 'a'; + if (c != *q) + return (unsigned)c - (unsigned)*q; + } + + return (unsigned)*p - (unsigned)*q; +} + +/* +** This function looks up an identifier to determine if it is a +** keyword. If it is a keyword, the token code of that keyword is +** returned. If the input is not a keyword, TK_ID is returned. +*/ +static int sqlite_find_keyword(const char *z, int n) +{ + char str[MAX_TOKEN_LEN + 1]; + Keyword *r; + + if (n > MAX_TOKEN_LEN) + return false; + + memcpy(str, z, n); + str[n] = 0; + r = bsearch(str, aKeywordTable, KEYWORD_COUNT, sizeof (Keyword), sql_compare_keyword); + return r != NULL; +} + + +/* +** If X is a character that can be used in an identifier then +** isIdChar[X] will be 1. Otherwise isIdChar[X] will be 0. +** +** In this implementation, an identifier can be a string of +** alphabetic characters, digits, and "_" plus any character +** with the high-order bit set. The latter rule means that +** any sequence of UTF-8 characters or characters taken from +** an extended ISO8859 character set can form an identifier. +*/ +static const uint8_t isIdChar[] = { +/* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */ + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1x */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, /* 2x */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 3x */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4x */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* 5x */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6x */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, /* 7x */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 8x */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 9x */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* Ax */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* Bx */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* Cx */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* Dx */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* Ex */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* Fx */ +}; + + +/* +** Return the length of the token that begins at z[0]. Return +** -1 if the token is (or might be) incomplete. Store the token +** type in *tokenType before returning. +*/ +static int sql_skip_token(const char **p, bool *cont) +{ + int i = 1; + const uint8_t *z = (uint8_t *) *p; + bool get_keyword = *cont; + + *cont = true; + switch (*z) { + case ' ': case '\t': case '\n': case '\f': + while (isspace(z[i]) && z[i] != '\r') i++; + *p += i; + return false; + case '-': + case '(': + case ')': + case '*': + case '=': + case '<': + case '>': + case '!': + case '?': + case ',': + case '.': + *p += 1; + return false; + case '`': case '\'': { + int delim = z[0]; + while (z[i]) + if (z[i++] == delim) + break; + *p += i; + return false; + } + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + while (isdigit(z[i])) i++; + *p += i; + return false; + case '[': + while (z[i] && z[i-1] != ']') i++; + *p += i; + return false; + default: + if (!isIdChar[*z]) { + *p += 1; + return true; + } + while (isIdChar[z[i]]) i++; + if (get_keyword && sqlite_find_keyword(z, i)) { + return true; + } else { + /* Do not recognize a keyword at the beginning of the next chunk. */ + if (!z[i]) { + *cont = false; + } + *p += i; + return false; + } + } +} + +int sql_get_statement(const char *start, + int (*fn)(const char *stmt, void *opaque), + void *opaque) +{ + static GString str; + static bool cont = false; + + const char *p = start; + char *stmt; + bool done; + int ret = 0; + + /* Final part? Build a statement with what's left. */ + if (!*p) { + goto stmt; + } + + while (*p) { + start = p; + /* A semicolon is not part of the SQL syntax, skip it and conclude + * this statement. + */ + if (*p == ';') { + done = true; + p++; + } else { + done = sql_skip_token(&p, &cont); + g_string_append_len(&str, start, p - start); + } + + if (done) { +stmt: + cont = false; + stmt = g_strndup(str.str, str.len); + g_string_erase(&str, 0, str.len); + if (stmt[0]) { + ret = fn(stmt, opaque); + } + free(stmt); + if (ret) { + return ret; + } + } + } + return 0; +} + +#if 0 +int main() +{ + uint8_t line[100], *stmt; + const uint8_t *p; + + while (fgets(line, sizeof(line), stdin)) { + sql_get_statement(line, puts); + } + sql_get_statement("", puts); +} +#endif diff --git a/tools/sqldelim.h b/tools/sqldelim.h new file mode 100644 index 0000000..ade9de0 --- /dev/null +++ b/tools/sqldelim.h @@ -0,0 +1,21 @@ +/* +** 2001 September 15 +** +** 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. +** +************************************************************************* +** A tokenizer for SQL +** +** This file contains C code that splits an SQL input string up into +** individual tokens and sends those tokens one-by-one over to the +** parser for analysis. +*/ + +int sql_get_statement(const char *start, + int (*fn)(const char *stmt, void *opaque), + void *opaque); |