/* 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; }