diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..01b5360 --- /dev/null +++ b/Makefile @@ -0,0 +1,56 @@ + +MAKE=gmake + +DEFS= + +SRCS= usqlite.c + +HDRS= + +OBJS= $(SRCS:.c=.o) +CC=gcc +#CC=mipsel-linux-gcc +DEFS=-D_XOPEN_SOURCE=500 -DTGT_SQLITE_VERSION_NUMBER=3007002 +CFLAGS=-g -std=c99 +INCS= +LIBS= +#CFLAGS=-g -O3 -fno-strict-aliasing +#WARN=-pedantic -Wall -Wnested-externs -Wpointer-arith -Werror -Wno-unused +WARN=-pedantic -Wall -W -Wnested-externs -Wpointer-arith -Wno-long-long + +PLATFORM=$(shell uname -s | cut -d- -f1) +ifeq ($(PLATFORM),Linux) +LIBS=-lsqlite3 +endif + +all: usqlite + +install: usqlite + strip $< + cp $< /mod/bin/ + +usqlite: ${OBJS} + @echo "Linking..." + @-[ -f $@ ] && mv $@ $@~ || exit 0 + ${CC} -static-libgcc \ + ${WARN} \ + ${DEFS} \ + ${CFLAGS} -o $@ \ + ${OBJS} \ + ${LIBS} + @echo "Done..." + +clean: + @-touch core + rm -f usqlite usqlite~ core ${OBJS} + +#tags: +# @-ctags *.[ch] 2>> /dev/null + +.c.o: + @echo " $<" + @$(CC) $(CFLAGS) ${WARN} ${DEFS} ${INCS} -c $< -o $@ + +${OBJS}: ${HDRS} + + diff --git a/usqlite.c b/usqlite.c new file mode 100644 index 0000000..6d8b0ed --- /dev/null +++ b/usqlite.c @@ -0,0 +1,473 @@ +/* printf, scanf, stdin, stderr */ +#include +/* malloc, free */ +#include +/* size_t */ +#include +/* strdup, strtok_r, strcmp, strncmp */ +#include +/* basename */ +#include +/* sqlite3_* */ +#include + +/* ABI version required by the code */ +#ifndef REQD_SQLITE_VERSION_NUMBER +#define REQD_SQLITE_VERSION_NUMBER 3007015 +#endif +/* ABI version available on target platform */ +#ifndef TGT_SQLITE_VERSION_NUMBER +#define TGT_SQLITE_VERSION_NUMBER SQLITE_VERSION_NUMBER +#endif + +static const char *zVersionNo = "0.3 (" __DATE__ " " __TIME__ ")"; +static int fDbg = 0; +static int fCols = 0; +static char *zColSep = "|"; + +#if 0 +/* readlink */ +#include +/* PAtH_MAX */ +#include + +static int get_pathname(FILE *f, char *buf, size_t bufsiz) +{ + char fnmbuf[sizeof "/proc/self/fd/0123456789"]; + size_t nr; + if (sizeof "/proc/self/fd/0" - 1 > snprintf(fnmbuf, sizeof fnmbuf, "/proc/self/fd/%d", fileno(f)) || + 0 > (nr = readlink(fnmbuf, buf, bufsiz)) || nr >= bufsiz) { + return -1; + } + buf[nr]='\0'; + return nr; +} + +static int line_callback(void *parm, int argc, char **argv, char **azColName) { + int i; + (void *) parm; + + for (i=0; i=0) && rc 0? zColSep:""), azColName[i]); + azHeader[i] = strdup(azColName[i]); + memset(azHeader[i],'=',strlen(azHeader[i])); + } + printf("\n"); + for (int i=0; i 0? " ":""), azHeader[i]); + free(azHeader[i]); + } + free(azHeader); + printf("\n"); + *(int*)parm = 0; + } + + for (int i=0; i 0? zColSep:""), argv[i]); + } + printf("\n"); + return 0; +} + +static int sql_exec( sqlite3 * db, const char *sql ) { + char *zErrMsg = 0; + int cb = /*fDbg ||*/ fCols; + + if (fDbg) fprintf( stderr, "About to run SQL ---\n%s\n---\n", sql); + int rc = sqlite3_exec(db, sql, col_callback, &cb, &zErrMsg); + if ( rc != SQLITE_OK ) { + fprintf(stderr, "SQL error: %s\n", zErrMsg); + sqlite3_free(zErrMsg); + return 1; + } + else if (fDbg) ok(); + return 0; +} + +static int cmdline_exec( sqlite3 *db, int argc, char *argv[]) { + int rc = 0; + + for (int ii=1; ii < argc; ++ii) { + rc = sql_exec(db, argv[ii]); + if ( rc != 0 ) { + return 1; + } + } + return rc; +} + +static int stream_exec( FILE * ff, sqlite3 *db) { + size_t bsiz = 4096; + char * zStmt = (char *)malloc(bsiz); + size_t nStmt; + size_t nStmtEnd = 0; + int rc = 0; + /* + * escaping " %n%[^;]%n%c%n": + * maybe trim whitespace, non-; string; possible ; + */ + const char fmt[] = "%s%%n%%%u[^;]%%n%%c%%n"; + char sfmt[32]; + + /* alloc failed? */ + if (!zStmt) return 1; + /* empty text */ + *zStmt = '\0'; + for (char *pStmt = zStmt; rc == 0; pStmt += (nStmtEnd > 0? nStmtEnd: nStmt)) { + /* if the allocation is full (include final \0), increase */ + if (pStmt+1 >= zStmt+bsiz) { + char *zz = (char *)sqlite3_realloc(zStmt,bsiz+=4096); + if (!zz) { + rc = 1; break; + } + pStmt = zz + (pStmt - zStmt); + zStmt = zz; + } + /* set the text field width, allowing for extra \0 */ + rc = snprintf( sfmt, sizeof sfmt, fmt, (pStmt==zStmt? " ":""), bsiz-(pStmt-zStmt)-1 ); + if (rc < 0 || rc > (int)(sizeof sfmt)) { + if (fDbg) fprintf( stderr, "Can't set format to read SQL: rc=%d\n", rc); + rc = 1; break; + } + nStmt = nStmtEnd = 0; + /* reading possible non-; text followed by possible ; */ + char scln; + rc = fscanf( ff, ";%n", (int *) &nStmtEnd ); + if (rc >= 0) { + if (nStmtEnd == 0) { /* not an empty statement */ + int nStart = 0; + rc = fscanf( ff, sfmt, &nStart, pStmt, &nStmt, &scln, &nStmtEnd ); + if (rc > 0) { + /* nStmt may randomly be set to 1024 - why?? */ + if (zStmt[0]) { + if (fDbg) fprintf(stderr, "Adjusting nStmt = %u", nStmt); + nStmt = strlen(pStmt); + if (fDbg) fprintf(stderr, " to %u\n", nStmt); + } + else if (nStmtEnd > nStmt) { + nStmt -= nStart; + } + nStmtEnd -= nStart; + } + } + else + scln = ';'; + } + /* err, EOF or read nothing: fail on error */ + if (rc <= 0 && ferror(ff)) { + if (fDbg) { + if (EOF == rc) + eof(); + else + fprintf( stderr, "SQL read error: rc=%d\n", rc); + } + rc = 1; + break; + } + if (rc == EOF) { + if (zStmt[0]) { + /* read some stuff but no ; ? */ + scln = ';'; + nStmtEnd = nStmt+1; + if (fDbg) fprintf( stderr, "Faking final ; for unterminated input\n"); + } + else { + if (fDbg) eof(); + break; + } + } + + if (fDbg) fprintf( stderr, "Read %u characters of SQL (rc=%d)\n", nStmt, rc); + + /* read or faked a possible ; - add it */ + if (nStmtEnd > nStmt) { + if (fDbg) fprintf( stderr, "Adding final ;\n"); + pStmt[nStmt] = scln; + pStmt[nStmtEnd] = '\0'; + } + /* no final ;, try again */ + if (nStmtEnd == 0 || pStmt[nStmt] != ';') { + if (fDbg) fprintf( stderr, "No final ;: reading more\n"); + if (rc > 0) rc = 0; continue; + } + /* potential command: what does sqlite say? -> 1, 0, SQLITE_NOMEM */ + if (fDbg) fprintf( stderr, "About to check SQL ---\n%s\n---\n", zStmt); + int src = sqlite3_complete(zStmt); + switch (src) { + case 1: { /* complete-ish - try it */ + if (fDbg) ok(); + src = sql_exec(db, zStmt); + if (rc >= 0) rc = src; + if (src != 0) break; + + /* now ready for the next one */ + pStmt = zStmt; + nStmt = nStmtEnd = 0; + zStmt[0] = '\0'; + break; + } + + case 0: { /* incomplete - get more */ + int nn = 0; + sscanf( zStmt, " ;%n", &nn); + if (nn == 0) { + /* non-empty statement */ + if (fDbg) fprintf( stderr, "Incomplete, reading more\n"); + rc = src; + } + break; + } + default: { + if (fDbg) fprintf( stderr, "Error: %s\n", sqlite3_errstr(src)); + rc = 1; + } + } + } + + free(zStmt); + + return rc == EOF? 0: rc; +} + +#if 0 +static int strcmpany( const char* str, const char* strtest[] ) { + for (size_t i = 0; strtest[i]; ++i) { + if (0 == strcmp(str, strtest[i])) return 0; + } + return 1; +} +#endif + +static int optcmp( const char* str, const char* strtest[], char** val ) { + + for (size_t i = 0; strtest[i]; ++i) { + if (strtest[i][1] == '-') { /* --opt */ + if (val && str[1] == '-') { /* expecting --opt= */ + char *save = NULL; + char *tmpStr = strdup(str); + if (tmpStr && (0 == strcmp(strtok_r(tmpStr, "=", &save), strtest[i]))) { + *val = strtok_r( NULL, "=", &save); + return 0; + } + free(tmpStr); + } + else if (0 == strcmp(str, strtest[i])) { + return 0; + } + } + else { /* -o */ + if (0 == strncmp(str, strtest[i], 2)) { + if (val) *val = strdup(str+2); + return 0; + } + } + } + + return 1; +} + +static void usage(const char* progname) { + fprintf( stderr, "Usage: %s [OPTIONS] [\"SQL command;\" ...] [< SQL_script]\n", progname); +} + +static void help(const char* progname) { + usage(progname); + fprintf( stderr, "\nExecute SQL commands on SQLite databases\n\n" +"Options:\n" +" -C, --show-columns Show column names (if any) as headers of \n" +" query results\n" +" -S, String to separate columns in query \n" +" --column-separator=SEP results (default \"|\")\n" +" -v, --verbose Log details to stderr\n" +" -V, --version, Print program version details to stderr\n" +" -- Subsequent parameters are not options\n" +" -h, --help, -? Print this help to stderr and exit\n" +"\nTo cancel reading from stdin in a POSIX shell, use the input \nredirection \"<&-\".\n" ); +} + +int main(int argc, char *argv[]) { + const char zDummyDB[] = ":memory:"; + const char *zHelp[] = { "--help", "-h", "-?", 0 }; + const char *zVersion[] = { "--version", "-V", 0 }; + const char *zVerbose[] = { "--verbose", "-v", 0 }; + const char *zCols[] = { "--show-columns", "-C", 0 }; + const char *zColSepOpt[] = { "--column-separator", "-S", 0 }; + const char *zCont[] = { "--", 0 }; + const char *zProgName = argv[0]; + + /* process options */ + while (argc >= 2 && (*(argv[1]) == '-')) { + char * tmpVal = NULL; + if (0 == optcmp(argv[1], zHelp, NULL)) { + help(zProgName); + return 0; + } + else if (0 == optcmp(argv[1], zCols, NULL)) { + fCols = 1; + } + else if (0 == optcmp(argv[1], zColSepOpt, &tmpVal)) { + if (tmpVal && *tmpVal) { + zColSep = tmpVal; + } + else if ((argv[1][1] != '-') && (argc > 2)) { + tmpVal = zColSep = argv[2]; + --argc; ++argv; + } + if (!(tmpVal && *tmpVal)) { + fprintf( stderr, "Bad or empty separator option value: %s\n", argv[1]); + } + } + else if (0 == optcmp(argv[1], zVersion, NULL)) { + /* basename() may modify its argument */ + fprintf( stderr, "%s SQLite exec wrapper version %s\n", + basename(strdup(zProgName)), zVersionNo); + } + else if (0 == optcmp(argv[1], zVerbose, NULL)) { + fDbg = 1; + } + else if (0 == optcmp(argv[1], zCont, NULL)) { + --argc; ++argv; + break; + } + else { + fprintf( stderr, "Unknown option: %s\n", argv[1]); + } + /* single-char option with no value: next char may be an option */ + if ((argv[1][1] != '-') && !(tmpVal && *tmpVal) && argv[1][2]) { + (argv[1])++; + argv[1][0] = '-'; + } + else { + --argc; ++argv; + } + } + + if (fDbg) { + fprintf( stderr, "Options: %s", zVerbose[0]); + if (fCols) fprintf( stderr, " %s", zCols[0]); + fprintf( stderr, " separator = '%s'\n", zColSep); + } + + if (fDbg) fprintf( stderr, "About to configure SQLITE_CONFIG_SINGLETHREAD\n"); + int rc = sqlite3_config(SQLITE_CONFIG_SINGLETHREAD); + if (SQLITE_OK != rc) { + fprintf( stderr, "Can't set single-thread: %s\n", sqlite3_errstr(rc)); + return 1; + } + else if (fDbg) ok(); + + + sqlite3 *db; + if (fDbg) fprintf( stderr, "About to open dummy database %s\n", zDummyDB); + rc = sqlite3_open(zDummyDB, &db); + if (SQLITE_OK != rc) { + fprintf(stderr, "Can't open database: %s\n", sqlite3_errstr(rc)); + } + else { + if (fDbg) ok(); + + rc = cmdline_exec( db, argc, argv); + + if (rc == 0) + rc = stream_exec( stdin, db); + } + + sqlite3_close(db); + return rc; +}