static int do_meta_command()

in fdbserver/sqlite/shell.c [1471:2361]


static int do_meta_command(char* zLine, struct callback_data* p) {
	int i = 1;
	int nArg = 0;
	int n, c;
	int rc = 0;
	char* azArg[50];

	/* Parse the input line into tokens.
	 */
	while (zLine[i] && nArg < ArraySize(azArg)) {
		while (isspace((unsigned char)zLine[i])) {
			i++;
		}
		if (zLine[i] == 0)
			break;
		if (zLine[i] == '\'' || zLine[i] == '"') {
			int delim = zLine[i++];
			azArg[nArg++] = &zLine[i];
			while (zLine[i] && zLine[i] != delim) {
				i++;
			}
			if (zLine[i] == delim) {
				zLine[i++] = 0;
			}
			if (delim == '"')
				resolve_backslashes(azArg[nArg - 1]);
		} else {
			azArg[nArg++] = &zLine[i];
			while (zLine[i] && !isspace((unsigned char)zLine[i])) {
				i++;
			}
			if (zLine[i])
				zLine[i++] = 0;
			resolve_backslashes(azArg[nArg - 1]);
		}
	}

	/* Process the input line.
	 */
	if (nArg == 0)
		return 0; /* no tokens, no error */
	n = strlen30(azArg[0]);
	c = azArg[0][0];
	if (c == 'b' && n >= 3 && strncmp(azArg[0], "backup", n) == 0 && nArg > 1 && nArg < 4) {
		const char* zDestFile;
		const char* zDb;
		sqlite3* pDest;
		sqlite3_backup* pBackup;
		if (nArg == 2) {
			zDestFile = azArg[1];
			zDb = "main";
		} else {
			zDestFile = azArg[2];
			zDb = azArg[1];
		}
		rc = sqlite3_open(zDestFile, &pDest);
		if (rc != SQLITE_OK) {
			fprintf(stderr, "Error: cannot open \"%s\"\n", zDestFile);
			sqlite3_close(pDest);
			return 1;
		}
		open_db(p);
		pBackup = sqlite3_backup_init(pDest, "main", p->db, zDb);
		if (pBackup == 0) {
			fprintf(stderr, "Error: %s\n", sqlite3_errmsg(pDest));
			sqlite3_close(pDest);
			return 1;
		}
		while ((rc = sqlite3_backup_step(pBackup, 100)) == SQLITE_OK) {
		}
		sqlite3_backup_finish(pBackup);
		if (rc == SQLITE_DONE) {
			rc = 0;
		} else {
			fprintf(stderr, "Error: %s\n", sqlite3_errmsg(pDest));
			rc = 1;
		}
		sqlite3_close(pDest);
	} else

	    if (c == 'b' && n >= 3 && strncmp(azArg[0], "bail", n) == 0 && nArg > 1 && nArg < 3) {
		bail_on_error = booleanValue(azArg[1]);
	} else

	    if (c == 'd' && n > 1 && strncmp(azArg[0], "databases", n) == 0 && nArg == 1) {
		struct callback_data data;
		char* zErrMsg = 0;
		open_db(p);
		memcpy(&data, p, sizeof(data));
		data.showHeader = 1;
		data.mode = MODE_Column;
		data.colWidth[0] = 3;
		data.colWidth[1] = 15;
		data.colWidth[2] = 58;
		data.cnt = 0;
		sqlite3_exec(p->db, "PRAGMA database_list; ", callback, &data, &zErrMsg);
		if (zErrMsg) {
			fprintf(stderr, "Error: %s\n", zErrMsg);
			sqlite3_free(zErrMsg);
			rc = 1;
		}
	} else

	    if (c == 'd' && strncmp(azArg[0], "dump", n) == 0 && nArg < 3) {
		char* zErrMsg = 0;
		open_db(p);
		/* When playing back a "dump", the content might appear in an order
		** which causes immediate foreign key constraints to be violated.
		** So disable foreign-key constraint enforcement to prevent problems. */
		fprintf(p->out, "PRAGMA foreign_keys=OFF;\n");
		fprintf(p->out, "BEGIN TRANSACTION;\n");
		p->writableSchema = 0;
		sqlite3_exec(p->db, "PRAGMA writable_schema=ON", 0, 0, 0);
		if (nArg == 1) {
			run_schema_dump_query(p,
			                      "SELECT name, type, sql FROM sqlite_master "
			                      "WHERE sql NOT NULL AND type=='table' AND name!='sqlite_sequence'",
			                      0);
			run_schema_dump_query(p,
			                      "SELECT name, type, sql FROM sqlite_master "
			                      "WHERE name=='sqlite_sequence'",
			                      0);
			run_table_dump_query(p->out,
			                     p->db,
			                     "SELECT sql FROM sqlite_master "
			                     "WHERE sql NOT NULL AND type IN ('index','trigger','view')",
			                     0);
		} else {
			int i;
			for (i = 1; i < nArg; i++) {
				zShellStatic = azArg[i];
				run_schema_dump_query(p,
				                      "SELECT name, type, sql FROM sqlite_master "
				                      "WHERE tbl_name LIKE shellstatic() AND type=='table'"
				                      "  AND sql NOT NULL",
				                      0);
				run_table_dump_query(p->out,
				                     p->db,
				                     "SELECT sql FROM sqlite_master "
				                     "WHERE sql NOT NULL"
				                     "  AND type IN ('index','trigger','view')"
				                     "  AND tbl_name LIKE shellstatic()",
				                     0);
				zShellStatic = 0;
			}
		}
		if (p->writableSchema) {
			fprintf(p->out, "PRAGMA writable_schema=OFF;\n");
			p->writableSchema = 0;
		}
		sqlite3_exec(p->db, "PRAGMA writable_schema=OFF", 0, 0, 0);
		if (zErrMsg) {
			fprintf(stderr, "Error: %s\n", zErrMsg);
			sqlite3_free(zErrMsg);
		} else {
			fprintf(p->out, "COMMIT;\n");
		}
	} else

	    if (c == 'e' && strncmp(azArg[0], "echo", n) == 0 && nArg > 1 && nArg < 3) {
		p->echoOn = booleanValue(azArg[1]);
	} else

	    if (c == 'e' && strncmp(azArg[0], "exit", n) == 0 && nArg == 1) {
		rc = 2;
	} else

	    if (c == 'e' && strncmp(azArg[0], "explain", n) == 0 && nArg < 3) {
		int val = nArg >= 2 ? booleanValue(azArg[1]) : 1;
		if (val == 1) {
			if (!p->explainPrev.valid) {
				p->explainPrev.valid = 1;
				p->explainPrev.mode = p->mode;
				p->explainPrev.showHeader = p->showHeader;
				memcpy(p->explainPrev.colWidth, p->colWidth, sizeof(p->colWidth));
			}
			/* We could put this code under the !p->explainValid
			** condition so that it does not execute if we are already in
			** explain mode. However, always executing it allows us an easy
			** was to reset to explain mode in case the user previously
			** did an .explain followed by a .width, .mode or .header
			** command.
			*/
			p->mode = MODE_Explain;
			p->showHeader = 1;
			memset(p->colWidth, 0, ArraySize(p->colWidth));
			p->colWidth[0] = 4; /* addr */
			p->colWidth[1] = 13; /* opcode */
			p->colWidth[2] = 4; /* P1 */
			p->colWidth[3] = 4; /* P2 */
			p->colWidth[4] = 4; /* P3 */
			p->colWidth[5] = 13; /* P4 */
			p->colWidth[6] = 2; /* P5 */
			p->colWidth[7] = 13; /* Comment */
		} else if (p->explainPrev.valid) {
			p->explainPrev.valid = 0;
			p->mode = p->explainPrev.mode;
			p->showHeader = p->explainPrev.showHeader;
			memcpy(p->colWidth, p->explainPrev.colWidth, sizeof(p->colWidth));
		}
	} else

	    if (c == 'h' && (strncmp(azArg[0], "header", n) == 0 || strncmp(azArg[0], "headers", n) == 0) && nArg > 1 &&
	        nArg < 3) {
		p->showHeader = booleanValue(azArg[1]);
	} else

	    if (c == 'h' && strncmp(azArg[0], "help", n) == 0) {
		fprintf(stderr, "%s", zHelp);
		if (HAS_TIMER) {
			fprintf(stderr, "%s", zTimerHelp);
		}
	} else

	    if (c == 'i' && strncmp(azArg[0], "import", n) == 0 && nArg == 3) {
		char* zTable = azArg[2]; /* Insert data into this table */
		char* zFile = azArg[1]; /* The file from which to extract data */
		sqlite3_stmt* pStmt = NULL; /* A statement */
		int nCol; /* Number of columns in the table */
		int nByte; /* Number of bytes in an SQL string */
		int i, j; /* Loop counters */
		int nSep; /* Number of bytes in p->separator[] */
		char* zSql; /* An SQL statement */
		char* zLine; /* A single line of input from the file */
		char** azCol; /* zLine[] broken up into columns */
		char* zCommit; /* How to commit changes */
		FILE* in; /* The input file */
		int lineno = 0; /* Line number of input file */

		open_db(p);
		nSep = strlen30(p->separator);
		if (nSep == 0) {
			fprintf(stderr, "Error: non-null separator required for import\n");
			return 1;
		}
		zSql = sqlite3_mprintf("SELECT * FROM '%q'", zTable);
		if (zSql == 0) {
			fprintf(stderr, "Error: out of memory\n");
			return 1;
		}
		nByte = strlen30(zSql);
		rc = sqlite3_prepare(p->db, zSql, -1, &pStmt, 0);
		sqlite3_free(zSql);
		if (rc) {
			if (pStmt)
				sqlite3_finalize(pStmt);
			fprintf(stderr, "Error: %s\n", sqlite3_errmsg(db));
			return 1;
		}
		nCol = sqlite3_column_count(pStmt);
		sqlite3_finalize(pStmt);
		pStmt = 0;
		if (nCol == 0)
			return 0; /* no columns, no error */
		zSql = malloc(nByte + 20 + nCol * 2);
		if (zSql == 0) {
			fprintf(stderr, "Error: out of memory\n");
			return 1;
		}
		sqlite3_snprintf(nByte + 20, zSql, "INSERT INTO '%q' VALUES(?", zTable);
		j = strlen30(zSql);
		for (i = 1; i < nCol; i++) {
			zSql[j++] = ',';
			zSql[j++] = '?';
		}
		zSql[j++] = ')';
		zSql[j] = 0;
		rc = sqlite3_prepare(p->db, zSql, -1, &pStmt, 0);
		free(zSql);
		if (rc) {
			fprintf(stderr, "Error: %s\n", sqlite3_errmsg(db));
			if (pStmt)
				sqlite3_finalize(pStmt);
			return 1;
		}
		in = fopen(zFile, "rb");
		if (in == 0) {
			fprintf(stderr, "Error: cannot open \"%s\"\n", zFile);
			sqlite3_finalize(pStmt);
			return 1;
		}
		azCol = malloc(sizeof(azCol[0]) * (nCol + 1));
		if (azCol == 0) {
			fprintf(stderr, "Error: out of memory\n");
			fclose(in);
			sqlite3_finalize(pStmt);
			return 1;
		}
		sqlite3_exec(p->db, "BEGIN", 0, 0, 0);
		zCommit = "COMMIT";
		while ((zLine = local_getline(0, in)) != 0) {
			char* z;
			i = 0;
			lineno++;
			azCol[0] = zLine;
			for (i = 0, z = zLine; *z && *z != '\n' && *z != '\r'; z++) {
				if (*z == p->separator[0] && strncmp(z, p->separator, nSep) == 0) {
					*z = 0;
					i++;
					if (i < nCol) {
						azCol[i] = &z[nSep];
						z += nSep - 1;
					}
				}
			} /* end for */
			*z = 0;
			if (i + 1 != nCol) {
				fprintf(stderr,
				        "Error: %s line %d: expected %d columns of data but found %d\n",
				        zFile,
				        lineno,
				        nCol,
				        i + 1);
				zCommit = "ROLLBACK";
				free(zLine);
				rc = 1;
				break; /* from while */
			}
			for (i = 0; i < nCol; i++) {
				sqlite3_bind_text(pStmt, i + 1, azCol[i], -1, SQLITE_STATIC);
			}
			sqlite3_step(pStmt);
			rc = sqlite3_reset(pStmt);
			free(zLine);
			if (rc != SQLITE_OK) {
				fprintf(stderr, "Error: %s\n", sqlite3_errmsg(db));
				zCommit = "ROLLBACK";
				rc = 1;
				break; /* from while */
			}
		} /* end while */
		free(azCol);
		fclose(in);
		sqlite3_finalize(pStmt);
		sqlite3_exec(p->db, zCommit, 0, 0, 0);
	} else

	    if (c == 'i' && strncmp(azArg[0], "indices", n) == 0 && nArg < 3) {
		struct callback_data data;
		char* zErrMsg = 0;
		open_db(p);
		memcpy(&data, p, sizeof(data));
		data.showHeader = 0;
		data.mode = MODE_List;
		if (nArg == 1) {
			rc = sqlite3_exec(p->db,
			                  "SELECT name FROM sqlite_master "
			                  "WHERE type='index' AND name NOT LIKE 'sqlite_%' "
			                  "UNION ALL "
			                  "SELECT name FROM sqlite_temp_master "
			                  "WHERE type='index' "
			                  "ORDER BY 1",
			                  callback,
			                  &data,
			                  &zErrMsg);
		} else {
			zShellStatic = azArg[1];
			rc = sqlite3_exec(p->db,
			                  "SELECT name FROM sqlite_master "
			                  "WHERE type='index' AND tbl_name LIKE shellstatic() "
			                  "UNION ALL "
			                  "SELECT name FROM sqlite_temp_master "
			                  "WHERE type='index' AND tbl_name LIKE shellstatic() "
			                  "ORDER BY 1",
			                  callback,
			                  &data,
			                  &zErrMsg);
			zShellStatic = 0;
		}
		if (zErrMsg) {
			fprintf(stderr, "Error: %s\n", zErrMsg);
			sqlite3_free(zErrMsg);
			rc = 1;
		} else if (rc != SQLITE_OK) {
			fprintf(stderr, "Error: querying sqlite_master and sqlite_temp_master\n");
			rc = 1;
		}
	} else

#ifdef SQLITE_ENABLE_IOTRACE
	    if (c == 'i' && strncmp(azArg[0], "iotrace", n) == 0) {
		extern void (*sqlite3IoTrace)(const char*, ...);
		if (iotrace && iotrace != stdout)
			fclose(iotrace);
		iotrace = 0;
		if (nArg < 2) {
			sqlite3IoTrace = 0;
		} else if (strcmp(azArg[1], "-") == 0) {
			sqlite3IoTrace = iotracePrintf;
			iotrace = stdout;
		} else {
			iotrace = fopen(azArg[1], "w");
			if (iotrace == 0) {
				fprintf(stderr, "Error: cannot open \"%s\"\n", azArg[1]);
				sqlite3IoTrace = 0;
				rc = 1;
			} else {
				sqlite3IoTrace = iotracePrintf;
			}
		}
	} else
#endif

#ifndef SQLITE_OMIT_LOAD_EXTENSION
	    if (c == 'l' && strncmp(azArg[0], "load", n) == 0 && nArg >= 2) {
		const char *zFile, *zProc;
		char* zErrMsg = 0;
		zFile = azArg[1];
		zProc = nArg >= 3 ? azArg[2] : 0;
		open_db(p);
		rc = sqlite3_load_extension(p->db, zFile, zProc, &zErrMsg);
		if (rc != SQLITE_OK) {
			fprintf(stderr, "Error: %s\n", zErrMsg);
			sqlite3_free(zErrMsg);
			rc = 1;
		}
	} else
#endif

	    if (c == 'l' && strncmp(azArg[0], "log", n) == 0 && nArg >= 1) {
		const char* zFile = azArg[1];
		if (p->pLog && p->pLog != stdout && p->pLog != stderr) {
			fclose(p->pLog);
			p->pLog = 0;
		}
		if (strcmp(zFile, "stdout") == 0) {
			p->pLog = stdout;
		} else if (strcmp(zFile, "stderr") == 0) {
			p->pLog = stderr;
		} else if (strcmp(zFile, "off") == 0) {
			p->pLog = 0;
		} else {
			p->pLog = fopen(zFile, "w");
			if (p->pLog == 0) {
				fprintf(stderr, "Error: cannot open \"%s\"\n", zFile);
			}
		}
	} else

	    if (c == 'm' && strncmp(azArg[0], "mode", n) == 0 && nArg == 2) {
		int n2 = strlen30(azArg[1]);
		if ((n2 == 4 && strncmp(azArg[1], "line", n2) == 0) || (n2 == 5 && strncmp(azArg[1], "lines", n2) == 0)) {
			p->mode = MODE_Line;
		} else if ((n2 == 6 && strncmp(azArg[1], "column", n2) == 0) ||
		           (n2 == 7 && strncmp(azArg[1], "columns", n2) == 0)) {
			p->mode = MODE_Column;
		} else if (n2 == 4 && strncmp(azArg[1], "list", n2) == 0) {
			p->mode = MODE_List;
		} else if (n2 == 4 && strncmp(azArg[1], "html", n2) == 0) {
			p->mode = MODE_Html;
		} else if (n2 == 3 && strncmp(azArg[1], "tcl", n2) == 0) {
			p->mode = MODE_Tcl;
		} else if (n2 == 3 && strncmp(azArg[1], "csv", n2) == 0) {
			p->mode = MODE_Csv;
			sqlite3_snprintf(sizeof(p->separator), p->separator, ",");
		} else if (n2 == 4 && strncmp(azArg[1], "tabs", n2) == 0) {
			p->mode = MODE_List;
			sqlite3_snprintf(sizeof(p->separator), p->separator, "\t");
		} else if (n2 == 6 && strncmp(azArg[1], "insert", n2) == 0) {
			p->mode = MODE_Insert;
			set_table_name(p, "table");
		} else {
			fprintf(stderr,
			        "Error: mode should be one of: "
			        "column csv html insert line list tabs tcl\n");
			rc = 1;
		}
	} else

	    if (c == 'm' && strncmp(azArg[0], "mode", n) == 0 && nArg == 3) {
		int n2 = strlen30(azArg[1]);
		if (n2 == 6 && strncmp(azArg[1], "insert", n2) == 0) {
			p->mode = MODE_Insert;
			set_table_name(p, azArg[2]);
		} else {
			fprintf(stderr,
			        "Error: invalid arguments: "
			        " \"%s\". Enter \".help\" for help\n",
			        azArg[2]);
			rc = 1;
		}
	} else

	    if (c == 'n' && strncmp(azArg[0], "nullvalue", n) == 0 && nArg == 2) {
		sqlite3_snprintf(sizeof(p->nullvalue), p->nullvalue, "%.*s", (int)ArraySize(p->nullvalue) - 1, azArg[1]);
	} else

	    if (c == 'o' && strncmp(azArg[0], "output", n) == 0 && nArg == 2) {
		if (p->out != stdout) {
			fclose(p->out);
		}
		if (strcmp(azArg[1], "stdout") == 0) {
			p->out = stdout;
			sqlite3_snprintf(sizeof(p->outfile), p->outfile, "stdout");
		} else {
			p->out = fopen(azArg[1], "wb");
			if (p->out == 0) {
				fprintf(stderr, "Error: cannot write to \"%s\"\n", azArg[1]);
				p->out = stdout;
				rc = 1;
			} else {
				sqlite3_snprintf(sizeof(p->outfile), p->outfile, "%s", azArg[1]);
			}
		}
	} else

	    if (c == 'p' && strncmp(azArg[0], "prompt", n) == 0 && (nArg == 2 || nArg == 3)) {
		if (nArg >= 2) {
			strncpy(mainPrompt, azArg[1], (int)ArraySize(mainPrompt) - 1);
		}
		if (nArg >= 3) {
			strncpy(continuePrompt, azArg[2], (int)ArraySize(continuePrompt) - 1);
		}
	} else

	    if (c == 'q' && strncmp(azArg[0], "quit", n) == 0 && nArg == 1) {
		rc = 2;
	} else

	    if (c == 'r' && n >= 3 && strncmp(azArg[0], "read", n) == 0 && nArg == 2) {
		FILE* alt = fopen(azArg[1], "rb");
		if (alt == 0) {
			fprintf(stderr, "Error: cannot open \"%s\"\n", azArg[1]);
			rc = 1;
		} else {
			rc = process_input(p, alt);
			fclose(alt);
		}
	} else

	    if (c == 'r' && n >= 3 && strncmp(azArg[0], "restore", n) == 0 && nArg > 1 && nArg < 4) {
		const char* zSrcFile;
		const char* zDb;
		sqlite3* pSrc;
		sqlite3_backup* pBackup;
		int nTimeout = 0;

		if (nArg == 2) {
			zSrcFile = azArg[1];
			zDb = "main";
		} else {
			zSrcFile = azArg[2];
			zDb = azArg[1];
		}
		rc = sqlite3_open(zSrcFile, &pSrc);
		if (rc != SQLITE_OK) {
			fprintf(stderr, "Error: cannot open \"%s\"\n", zSrcFile);
			sqlite3_close(pSrc);
			return 1;
		}
		open_db(p);
		pBackup = sqlite3_backup_init(p->db, zDb, pSrc, "main");
		if (pBackup == 0) {
			fprintf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
			sqlite3_close(pSrc);
			return 1;
		}
		while ((rc = sqlite3_backup_step(pBackup, 100)) == SQLITE_OK || rc == SQLITE_BUSY) {
			if (rc == SQLITE_BUSY) {
				if (nTimeout++ >= 3)
					break;
				sqlite3_sleep(100);
			}
		}
		sqlite3_backup_finish(pBackup);
		if (rc == SQLITE_DONE) {
			rc = 0;
		} else if (rc == SQLITE_BUSY || rc == SQLITE_LOCKED) {
			fprintf(stderr, "Error: source database is busy\n");
			rc = 1;
		} else {
			fprintf(stderr, "Error: %s\n", sqlite3_errmsg(p->db));
			rc = 1;
		}
		sqlite3_close(pSrc);
	} else

	    if (c == 's' && strncmp(azArg[0], "schema", n) == 0 && nArg < 3) {
		struct callback_data data;
		char* zErrMsg = 0;
		open_db(p);
		memcpy(&data, p, sizeof(data));
		data.showHeader = 0;
		data.mode = MODE_Semi;
		if (nArg > 1) {
			int i;
			for (i = 0; azArg[1][i]; i++)
				azArg[1][i] = (char)tolower(azArg[1][i]);
			if (strcmp(azArg[1], "sqlite_master") == 0) {
				char *new_argv[2], *new_colv[2];
				new_argv[0] = "CREATE TABLE sqlite_master (\n"
				              "  type text,\n"
				              "  name text,\n"
				              "  tbl_name text,\n"
				              "  rootpage integer,\n"
				              "  sql text\n"
				              ")";
				new_argv[1] = 0;
				new_colv[0] = "sql";
				new_colv[1] = 0;
				callback(&data, 1, new_argv, new_colv);
				rc = SQLITE_OK;
			} else if (strcmp(azArg[1], "sqlite_temp_master") == 0) {
				char *new_argv[2], *new_colv[2];
				new_argv[0] = "CREATE TEMP TABLE sqlite_temp_master (\n"
				              "  type text,\n"
				              "  name text,\n"
				              "  tbl_name text,\n"
				              "  rootpage integer,\n"
				              "  sql text\n"
				              ")";
				new_argv[1] = 0;
				new_colv[0] = "sql";
				new_colv[1] = 0;
				callback(&data, 1, new_argv, new_colv);
				rc = SQLITE_OK;
			} else {
				zShellStatic = azArg[1];
				rc = sqlite3_exec(p->db,
				                  "SELECT sql FROM "
				                  "  (SELECT sql sql, type type, tbl_name tbl_name, name name"
				                  "     FROM sqlite_master UNION ALL"
				                  "   SELECT sql, type, tbl_name, name FROM sqlite_temp_master) "
				                  "WHERE tbl_name LIKE shellstatic() AND type!='meta' AND sql NOTNULL "
				                  "ORDER BY substr(type,2,1), name",
				                  callback,
				                  &data,
				                  &zErrMsg);
				zShellStatic = 0;
			}
		} else {
			rc = sqlite3_exec(p->db,
			                  "SELECT sql FROM "
			                  "  (SELECT sql sql, type type, tbl_name tbl_name, name name"
			                  "     FROM sqlite_master UNION ALL"
			                  "   SELECT sql, type, tbl_name, name FROM sqlite_temp_master) "
			                  "WHERE type!='meta' AND sql NOTNULL AND name NOT LIKE 'sqlite_%'"
			                  "ORDER BY substr(type,2,1), name",
			                  callback,
			                  &data,
			                  &zErrMsg);
		}
		if (zErrMsg) {
			fprintf(stderr, "Error: %s\n", zErrMsg);
			sqlite3_free(zErrMsg);
			rc = 1;
		} else if (rc != SQLITE_OK) {
			fprintf(stderr, "Error: querying schema information\n");
			rc = 1;
		} else {
			rc = 0;
		}
	} else

	    if (c == 's' && strncmp(azArg[0], "separator", n) == 0 && nArg == 2) {
		sqlite3_snprintf(sizeof(p->separator), p->separator, "%.*s", (int)sizeof(p->separator) - 1, azArg[1]);
	} else

	    if (c == 's' && strncmp(azArg[0], "show", n) == 0 && nArg == 1) {
		int i;
		fprintf(p->out, "%9.9s: %s\n", "echo", p->echoOn ? "on" : "off");
		fprintf(p->out, "%9.9s: %s\n", "explain", p->explainPrev.valid ? "on" : "off");
		fprintf(p->out, "%9.9s: %s\n", "headers", p->showHeader ? "on" : "off");
		fprintf(p->out, "%9.9s: %s\n", "mode", modeDescr[p->mode]);
		fprintf(p->out, "%9.9s: ", "nullvalue");
		output_c_string(p->out, p->nullvalue);
		fprintf(p->out, "\n");
		fprintf(p->out, "%9.9s: %s\n", "output", strlen30(p->outfile) ? p->outfile : "stdout");
		fprintf(p->out, "%9.9s: ", "separator");
		output_c_string(p->out, p->separator);
		fprintf(p->out, "\n");
		fprintf(p->out, "%9.9s: %s\n", "stats", p->statsOn ? "on" : "off");
		fprintf(p->out, "%9.9s: ", "width");
		for (i = 0; i < (int)ArraySize(p->colWidth) && p->colWidth[i] != 0; i++) {
			fprintf(p->out, "%d ", p->colWidth[i]);
		}
		fprintf(p->out, "\n");
	} else

	    if (c == 's' && strncmp(azArg[0], "stats", n) == 0 && nArg > 1 && nArg < 3) {
		p->statsOn = booleanValue(azArg[1]);
	} else

	    if (c == 't' && n > 1 && strncmp(azArg[0], "tables", n) == 0 && nArg < 3) {
		char** azResult;
		int nRow;
		char* zErrMsg;
		open_db(p);
		if (nArg == 1) {
			rc = sqlite3_get_table(p->db,
			                       "SELECT name FROM sqlite_master "
			                       "WHERE type IN ('table','view') AND name NOT LIKE 'sqlite_%' "
			                       "UNION ALL "
			                       "SELECT name FROM sqlite_temp_master "
			                       "WHERE type IN ('table','view') "
			                       "ORDER BY 1",
			                       &azResult,
			                       &nRow,
			                       0,
			                       &zErrMsg);
		} else {
			zShellStatic = azArg[1];
			rc = sqlite3_get_table(p->db,
			                       "SELECT name FROM sqlite_master "
			                       "WHERE type IN ('table','view') AND name LIKE shellstatic() "
			                       "UNION ALL "
			                       "SELECT name FROM sqlite_temp_master "
			                       "WHERE type IN ('table','view') AND name LIKE shellstatic() "
			                       "ORDER BY 1",
			                       &azResult,
			                       &nRow,
			                       0,
			                       &zErrMsg);
			zShellStatic = 0;
		}
		if (zErrMsg) {
			fprintf(stderr, "Error: %s\n", zErrMsg);
			sqlite3_free(zErrMsg);
			rc = 1;
		} else if (rc != SQLITE_OK) {
			fprintf(stderr, "Error: querying sqlite_master and sqlite_temp_master\n");
			rc = 1;
		} else {
			int len, maxlen = 0;
			int i, j;
			int nPrintCol, nPrintRow;
			for (i = 1; i <= nRow; i++) {
				if (azResult[i] == 0)
					continue;
				len = strlen30(azResult[i]);
				if (len > maxlen)
					maxlen = len;
			}
			nPrintCol = 80 / (maxlen + 2);
			if (nPrintCol < 1)
				nPrintCol = 1;
			nPrintRow = (nRow + nPrintCol - 1) / nPrintCol;
			for (i = 0; i < nPrintRow; i++) {
				for (j = i + 1; j <= nRow; j += nPrintRow) {
					char* zSp = j <= nPrintRow ? "" : "  ";
					printf("%s%-*s", zSp, maxlen, azResult[j] ? azResult[j] : "");
				}
				printf("\n");
			}
		}
		sqlite3_free_table(azResult);
	} else

	    if (c == 't' && n >= 8 && strncmp(azArg[0], "testctrl", n) == 0 && nArg >= 2) {
		int testctrl = -1;
		int rc = 0;
		open_db(p);

		/* convert testctrl text option to value. allow only the first
		** three characters of the option to be used or the numerical
		** value. */
		if (strncmp(azArg[1], "prng_save", 6) == 0)
			testctrl = SQLITE_TESTCTRL_PRNG_SAVE;
		else if (strncmp(azArg[1], "prng_restore", 10) == 0)
			testctrl = SQLITE_TESTCTRL_PRNG_RESTORE;
		else if (strncmp(azArg[1], "prng_reset", 10) == 0)
			testctrl = SQLITE_TESTCTRL_PRNG_RESET;
		else if (strncmp(azArg[1], "bitvec_test", 6) == 3)
			testctrl = SQLITE_TESTCTRL_BITVEC_TEST;
		else if (strncmp(azArg[1], "fault_install", 6) == 3)
			testctrl = SQLITE_TESTCTRL_FAULT_INSTALL;
		else if (strncmp(azArg[1], "benign_malloc_hooks", 3) == 0)
			testctrl = SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS;
		else if (strncmp(azArg[1], "pending_byte", 3) == 0)
			testctrl = SQLITE_TESTCTRL_PENDING_BYTE;
		else if (strncmp(azArg[1], "assert", 3) == 0)
			testctrl = SQLITE_TESTCTRL_ASSERT;
		else if (strncmp(azArg[1], "always", 3) == 0)
			testctrl = SQLITE_TESTCTRL_ALWAYS;
		else if (strncmp(azArg[1], "reserve", 3) == 0)
			testctrl = SQLITE_TESTCTRL_RESERVE;
		else if (strncmp(azArg[1], "optimizations", 3) == 0)
			testctrl = SQLITE_TESTCTRL_OPTIMIZATIONS;
		else if (strncmp(azArg[1], "iskeyword", 3) == 0)
			testctrl = SQLITE_TESTCTRL_ISKEYWORD;
		else if (strncmp(azArg[1], "pghdrsz", 3) == 0)
			testctrl = SQLITE_TESTCTRL_PGHDRSZ;
		else if (strncmp(azArg[1], "scratchmalloc", 3) == 0)
			testctrl = SQLITE_TESTCTRL_SCRATCHMALLOC;
		else
			testctrl = atoi(azArg[1]);

		if ((testctrl < SQLITE_TESTCTRL_FIRST) || (testctrl > SQLITE_TESTCTRL_LAST)) {
			fprintf(stderr, "Error: invalid testctrl option: %s\n", azArg[1]);
		} else {
			switch (testctrl) {

			/* sqlite3_test_control(int, db, int) */
			case SQLITE_TESTCTRL_OPTIMIZATIONS:
			case SQLITE_TESTCTRL_RESERVE:
				if (nArg == 3) {
					int opt = (int)strtol(azArg[2], 0, 0);
					rc = sqlite3_test_control(testctrl, p->db, opt);
					printf("%d (0x%08x)\n", rc, rc);
				} else {
					fprintf(stderr, "Error: testctrl %s takes a single int option\n", azArg[1]);
				}
				break;

			/* sqlite3_test_control(int) */
			case SQLITE_TESTCTRL_PRNG_SAVE:
			case SQLITE_TESTCTRL_PRNG_RESTORE:
			case SQLITE_TESTCTRL_PRNG_RESET:
			case SQLITE_TESTCTRL_PGHDRSZ:
				if (nArg == 2) {
					rc = sqlite3_test_control(testctrl);
					printf("%d (0x%08x)\n", rc, rc);
				} else {
					fprintf(stderr, "Error: testctrl %s takes no options\n", azArg[1]);
				}
				break;

			/* sqlite3_test_control(int, uint) */
			case SQLITE_TESTCTRL_PENDING_BYTE:
				if (nArg == 3) {
					unsigned int opt = (unsigned int)atoi(azArg[2]);
					rc = sqlite3_test_control(testctrl, opt);
					printf("%d (0x%08x)\n", rc, rc);
				} else {
					fprintf(stderr, "Error: testctrl %s takes a single unsigned int option\n", azArg[1]);
				}
				break;

			/* sqlite3_test_control(int, int) */
			case SQLITE_TESTCTRL_ASSERT:
			case SQLITE_TESTCTRL_ALWAYS:
				if (nArg == 3) {
					int opt = atoi(azArg[2]);
					rc = sqlite3_test_control(testctrl, opt);
					printf("%d (0x%08x)\n", rc, rc);
				} else {
					fprintf(stderr, "Error: testctrl %s takes a single int option\n", azArg[1]);
				}
				break;

				/* sqlite3_test_control(int, char *) */
#ifdef SQLITE_N_KEYWORD
			case SQLITE_TESTCTRL_ISKEYWORD:
				if (nArg == 3) {
					const char* opt = azArg[2];
					rc = sqlite3_test_control(testctrl, opt);
					printf("%d (0x%08x)\n", rc, rc);
				} else {
					fprintf(stderr, "Error: testctrl %s takes a single char * option\n", azArg[1]);
				}
				break;
#endif

			case SQLITE_TESTCTRL_BITVEC_TEST:
			case SQLITE_TESTCTRL_FAULT_INSTALL:
			case SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS:
			case SQLITE_TESTCTRL_SCRATCHMALLOC:
			default:
				fprintf(stderr, "Error: CLI support for testctrl %s not implemented\n", azArg[1]);
				break;
			}
		}
	} else

	    if (c == 't' && n > 4 && strncmp(azArg[0], "timeout", n) == 0 && nArg == 2) {
		open_db(p);
		sqlite3_busy_timeout(p->db, atoi(azArg[1]));
	} else

	    if (HAS_TIMER && c == 't' && n >= 5 && strncmp(azArg[0], "timer", n) == 0 && nArg == 2) {
		enableTimer = booleanValue(azArg[1]);
	} else

	    if (c == 'w' && strncmp(azArg[0], "width", n) == 0 && nArg > 1) {
		int j;
		assert(nArg <= ArraySize(azArg));
		for (j = 1; j < nArg && j < ArraySize(p->colWidth); j++) {
			p->colWidth[j - 1] = atoi(azArg[j]);
		}
	} else

	{
		fprintf(stderr,
		        "Error: unknown command or invalid arguments: "
		        " \"%s\". Enter \".help\" for help\n",
		        azArg[0]);
		rc = 1;
	}

	return rc;
}