summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile.am4
-rw-r--r--tools/msibuild.c36
-rw-r--r--tools/sqldelim.c253
-rw-r--r--tools/sqldelim.h21
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);