/* usqlite - minimal SQLite exec wrapper * * Copyright (C) 2020 /df * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program (the file LICENSE in the source directory). If not, see * . */ /* 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 gpl(void) { fprintf( stderr, "Copyright (C) 2020 /df \n" "\nThis program comes with ABSOLUTELY NO WARRANTY.\n" "This is free software, and you are welcome to redistribute it under the\n" "terms of the GNU General Public License as published by the Free Software\n" "Foundation, either version 3 of the License, or (at your option) any later\n" "version: see .\n\n" ); } 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, licensing and \n" " warranty details to stderr, and continue\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); gpl(); } 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; }