usqlite/usqlite.c
2020-08-15 00:10:03 +01:00

474 lines
13 KiB
C

/* printf, scanf, stdin, stderr */
#include <stdio.h>
/* malloc, free */
#include <stdlib.h>
/* size_t */
#include <sys/types.h>
/* strdup, strtok_r, strcmp, strncmp */
#include <string.h>
/* basename */
#include <libgen.h>
/* sqlite3_* */
#include <sqlite3.h>
/* 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 <unistd.h>
/* PAtH_MAX */
#include <limits.h>
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<argc; i++) {
printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
}
printf("\n");
return 0;
}
#endif
/* provide missing functions from newer ABI if needed */
#if (TGT_SQLITE_VERSION_NUMBER < REQD_SQLITE_VERSION_NUMBER)
#ifndef ArraySize
#define ArraySize(a) ((int)(sizeof(a) / sizeof(*(a))))
#endif
#ifndef ALWAYS
#define ALWAYS(a) (a)
#endif
/*
** Return a static string that describes the kind of error specified in the
** argument.
*/
static const char *sqlite3ErrStr(int rc){
static const char* const aMsg[] = {
/* SQLITE_OK */ "not an error",
/* SQLITE_ERROR */ "SQL logic error",
/* SQLITE_INTERNAL */ 0,
/* SQLITE_PERM */ "access permission denied",
/* SQLITE_ABORT */ "query aborted",
/* SQLITE_BUSY */ "database is locked",
/* SQLITE_LOCKED */ "database table is locked",
/* SQLITE_NOMEM */ "out of memory",
/* SQLITE_READONLY */ "attempt to write a readonly database",
/* SQLITE_INTERRUPT */ "interrupted",
/* SQLITE_IOERR */ "disk I/O error",
/* SQLITE_CORRUPT */ "database disk image is malformed",
/* SQLITE_NOTFOUND */ "unknown operation",
/* SQLITE_FULL */ "database or disk is full",
/* SQLITE_CANTOPEN */ "unable to open database file",
/* SQLITE_PROTOCOL */ "locking protocol",
/* SQLITE_EMPTY */ 0,
/* SQLITE_SCHEMA */ "database schema has changed",
/* SQLITE_TOOBIG */ "string or blob too big",
/* SQLITE_CONSTRAINT */ "constraint failed",
/* SQLITE_MISMATCH */ "datatype mismatch",
/* SQLITE_MISUSE */ "bad parameter or other API misuse",
#ifdef SQLITE_DISABLE_LFS
/* SQLITE_NOLFS */ "large file support is disabled",
#else
/* SQLITE_NOLFS */ 0,
#endif
/* SQLITE_AUTH */ "authorization denied",
/* SQLITE_FORMAT */ 0,
/* SQLITE_RANGE */ "column index out of range",
/* SQLITE_NOTADB */ "file is not a database",
/* SQLITE_NOTICE */ "notification message",
/* SQLITE_WARNING */ "warning message",
};
const char *zErr = "unknown error";
switch( rc ){
case SQLITE_ABORT_ROLLBACK: {
zErr = "abort due to ROLLBACK";
break;
}
case SQLITE_ROW: {
zErr = "another row available";
break;
}
case SQLITE_DONE: {
zErr = "no more rows available";
break;
}
default: {
rc &= 0xff;
if( ALWAYS(rc>=0) && rc<ArraySize(aMsg) && aMsg[rc]!=0 ){
zErr = aMsg[rc];
}
break;
}
}
return zErr;
}
#define sqlite3_errstr(a) sqlite3ErrStr(a)
#endif
static void ok(void) {
fprintf( stderr, "OK\n");
}
static void eof(void) {
fprintf( stderr, "EOF on input\n");
}
static int col_callback(void *parm, int argc, char **argv, char **azColName) {
if (parm && *(int*)parm) {
char ** azHeader = (char **) malloc(argc*sizeof(char *));
for (int i=0; i<argc; i++) {
printf("%s%s", (i > 0? zColSep:""), azColName[i]);
azHeader[i] = strdup(azColName[i]);
memset(azHeader[i],'=',strlen(azHeader[i]));
}
printf("\n");
for (int i=0; i<argc; i++) {
printf("%s%s", (i > 0? " ":""), azHeader[i]);
free(azHeader[i]);
}
free(azHeader);
printf("\n");
*(int*)parm = 0;
}
for (int i=0; i<argc; i++) {
printf("%s%s", (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%<size>[^;]%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;
}