1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-12-24 14:17:58 +03:00

New subcomponent, the Query Result Formatter (QRF), that formats query

results for display to humans on a fixed-width font terminal.  Rework the
CLI to make use of the QRF.  Renovate the .mode command of the CLI.  Also
incorporate the QRF into the TCL interface as the "format" method.

FossilOrigin-Name: 7e460ffa5aae884807db9e7c8214d6d822d5d38ea406fe3b3eac04ac16f158fa
This commit is contained in:
drh
2025-11-18 17:49:48 +00:00
31 changed files with 8182 additions and 3771 deletions

View File

@@ -1459,7 +1459,7 @@ SRC01 = \
$(TOP)\src\status.c \
$(TOP)\src\table.c \
$(TOP)\src\threads.c \
$(TOP)\src\tclsqlite.c \
tclsqlite-ex.c \
$(TOP)\src\tokenize.c \
$(TOP)\src\treeview.c \
$(TOP)\src\trigger.c \
@@ -2020,8 +2020,18 @@ sqlite3.c: .target_source sqlite3ext.h sqlite3session.h $(MKSQLITE3C_TOOL) src-v
sqlite3-all.c: sqlite3.c $(TOP)\tool\split-sqlite3c.tcl $(JIM_TCLSH)
$(JIM_TCLSH) $(TOP)\tool\split-sqlite3c.tcl
TCLSQLITEEX = \
$(TOP)\ext\qrf\qrf.h \
$(TOP)\ext\qrf\qrf.c \
$(TOP)\src\tclsqlite.c
tclsqlite-ex.c: $(TCLSQLITEEX) $(TOP)\tool\mkcombo.tcl $(JIM_TCLSH)
$(JIM_TCLSH) $(TOP)\tool\mkcombo.tcl $(TCLSQLITEEX) -o $@
# <</mark>>
tclsqlite-ex.c:
# Rule to build the amalgamation
#
sqlite3.lo: $(SQLITE3C)
@@ -2321,11 +2331,11 @@ whereexpr.lo: $(TOP)\src\whereexpr.c $(HDR)
window.lo: $(TOP)\src\window.c $(HDR)
$(LTCOMPILE) $(CORE_COMPILE_OPTS) -c $(TOP)\src\window.c
tclsqlite.lo: $(TOP)\src\tclsqlite.c $(HDR) $(SQLITE_TCL_DEP)
$(LTCOMPILE) $(NO_WARN) -DUSE_TCL_STUBS=1 -DBUILD_sqlite -I$(TCLINCDIR) -c $(TOP)\src\tclsqlite.c
tclsqlite.lo: tclsqlite-ex.c $(HDR) $(SQLITE_TCL_DEP)
$(LTCOMPILE) $(NO_WARN) -DUSE_TCL_STUBS=1 -DBUILD_sqlite -I$(TCLINCDIR) -c tclsqlite-ex.c /OUT:tclsqlite.lo
tclsqlite-shell.lo: $(TOP)\src\tclsqlite.c $(HDR) $(SQLITE_TCL_DEP)
$(LTCOMPILE) $(NO_WARN) -DTCLSH -DBUILD_sqlite -I$(TCLINCDIR) -c $(TOP)\src\tclsqlite.c
tclsqlite-shell.lo: tclsqlite-ex.c $(HDR) $(SQLITE_TCL_DEP)
$(LTCOMPILE) $(NO_WARN) -DTCLSH -DBUILD_sqlite -I$(TCLINCDIR) -c tclsqlite-ex.c
tclsqlite3.exe: tclsqlite-shell.lo $(SQLITE3C) $(SQLITE3H) $(LIBRESOBJS)
$(LTLINK) $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) $(TCLLIBPATHS) $(LTLIBPATHS) /OUT:$@ tclsqlite-shell.lo $(LIBRESOBJS) $(TCLLIBS) $(LTLIBS) $(TLIBS)
@@ -2378,6 +2388,8 @@ keywordhash.h: $(TOP)\tool\mkkeywordhash.c mkkeywordhash.exe
# Source and header files that shell.c depends on
SHELL_DEP = \
$(TOP)\src\shell.c.in \
$(TOP)\ext\qrf\qrf.c \
$(TOP)\ext\qrf\qrf.h \
$(TOP)\ext\expert\sqlite3expert.c \
$(TOP)\ext\expert\sqlite3expert.h \
$(TOP)\ext\intck\sqlite3intck.c \
@@ -2541,9 +2553,9 @@ TESTFIXTURE_FLAGS = $(TESTFIXTURE_FLAGS) -DSQLITE_STRICT_SUBTYPE=1
TESTFIXTURE_SRC0 = $(TESTEXT) $(TESTSRC2)
TESTFIXTURE_SRC1 = $(TESTEXT) $(SQLITE3C)
!IF $(USE_AMALGAMATION)==0
TESTFIXTURE_SRC = $(TESTSRC) $(TOP)\src\tclsqlite.c $(TESTFIXTURE_SRC0)
TESTFIXTURE_SRC = $(TESTSRC) tclsqlite-ex.c $(TESTFIXTURE_SRC0)
!ELSE
TESTFIXTURE_SRC = $(TESTSRC) $(TOP)\src\tclsqlite.c $(TESTFIXTURE_SRC1)
TESTFIXTURE_SRC = $(TESTSRC) tclsqlite-ex.c $(TESTFIXTURE_SRC1)
!ENDIF
!IF $(USE_STDCALL)!=0 || $(FOR_WIN10)!=0
@@ -2678,14 +2690,14 @@ smoketest: $(TESTPROGS)
shelltest: $(TESTPROGS)
.\testfixture.exe $(TOP)\test\permutations.test shell
sqlite3_analyzer.c: $(SQLITE3C) $(SQLITE3H) $(TOP)\src\tclsqlite.c $(TOP)\tool\spaceanal.tcl $(TOP)\tool\mkccode.tcl $(TOP)\tool\sqlite3_analyzer.c.in $(TOP)\ext\misc\sqlite3_stdio.h $(TOP)\ext\misc\sqlite3_stdio.c $(SQLITE_TCL_DEP)
sqlite3_analyzer.c: $(SQLITE3C) $(SQLITE3H) tclsqlite-ex.c $(TOP)\tool\spaceanal.tcl $(TOP)\tool\mkccode.tcl $(TOP)\tool\sqlite3_analyzer.c.in $(TOP)\ext\misc\sqlite3_stdio.h $(TOP)\ext\misc\sqlite3_stdio.c $(SQLITE_TCL_DEP)
$(TCLSH_CMD) $(TOP)\tool\mkccode.tcl -DINCLUDE_SQLITE3_C $(TOP)\tool\sqlite3_analyzer.c.in > $@
sqlite3_analyzer.exe: sqlite3_analyzer.c $(LIBRESOBJS)
$(LTLINK) $(NO_WARN) -DBUILD_sqlite -I$(TCLINCDIR) sqlite3_analyzer.c \
/link $(LDFLAGS) $(LTLINKOPTS) $(TCLLIBPATHS) $(LTLIBPATHS) $(LIBRESOBJS) $(TCLLIBS) $(LTLIBS) $(TLIBS)
sqltclsh.c: sqlite3.c $(TOP)\src\tclsqlite.c $(TOP)\tool\sqltclsh.tcl $(TOP)\ext\misc\appendvfs.c $(TOP)\tool\mkccode.tcl $(TOP)\tool\sqltclsh.c.in
sqltclsh.c: sqlite3.c tclsqlite-ex.c $(TOP)\tool\sqltclsh.tcl $(TOP)\ext\misc\appendvfs.c $(TOP)\tool\mkccode.tcl $(TOP)\tool\sqltclsh.c.in
$(TCLSH_CMD) $(TOP)\tool\mkccode.tcl $(TOP)\tool\sqltclsh.c.in >sqltclsh.c
sqltclsh.exe: sqltclsh.c $(SHELL_CORE_DEP) $(LIBRESOBJS)
@@ -2698,7 +2710,7 @@ sqlite3_expert.exe: $(SQLITE3C) $(TOP)\ext\expert\sqlite3expert.h $(TOP)\ext\exp
CHECKER_DEPS =\
$(TOP)\tool\mkccode.tcl \
sqlite3.c \
$(TOP)\src\tclsqlite.c \
tclsqlite-ex.c \
$(TOP)\ext\repair\sqlite3_checker.tcl \
$(TOP)\ext\repair\checkindex.c \
$(TOP)\ext\repair\checkfreelist.c \

View File

@@ -805,7 +805,8 @@ proc sqlite-handle-common-feature-flags {} {
sqlite-add-feature-flag -DSQLITE_ENABLE_MEMSYS3
}
}
scanstatus -DSQLITE_ENABLE_STMT_SCANSTATUS {}
bytecode-vtab -DSQLITE_ENABLE_BYTECODE_VTAB {}
scanstatus {-DSQLITE_ENABLE_STMT_SCANSTATUS -DSQLITE_ENABLE_BYTECODE_VTAB} {}
column-metadata -DSQLITE_ENABLE_COLUMN_METADATA {}
dbpage -DSQLITE_ENABLE_DBPAGE_VTAB {}
dbstat -DSQLITE_ENABLE_DBSTAT_VTAB {}

View File

@@ -90,7 +90,7 @@ foreach {tn setup} {
proc do_rec_test {tn sql res} {
set res [squish [string trim $res]]
set tst [subst -nocommands {
squish [string trim [exec $::CLI test.db ".expert" {$sql;}]]
squish [string trim [exec $::CLI -noinit test.db ".expert" {$sql;}]]
}]
uplevel [list do_test $tn $tst $res]
}

View File

@@ -258,7 +258,7 @@ int sqlite3_fputs(const char *z, FILE *out){
/*
** Work-alike for fprintf() from the standard C library.
** Work-alikes for fprintf() and vfprintf() from the standard C library.
*/
int sqlite3_fprintf(FILE *out, const char *zFormat, ...){
int rc;
@@ -285,6 +285,24 @@ int sqlite3_fprintf(FILE *out, const char *zFormat, ...){
}
return rc;
}
int sqlite3_vfprintf(FILE *out, const char *zFormat, va_list ap){
int rc;
if( UseWtextForOutput(out) ){
/* When writing to the command-prompt in Windows, it is necessary
** to use _O_WTEXT input mode and write UTF-16 characters.
*/
char *z;
z = sqlite3_vmprintf(zFormat, ap);
sqlite3_fputs(z, out);
rc = (int)strlen(z);
sqlite3_free(z);
}else{
/* Writing to a file or other destination, just write bytes without
** any translation. */
rc = vfprintf(out, zFormat, ap);
}
return rc;
}
/*
** Set the mode for an output stream. mode argument is typically _O_BINARY or

View File

@@ -31,6 +31,7 @@
#ifdef _WIN32
/**** Definitions For Windows ****/
#include <stdio.h>
#include <stdarg.h>
#include <windows.h>
FILE *sqlite3_fopen(const char *zFilename, const char *zMode);
@@ -38,6 +39,7 @@ FILE *sqlite3_popen(const char *zCommand, const char *type);
char *sqlite3_fgets(char *s, int size, FILE *stream);
int sqlite3_fputs(const char *s, FILE *stream);
int sqlite3_fprintf(FILE *stream, const char *format, ...);
int sqlite3_vfprintf(FILE *stream, const char *format, va_list);
void sqlite3_fsetmode(FILE *stream, int mode);
@@ -49,6 +51,7 @@ void sqlite3_fsetmode(FILE *stream, int mode);
#define sqlite3_fgets fgets
#define sqlite3_fputs fputs
#define sqlite3_fprintf fprintf
#define sqlite3_vfprintf vfprintf
#define sqlite3_fsetmode(F,X) /*no-op*/
#endif

674
ext/qrf/README.md Normal file
View File

@@ -0,0 +1,674 @@
# SQLite Query Result Formatting Subsystem
The "Query Result Formatter" or "QRF" subsystem is a C-language
subroutine that formats the output from an SQLite query for display using
a fix-width font, for example on a terminal window over an SSH connection.
The output format is configurable. The application can request various
table formats, with flexible column widths and alignments, row-oriented
formats, such as CSV and similar, as well as various special purpose formats
like JSON.
For the first 25 years of SQLite's existance, the
[command-line interface](https://sqlite.org/cli.html) (CLI)
formatted query results using a hodge-podge of routines
that had grown slowly by accretion. The QRF was created
in fall of 2025 to refactor and reorganize this code into
a more usable form. The idea behind QRF is to implement all the
query result formatting capabilities of the CLI in a subroutine
that can be incorporated and reused by other applications.
## 1.0 Overview Of Operation
Suppose `pStmt` is a pointer to an SQLite prepared statement
(a pointer to an `sqlite3_stmt` object) that has been reset and
bound and is ready to run. Then to format the output from this
prepared statement, use code similar to the following:
> ~~~
sqlite3_qrf_spec spec; /* Format specification */
char *zErrMsg; /* Text error message (optional) */
char *zResult = 0; /* Formatted output written here */
int rc; /* Result code */
memset(&spec, 0, sizeof(spec)); /* Initialize the spec */
spec.iVersion = 1; /* Version number must be 1 */
spec.pzOutput = &zResult; /* Write results in variable zResult */
/* Optionally fill in other settings in spec here, as needed */
zErrMsg = 0; /* Not required; just being pedantic */
rc = sqlite3_format_query_result(pStmt, &spec, &zErrMsg); /* Format results */
if( rc ){
printf("Error (%d): %s\n", rc, zErrMsg); /* Report an error */
sqlite3_free(zErrMsg); /* Free the error message text */
}else{
printf("%s", zResult); /* Report the results */
sqlite3_free(zResult); /* Free memory used to hold results */
}
~~~
The `sqlite3_qrf_spec` object describes the desired output format
and what to do with the generated output. Most of the work in using
the QRF involves filling out the sqlite3_qrf_spec.
## 2.0 The `sqlite3_qrf_spec` object
The `sqlite3_qrf_spec` structure defines how the results of a query
are to be formatted, and what to do with the formatted text. The
most recent definition of `sqlite3_qrf_spec` is shown below.
Do not be alarmed by the complexity of this structure. You only have
to understand the properties you want to modify. Zero is always a good
default for all of the attributes (except iVersion and pzOutput/xWrite)
and so simply zeroing out the bulk of this structure is a good start.
You can then slowly make adjustments to individual fields to get the
results you desire.
> ~~~
typedef struct sqlite3_qrf_spec sqlite3_qrf_spec;
struct sqlite3_qrf_spec {
unsigned char iVersion; /* Version number of this structure */
unsigned char eStyle; /* Formatting style. "box", "csv", etc... */
unsigned char eEsc; /* How to escape control characters in text */
unsigned char eText; /* Quoting style for text */
unsigned char eTitle; /* Quating style for the text of column names */
unsigned char eBlob; /* Quoting style for BLOBs */
unsigned char bTitles; /* True to show column names */
unsigned char bWordWrap; /* Try to wrap on word boundaries */
unsigned char bTextJsonb; /* Render JSONB blobs as JSON text */
unsigned char bTextNull; /* Apply eText encoding to zNull[] */
unsigned char eDfltAlign; /* Default alignment, no covered by aAlignment */
unsigned char eTitleAlign; /* Alignment for column headers */
short int nWrap; /* Wrap columns wider than this */
short int nScreenWidth; /* Maximum overall table width */
short int nLineLimit; /* Maximum number of lines for any row */
int nCharLimit; /* Maximum number of characters in a cell */
int nWidth; /* Number of entries in aWidth[] */
int nAlign; /* Number of entries in aAlignment[] */
short int *aWidth; /* Column widths */
unsigned char *aAlign; /* Column alignments */
char *zColumnSep; /* Alternative column separator */
char *zRowSep; /* Alternative row separator */
char *zTableName; /* Output table name */
char *zNull; /* Rendering of NULL */
char *(*xRender)(void*,sqlite3_value*); /* Render a value */
int (*xWrite)(void*,const char*,sqlite3_int64); /* Write output */
void *pRenderArg; /* First argument to the xRender callback */
void *pWriteArg; /* First argument to the xWrite callback */
char **pzOutput; /* Storage location for output string */
/* Additional fields may be added in the future */
};
~~~
Again, the only fields that must initialized are:
* `.iVersion`
* One of `.pzOutput` or `.xWrite`.
All other fields can be zeroed. Or they can contain other values to
alter the formatting of the query results.
Further detail on the meanings of each of the fields in the
`sqlite3_qrf_spec` object are described below.
### 2.1 Structure Version Number
The sqlite3_qrf_spec.iVersion field must be 1. Future enhancements to
the QRF might add new fields onto the bottom of the sqlite3_qrf_spec
object. Those new fields will only be accessible if the iVersion is greater
than 1. Thus the iVersion field is used to support upgradability.
### 2.2 Output Deposition (xWrite and pzOutput)
The formatted output can either be sent to a callback function
or accumulated into an output buffer in memory obtained
from sqlite3_malloc(). If the sqlite3_qrf_spec.xWrite column is not NULL,
then that function is invoked (using sqlite3_qrf_spec.xWriteArg as its
first argument) to transmit the formatted output. Or, if
sqlite3_qrf_spec.pzOutput points to a pointer to a character, then that
pointer is made to point to memory obtained from sqlite3_malloc() that
contains the complete text of the formatted output. If spec.pzOutput\[0\]
is initially non-NULL, then it is assumed to point to memory obtained
from sqlite3_malloc(). In that case, the buffer is resized using
sqlite3_realloc() and the new text is appended.
One of either sqlite3_qrf_spec.xWrite and sqlite3_qrf_spec.pzOutput must be
non-NULL and the other must be NULL.
The return value from xWrite is an SQLITE result code. The usual return
should be SQLITE_OK. But if for some reason the write fails, a different
value might be returned.
### 2.3 Output Format
The sqlite3_qrf_spec.eStyle field is an integer code that defines the
specific output format that will be generated. See section 4.0 below
for details on the meaning of the various style options.
Other fields in sqlite3_qrf_spec might be used or might be
ignored, depending on the value of eStyle.
### 2.4 Show Column Names (bTitles)
The sqlite3_qrf_spec.bTitles field can be either QRF_SW_Auto,
QRF_SW_On, or QRF_SW_Off. Those three constants also have shorter
alternative spellings: QRF_Auto, QRF_No, and
QRF_Yes.
> ~~~
#define QRF_SW_Auto 0 /* Let QRF choose the best value */
#define QRF_SW_Off 1 /* This setting is forced off */
#define QRF_SW_On 2 /* This setting is forced on */
#define QRF_Auto 0 /* Alternate spelling for QRF_SW_Auto and others */
#define QRF_No 1 /* Alternate spelling for QRF_SW_Off */
#define QRF_Yes 2 /* Alternate spelling for QRF_SW_On */
~~~
If the value is QRF_Yes, then column names appear in the output.
If the value is QRF_No, column names are omitted. If the
value is QRF_Auto, then an appropriate default is chosen.
### 2.5 Control Character Escapes (eEsc)
The sqlite3_qrf_spec.eEsc determines how ASCII control characters are
formatted when displaying TEXT values in the result. These are the allowed
values:
> ~~~
#define QRF_ESC_Auto 0 /* Choose the ctrl-char escape automatically */
#define QRF_ESC_Off 1 /* Do not escape control characters */
#define QRF_ESC_Ascii 2 /* Unix-style escapes. Ex: U+0007 shows ^G */
#define QRF_ESC_Symbol 3 /* Unicode escapes. Ex: U+0007 shows U+2407 */
~~~
If the value of eEsc is QRF_ESC_Ascii, then the control character
with value X is displayed as ^Y where Y is X+0x40. Hence, a
backspace character (U+0008) is shown as "^H".
If eEsc is QRF_ESC_Symbol, then control characters in the range of U+0001
through U+001f are mapped into U+2401 through U+241f, respectively.
If the value of eEsc is QRF_ESC_Off, then no translation occurs
and control characters that appear in TEXT strings are transmitted
to the formatted output as-is. This can be dangerous in applications,
since an adversary who can control TEXT values might be able to
inject ANSI cursor movement sequences to hide nefarious values.
The QRF_ESC_Auto value for eEsc means that the query result formatter
gets to pick whichever control-character encoding it thinks is best for
the situation. This will usually be QRF_ESC_Ascii.
The TAB (U+0009), LF (U+000a) and CR-LF (U+000d,U+000a) character
sequence are always output literally and are not mapped to alternative
display values, regardless of this setting.
### 2.6 Display of TEXT values (eText, eTitle)
The sqlite3_qrf_spec.eText controls how text values are rendered in the
display. sqlite3_qrf_spec.eTitle controls how column names are rendered.
Both fields can have one of the following values:
> ~~~
#define QRF_TEXT_Auto 0 /* Choose text encoding automatically */
#define QRF_TEXT_Plain 1 /* Literal text */
#define QRF_TEXT_Sql 2 /* Quote as an SQL literal */
#define QRF_TEXT_Csv 3 /* CSV-style quoting */
#define QRF_TEXT_Html 4 /* HTML-style quoting */
#define QRF_TEXT_Tcl 5 /* C/Tcl quoting */
#define QRF_TEXT_Json 6 /* JSON quoting */
~~~
A value of QRF_TEXT_Auto means that the query result formatter will choose
what it thinks will be the best text encoding.
A value of QRF_TEXT_Plain means that text values appear in the output exactly
as they are found in the database file, with no translation.
A value of QRF_TEXT_Sql means that text values are escaped so that they
look like SQL literals. That means the value will be surrounded by
single-quotes (U+0027) and any single-quotes contained within the text
will be doubled.
A value of QRF_TEXT_Csv means that text values are escaped in accordance
with RFC&nbsp;4180, which defines Comma-Separated-Value or CSV files.
Text strings that contain no special values appears as-is. Text strings
that contain special values are contained in double-quotes (U+0022) and
any double-quotes within the value are doubled.
A value of QRF_TEXT_Html means that text values are escaped for use in
HTML. Special characters "&lt;", "&amp;", "&gt;", "&quot;", and "&#39;"
are displayed as "&amp;lt;", "&amp;amp;", "&amp;gt;", "&amp;quot;",
and "&amp;#39;", respectively.
A value of QRF_TEXT_Tcl means that text values are displayed inside of
double-quotes and special characters within the string are escaped using
backslash escape, as in ANSI-C or TCL or Perl or other popular programming
languages.
A value of QRF_TEXT_Json gives similar results as QRF_TEXT_Tcl except that the
rules are adjusted so that the displayed string is strictly conforming
the JSON specification.
### 2.7 How to display BLOB values (eBlob and bTextJsonb)
If the sqlite3_qrf_spec.bTextJsonb flag is QRF_SW_On and if the value to be
displayed is JSONB, then the JSONB is translated into text JSON and the
text is shown according to the sqlite3_qrf_spec.eText setting as
described in the previous section.
If the bTextJsonb flag is QRF_SW_Off (the usual case) or if the BLOB value to
be displayed is not JSONB, then the sqlite3_qrf_spec.eBlob field determines
how the BLOB value is formatted. The following options are available;
> ~~~
#define QRF_BLOB_Auto 0 /* Determine BLOB quoting using eText */
#define QRF_BLOB_Text 1 /* Display content exactly as it is */
#define QRF_BLOB_Sql 2 /* Quote as an SQL literal */
#define QRF_BLOB_Hex 3 /* Hexadecimal representation */
#define QRF_BLOB_Tcl 4 /* "\000" notation */
#define QRF_BLOB_Json 5 /* A JSON string */
~~~
A value of QRF_BLOB_Auto means that display format is selected automatically
by sqlite3_format_query_result() based on eStyle and eText.
A value of QRF_BLOB_Text means that BLOB values are interpreted as UTF8
text and are displayed using formatting results set by eEsc and
eText.
A value of QRF_BLOB_Sql means that BLOB values are shown as SQL BLOB
literals: a prefix "`x'`" following by hexadecimal and ending with a
final "`'`".
A value of QRF_BLOB_Hex means that BLOB values are shown as
hexadecimal text with no delimiters.
A value of QRF_BLOB_Tcl means that BLOB values are shown as a
C/Tcl/Perl string literal where every byte is an octal backslash
escape. So a BLOB of `x'052881f3'` would be displayed as
`"\005\050\201\363"`.
A value of QRF_BLOB_Json is similar to QRF_BLOB_Tcl except that is
uses unicode backslash escapes, since JSON does not understand
the C/Tcl/Perl octal backslash escapes. So the string from the
previous paragraph would be shown as
`"\u0005\u0028\u0081\u00f3"`.
### 2.8 Maximum size of displayed content (nLineLimit, nCharLimit)
If the sqlite3_qrf_spec.nCharLimit setting is non-zero, then the formatter
will display only the first nCharLimit characters of each value.
Only characters that take up space are counted when enforcing this
limit. Zero-width characters and VT100 escape sequences do not count
toward this limit. The count is in characters, not bytes. When
imposing this limit, the formatter adds the three characters "..."
to the end of the value. Those added characters are not counted
as part of the limit. Very small limits still result in truncation,
but might render a few more characters than the limit.
If the sqlite3_qrf_spec.nLineLimit setting is non-zero, then the
formatter will only display the first nLineLimit lines of each value.
It does not matter if the value is split because it contains a newline
character, or if it split by wrapping. This setting merely limits
the number of displayed lines. This setting only works for
**Box**, **Column**, **Line**, **Markdown**, and **Table** styles.
The idea behind both of these settings is to prevent large renderings
when doing a query that (unexpectedly) contains very large text or
blob values: perhaps megabyes of text.
### 2.9 Word Wrapping In Columnar Styles (nWrap, bWordWrap)
When using columnar formatting modes (QRF_STYLE_Box, QRF_STYLE_Column,
QRF_STYLE_Markdown, or QRF_STYLE_Table), the formatter attempts to limit
the width of any individual column to sqlite3_qrf_spec.nWrap characters
if nWrap is non-zero. A zero value for nWrap means "unlimited".
The nWrap limit might be exceeded if the limit is very small.
In order to keep individual columns within requested width limits,
it is sometimes necessary to wrap the content for a single row of
a single column across multiple lines. When this
becomes necessary and if the bWordWrap setting is QRF_Yes, then the
formatter attempts to split the content on whitespace or at a word boundary.
If bWordWrap is QRF_No, then the formatter is free to split content
anywhere, including in the middle of a word.
For narrow columns and wide words, it might sometimes be necessary to split
a column in the middle of a word, even when bWordWrap is QRF_Yes.
### 2.10 Helping The Output To Fit On The Terminal (nScreenWidth)
The sqlite3_qrf_spec.nScreenWidth field can be set the number of
characters that will fit on one line on the viewer output device.
This is typically a number like 80 or 132. The formatter will attempt
to reduce the length of output lines, depending on the style, so
that all output fits on that screen.
A value of zero for nScreenWidth means "unknown" or "no width limit".
When the value is zero, the formatter makes no attempt to keep the
lines of output short.
The nScreenWidth is a hint to the formatter, not a requirement.
The formatter trieds to keep lines below the nScreenWidth limit,
but it does not guarantee that it will.
### 2.11 Individual Column Width (nWidth and aWidth)
The sqlite3_qrf_spec.aWidth field is a pointer to an array of
signed 16-bit integers that control the width of individual columns
in columnar output modes (QRF_STYLE_Box, QRF_STYLE_Column,
QRF_STYLE_Markdown, or QRF_STYLE_Table). The sqlite3_qrf_spec.nWidth
field is the number of integers in the aWidth array.
If aWidth is a NULL pointer or if nWidth is zero, then the array is
assumed to be all zeros. If nWidth is less then the number of
columns in the output, then zero is used for the width
for all columns past then end of the aWidth array.
The aWidth array is deliberately an array of 16-bit signed integers.
Only 16 bits are used because no good comes for having very large
column widths. The range if further restricted as follows:
> ~~~
#define QRF_MAX_WIDTH 10000 /* Maximum column width */
#define QRF_MIN_WIDTH 0 /* Minimum column width */
~~~
A width greater than then QRF_MAX_WIDTH is interpreted as QRF_MAX_WIDTH.
Any aWidth\[\] value of zero means the formatter should use a flexible
width column (limited only by sqlite_qrf_spec.mxWidth) that is just
big enough to hold the largest row.
For historical compatibility, aWidth\[\] can contain negative values,
down to -QRF_MAX_WIDTH. The column width used is the absolute value
of the number in aWidth\[\]. The only difference is that negative
values cause the default horizontal alignment to be QRF_ALIGN_Right.
The sign of the aWidth\[\] values only affects alignment if the
alignment is not otherwise specified by aAlign\[\] or eDfltAlign.
Again, negative values for aWidth\[\] entries are supported for
backwards compatibility only, and are not recommended for new
applications.
### 2.12 Alignment (nAlignment, aAlignment, eDfltAlign, eTitleAlign)
Some cells in a display table might contain a lot of text and thus
be wide, or they might contain newline characters or be wrapped by
width constraints so that they span many rows of text. Other cells
might be narrower and shorter. In columnar formats, the display width
of a cell is the maximum of the widest value in the same column, and the
display height is the height of the tallest value in the same row.
So some cells might be much taller and wider than necessary to hold
their values.
Alignment determines where smaller values are placed within larger cells.
The sqlite3_qrf_spec.aAlign field points to an array of unsigned characters
that specifies alignment (both vertical and horizontal) of individual
columns within the table. The sqlite3_qrf_spec.nAlign fields holds
the number of entries in the aAlign\[\] array.
If sqlite3_qrf_spec.aAlign is a NULL pointer or if sqlite3_qrf_spec.nAlign
is zero, or for columns to the right of what are specified by
sqlite3_qrf_spec.nAlign, the sqlite3_qrf_spec.eDfltAlign value is used
for the alignment. Column names can be (and often are) aligned
differently, as specified by sqlite3_qrf_spec.eTitleAlign.
Each alignment value specifies both vertical and horizontal alignment.
Horizontal alignment can be left, center, right, or no preference.
Vertical alignment can be top, middle, bottom, or no preference.
Thus there are 16 possible alignment values, as follows:
> ~~~
/*
** Horizontal Vertial
** ---------- -------- */
#define QRF_ALIGN_Auto 0 /* auto auto */
#define QRF_ALIGN_Left 1 /* left auto */
#define QRF_ALIGN_Center 2 /* center auto */
#define QRF_ALIGN_Right 3 /* right auto */
#define QRF_ALIGN_Top 4 /* auto top */
#define QRF_ALIGN_NW 5 /* left top */
#define QRF_ALIGN_N 6 /* center top */
#define QRF_ALIGN_NE 7 /* right top */
#define QRF_ALIGN_Middle 8 /* auto middle */
#define QRF_ALIGN_W 9 /* left middle */
#define QRF_ALIGN_C 10 /* center middle */
#define QRF_ALIGN_E 11 /* right middle */
#define QRF_ALIGN_Bottom 12 /* auto bottom */
#define QRF_ALIGN_SW 13 /* left bottom */
#define QRF_ALIGN_S 14 /* center bottom */
#define QRF_ALIGN_SE 15 /* right bottom */
~~~
Notice how alignment values with an unspecified horizontal
or vertical component can be added to another alignment value
for which that component is specified, to get a fully
specified alignment. For eample:
> QRF_ALIGN_Center + QRF_ALIGN_Bottom == QRF_ALIGN_S.
The alignment for column names is always determined by the
eTitleAlign setting. If eTitleAlign is QRF_Auto, then column
names use center-bottom alignment, QRF_ALIGN_W, value 14.
The aAlign\[\] and eDfltAlign settings have no affect on
column names.
For data in the first nAlign columns, the aAlign\[\] array
entry for that column takes precedence. If either the horizontal
or vertical alignment has an "auto" value for that column or if
a column is beyond the first nAlign entries, then eDfltAlign
is used as a backup. If neither aAlign\[\] nor eDfltAlign
specify a horizontal alignment, then values are left-aligned
(QRF_ALIGN_Left). If neither aAlign\[\] nor eDfltAlign
specify a vertical alignment, then values are top-aligned
(QRF_ALIGN_Top).
*As of 2025-11-08, only horizontal alignment is implemented.
The vertical alignment settings are currently ignored and
the vertical alignment is always QRF_ALIGN_Top.*
### 2.13 Row and Column Separator Strings
The sqlite3_qrf_spec.zColumnSep and sqlite3_qrf_spec.zRowSep strings
are alternative column and row separator character sequences. If not
specified (if these pointers are left as NULL) then appropriate defaults
are used. Some output styles have hard-coded column and row separators
and these settings are ignored for those styles.
### 2.14 The Output Table Name
The sqlite3_qrf_spec.zTableName value is the name of the output table
when eStyle is QRF_STYLE_Insert.
### 2.15 The Rendering Of NULL (zNull, eTextNull)
If a value is NULL then show the NULL using the string
found in sqlite3_qrf_spec.zNull. If zNull is itself a NULL pointer
then NULL values are rendered as an empty string.
If the sqlite3_qrf_spec.bTextNull field is QRF_Yes, then the
text encoding specified by eText is applied to the value in
zNull. If bTextNull is QRF_No or QRF_Auto, then the value
in zNull is shown verbatim.
### 2.16 Optional Value Rendering Callback
If the sqlite3_qrf_spec.xRender field is not NULL, then each
sqlite3_value coming out of the query is first passed to the
xRender function, giving that function an opportunity to render
the results itself, using whatever custom format is desired.
If xRender chooses to render, it should write the rendering
into memory obtained from sqlite3_malloc() and return a pointer
to that memory. The xRender function can decline
to render (for example, based on the sqlite3_value_type() or other
characteristics of the value) in which case it can simply return a
NULL pointer and the usual default rendering will be used instead.
The sqlite3_format_query_result() function (which calls xRender)
will take responsibility for freeing the string returned by xRender
after it has finished using it.
The eText, eBlob, and eEsc settings above become no-ops if the xRender
routine returns non-NULL. In other words, the application-supplied
xRender routine is expected to do all of its own quoting and formatting.
## 3.0 The `sqlite3_format_query_result()` Interface
Invoke the `sqlite3_format_query_result(P,S,E)` interface to run
the prepared statement P and format its results according to the
specification found in S. The sqlite3_format_query_result() function
will return an SQLite result code, usually SQLITE_OK, but perhaps
SQLITE_NOMEM or SQLITE_ERROR or similar. If an error occurs and if
the E parameter is not NULL, then error message text might be written
into *E. Any error message text will be stored in memory obtained
from sqlite3_malloc() and it is the responsibility of the caller to
free that memory by a subsequent call to sqlite3_free().
## 4.0 Output Styles
The result formatter supports a variety of output styles. The
output style used is determined by the eStyle setting of the
sqlite3_qrf_spec object. The set of supported output modes
might increase in future versions.
The following output modes are currently defined:
> ~~~
#define QRF_STYLE_Auto 0 /* Choose a style automatically */
#define QRF_STYLE_Box 1 /* Unicode box-drawing characters */
#define QRF_STYLE_Column 2 /* One record per line in neat columns */
#define QRF_STYLE_Count 3 /* Output only a count of the rows of output */
#define QRF_STYLE_Csv 4 /* Comma-separated-value */
#define QRF_STYLE_Eqp 5 /* Format EXPLAIN QUERY PLAN output */
#define QRF_STYLE_Explain 6 /* EXPLAIN output */
#define QRF_STYLE_Html 7 /* Generate an XHTML table */
#define QRF_STYLE_Insert 8 /* Generate SQL "insert" statements */
#define QRF_STYLE_Json 9 /* Output is a list of JSON objects */
#define QRF_STYLE_JObject 10 /* Independent JSON objects for each row */
#define QRF_STYLE_Line 11 /* One column per line. */
#define QRF_STYLE_List 12 /* One record per line with a separator */
#define QRF_STYLE_Markdown 13 /* Markdown formatting */
#define QRF_STYLE_Off 14 /* No query output shown */
#define QRF_STYLE_Quote 15 /* SQL-quoted, comma-separated */
#define QRF_STYLE_Stats 16 /* EQP-like output but with performance stats */
#define QRF_STYLE_StatsEst 17 /* EQP-like output with planner estimates */
#define QRF_STYLE_StatsVm 18 /* EXPLAIN-like output with performance stats */
#define QRF_STYLE_Table 19 /* MySQL-style table formatting */
~~~
In the following subsections, these styles will often be referred
to without the "QRF_STYLE_" prefix.
### 4.1 Default Style (Auto)
The **Auto** style means QRF gets to choose an appropriate output
style. It will usually choose **Box**, but might also pick one of
**Explain** or **Eqp** if the `sqlite3_stmt_explain()` function
returns 1 or 2, respectively.
### 4.2 Columnar Styles (Box, Column, Markdown, Table)
The **Box**, **Column**, **Markdown**, and **Table**
modes are columnar. This means the output is arranged into neat,
uniform-width columns. These styles can use more memory, especially when
the query result has many rows, because they need to load the entire output
into memory first in order to determine how wide to make each column.
The nWidth, aWidth, and mxWidth fields of the `sqlite3_qrf_spec` object
are used by these styles only, and are ignored by all other styles.
The zRowSep and zColumnSep settings are ignored by these styles. The
bTitles setting is honored by these styles; it defaults to QRF_SW_On.
The **Box** style uses Unicode box-drawing character to draw a grid
of columns and rows to show the result. The **Table** is the same,
except that it uses ASCII-art rather than Unicode box-drawing characters
to draw the grid. The **Column** arranges the results in neat columns
but does not draw in column or row separator, except that it does draw
lines horizontal lines using "`-`" characters to separate the column names
from the data below. This is very similar to default output styling in
psql. The **Markdown** renders its result in the
Markdown table format.
### 4.3 Line-oriented Styles
The line-oriented styles output each row of result as it is received from
the prepared statement.
The **List** style is the most familiar line-oriented output format.
The **List** style shows output columns for each row on the
same line, each separated by a single "`|`" character and with lines
terminated by a single newline (\\u000a or \\n). These column
and row separator choices can be overridden using the zColumnSep
and zRowSep fields of the `sqlite3_qrf_spec` structure. The text
formatting is QRF_TEXT_Plain, and BLOB encoding is QRF_BLOB_Text. So
characters appear in the output exactly as they appear in the database.
Except the eEsp mode defaults to `QRF_ESC_On`, so that control
characters are escaped, for safety.
The **Csv** and **Quote** styles are simply variations on **List**
with hard-coded values for some of the sqlite3_qrf_spec settings:
<table border=1 cellpadding=2 cellspacing=0>
<tr><th>&nbsp;<th>Quote<th>Csv
<tr><td>zColumnSep<td>","<td>","
<tr><td>zRowSep<td>"\\n"<td>"\\r\\n"
<tr><td>zNull<td>"NULL"<td>""
<tr><td>eText<td>QRF_TEXT_Sql<td>QRF_TEXT_Csv
<tr><td>eBlob<td>QRF_BLOB_Sql<td>QRF_BLOB_Text
</table>
The **Html** style generates HTML table content, just without
the `<TABLE>..</TABLE>` around the outside.
The **Insert** style generates a series of SQL "INSERT" statements
that will inserts the data that is output into a table whose name is defined
by the zTableName field of `sqlite3_qrf_spec`. If zTableName is NULL,
then a substitute name is used.
The **Json** and **JObject** styles generates JSON text for the query result.
The **Json** style produces a JSON array of structures with one
structure per row. **JObject** outputs independent JSON objects, one per
row, with each structure on a separate line all by itself, and not
part of a larger array. In both cases, the labels on the elements of the
JSON objects are taken from the column names of the SQL query. So if
you have an SQL query that has two or more output columns with the same
name, you will end up with JSON structures that have duplicate elements.
Finally, the **Line** style paints each column of a row on a
separate line with the column name on the left and a "`=`" separating the
column name from its value. A single blank line appears between rows.
### 4.4 EXPLAIN Styles (Eqp, Explain)
The **Eqp** and **Explain** styles format output for
EXPLAIN QUERY PLAN and EXPLAIN statements, respectively. If the input
statement is not already an EXPLAIN QUERY PLAN or EXPLAIN statement is
is temporarily converted for the duration of the rendering, but
is converted back before `sqlite3_format_query_result()` returns.
### 4.5 ScanStatus Styles (Stats, StatsEst, StatsVm)
The **Stats**, **StatsEst**, and **StatsVm** styles are similar to **Eqp**
and **Explain** except that they include profiling information
from prior executions of the input prepared statement.
These modes only work if SQLite has been compiled with
-DSQLITE_ENABLE_STMT_SCANSTATUS and if the SQLITE_DBCONFIG_STMT_SCANSTATUS
is enabled for the database connection. The **StatsVm** style
also requires the bytecode() virtual table which is enabled using
the -DSQLITE_ENABLE_BYTECODE_VTAB compile-time option.
### 4.6 Other Styles (Count, Off)
The **Count** style discards all query results and returns
a count of the number of rows of output at the end. The **Off**
style is completely silent; it generates no output. These corner-case
modes are sometimes useful for debugging.
### 5.0 Source Code Files
The SQLite Query Result Formatter is implemented in three source code files:
* `qrf.c` &rarr; The implementation, written in portable C99
* `qrf.h` &rarr; A header file defining interfaces
* `README.md` &rarr; This documentation, in Markdown
To use the SQLite result formatter, include the "`qrf.h`" header file
and link the application against the "`qrf.c`" source file.

2496
ext/qrf/qrf.c Normal file

File diff suppressed because it is too large Load Diff

184
ext/qrf/qrf.h Normal file
View File

@@ -0,0 +1,184 @@
/*
** 2025-10-20
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** Header file for the Result-Format or "resfmt" utility library for SQLite.
** See the resfmt.md documentation for additional information.
*/
#ifndef SQLITE_QRF_H
#define SQLITE_QRF_H
#include <stdlib.h>
#include "sqlite3.h"
/*
** Specification used by clients to define the output format they want
*/
typedef struct sqlite3_qrf_spec sqlite3_qrf_spec;
struct sqlite3_qrf_spec {
unsigned char iVersion; /* Version number of this structure */
unsigned char eStyle; /* Formatting style. "box", "csv", etc... */
unsigned char eEsc; /* How to escape control characters in text */
unsigned char eText; /* Quoting style for text */
unsigned char eTitle; /* Quating style for the text of column names */
unsigned char eBlob; /* Quoting style for BLOBs */
unsigned char bTitles; /* True to show column names */
unsigned char bWordWrap; /* Try to wrap on word boundaries */
unsigned char bTextJsonb; /* Render JSONB blobs as JSON text */
unsigned char bTextNull; /* Apply eText encoding to zNull[] */
unsigned char eDfltAlign; /* Default alignment, no covered by aAlignment */
unsigned char eTitleAlign; /* Alignment for column headers */
short int nWrap; /* Wrap columns wider than this */
short int nScreenWidth; /* Maximum overall table width */
short int nLineLimit; /* Maximum number of lines for any row */
int nCharLimit; /* Maximum number of characters in a cell */
int nWidth; /* Number of entries in aWidth[] */
int nAlign; /* Number of entries in aAlignment[] */
short int *aWidth; /* Column widths */
unsigned char *aAlign; /* Column alignments */
char *zColumnSep; /* Alternative column separator */
char *zRowSep; /* Alternative row separator */
char *zTableName; /* Output table name */
char *zNull; /* Rendering of NULL */
char *(*xRender)(void*,sqlite3_value*); /* Render a value */
int (*xWrite)(void*,const char*,sqlite3_int64); /* Write output */
void *pRenderArg; /* First argument to the xRender callback */
void *pWriteArg; /* First argument to the xWrite callback */
char **pzOutput; /* Storage location for output string */
/* Additional fields may be added in the future */
};
/*
** Interfaces
*/
int sqlite3_format_query_result(
sqlite3_stmt *pStmt, /* SQL statement to run */
const sqlite3_qrf_spec *pSpec, /* Result format specification */
char **pzErr /* OUT: Write error message here */
);
/*
** Range of values for sqlite3_qrf_spec.aWidth[] entries and for
** sqlite3_qrf_spec.mxColWidth and .nScreenWidth
*/
#define QRF_MAX_WIDTH 10000
#define QRF_MIN_WIDTH 0
/*
** Output styles:
*/
#define QRF_STYLE_Auto 0 /* Choose a style automatically */
#define QRF_STYLE_Box 1 /* Unicode box-drawing characters */
#define QRF_STYLE_Column 2 /* One record per line in neat columns */
#define QRF_STYLE_Count 3 /* Output only a count of the rows of output */
#define QRF_STYLE_Csv 4 /* Comma-separated-value */
#define QRF_STYLE_Eqp 5 /* Format EXPLAIN QUERY PLAN output */
#define QRF_STYLE_Explain 6 /* EXPLAIN output */
#define QRF_STYLE_Html 7 /* Generate an XHTML table */
#define QRF_STYLE_Insert 8 /* Generate SQL "insert" statements */
#define QRF_STYLE_Json 9 /* Output is a list of JSON objects */
#define QRF_STYLE_JObject 10 /* Independent JSON objects for each row */
#define QRF_STYLE_Line 11 /* One column per line. */
#define QRF_STYLE_List 12 /* One record per line with a separator */
#define QRF_STYLE_Markdown 13 /* Markdown formatting */
#define QRF_STYLE_Off 14 /* No query output shown */
#define QRF_STYLE_Quote 15 /* SQL-quoted, comma-separated */
#define QRF_STYLE_Stats 16 /* EQP-like output but with performance stats */
#define QRF_STYLE_StatsEst 17 /* EQP-like output with planner estimates */
#define QRF_STYLE_StatsVm 18 /* EXPLAIN-like output with performance stats */
#define QRF_STYLE_Table 19 /* MySQL-style table formatting */
/*
** Quoting styles for text.
** Allowed values for sqlite3_qrf_spec.eText
*/
#define QRF_TEXT_Auto 0 /* Choose text encoding automatically */
#define QRF_TEXT_Plain 1 /* Literal text */
#define QRF_TEXT_Sql 2 /* Quote as an SQL literal */
#define QRF_TEXT_Csv 3 /* CSV-style quoting */
#define QRF_TEXT_Html 4 /* HTML-style quoting */
#define QRF_TEXT_Tcl 5 /* C/Tcl quoting */
#define QRF_TEXT_Json 6 /* JSON quoting */
/*
** Quoting styles for BLOBs
** Allowed values for sqlite3_qrf_spec.eBlob
*/
#define QRF_BLOB_Auto 0 /* Determine BLOB quoting using eText */
#define QRF_BLOB_Text 1 /* Display content exactly as it is */
#define QRF_BLOB_Sql 2 /* Quote as an SQL literal */
#define QRF_BLOB_Hex 3 /* Hexadecimal representation */
#define QRF_BLOB_Tcl 4 /* "\000" notation */
#define QRF_BLOB_Json 5 /* A JSON string */
/*
** Control-character escape modes.
** Allowed values for sqlite3_qrf_spec.eEsc
*/
#define QRF_ESC_Auto 0 /* Choose the ctrl-char escape automatically */
#define QRF_ESC_Off 1 /* Do not escape control characters */
#define QRF_ESC_Ascii 2 /* Unix-style escapes. Ex: U+0007 shows ^G */
#define QRF_ESC_Symbol 3 /* Unicode escapes. Ex: U+0007 shows U+2407 */
/*
** Allowed values for "boolean" fields, such as "bColumnNames", "bWordWrap",
** and "bTextJsonb". There is an extra "auto" variants so these are actually
** tri-state settings, not booleans.
*/
#define QRF_SW_Auto 0 /* Let QRF choose the best value */
#define QRF_SW_Off 1 /* This setting is forced off */
#define QRF_SW_On 2 /* This setting is forced on */
#define QRF_Auto 0 /* Alternate spelling for QRF_*_Auto */
#define QRF_No 1 /* Alternate spelling for QRF_SW_Off */
#define QRF_Yes 2 /* Alternate spelling for QRF_SW_On */
/*
** Possible alignment values alignment settings
**
** Horizontal Vertial
** ---------- -------- */
#define QRF_ALIGN_Auto 0 /* auto auto */
#define QRF_ALIGN_Left 1 /* left auto */
#define QRF_ALIGN_Center 2 /* center auto */
#define QRF_ALIGN_Right 3 /* right auto */
#define QRF_ALIGN_Top 4 /* auto top */
#define QRF_ALIGN_NW 5 /* left top */
#define QRF_ALIGN_N 6 /* center top */
#define QRF_ALIGN_NE 7 /* right top */
#define QRF_ALIGN_Middle 8 /* auto middle */
#define QRF_ALIGN_W 9 /* left middle */
#define QRF_ALIGN_C 10 /* center middle */
#define QRF_ALIGN_E 11 /* right middle */
#define QRF_ALIGN_Bottom 12 /* auto bottom */
#define QRF_ALIGN_SW 13 /* left bottom */
#define QRF_ALIGN_S 14 /* center bottom */
#define QRF_ALIGN_SE 15 /* right bottom */
#define QRF_ALIGN_HMASK 3 /* Horizontal alignment mask */
#define QRF_ALIGN_VMASK 12 /* Vertical alignment mask */
/*
** Auxiliary routines contined within this module that might be useful
** in other contexts, and which are therefore exported.
*/
/*
** Return an estimate of the width, in columns, for the single Unicode
** character c. For normal characters, the answer is always 1. But the
** estimate might be 0 or 2 for zero-width and double-width characters.
**
** Different display devices display unicode using different widths. So
** it is impossible to know that true display width with 100% accuracy.
** Inaccuracies in the width estimates might cause columns to be misaligned.
** Unfortunately, there is nothing we can do about that.
*/
int sqlite3_qrf_wcwidth(int c);
#endif /* !defined(SQLITE_QRF_H) */

36
main.mk
View File

@@ -652,7 +652,7 @@ SRC = \
$(TOP)/src/sqliteInt.h \
$(TOP)/src/sqliteLimit.h \
$(TOP)/src/table.c \
$(TOP)/src/tclsqlite.c \
tclsqlite-ex.c \
$(TOP)/src/threads.c \
$(TOP)/src/tokenize.c \
$(TOP)/src/treeview.c \
@@ -1409,15 +1409,15 @@ whereexpr.o: $(TOP)/src/whereexpr.c $(DEPS_OBJ_COMMON)
window.o: $(TOP)/src/window.c $(DEPS_OBJ_COMMON)
$(T.cc.sqlite) -c $(TOP)/src/window.c
tclsqlite.o: $(T.tcl.env.sh) $(TOP)/src/tclsqlite.c $(DEPS_OBJ_COMMON)
tclsqlite.o: $(T.tcl.env.sh) tclsqlite-ex.c $(DEPS_OBJ_COMMON)
$(T.compile.tcl) -DUSE_TCL_STUBS=1 $$TCL_INCLUDE_SPEC \
-c $(TOP)/src/tclsqlite.c
-c tclsqlite-ex.c -o tclsqlite.o
tclsqlite-shell.o: $(T.tcl.env.sh) $(TOP)/src/tclsqlite.c $(DEPS_OBJ_COMMON)
$(T.compile.tcl) -DTCLSH -o $@ -c $(TOP)/src/tclsqlite.c $$TCL_INCLUDE_SPEC
tclsqlite-shell.o: $(T.tcl.env.sh) tclsqlite-ex.c $(DEPS_OBJ_COMMON)
$(T.compile.tcl) -DTCLSH -o $@ -c tclsqlite-ex.c $$TCL_INCLUDE_SPEC
tclsqlite-stubs.o: $(T.tcl.env.sh) $(TOP)/src/tclsqlite.c $(DEPS_OBJ_COMMON)
$(T.compile.tcl) -DUSE_TCL_STUBS=1 -o $@ -c $(TOP)/src/tclsqlite.c $$TCL_INCLUDE_SPEC
tclsqlite-stubs.o: $(T.tcl.env.sh) tclsqlite-ex.c $(DEPS_OBJ_COMMON)
$(T.compile.tcl) -DUSE_TCL_STUBS=1 -o $@ -c tclsqlite-ex.c $$TCL_INCLUDE_SPEC
#
# STATIC_TCLSQLITE3 = 1 to statically link tclsqlite3, else
@@ -1661,11 +1661,19 @@ install-tcl-0 install-tcl-:
install-tcl: install-tcl-$(HAVE_TCL)
install: install-tcl
tclsqlite3.c: sqlite3.c
TCLSQLITEEX = \
$(TOP)/ext/qrf/qrf.h \
$(TOP)/ext/qrf/qrf.c \
$(TOP)/src/tclsqlite.c
tclsqlite-ex.c: $(TCLSQLITEEX) $(TOP)/tool/mkcombo.tcl $(B.tclsh)
$(B.tclsh) $(TOP)/tool/mkcombo.tcl $(TCLSQLITEEX) -o $@
tclsqlite3.c: sqlite3.c tclsqlite-ex.c
echo '#ifndef USE_SYSTEM_SQLITE' >tclsqlite3.c
cat sqlite3.c >>tclsqlite3.c
echo '#endif /* USE_SYSTEM_SQLITE */' >>tclsqlite3.c
cat $(TOP)/src/tclsqlite.c >>tclsqlite3.c
cat tclsqlite-ex.c >>tclsqlite3.c
#
# $(CFLAGS.tclextension) = CFLAGS for the tclextension* targets.
@@ -1782,7 +1790,7 @@ TESTFIXTURE_FLAGS += -DSQLITE_STRICT_SUBTYPE=1
TESTFIXTURE_SRC0 = $(TESTSRC2) $(libsqlite3.LIB)
TESTFIXTURE_SRC1 = sqlite3.c
TESTFIXTURE_SRC = $(TESTSRC) $(TOP)/src/tclsqlite.c
TESTFIXTURE_SRC = $(TESTSRC) tclsqlite-ex.c
TESTFIXTURE_SRC += $(TESTFIXTURE_SRC$(USE_AMALGAMATION))
testfixture$(T.exe): $(T.tcl.env.sh) has_tclsh85 $(TESTFIXTURE_SRC)
@@ -1912,7 +1920,7 @@ shelltest:
#
sqlite3_analyzer.c.flags.0 = -DINCLUDE_SQLITE3_C=1
sqlite3_analyzer.c.flags.1 =
sqlite3_analyzer.c: sqlite3.c $(TOP)/src/tclsqlite.c $(TOP)/tool/spaceanal.tcl \
sqlite3_analyzer.c: sqlite3.c tclsqlite-ex.c $(TOP)/tool/spaceanal.tcl \
$(TOP)/tool/mkccode.tcl $(TOP)/tool/sqlite3_analyzer.c.in
$(B.tclsh) $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqlite3_analyzer.c.in \
$(sqlite3_analyzer.c.flags.$(LINK_TOOLS_DYNAMICALLY)) \
@@ -1936,7 +1944,7 @@ sqlite3_analyzer$(T.exe): $(T.tcl.env.sh) sqlite3_analyzer.c \
# can cause the $@ to link to an out-of-tree libsqlite3.so, which may
# or may not fail or otherwise cause confusion.
sqltclsh.c: sqlite3.c $(TOP)/src/tclsqlite.c $(TOP)/tool/sqltclsh.tcl \
sqltclsh.c: sqlite3.c tclsqlite-ex.c $(TOP)/tool/sqltclsh.tcl \
$(TOP)/ext/misc/appendvfs.c $(TOP)/tool/mkccode.tcl \
$(TOP)/tool/sqltclsh.c.in
$(B.tclsh) $(TOP)/tool/mkccode.tcl $(TOP)/tool/sqltclsh.c.in >sqltclsh.c
@@ -1957,7 +1965,7 @@ xbin: sqlite3_expert$(T.exe)
CHECKER_DEPS =\
$(TOP)/tool/mkccode.tcl \
sqlite3.c \
$(TOP)/src/tclsqlite.c \
tclsqlite-ex.c \
$(TOP)/ext/repair/sqlite3_checker.tcl \
$(TOP)/ext/repair/checkindex.c \
$(TOP)/ext/repair/checkfreelist.c \
@@ -2333,6 +2341,8 @@ mptest: mptester$(T.exe)
# Source and header files that shell.c depends on
SHELL_DEP = \
$(TOP)/src/shell.c.in \
$(TOP)/ext/qrf/qrf.c \
$(TOP)/ext/qrf/qrf.h \
$(TOP)/ext/expert/sqlite3expert.c \
$(TOP)/ext/expert/sqlite3expert.h \
$(TOP)/ext/intck/sqlite3intck.c \

View File

@@ -1,12 +1,12 @@
C Add\ssupport\sfor\sSQLITE_LIMIT_PARSER_DEPTH\sto\slimit\sthe\ssize\sof\sthe\sstack\nused\sby\sthe\sparser.\s\sThis\scan\shelp\sprevent\sdeeply\snested\sparse\strees\sthat\nthen\scause\sproblems\son\smachines\swith\ssmaller\sCPU\sstacks.\s\sModify\sthe\n%realloc\sand\s%free\sdirectives\sof\sLemon\sand\sadd\sthe\snew\s%stack_size_limit\ndirective\sin\ssupport\sof\sthis\scapability.
D 2025-11-18T17:27:46.970
C New\ssubcomponent,\sthe\sQuery\sResult\sFormatter\s(QRF),\sthat\sformats\squery\nresults\sfor\sdisplay\sto\shumans\son\sa\sfixed-width\sfont\sterminal.\s\sRework\sthe\nCLI\sto\smake\suse\sof\sthe\sQRF.\s\sRenovate\sthe\s.mode\scommand\sof\sthe\sCLI.\s\sAlso\nincorporate\sthe\sQRF\sinto\sthe\sTCL\sinterface\sas\sthe\s"format"\smethod.
D 2025-11-18T17:49:48.189
F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md 6bc480fc673fb4acbc4094e77edb326267dd460162d7723c7f30bee2d3d9e97d
F Makefile.in 3ce07126d7e87c7464301482e161fdae6a51d0a2aa06b200b8f0000ef4d6163b
F Makefile.linux-generic bd3e3cacd369821a6241d4ea1967395c962dfe3057e38cb0a435cee0e8b789d0
F Makefile.msc 523bab2f6569e912a0aaf2d13e0b3f9bcb36d52310b236d950b354068c9de3f3
F Makefile.msc 8c5ed7173ee4cc3c03ba8e21065128f7ca5292c83d68f3bf971786e21fc6e382
F README.md dae499194b75deed76a13a4a83c82493f2530331882d7dfe5754d63287d3f8f7
F VERSION 74672bfd4c7826c0fc6f84762488a707c52e7d2d94af42ccb0edcc6c74311c41
F art/icon-243x273.gif 9750b734f82fdb3dc43127753d5e6fbf3b62c9f4e136c2fbf573b2f57ea87af5
@@ -47,7 +47,7 @@ F autosetup/find_tclconfig.tcl e64886ffe3b982d4df42cd28ed91fe0b5940c2c5785e126c1
F autosetup/jimsh0.c a57c16e65dcffc9c76e496757cb3f7fb47e01ecbd1631a0a5e01751fc856f049
F autosetup/pkg-config.tcl 4e635bf39022ff65e0d5434339dd41503ea48fc53822c9c5bde88b02d3d952ba
F autosetup/proj.tcl 6fc14ef82b19b77a95788ffbcfad7989b4e3cb4ce96a21dcb5cf7312f362fba9
F autosetup/sqlite-config.tcl 1fd7c55394051fbfba2fb9fa9192c027ee7d74612dbf772b06e27feb9f1de5df
F autosetup/sqlite-config.tcl 97dc76d332ae3344d02d15c99c57db0ac063537e9133cbee0520d48ba0eddc08
F autosetup/system.tcl 51d4be76cd9a9074704b584e5c9cbba616202c8468cf9ba8a4f8294a7ab1dba9
F autosetup/teaish/README.txt b40071e6f8506500a2f7f71d5fc69e0bf87b9d7678dd9da1e5b4d0acbf40b1ca
F autosetup/teaish/core.tcl e014dd95900c7f9a34e8e0f460f47e94841059827bce8b4c49668b0c7ae3f1a0
@@ -71,7 +71,7 @@ F doc/wal-lock.md 7db0cd61e2000b545b78ce89b0c2a9a8dd8d64c097839258ac10d7c5c4156e
F ext/README.md 6eb1ac267d917767952ed0ef63f55de003b6a5da433ce1fa389e1a9532e73132
F ext/expert/README.md b321c2762bb93c18ea102d5a5f7753a4b8bac646cb392b3b437f633caf2020c3
F ext/expert/expert.c d548d603a4cc9e61f446cc179c120c6713511c413f82a4a32b1e1e69d3f086a4
F ext/expert/expert1.test 1d2da6606623b57bb47064e02140823ce1daecd4cacbf402c73ad3473d7f000c
F ext/expert/expert1.test d9dfbf7fb527cfd43049e30a6238ef02c94484041fa4461ed41acbc6435425d6
F ext/expert/sqlite3expert.c 546010043fbec93544f762de5161b3d553165859e6bd853c4b85c05f93484260
F ext/expert/sqlite3expert.h ca81efc2679a92373a13a3e76a6138d0310e32be53d6c3bfaedabd158ea8969b
F ext/expert/test_expert.c c395134bd6d4efa594a7d26578a1cb624c4027b79b4b5fcd44736c5ef1f5f725
@@ -397,8 +397,8 @@ F ext/misc/shathree.c fd22d70620f86a0467acfdd3acd8435d5cb54eb1e2d9ff36ae44e38982
F ext/misc/showauth.c 732578f0fe4ce42d577e1c86dc89dd14a006ab52
F ext/misc/spellfix.c 693c8fd3293087fa821322967a97e59dfa24051e5d2ca7fa85790a4034db6fa4
F ext/misc/sqlar.c a6175790482328171da47095f87608b48a476d4fac78d8a9ff18b03a2454f634
F ext/misc/sqlite3_stdio.c 0fe5a45bd332b30aef2b68c64edbe69e31e9c42365b0fa79ce95a034bca6fbb0
F ext/misc/sqlite3_stdio.h f05eaf5e0258f0573910324a789a9586fc360a57678c57a6d63cfaa2245b6176
F ext/misc/sqlite3_stdio.c e49c07050bf7bdc87866da7583beda236f2f8c462018a34b61785d99cbddedfd
F ext/misc/sqlite3_stdio.h 27a4ecea47e61bc9574ccdf2806f468afe23af2f95028c9b689bfa08ab1ce99f
F ext/misc/stmt.c b090086cd6bd6281c21271d38d576eeffe662f0e6b67536352ce32bbaa438321
F ext/misc/stmtrand.c 59cffa5d8e158943ff1ce078956d8e208e8c04e67307e8f249dece2436dcb7fc
F ext/misc/templatevtab.c 10f15b165b95423ddef593bc5dcb915ec4eb5e0f1066d585e5435a368b8bc22b
@@ -416,6 +416,9 @@ F ext/misc/wholenumber.c 0fa0c082676b7868bf2fa918e911133f2b349bcdceabd1198bba5f6
F ext/misc/windirent.h 02211ce51f3034c675f2dbf4d228194d51b3ee05734678bad5106fff6292e60c
F ext/misc/zipfile.c 09e6e3a3ff40a99677de3c0bc6569bd5f4709b1844ac3d1c1452a456c5a62f1c
F ext/misc/zorder.c bddff2e1b9661a90c95c2a9a9c7ecd8908afab5763256294dd12d609d4664eee
F ext/qrf/README.md dd565fd1ca0c46ea37dbf4d496e368b9ecade768c92669640bc106e039629016
F ext/qrf/qrf.c ee3964addf075c87fc60e312a472eec35112ba34581edf655fcd5c34e2492602
F ext/qrf/qrf.h b4b3489b3b3683523fd248d15cf5945830643b036943efacdb772a3e00367aa2
F ext/rbu/rbu.c 801450b24eaf14440d8fd20385aacc751d5c9d6123398df41b1b5aa804bf4ce8
F ext/rbu/rbu1.test 25870dd7db7eb5597e2b4d6e29e7a7e095abf332660f67d89959552ce8f8f255
F ext/rbu/rbu10.test 7c22caa32c2ff26983ca8320779a31495a6555737684af7aba3daaf762ef3363
@@ -653,7 +656,7 @@ F ext/wasm/tests/opfs/sahpool/index.html be736567fd92d3ecb9754c145755037cbbd2bca
F ext/wasm/tests/opfs/sahpool/sahpool-pausing.js f264925cfc82155de38cecb3d204c36e0f6991460fff0cb7c15079454679a4e2
F ext/wasm/tests/opfs/sahpool/sahpool-worker.js bd25a43fc2ab2d1bafd8f2854ad3943ef673f7c3be03e95ecf1612ff6e8e2a61
F magic.txt 5ade0bc977aa135e79e3faaea894d5671b26107cc91e70783aa7dc83f22f3ba0
F main.mk 29078d11f7545a8fae4347305b164bb4e777b801da16baeb48214014a8d63a06
F main.mk 27a0769b40a813343f19e8fca2e9d55405f0311cb3cc5327f5702640df0182a0
F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
F mptest/crash01.test 61e61469e257df0850df4293d7d4d6c2af301421
@@ -731,7 +734,7 @@ F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
F src/resolve.c 5616fbcf3b833c7c705b24371828215ad0925d0c0073216c4f153348d5753f0a
F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97
F src/select.c ba9cd07ffa3277883c1986085f6ddc4320f4d35d5f212ab58df79a7ecc1a576a
F src/shell.c.in 2e4d5dc7978fbf586f590cdc9a30dbf01398bb81159f846293f840f016d218ec
F src/shell.c.in 4fdceb103ba8761bf3d68108c3a6e6499be913a2490a7c384ee79ee547a9517c
F src/sqlite.h.in f1363321ca55cc2feaa289e9fe6dfb08102a28c54edf005564711a2348b06eef
F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479
F src/sqlite3ext.h 5d5330f5f8461f5ce74960436ddcfa53ecd09c2b8b23901e22ae38aec3243998
@@ -739,7 +742,7 @@ F src/sqliteInt.h e53f8c6f9a809206b8db9524d294c29e21d0c07bea5114121980bbef30333c
F src/sqliteLimit.h 0a5516b4ec192a205c541e05f67009028a9451dc6678aae4cf8e68596903c246
F src/status.c 7565d63a79aa2f326339a24a0461a60096d0bd2bce711fefb50b5c89335f3592
F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
F src/tclsqlite.c 3c604c49e6cf4211960a9ddb9505280fd22cde32175f40884c641c0f5a286036
F src/tclsqlite.c 381384fbe3cf342115f9ad01208fa81092e9a2156a4ea4d44de87852b8df3a8a
F src/tclsqlite.h 614b3780a62522bc9f8f2b9fb22689e8009958e7aa77e572d0f3149050af348a
F src/test1.c 0e71fbcb484a271564e98e0158192c28c24f5521594218c3ba48bcb4cf634f91
F src/test2.c 62f0830958f9075692c29c6de51b495ae8969e1bef85f239ffcd9ba5fb44a5ff
@@ -883,7 +886,7 @@ F test/autoindex5.test 3fb938cbf4e7f3896563ce04e2a24b0bc653fc6245b4bf3268cd7b20f
F test/autovacuum.test 00671369bbf96c6a49989a9425f5b78b94075d6a4b031e5e00000c2c32f365df
F test/autovacuum2.test 76f7eb4fe6a6bf6d33a196a7141dba98886d2fb53a268d7feca285d5da4759d7
F test/autovacuum_ioerr2.test 8a367b224183ad801e0e24dcb7d1501f45f244b4
F test/avfs.test 76f59743dc1f5fa533840d1818b420fe1ee45e21c0fd6bbac7942ba677903128
F test/avfs.test 95bb8d04f8edad6dc9e600221d103f7e2cc3da398af84df215a3a819e560c45c
F test/avtrans.test 7a6eae44763293024b137b53ff824d8500d754dbae060a8d940afbacfc1d4a15
F test/backcompat.test f2431465ed668f09fc3f6998e56e893a1506ccea6e8b6f409f085f759f431b48
F test/backup.test 3b08fd4af69f0fa786931103a31f4542b184aba16e239e5f22b18c3c2476697f
@@ -1433,6 +1436,7 @@ F test/mmap4.test 2e2b4e32555b58da15176e6fe750f17c9dcf7f93
F test/mmapcorrupt.test 470fb44fe92e99c1d23701d156f8c17865f5b027063c9119dcfdb842791f4465
F test/mmapfault.test d4c9eff9cd8c2dc14bc43e71e042f175b0a26fe3
F test/mmapwarm.test 2272005969cd17a910077bd5082f70bc1fefad9a875afec7fc9af483898ecaf3
F test/modeA.clitest 73d091c785671f629af0900f0654a28468bbba5685a545c1513049c57f1b8cbc
F test/multiplex.test d74c034e52805f6de8cc5432cef8c9eb774bb64ec29b83a22effc8ca4dac1f08
F test/multiplex2.test 580ca5817c7edbe4cc68fa150609c9473393003a
F test/multiplex3.test fac575e0b1b852025575a6a8357701d80933e98b5d2fe6d35ddaa68f92f6a1f7
@@ -1502,6 +1506,9 @@ F test/printf2.test 3f55c1871a5a65507416076f6eb97e738d5210aeda7595a74ee895f2224c
F test/progress.test ebab27f670bd0d4eb9d20d49cef96e68141d92fb
F test/ptrchng.test ef1aa72d6cf35a2bbd0869a649b744e9d84977fc
F test/pushdown.test 46a626ef1c0ca79b85296ff2e078b9da20a50e9b804b38f441590c3987580ddd
F test/qrf01.test dfdbbd2b5f2808f5ff6598c8632ee002389b690eb5016450c2f847d8e51b9b92
F test/qrf02.test 39b4afdc000bedccdafc0aecf17638df67a67aaa2d2942865ae6abcc48ba0e92
F test/qrf03.test 9d88aeb5cdd53f050b7ab9bd203281f7c9d063c33f22f8808e441b7ac0874ccf
F test/queryonly.test 5f653159e0f552f0552d43259890c1089391dcca
F test/quick.test 1681febc928d686362d50057c642f77a02c62e57
F test/quickcheck.test a4b7e878cd97e46108291c409b0bf8214f29e18fddd68a42bc5c1375ad1fb80a
@@ -1514,7 +1521,7 @@ F test/randexpr1.test eda062a97e60f9c38ae8d806b03b0ddf23d796df
F test/rbu.test 168573d353cd0fd10196b87b0caa322c144ef736
F test/rdonly.test 64e2696c322e3538df0b1ed624e21f9a23ed9ff8
F test/readonly.test 0d307c335b3421898cfe64a783a376138aa003849b6bff61ee2d21e805bc0051
F test/recover.test c76d05f33f0271fba0f0752170e03b0ab5952dc61dcea7ab3ba40df03c4c42de
F test/recover.test 643139b911ac880a1e881d7621f02cfb546b608b8f2494d7d26fd5ed103b1ceb
F test/regexp1.test 8f2a8bc1569666e29a4cee6c1a666cd224eb6d50e2470d1dc1df995170f3e0f1
F test/regexp2.test 64f9726b2ddc71aea06725fcad53231833d038d58b936d49083ace658b370a13
F test/reindex.test cd9d6021729910ece82267b4f5e1b5ac2911a7566c43b43c176a6a4732e2118d
@@ -1595,16 +1602,17 @@ F test/sharedA.test 64bdd21216dda2c6a3bd3475348ccdc108160f34682c97f2f51c19fc0e21
F test/sharedB.test 1a84863d7a2204e0d42f2e1606577c5e92e4473fa37ea0f5bdf829e4bf8ee707
F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939
F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304
F test/shell1.test ebe953d64c937ad42a0f33170ac0d2d2568faae26813fc7a95203756446d54aa
F test/shell2.test ab23f01ea2347e4b72bb2399af7ee82aa00f9c059141749f7c4064abca5ad728
F test/shell3.test 603b448e917537cf77be0f265c05c6f63bc677c63a533c8e96aae923b56f4a0e
F test/shell4.test 03593fa7908a55f255916ffeda707cdf55680c777736e3da62b1d78cde0d684d
F test/shell5.test d17e7927ab8b7f720efbdd9b5d05fceb6c3c56c25917901b315400214bf24ef4
F test/shell1.test 8fbb73650b685766cdf4b5e8b5d12dee72664f44195eeb788d0f52fce08e04eb
F test/shell2.test 103140814bdc7508aa41dd3462413cbc4aa84b4261112cb8d501d74275cb7d48
F test/shell3.test 840192774cc4edf7653520c0434a311c7477b9bc324abbc7bd2887915792fa8c
F test/shell4.test e25580a792b7b54560c3a76b6968bd8189261f38979fe28e6bc6312c5db280db
F test/shell5.test 7a249400bb2af59ac8524f357f8cf2844a62b6ac5ff8ecd69b045ceb688700ae
F test/shell6.test e3b883b61d4916b6906678a35f9d19054861123ad91b856461e0a456273bdbb8
F test/shell7.test 43fd8e511c533bab5232e95c7b4be93b243451709e89582600d4b6e67693d5c3
F test/shell8.test 641cf21a99c59404c24e3062923734951c4099a6b6b6520de00cf7a1249ee871
F test/shell9.test 8742a5b390cdcef6369f5aa223e415aa4255a4129ef249b177887dc635a87209
F test/shellA.test 4ecff8b7b2c0122ba8174abfbcc4b0f59e44d80f2a911068f8cd4cfc6661032d
F test/shellA.test 4ece22f204532fb04ab702c5b9c70d8680ff610bff1e82c849c16a7b5cd57d83
F test/shellB.test ca8a5ce5b9a59098732fa140c911162f0306f70239c6c2de6da9b718ca304cad
F test/shmlock.test 9f1f729a7fe2c46c88b156af819ac9b72c0714ac6f7246638a73c5752b5fd13c
F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3
F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5
@@ -1678,7 +1686,7 @@ F test/tabfunc01.test 56eeae736217204bb1d9f9ef38340d48058f809b64249217cf77ff4ba6
F test/table.test e87294bf1c80bfd7792142b84ab32ea5beb4f3f71e535d7fb263a6b2068377bf
F test/tableapi.test e37c33e6be2276e3a96bb54b00eea7f321277115d10e5b30fdb52a112b432750
F test/tableopts.test dba698ba97251017b7c80d738c198d39ab747930
F test/tclsqlite.test 3f697424cfc1cdc9c076ec0cadb0e700f059400a3e3ce134b7d856fc9f880e1c
F test/tclsqlite.test 5d6c73bfe7006c85e2f7fb7db8638b521eb2043d5451aaacdac4851eab895443
F test/tempdb.test 4cdaa23ddd8acb4d79cbb1b68ccdfd09b0537aaba909ca69a876157c2a2cbd08
F test/tempdb2.test 353864e96fd3ae2f70773d0ffbf8b1fe48589b02c2ec05013b540879410c3440
F test/tempfault.test 0c0d349c9a99bf5f374655742577f8712c647900
@@ -1690,7 +1698,7 @@ F test/tester.tcl 463ae33b8bf75ac77451df19bd65e7c415c2e9891227c7c9e657d0a2d8e107
F test/testloadext.c 862b848783eaed9985fbce46c65cd214664376b549fae252b364d5d1ef350a27
F test/testrunner.tcl 86179a8e78997e9257cb8f738c5624cb23897da5297855578ba74715e64f1602 x
F test/testrunner_data.tcl c507a9afa911c03446ed90442ffd4a98aca02882c3d51bd1177c24795674def8
F test/testrunner_estwork.tcl 7927a84327259a32854926f68a75292e33a61e7e052fdbfcb01f18696c99c724
F test/testrunner_estwork.tcl 81e2ae10238f50540f42fbf2d94913052a99bfb494b69e546506323f195dcff9
F test/thread001.test a0985c117eab62c0c65526e9fa5d1360dd1cac5b03bde223902763274ce21899
F test/thread002.test c24c83408e35ba5a952a3638b7ac03ccdf1ce4409289c54a050ac4c5f1de7502
F test/thread003.test ee4c9efc3b86a6a2767516a37bd64251272560a7
@@ -2111,6 +2119,7 @@ F tool/merge-test.tcl de76b62f2de2a92d4c1ca4f976bce0aea6899e0229e250479b229b2a19
F tool/mkamalzip.tcl 8aa5ebe7973c8b8774062d34e15fea9815c4cc2ceea3a9b184695f005910876a
F tool/mkautoconfamal.sh 647dada5e34c466bef62a4408e1c99a7e5e1922805479dd57944f33f9803f2f8
F tool/mkccode.tcl c42a8f8cf78f92e83795d5447460dbce7aaf78a3bbf9082f1507dc71a3665f3c x
F tool/mkcombo.tcl 2a5189b219c4a495e1ff7fc980bd568d3cfb82ae9d50c84e77f7a161e96fc132
F tool/mkctimec.tcl 3fb5cad05922f5da61262cb6bcd5868a34e94a49ca8833ae2d7796e7df075576 x
F tool/mkkeywordhash.c 6b0be901c47f9ad42215fc995eb2f4384ac49213b1fba395102ec3e999acf559
F tool/mkmsvcmin.tcl d76c45efda1cce2d4005bcea7b8a22bb752e3256009f331120fb4fecb14ebb7a
@@ -2118,7 +2127,7 @@ F tool/mkopcodec.tcl 33d20791e191df43209b77d37f0ff0904620b28465cca6990cf8d60da61
F tool/mkopcodeh.tcl 2b4e6967a670ef21bf53a164964c35c6163277d002a4c6f56fa231d68c88d023
F tool/mkopts.tcl 680f785fdb09729fd9ac50632413da4eadbdf9071535e3f26d03795828ab07fa
F tool/mkpragmatab.tcl 3801ce32f8c55fe63a3b279f231fb26c2c1a2ea9a09d2dd599239d87a609acec
F tool/mkshellc.tcl bab0a72a68384181a5706712dfdf6815f6526446d4e8aacace2de5e80cda91b2
F tool/mkshellc.tcl 1c9197524174237b580dc1a6d1552541c5f5b78162949b4b8e640a7aef688d40
F tool/mksourceid.c 36aa8020014aed0836fd13c51d6dc9219b0df1761d6b5f58ff5b616211b079b9
F tool/mksqlite3c-noext.tcl 351c55256213154cabb051a3c870ef9f4487de905015141ae50dc7578a901b84
F tool/mksqlite3c.tcl 7a268139158e5deef27a370bc2f8db6ccf100c1ad7ac5e5b23743c0fd354f609
@@ -2149,7 +2158,7 @@ F tool/split-sqlite3c.tcl 4969fd642dad0ea483e4e104163021d92baf98f6a8eac981fe4852
F tool/sqldiff.c 134be7866be19f8beb32043d5aea5657f01aaeae2df8d33d758ff722c78666b9
F tool/sqlite3_analyzer.c.in 14f02cb5ec3c264cd6107d1f1dad77092b1cf440fc196c30b69ae87b56a1a43b
F tool/sqlite3_rsync.c d0e58a1e49fe2192c3ee0b697aed182d502bebfe5b4b406ba6b2baa52a04ecbe
F tool/sqltclsh.c.in 1bcc2e9da58fadf17b0bf6a50e68c1159e602ce057210b655d50bad5aaaef898
F tool/sqltclsh.c.in c103c6fc7d42bce611f9d4596774d60b7ef3d0b291a1f58c9e6184e458b89296
F tool/sqltclsh.tcl 862f4cf1418df5e1315b5db3b5ebe88969e2a784525af5fbf9596592f14ed848
F tool/src-verify.c 6c655d9a8d6b30f3648fc78a79bf3838ed68f8543869d380c43ea9f17b3b8501
F tool/srcck1.c 559e703c6cca1d70398bdba1d7f91036c1a71adf718a1aaa6401a562ccaed154
@@ -2166,9 +2175,9 @@ F tool/version-info.c 33d0390ef484b3b1cb685d59362be891ea162123cea181cb8e6d2cf6dd
F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7
F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
P 5c0214df2c0a7470ac2edca0c483a3edd3c39ef0739688ab9a06e23882200360 9862c945d9a8531f9bef123aee9ed1fd3f64542250a57beb3a150227bc3c1a12
R eaedc8a6fb66bf638a3308ee531542c6
T +closed 9862c945d9a8531f9bef123aee9ed1fd3f64542250a57beb3a150227bc3c1a12
P 52ba0c731d004409353a55ce8ca5a514ce486a077a2be82db5b8fea7619848d5 3d55ec15a9e4dc8af4bf1e2884eaa2c809995fb1529633f73287dc7a54153629
R 90c9d6f86b8c5c7df96c212de7de0225
T +closed 3d55ec15a9e4dc8af4bf1e2884eaa2c809995fb1529633f73287dc7a54153629
U drh
Z 8fa707feb9cf8e2d7e31b1de8f81f137
Z e451421aeb4f1164bb161f700227d1d5
# Remove this line to create a well-formed Fossil manifest.

View File

@@ -1 +1 @@
52ba0c731d004409353a55ce8ca5a514ce486a077a2be82db5b8fea7619848d5
7e460ffa5aae884807db9e7c8214d6d822d5d38ea406fe3b3eac04ac16f158fa

File diff suppressed because it is too large Load Diff

View File

@@ -124,6 +124,15 @@
/* Forward declaration */
typedef struct SqliteDb SqliteDb;
/* Add -DSQLITE_ENABLE_QRF_IN_TCL to add the Query Result Formatter (QRF)
** into the build of the TCL extension, when building using separate
** source files. The QRF is included automatically when building from
** the tclsqlite3.c amalgamation.
*/
#if defined(SQLITE_ENABLE_QRF_IN_TCL)
#include "qrf.h"
#endif
/*
** New SQL functions can be created as TCL scripts. Each such function
** is described by an instance of the following structure.
@@ -2035,6 +2044,349 @@ static void DbHookCmd(
sqlite3_wal_hook(db, (pDb->pWalHook?DbWalHandler:0), pDb);
}
/*
** Implementation of the "db format" command.
**
** Based on provided options, format the results of the SQL statement(s)
** provided into human-readable form using the Query Result Formatter (QRF)
** and return the resuling text.
**
** Syntax: db format OPTIONS SQL
**
** OPTIONS may be:
**
** -style ("auto"|"box"|"column"|...) Output style
** -esc ("auto"|"off"|"ascii"|"symbol") How to deal with ctrl chars
** -text ("auto"|"off"|"sql"|"csv"|...) How to escape TEXT values
** -title ("auto"|"off"|"sql"|...|"off") How to escape column names
** -blob ("auto"|"text"|"sql"|...) How to escape BLOB values
** -wordwrap ("auto"|"off"|"on") Try to wrap at word boundry?
** -textjsonb ("auto"|"off"|"on") Auto-convert JSONB to text?
** -textnull ("auto"|"off"|"on") Use text encoding for -null.
** -defaultalign ("auto"|"left"|...) Default alignment
** -titalalign ("auto"|"left"|"right"|...) Default column name alignment
** -wrap NUMBER Max width of any single column
** -screenwidth NUMBER Width of the display TTY
** -linelimit NUMBER Max lines for any cell
** -charlimit NUMBER Content truncated to this size
** -align LIST-OF-ALIGNMENT Alignment of columns
** -widths LIST-OF-NUMBERS Widths for individual columns
** -columnsep TEXT Column separator text
** -rowsep TEXT Row separator text
** -tablename TEXT Table name for style "insert"
** -null TEXT Text for NULL values
**
** A mapping from TCL "format" command options to sqlite3_qrf_spec fields
** is below. Use this to reference the QRF documentation:
**
** TCL Option spec field
** ---------- ----------
** -style eStyle
** -esc eEsc
** -text eText
** -title eTitle, bTitle
** -blob eBlob
** -wordwrap bWordWrap
** -textjsonb bTextJsonb
** -textnull bTestNull
** -defaultalign eDfltAlign
** -titlealign eTitleAlign
** -wrap nWrap
** -screenwidth nScreenWidth
** -linelimit nLineLimit
** -charlimit nCharLimit
** -align nAlign, aAlign
** -widths nWidth, aWidth
** -columnsep zColumnSep
** -rowsep zRowSep
** -tablename zTableName
** -null zNull
*/
static int dbQrf(SqliteDb *pDb, int objc, Tcl_Obj *const*objv){
#ifndef SQLITE_QRF_H
Tcl_SetResult(pDb->interp, "QRF not available in this build", TCL_VOLATILE);
return TCL_ERROR;
#else
char *zResult = 0; /* Result to be returned */
const char *zSql = 0; /* SQL to run */
int i; /* Loop counter */
int rc; /* Result code */
sqlite3_qrf_spec qrf; /* Formatting spec */
static const char *azAlign[] = {
"auto", "bottom", "c",
"center", "e", "left",
"middle", "n", "ne",
"nw", "right", "s",
"se", "sw", "top",
"w", 0
};
static const unsigned char aAlignMap[] = {
QRF_ALIGN_Auto, QRF_ALIGN_Bottom, QRF_ALIGN_C,
QRF_ALIGN_Center, QRF_ALIGN_E, QRF_ALIGN_Left,
QRF_ALIGN_Middle, QRF_ALIGN_N, QRF_ALIGN_NE,
QRF_ALIGN_NW, QRF_ALIGN_Right, QRF_ALIGN_S,
QRF_ALIGN_SE, QRF_ALIGN_SW, QRF_ALIGN_Top,
QRF_ALIGN_W
};
memset(&qrf, 0, sizeof(qrf));
qrf.iVersion = 1;
qrf.pzOutput = &zResult;
for(i=2; i<objc; i++){
const char *zArg = Tcl_GetString(objv[i]);
const char *azBool[] = { "auto", "yes", "no", "on", "off", 0 };
const unsigned char aBoolMap[] = { 0, 2, 1, 2, 1 };
if( zArg[0]!='-' ){
if( zSql ){
Tcl_AppendResult(pDb->interp, "unknown argument: ", zArg, (char*)0);
rc = TCL_ERROR;
goto format_failed;
}
zSql = zArg;
}else if( i==objc-1 ){
Tcl_AppendResult(pDb->interp, "option has no argument: ", zArg, (char*)0);
rc = TCL_ERROR;
goto format_failed;
}else if( strcmp(zArg,"-style")==0 ){
static const char *azStyles[] = {
"auto", "box", "column",
"count", "csv", "eqp",
"explain", "html", "insert",
"jobject", "json", "line",
"list", "markdown", "quote",
"stats", "stats-est", "stats-vm",
"table", 0
};
static unsigned char aStyleMap[] = {
QRF_STYLE_Auto, QRF_STYLE_Box, QRF_STYLE_Column,
QRF_STYLE_Count, QRF_STYLE_Csv, QRF_STYLE_Eqp,
QRF_STYLE_Explain, QRF_STYLE_Html, QRF_STYLE_Insert,
QRF_STYLE_JObject, QRF_STYLE_Json, QRF_STYLE_Line,
QRF_STYLE_List, QRF_STYLE_Markdown, QRF_STYLE_Quote,
QRF_STYLE_Stats, QRF_STYLE_StatsEst, QRF_STYLE_StatsVm,
QRF_STYLE_Table
};
int style;
rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azStyles,
"format style (-style)", 0, &style);
if( rc ) goto format_failed;
qrf.eStyle = aStyleMap[style];
i++;
}else if( strcmp(zArg,"-esc")==0 ){
static const char *azEsc[] = {
"ascii", "auto", "off", "symbol", 0
};
static unsigned char aEscMap[] = {
QRF_ESC_Ascii, QRF_ESC_Auto, QRF_ESC_Off, QRF_ESC_Symbol
};
int esc;
rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azEsc,
"control character escape (-esc)", 0, &esc);
if( rc ) goto format_failed;
qrf.eEsc = aEscMap[esc];
i++;
}else if( strcmp(zArg,"-text")==0 || strcmp(zArg, "-title")==0 ){
/* NB: --title can be "off" or "on but --text may not be. Thus we put
** the "off" and "on" choices first and start the search on the
** thrid element of the array when processing --text */
static const char *azText[] = { "off", "on",
"auto", "csv", "html",
"json", "plain", "sql",
"tcl", 0
};
static unsigned char aTextMap[] = {
QRF_TEXT_Auto, QRF_TEXT_Csv, QRF_TEXT_Html,
QRF_TEXT_Json, QRF_TEXT_Plain, QRF_TEXT_Sql,
QRF_TEXT_Tcl
};
int txt;
int k = zArg[2]=='e';
rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], &azText[k*2], zArg,
0, &txt);
if( rc ) goto format_failed;
if( k ){
qrf.eText = aTextMap[txt];
}else if( txt<=1 ){
qrf.bTitles = txt ? QRF_Yes : QRF_No;
qrf.eTitle = QRF_TEXT_Auto;
}else{
qrf.bTitles = QRF_Yes;
qrf.eTitle = aTextMap[txt-2];
}
i++;
}else if( strcmp(zArg,"-blob")==0 ){
static const char *azBlob[] = {
"auto", "hex", "json",
"tcl", "text", "sql", 0
};
static unsigned char aBlobMap[] = {
QRF_BLOB_Auto, QRF_BLOB_Hex, QRF_BLOB_Json,
QRF_BLOB_Tcl, QRF_BLOB_Text, QRF_BLOB_Sql
};
int blob;
rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azBlob,
"BLOB encoding (-blob)", 0, &blob);
if( rc ) goto format_failed;
qrf.eBlob = aBlobMap[blob];
i++;
}else if( strcmp(zArg,"-wordwrap")==0 ){
int v = 0;
rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azBool,
"-wordwrap", 0, &v);
if( rc ) goto format_failed;
qrf.bWordWrap = aBoolMap[v];
i++;
}else if( strcmp(zArg,"-textjsonb")==0 || strcmp(zArg,"-textnull")==0 ){
int v = 0;
rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azBool,
zArg, 0, &v);
if( rc ) goto format_failed;
if( zArg[5]=='j' ){
qrf.bTextJsonb = aBoolMap[v];
}else{
qrf.bTextNull = aBoolMap[v];
}
i++;
}else if( strcmp(zArg,"-defaultalign")==0 || strcmp(zArg,"-titlealign")==0){
int ax = 0;
rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azAlign,
zArg[1]=='d' ? "default alignment (-defaultalign)" :
"title alignment (-titlealign)",
0, &ax);
if( rc ) goto format_failed;
if( zArg[1]=='d' ){
qrf.eDfltAlign = aAlignMap[ax];
}else{
qrf.eTitleAlign = aAlignMap[ax];
}
i++;
}else if( strcmp(zArg,"-wrap")==0
|| strcmp(zArg,"-screenwidth")==0
|| strcmp(zArg,"-linelimit")==0
){
int v = 0;
rc = Tcl_GetIntFromObj(pDb->interp, objv[i+1], &v);
if( rc ) goto format_failed;
if( v<QRF_MIN_WIDTH ){
v = QRF_MIN_WIDTH;
}else if( v>QRF_MAX_WIDTH ){
v = QRF_MAX_WIDTH;
}
if( zArg[1]=='w' ){
qrf.nWrap = v;
}else if( zArg[1]=='s' ){
qrf.nScreenWidth = v;
}else{
qrf.nLineLimit = v;
}
i++;
}else if( strcmp(zArg,"-charlimit")==0 ){
int v = 0;
rc = Tcl_GetIntFromObj(pDb->interp, objv[i+1], &v);
if( rc ) goto format_failed;
if( v<0 ) v = 0;
qrf.nCharLimit = v;
i++;
}else if( strcmp(zArg,"-align")==0 ){
Tcl_Size n = 0;
int jj;
rc = Tcl_ListObjLength(pDb->interp, objv[i+1], &n);
if( rc ) goto format_failed;
sqlite3_free(qrf.aAlign);
qrf.aAlign = sqlite3_malloc64( (n+1)*sizeof(qrf.aAlign[0]) );
if( qrf.aAlign==0 ){
Tcl_AppendResult(pDb->interp, "out of memory", (char*)0);
rc = TCL_ERROR;
goto format_failed;
}
memset(qrf.aAlign, 0, (n+1)*sizeof(qrf.aAlign[0]));
qrf.nAlign = n;
for(jj=0; jj<n; jj++){
int x;
Tcl_Obj *pTerm;
rc = Tcl_ListObjIndex(pDb->interp, objv[i+1], jj, &pTerm);
if( rc ) goto format_failed;
rc = Tcl_GetIndexFromObj(pDb->interp, pTerm, azAlign,
"column alignment (-align)", 0, &x);
if( rc ) goto format_failed;
qrf.aAlign[jj] = aAlignMap[x];
}
i++;
}else if( strcmp(zArg,"-widths")==0 ){
Tcl_Size n = 0;
int jj;
rc = Tcl_ListObjLength(pDb->interp, objv[i+1], &n);
if( rc ) goto format_failed;
sqlite3_free(qrf.aWidth);
qrf.aWidth = sqlite3_malloc64( (n+1)*sizeof(qrf.aWidth[0]) );
if( qrf.aWidth==0 ){
Tcl_AppendResult(pDb->interp, "out of memory", (char*)0);
rc = TCL_ERROR;
goto format_failed;
}
memset(qrf.aWidth, 0, (n+1)*sizeof(qrf.aWidth[0]));
qrf.nWidth = n;
for(jj=0; jj<n; jj++){
Tcl_Obj *pTerm;
int v;
rc = Tcl_ListObjIndex(pDb->interp, objv[i+1], jj, &pTerm);
if( rc ) goto format_failed;
rc = Tcl_GetIntFromObj(pDb->interp, pTerm, &v);
if( v<(-QRF_MAX_WIDTH) ){
v = -QRF_MAX_WIDTH;
}else if( v>QRF_MAX_WIDTH ){
v = QRF_MAX_WIDTH;
}
qrf.aWidth[jj] = (short int)v;
}
i++;
}else if( strcmp(zArg,"-columnsep")==0 ){
qrf.zColumnSep = Tcl_GetString(objv[i+1]);
i++;
}else if( strcmp(zArg,"-rowsep")==0 ){
qrf.zRowSep = Tcl_GetString(objv[i+1]);
i++;
}else if( strcmp(zArg,"-tablename")==0 ){
qrf.zTableName = Tcl_GetString(objv[i+1]);
i++;
}else if( strcmp(zArg,"-null")==0 ){
qrf.zNull = Tcl_GetString(objv[i+1]);
i++;
}else{
Tcl_AppendResult(pDb->interp, "unknown option: ", zArg, (char*)0);
rc = TCL_ERROR;
goto format_failed;
}
}
while( zSql && zSql[0] ){
SqlPreparedStmt *pStmt = 0; /* Next statement to run */
char *zErr = 0; /* Error message from QRF */
rc = dbPrepareAndBind(pDb, zSql, &zSql, &pStmt);
if( rc ) goto format_failed;
if( pStmt==0 ) continue;
rc = sqlite3_format_query_result(pStmt->pStmt, &qrf, &zErr);
dbReleaseStmt(pDb, pStmt, 0);
if( rc ){
Tcl_SetResult(pDb->interp, zErr, TCL_VOLATILE);
sqlite3_free(zErr);
rc = TCL_ERROR;
goto format_failed;
}
}
Tcl_SetResult(pDb->interp, zResult, TCL_VOLATILE);
rc = TCL_OK;
/* Fall through...*/
format_failed:
sqlite3_free(qrf.aWidth);
sqlite3_free(qrf.aAlign);
sqlite3_free(zResult);
return rc;
#endif
}
/*
** The "sqlite" command below creates a new Tcl command for each
** connection it opens to an SQLite database. This routine is invoked
@@ -2064,15 +2416,15 @@ static int SQLITE_TCLAPI DbObjCmd(
"commit_hook", "complete", "config",
"copy", "deserialize", "enable_load_extension",
"errorcode", "erroroffset", "eval",
"exists", "function", "incrblob",
"interrupt", "last_insert_rowid", "nullvalue",
"onecolumn", "preupdate", "profile",
"progress", "rekey", "restore",
"rollback_hook", "serialize", "status",
"timeout", "total_changes", "trace",
"trace_v2", "transaction", "unlock_notify",
"update_hook", "version", "wal_hook",
0
"exists", "format", "function",
"incrblob", "interrupt", "last_insert_rowid",
"nullvalue", "onecolumn", "preupdate",
"profile", "progress", "rekey",
"restore", "rollback_hook", "serialize",
"status", "timeout", "total_changes",
"trace", "trace_v2", "transaction",
"unlock_notify", "update_hook", "version",
"wal_hook", 0
};
enum DB_enum {
DB_AUTHORIZER, DB_BACKUP, DB_BIND_FALLBACK,
@@ -2081,14 +2433,15 @@ static int SQLITE_TCLAPI DbObjCmd(
DB_COMMIT_HOOK, DB_COMPLETE, DB_CONFIG,
DB_COPY, DB_DESERIALIZE, DB_ENABLE_LOAD_EXTENSION,
DB_ERRORCODE, DB_ERROROFFSET, DB_EVAL,
DB_EXISTS, DB_FUNCTION, DB_INCRBLOB,
DB_INTERRUPT, DB_LAST_INSERT_ROWID, DB_NULLVALUE,
DB_ONECOLUMN, DB_PREUPDATE, DB_PROFILE,
DB_PROGRESS, DB_REKEY, DB_RESTORE,
DB_ROLLBACK_HOOK, DB_SERIALIZE, DB_STATUS,
DB_TIMEOUT, DB_TOTAL_CHANGES, DB_TRACE,
DB_TRACE_V2, DB_TRANSACTION, DB_UNLOCK_NOTIFY,
DB_UPDATE_HOOK, DB_VERSION, DB_WAL_HOOK,
DB_EXISTS, DB_FORMAT, DB_FUNCTION,
DB_INCRBLOB, DB_INTERRUPT, DB_LAST_INSERT_ROWID,
DB_NULLVALUE, DB_ONECOLUMN, DB_PREUPDATE,
DB_PROFILE, DB_PROGRESS, DB_REKEY,
DB_RESTORE, DB_ROLLBACK_HOOK, DB_SERIALIZE,
DB_STATUS, DB_TIMEOUT, DB_TOTAL_CHANGES,
DB_TRACE, DB_TRACE_V2, DB_TRANSACTION,
DB_UNLOCK_NOTIFY, DB_UPDATE_HOOK, DB_VERSION,
DB_WAL_HOOK
};
/* don't leave trailing commas on DB_enum, it confuses the AIX xlc compiler */
@@ -2978,6 +3331,18 @@ deserialize_error:
break;
}
/*
** $db format [OPTIONS] SQL
**
** Run the SQL statement(s) given as the final argument. Use the
** Query Result Formatter extension of SQLite to format the output as
** text and return that text.
*/
case DB_FORMAT: {
rc = dbQrf(pDb, objc, objv);
break;
}
/*
** $db function NAME [OPTIONS] SCRIPT
**

View File

@@ -332,9 +332,11 @@ do_test 4.3 {
set ofd [open $shdo w]
if {$::cliDoesAr} {
puts $ofd ".ar -u $shdo"
puts $ofd ".mode list"
puts $ofd "select count(*) from sqlar where name = '$shdo';"
} else {
puts $ofd "insert into sqlar values (1);"
puts $ofd ".mode list"
puts $ofd "select count(*) from sqlar;"
}
puts $ofd ".q"

64
test/modeA.clitest Normal file
View File

@@ -0,0 +1,64 @@
# 2025-11-12
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
#
# Test cases for the ".mode" command of the CLI.
# To run these tests:
#
# ./sqlite3 <test/modeA.clitest
#
#
CREATE TABLE t1(a,b,c,d,e);
INSERT INTO t1 VALUES(1,2.5,'three',x'4444',NULL);
INSERT INTO t1 SELECT b,c,d,e,a FROM t1;
INSERT INTO t1 SELECT d,e,a,b,c FROM t1;
.mode box
.output memory
SELECT * FROM t1;
.output --verify END
┌─────┬───────┬───────┬───────┬───────┐
│ a │ b │ c │ d │ e │
├─────┼───────┼───────┼───────┼───────┤
│ 1 │ 2.5 │ three │ DD │ │
│ 2.5 │ three │ DD │ │ 1 │
│ DD │ │ 1 │ 2.5 │ three │
│ │ 1 │ 2.5 │ three │ DD │
└─────┴───────┴───────┴───────┴───────┘
END
.output memory
.mode --null xyz
SELECT * FROM t1;
.output --verify END
┌─────┬───────┬───────┬───────┬───────┐
│ a │ b │ c │ d │ e │
├─────┼───────┼───────┼───────┼───────┤
│ 1 │ 2.5 │ three │ DD │ xyz │
│ 2.5 │ three │ DD │ xyz │ 1 │
│ DD │ xyz │ 1 │ 2.5 │ three │
│ xyz │ 1 │ 2.5 │ three │ DD │
└─────┴───────┴───────┴───────┴───────┘
END
.output memory --error-prefix "Error:"
.mode foo
.output --verify END
Error: .mode foo
Error: ^--- unknown mode
Error: Use ".help .mode" for more info
END
.output memory
.mode --null xyzzy -v
.output -glob ' --null "xyzzy"'
.output memory
.mode -null abcde -v
.output -glob ' --null "abcde"'

893
test/qrf01.test Normal file
View File

@@ -0,0 +1,893 @@
# 2025-11-05
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
#
# Test cases for the Query Result Formatter (QRF)
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix qrf01
do_execsql_test 1.0 {
CREATE TABLE t1(a, b, c);
INSERT INTO t1 VALUES(1,2.5,'three'),(x'424c4f42',NULL,'Ἀμήν');
}
do_test 1.10 {
set result "\n[db format {SELECT * FROM t1}]"
} {
┌──────┬─────┬───────┐
│ a │ b │ c │
├──────┼─────┼───────┤
│ 1 │ 2.5 │ three │
│ BLOB │ │ Ἀμήν │
└──────┴─────┴───────┘
}
do_test 1.11a {
set result "\n[db format -title off {SELECT * FROM t1}]"
} {
┌──────┬─────┬───────┐
│ 1 │ 2.5 │ three │
│ BLOB │ │ Ἀμήν │
└──────┴─────┴───────┘
}
do_test 1.11b {
set result "\n[db format -text sql {SELECT * FROM t1}]"
} {
┌─────────────┬─────┬─────────┐
│ a │ b │ c │
├─────────────┼─────┼─────────┤
│ 1 │ 2.5 │ 'three' │
│ x'424c4f42' │ │ 'Ἀμήν' │
└─────────────┴─────┴─────────┘
}
do_test 1.12 {
set result "\n[db format -text csv {SELECT * FROM t1}]"
} {
┌────────────────────┬─────┬────────┐
│ a │ b │ c │
├────────────────────┼─────┼────────┤
│ 1 │ 2.5 │ three │
│ "\102\114\117\102" │ │ "Ἀμήν" │
└────────────────────┴─────┴────────┘
}
do_test 1.13 {
set result "\n[db format -text csv -blob hex {SELECT * FROM t1}]"
} {
┌──────────┬─────┬────────┐
│ a │ b │ c │
├──────────┼─────┼────────┤
│ 1 │ 2.5 │ three │
│ 424c4f42 │ │ "Ἀμήν" │
└──────────┴─────┴────────┘
}
do_test 1.14 {
catch {db format -text unk -blob hex {SELECT * FROM t1}} res
set res
} {bad -text "unk": must be auto, csv, html, json, plain, sql, or tcl}
do_test 1.15 {
catch {db format -text sql -blob unk {SELECT * FROM t1}} res
set res
} {bad BLOB encoding (-blob) "unk": must be auto, hex, json, tcl, text, or sql}
do_test 1.16 {
catch {db format -text sql -style unk {SELECT * FROM t1}} res
set res
} {bad format style (-style) "unk": must be auto, box, column, count, csv, eqp, explain, html, insert, jobject, json, line, list, markdown, quote, stats, stats-est, stats-vm, or table}
do_test 1.20 {
set result "\n[db format -style box {SELECT * FROM t1}]"
} {
┌──────┬─────┬───────┐
│ a │ b │ c │
├──────┼─────┼───────┤
│ 1 │ 2.5 │ three │
│ BLOB │ │ Ἀμήν │
└──────┴─────┴───────┘
}
do_test 1.30 {
set result "\n[db format -style table {SELECT * FROM t1}]"
} {
+------+-----+-------+
| a | b | c |
+------+-----+-------+
| 1 | 2.5 | three |
| BLOB | | Ἀμήν |
+------+-----+-------+
}
do_test 1.31 {
set result "\n[db format -style table -title off {SELECT * FROM t1}]"
} {
+------+-----+-------+
| 1 | 2.5 | three |
| BLOB | | Ἀμήν |
+------+-----+-------+
}
do_test 1.40 {
set result "\n[db format -style column {SELECT * FROM t1}]"
} {
a b c
---- --- -----
1 2.5 three
BLOB Ἀμήν
}
do_test 1.41 {
set result "\n[db format -style column -title off {SELECT * FROM t1}]"
} {
1 2.5 three
BLOB Ἀμήν
}
do_test 1.50 {
db format -style count {SELECT * FROM t1}
} 2
do_test 1.60a {
db format -style list -columnsep , -rowsep \r\n -text csv -blob tcl {SELECT * FROM t1}
} "1,2.5,three\r\n\"\\102\\114\\117\\102\",,\"Ἀμήν\"\r\n"
do_test 1.60b {
db format -style csv -columnsep xyz -rowsep pqr -text sql -blob sql {SELECT * FROM t1}
} "1,2.5,three\r\nBLOB,,\"Ἀμήν\"\r\n"
do_test 1.61a {
db format -style list -columnsep , -rowsep \r\n -text csv -title auto -blob tcl {SELECT * FROM t1}
} "a,b,c\r\n1,2.5,three\r\n\"\\102\\114\\117\\102\",,\"Ἀμήν\"\r\n"
do_test 1.61b {
db format -style csv -title auto -blob tcl {SELECT * FROM t1}
} "a,b,c\r\n1,2.5,three\r\nBLOB,,\"Ἀμήν\"\r\n"
do_test 1.62a {
db format -style list -columnsep , -rowsep \r\n -text csv -title csv -blob tcl {SELECT a AS 'a x y', b, c FROM t1}
} "\"a x y\",b,c\r\n1,2.5,three\r\n\"\\102\\114\\117\\102\",,\"Ἀμήν\"\r\n"
do_test 1.62b {
db format -style csv -title csv -blob tcl {SELECT a AS 'a x y', b, c FROM t1}
} "\"a x y\",b,c\r\n1,2.5,three\r\nBLOB,,\"Ἀμήν\"\r\n"
do_test 1.70 {
set result "\n[db format -style html {SELECT * FROM t1}]"
} {
<TR>
<TD>1
<TD>2.5
<TD>three
</TR>
<TR>
<TD>BLOB
<TD>null
<TD>Ἀμήν
</TR>
}
do_test 1.71 {
set result "\n[db format -style html -title auto {SELECT * FROM t1}]"
} {
<TR>
<TH>a
<TH>b
<TH>c
</TR>
<TR>
<TD>1
<TD>2.5
<TD>three
</TR>
<TR>
<TD>BLOB
<TD>null
<TD>Ἀμήν
</TR>
}
do_test 1.80 {
set result "\n[db format -style insert {SELECT * FROM t1}]"
} {
INSERT INTO tab VALUES(1,2.5,'three');
INSERT INTO tab VALUES(x'424c4f42',NULL,'Ἀμήν');
}
do_test 1.81 {
set result "\n[db format -style insert -tablename t1 {SELECT * FROM t1}]"
} {
INSERT INTO t1 VALUES(1,2.5,'three');
INSERT INTO t1 VALUES(x'424c4f42',NULL,'Ἀμήν');
}
do_test 1.82 {
set result "\n[db format -style insert -tablename t1 -title auto \
{SELECT * FROM t1}]"
} {
INSERT INTO t1(a,b,c) VALUES(1,2.5,'three');
INSERT INTO t1(a,b,c) VALUES(x'424c4f42',NULL,'Ἀμήν');
}
do_test 1.83 {
set result "\n[db format -style insert -tablename drop -title on \
{SELECT a AS "a-b", b, c AS "123" FROM t1}]"
} {
INSERT INTO "drop"("a-b",b,"123") VALUES(1,2.5,'three');
INSERT INTO "drop"("a-b",b,"123") VALUES(x'424c4f42',NULL,'Ἀμήν');
}
do_test 1.90 {
set result "\n[db format -style json {SELECT * FROM t1}]"
} {
[{"a":1,"b":2.5,"c":"three"},
{"a":"\u0042\u004c\u004f\u0042","b":null,"c":"Ἀμήν"}]
}
do_test 1.91 {
set result "\n[db format -style jobject {SELECT * FROM t1}]"
} {
{"a":1,"b":2.5,"c":"three"}
{"a":"\u0042\u004c\u004f\u0042","b":null,"c":"Ἀμήν"}
}
do_test 1.92 {
set result "\n[db format -style jobject {SELECT *, unistr('abc\u000a123\u000d\u000axyz') AS xyz FROM t1}]"
} {
{"a":1,"b":2.5,"c":"three","xyz":"abc\n123\r\nxyz"}
{"a":"\u0042\u004c\u004f\u0042","b":null,"c":"Ἀμήν","xyz":"abc\n123\r\nxyz"}
}
do_test 1.100 {
set result "\n[db format -style line {SELECT * FROM t1}]"
} {
a = 1
b = 2.5
c = three
a = BLOB
b =
c = Ἀμήν
}
do_test 1.101 {
set result "\n[db format -style line -null (NULL) {SELECT * FROM t1}]"
} {
a = 1
b = 2.5
c = three
a = BLOB
b = (NULL)
c = Ἀμήν
}
do_test 1.102 {
set result "\n[db format -style line -null (NULL) \
-text sql {SELECT * FROM t1}]"
} {
a = 1
b = 2.5
c = 'three'
a = x'424c4f42'
b = (NULL)
c = 'Ἀμήν'
}
do_test 1.110 {
set result "\n[db format -style list {SELECT * FROM t1}]"
} {
1|2.5|three
BLOB||Ἀμήν
}
do_test 1.111 {
set result "\n[db format -style list -title on {SELECT * FROM t1}]"
} {
a|b|c
1|2.5|three
BLOB||Ἀμήν
}
do_test 1.112 {
set result "\n[db format -style list -title on -text sql -null NULL \
-title plain {SELECT * FROM t1}]"
} {
a|b|c
1|2.5|'three'
x'424c4f42'|NULL|'Ἀμήν'
}
do_test 1.118 {
set rc [catch {db format -style list -title unk {SELECT * FROM t1}} res]
lappend rc $res
} {1 {bad -title "unk": must be off, on, auto, csv, html, json, plain, sql, or tcl}}
do_test 1.120 {
set result "\n[db format -style markdown {SELECT * FROM t1}]"
} {
| a | b | c |
|------|-----|-------|
| 1 | 2.5 | three |
| BLOB | | Ἀμήν |
}
do_test 1.121 {
set result "\n[db format -style markdown -title off {SELECT * FROM t1}]"
} {
| 1 | 2.5 | three |
| BLOB | | Ἀμήν |
}
do_test 1.130 {
set result "\n[db format -style quote {SELECT * FROM t1}]"
} {
1,2.5,'three'
x'424c4f42',NULL,'Ἀμήν'
}
do_test 1.131 {
set result "\n[db format -style quote -title on {SELECT * FROM t1}]"
} {
'a','b','c'
1,2.5,'three'
x'424c4f42',NULL,'Ἀμήν'
}
do_execsql_test 2.0 {
DELETE FROM t1;
INSERT INTO t1 VALUES(1,2,'The quick fox jumps over the lazy brown dog.');
}
do_test 2.1 {
set result "\n[db format -widths {5 -5 19} -wordwrap on \
{SELECT * FROM t1}]"
} {
┌───────┬───────┬─────────────────────┐
│ a │ b │ c │
├───────┼───────┼─────────────────────┤
│ 1 │ 2 │ The quick fox jumps │
│ │ │ over the lazy brown │
│ │ │ dog. │
└───────┴───────┴─────────────────────┘
}
do_test 2.2 {
set result "\n[db format -widths {5 -5 19} -wordwrap off \
{SELECT * FROM t1}]"
} {
┌───────┬───────┬─────────────────────┐
│ a │ b │ c │
├───────┼───────┼─────────────────────┤
│ 1 │ 2 │ The quick fox jumps │
│ │ │ over the lazy brown │
│ │ │ dog. │
└───────┴───────┴─────────────────────┘
}
do_test 2.3 {
set result "\n[db format -widths {5 -5 18} -wordwrap on \
{SELECT * FROM t1}]"
} {
┌───────┬───────┬────────────────────┐
│ a │ b │ c │
├───────┼───────┼────────────────────┤
│ 1 │ 2 │ The quick fox │
│ │ │ jumps over the │
│ │ │ lazy brown dog. │
└───────┴───────┴────────────────────┘
}
do_test 2.4 {
set result "\n[db format -widths {5 -5 -18} -wordwrap on \
{SELECT * FROM t1}]"
} {
┌───────┬───────┬────────────────────┐
│ a │ b │ c │
├───────┼───────┼────────────────────┤
│ 1 │ 2 │ The quick fox │
│ │ │ jumps over the │
│ │ │ lazy brown dog. │
└───────┴───────┴────────────────────┘
}
do_test 2.5 {
set result "\n[db format -widths {5 -5 19} -wordwrap off \
{SELECT * FROM t1}]"
} {
┌───────┬───────┬─────────────────────┐
│ a │ b │ c │
├───────┼───────┼─────────────────────┤
│ 1 │ 2 │ The quick fox jumps │
│ │ │ over the lazy brown │
│ │ │ dog. │
└───────┴───────┴─────────────────────┘
}
do_test 2.6 {
set result "\n[db format -widths {5 -5 18} -wordwrap off \
{SELECT * FROM t1}]"
} {
┌───────┬───────┬────────────────────┐
│ a │ b │ c │
├───────┼───────┼────────────────────┤
│ 1 │ 2 │ The quick fox jump │
│ │ │ s over the lazy br │
│ │ │ own dog. │
└───────┴───────┴────────────────────┘
}
do_test 2.7 {
set result "\n[db format -widths {5 5 18} -wordwrap yes \
-align {left center right} -titlealign right \
{SELECT * FROM t1}]"
} {
┌───────┬───────┬────────────────────┐
│ a │ b │ c │
├───────┼───────┼────────────────────┤
│ 1 │ 2 │ The quick fox │
│ │ │ jumps over the │
│ │ │ lazy brown dog. │
└───────┴───────┴────────────────────┘
}
do_test 2.8 {
set result "\n[db format -widths {5 8 11} -wordwrap yes \
-align {auto auto center} -titlealign left \
-defaultalign right \
{SELECT * FROM t1}]"
} {
┌───────┬──────────┬─────────────┐
│ a │ b │ c │
├───────┼──────────┼─────────────┤
│ 1 │ 2 │ The quick │
│ │ │ fox jumps │
│ │ │ over the │
│ │ │ lazy brown │
│ │ │ dog. │
└───────┴──────────┴─────────────┘
}
do_test 2.9 {
catch {db format -align {auto xyz 123} {SELECT * FROM t1}} res
set res
} {bad column alignment (-align) "xyz": must be auto, bottom, c, center, e, left, middle, n, ne, nw, right, s, se, sw, top, or w}
do_test 2.10 {
catch {db format -defaultalign xyz {SELECT * FROM t1}} res
set res
} {bad default alignment (-defaultalign) "xyz": must be auto, bottom, c, center, e, left, middle, n, ne, nw, right, s, se, sw, top, or w}
do_test 2.11 {
catch {db format -titlealign xyz {SELECT * FROM t1}} res
set res
} {bad title alignment (-titlealign) "xyz": must be auto, bottom, c, center, e, left, middle, n, ne, nw, right, s, se, sw, top, or w}
do_execsql_test 2.30 {
UPDATE t1 SET c='Η γρήγορη αλεπού πηδάει πάνω από το τεμπέλικο καφέ σκυλί';
SELECT hex(c) FROM t1;
} {CE9720CEB3CF81CEAECEB3CEBFCF81CEB720CEB1CEBBCEB5CF80CEBFCF8D20CF80CEB7CEB4CEACCEB5CEB920CF80CEACCEBDCF8920CEB1CF80CF8C20CF84CEBF20CF84CEB5CEBCCF80CEADCEBBCEB9CEBACEBF20CEBACEB1CF86CEAD20CF83CEBACF85CEBBCEAF}
do_test 2.31 {
set result "\n[db format -widths {5 -5 18} -wordwrap on \
{SELECT * FROM t1}]"
} {
┌───────┬───────┬────────────────────┐
│ a │ b │ c │
├───────┼───────┼────────────────────┤
│ 1 │ 2 │ Η γρήγορη αλεπού │
│ │ │ πηδάει πάνω από το │
│ │ │ τεμπέλικο καφέ │
│ │ │ σκυλί │
└───────┴───────┴────────────────────┘
}
do_test 2.32 {
set result "\n[db format -widths {5 5 18} -align {left center center} -wordwrap on \
{SELECT * FROM t1}]"
} {
┌───────┬───────┬────────────────────┐
│ a │ b │ c │
├───────┼───────┼────────────────────┤
│ 1 │ 2 │ Η γρήγορη αλεπού │
│ │ │ πηδάει πάνω από το │
│ │ │ τεμπέλικο καφέ │
│ │ │ σκυλί │
└───────┴───────┴────────────────────┘
}
do_execsql_test 3.0 {
DELETE FROM t1;
INSERT INTO t1 VALUES(1,2,unistr('abc\u001b[1;31m123\u001b[0mxyz'));
}
do_test 3.1 {
set result "\n[db format {SELECT * FROM t1}]"
} {
┌───┬───┬────────────────────────┐
│ a │ b │ c │
├───┼───┼────────────────────────┤
│ 1 │ 2 │ abc^[[1;31m123^[[0mxyz │
└───┴───┴────────────────────────┘
}
do_test 3.2 {
set result "\n[db format -esc off {SELECT * FROM t1}]"
string map [list \033 X] $result
} {
┌───┬───┬───────────┐
│ a │ b │ c │
├───┼───┼───────────┤
│ 1 │ 2 │ abcX[1;31m123X[0mxyz │
└───┴───┴───────────┘
}
do_test 3.3 {
set result "\n[db format -esc symbol {SELECT * FROM t1}]"
} {
┌───┬───┬──────────────────────┐
│ a │ b │ c │
├───┼───┼──────────────────────┤
│ 1 │ 2 │ abc␛[1;31m123␛[0mxyz │
└───┴───┴──────────────────────┘
}
do_test 3.4 {
set result "\n[db format -esc ascii {SELECT * FROM t1}]"
} {
┌───┬───┬────────────────────────┐
│ a │ b │ c │
├───┼───┼────────────────────────┤
│ 1 │ 2 │ abc^[[1;31m123^[[0mxyz │
└───┴───┴────────────────────────┘
}
do_test 3.5 {
catch {db format -esc unk {SELECT * FROM t1}} res
set res
} {bad control character escape (-esc) "unk": must be ascii, auto, off, or symbol}
do_execsql_test 4.0 {
DELETE FROM t1;
INSERT INTO t1 VALUES(json('{a:5,b:6}'), jsonb('{c:1,d:2}'), 99);
}
do_test 4.1 {
set result "\n[db format -text sql {SELECT * FROM t1}]"
} {
┌─────────────────┬───────────────────────┬────┐
│ a │ b │ c │
├─────────────────┼───────────────────────┼────┤
│ '{"a":5,"b":6}' │ x'8c1763133117641332' │ 99 │
└─────────────────┴───────────────────────┴────┘
}
do_test 4.2 {
set result "\n[db format -text sql -textjsonb on {SELECT * FROM t1}]"
} {
┌─────────────────┬────────────────────────┬────┐
│ a │ b │ c │
├─────────────────┼────────────────────────┼────┤
│ '{"a":5,"b":6}' │ jsonb('{"c":1,"d":2}') │ 99 │
└─────────────────┴────────────────────────┴────┘
}
do_test 4.3 {
set result "\n[db format -text plain -textjsonb on -wrap 11 \
{SELECT a AS json, b AS jsonb, c AS num FROM t1}]"
} {
┌─────────────┬─────────────┬─────┐
│ json │ jsonb │ num │
├─────────────┼─────────────┼─────┤
│ {"a":5,"b": │ {"c":1,"d": │ 99 │
│ 6} │ 2} │ │
└─────────────┴─────────────┴─────┘
}
do_execsql_test 5.0 {
DROP TABLE t1;
CREATE TABLE t1(name, mtime, value);
INSERT INTO t1 VALUES
('entry-one',1708791504,zeroblob(300)),
(unistr('one\u000atwo\u000athree'),1333206973,NULL),
('sample-jsonb',1333101221,jsonb('{
"alpha":53.11688723,
"beta":"qrfWidthPrint(p, p->pOut, -p->u.sLine.mxColWth);",
"zeta":[15,null,1333206973,"fd8ffe000104a46494600010101"]}'));
}
do_test 5.1 {
set sql {SELECT name, mtime, datetime(mtime,'unixepoch') AS time,
value FROM t1 ORDER BY mtime}
set result "\n[db format -style line -screenwidth 60 -blob sql \
-text sql -wordwrap off -linelimit 77 $sql]"
} {
name = 'sample-jsonb'
mtime = 1333101221
time = '2012-03-30 09:53:41'
value = x'cc7c57616c706861b535332e31313638383732334762657461
c73071726657696474685072696e7428702c20702d3e704f7574
2c202d702d3e752e734c696e652e6d78436f6c577468293b477a
657461cb2c23313500a331333333323036393733c71b66643866
6665303030313034613436343934363030303130313031'
name = unistr('one\u000atwo\u000athree')
mtime = 1333206973
time = '2012-03-31 15:16:13'
value =
name = 'entry-one'
mtime = 1708791504
time = '2024-02-24 16:18:24'
value = x'00000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000
000000000000000000000000000000'
}
do_test 5.2 {
set sql {SELECT name, mtime, datetime(mtime,'unixepoch') AS time,
value FROM t1 ORDER BY mtime}
set result "\n[db format -style line -screenwidth 60 -blob sql \
-text plain -esc off -textjsonb yes \
-wordwrap yes -linelimit 3 $sql]"
} {
name = sample-jsonb
mtime = 1333101221
time = 2012-03-30 09:53:41
value = {"alpha":53.11688723,"beta":"qrfWidthPrint(p,
p->pOut, -p->u.sLine.mxColWth);","zeta":[15,null,
1333206973,"fd8ffe000104a46494600010101"]}
name = one
two
three
mtime = 1333206973
time = 2012-03-31 15:16:13
value =
name = entry-one
mtime = 1708791504
time = 2024-02-24 16:18:24
value = x'00000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000
...
}
do_test 5.3a {
set result "\n[db format -style box -widths {0 10 10 14}\
-align {left right right center} \
-blob sql \
-text plain -esc off -textjsonb no \
-wordwrap yes -linelimit 2 $sql]"
} {
┌──────────────┬────────────┬────────────┬────────────────┐
│ name │ mtime │ time │ value │
├──────────────┼────────────┼────────────┼────────────────┤
│ sample-jsonb │ 1333101221 │ 2012-03-30 │ x'cc7c57616c70 │
│ │ │ 09:53:41 │ 6861b535332e31 │
│ │ │ │ ... │
├──────────────┼────────────┼────────────┼────────────────┤
│ one │ 1333206973 │ 2012-03-31 │ │
│ two │ │ 15:16:13 │ │
│ ... │ │ │ │
├──────────────┼────────────┼────────────┼────────────────┤
│ entry-one │ 1708791504 │ 2024-02-24 │ x'000000000000 │
│ │ │ 16:18:24 │ 00000000000000 │
│ │ │ │ ... │
└──────────────┴────────────┴────────────┴────────────────┘
}
do_test 5.3b {
set result "\n[db format -style table -widths {0 10 10 14}\
-align {center right right right} \
-blob sql \
-text plain -esc off -textjsonb no \
-wordwrap yes -linelimit 2 $sql]"
} {
+--------------+------------+------------+----------------+
| name | mtime | time | value |
+--------------+------------+------------+----------------+
| sample-jsonb | 1333101221 | 2012-03-30 | x'cc7c57616c70 |
| | | 09:53:41 | 6861b535332e31 |
| | | | ... |
+--------------+------------+------------+----------------+
| one | 1333206973 | 2012-03-31 | |
| two | | 15:16:13 | |
| ... | | | |
+--------------+------------+------------+----------------+
| entry-one | 1708791504 | 2024-02-24 | x'000000000000 |
| | | 16:18:24 | 00000000000000 |
| | | | ... |
+--------------+------------+------------+----------------+
}
do_test 5.3c {
set result "\n[db format -style column -widths {0 10 10 14}\
-align {center right right right} \
-blob sql \
-text plain -esc off -textjsonb no \
-wordwrap yes -linelimit 2 $sql]"
} {
name mtime time value
------------ ---------- ---------- --------------
sample-jsonb 1333101221 2012-03-30 x'cc7c57616c70
09:53:41 6861b535332e31
...
one 1333206973 2012-03-31
two 15:16:13
...
entry-one 1708791504 2024-02-24 x'000000000000
16:18:24 00000000000000
...
}
do_test 5.4 {
db eval {
CREATE TABLE t2(a,b,c,d,e);
WITH v(x) AS (SELECT 'abcdefghijklmnopqrstuvwxyz')
INSERT INTO t2 SELECT x,x,x,x,x FROM v;
}
set sql {SELECT char(0x61,0xa,0x62,0xa,0x63,0xa,0x64) a,
mtime b, mtime c, mtime d, mtime e FROM t1}
set result "\n[db format -style box -widths {1 2 3 4 5}\
-linelimit 3 -wordwrap off {SELECT *, 'x' AS x FROM t2}]"
} {
┌────┬────┬─────┬──────┬───────┬───┐
│ a │ b │ c │ d │ e │ x │
├────┼────┼─────┼──────┼───────┼───┤
│ ab │ ab │ abc │ abcd │ abcde │ x │
│ cd │ cd │ def │ efgh │ fghij │ │
│ ef │ ef │ ghi │ ijkl │ klmno │ │
│ .. │ .. │ ... │ ... │ ... │ │
└────┴────┴─────┴──────┴───────┴───┘
}
do_execsql_test 6.0 {
DELETE FROM t2;
INSERT INTO t2 VALUES
(1, 2.5, 'three', x'342028666f757229', null);
}
do_test 6.1a {
set result "\n[db format -style list -null NULL \
-text tcl -columnsep , \
{SELECT * FROM t2}]"
} {
1,2.5,"three","\064\040\050\146\157\165\162\051",NULL
}
do_test 6.1b {
set result "\n[db format -style list -null NULL \
-text tcl -columnsep , -textnull off \
{SELECT * FROM t2}]"
} {
1,2.5,"three","\064\040\050\146\157\165\162\051",NULL
}
do_test 6.1c {
set result "\n[db format -style list -null NULL \
-text tcl -columnsep , -textnull auto \
{SELECT * FROM t2}]"
} {
1,2.5,"three","\064\040\050\146\157\165\162\051",NULL
}
do_test 6.2 {
set result "\n[db format -style list -null NULL \
-text tcl -columnsep , -textnull yes \
{SELECT * FROM t2}]"
} {
1,2.5,"three","\064\040\050\146\157\165\162\051","NULL"
}
do_test 6.3 {
catch {db format -textnull xyz {SELECT * FROM t2}} res
set res
} {bad -textnull "xyz": must be auto, yes, no, on, or off}
do_execsql_test 7.0 {
CREATE TABLE t7(a,b);
INSERT INTO t7 VALUES('abcdefghijklmnop',
'abcぁdefかghiのjklはmnop');
}
do_test 7.1 {
set result "\n[db format -style list -charlimit 13 \
{SELECT * FROM t7}]"
} {
abcdefghijklm...|abcぁdefかghi...
}
do_test 7.2 {
set result "\n[db format -style list -charlimit 14 \
{SELECT * FROM t7}]"
} {
abcdefghijklmn...|abcぁdefかghi...
}
do_test 7.3 {
set result "\n[db format -style list -charlimit 15 \
{SELECT * FROM t7}]"
} {
abcdefghijklmno...|abcぁdefかghiの...
}
do_test 7.4 {
set result "\n[db format -style list -charlimit 16 \
{SELECT * FROM t7}]"
} {
abcdefghijklmnop|abcぁdefかghiのj...
}
do_test 8.0 {
set result "\n[db format -style table {
WITH c(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c WHERE n<10)
SELECT 'aaa' AS 'a', format('%.*c',n,'b')||char(9)||'xx' AS x FROM c}]"
} {
+-----+--------------------+
| a | x |
+-----+--------------------+
| aaa | b xx |
| aaa | bb xx |
| aaa | bbb xx |
| aaa | bbbb xx |
| aaa | bbbbb xx |
| aaa | bbbbbb xx |
| aaa | bbbbbbb xx |
| aaa | bbbbbbbb xx |
| aaa | bbbbbbbbb xx |
| aaa | bbbbbbbbbb xx |
+-----+--------------------+
}
do_test 8.1 {
set result "\n[db format -style table {
WITH c(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c WHERE n<10)
SELECT 'aaaa' AS 'a', format('%.*c',n,'b')||char(9)||'xx' AS x FROM c}]"
} {
+------+--------------------+
| a | x |
+------+--------------------+
| aaaa | b xx |
| aaaa | bb xx |
| aaaa | bbb xx |
| aaaa | bbbb xx |
| aaaa | bbbbb xx |
| aaaa | bbbbbb xx |
| aaaa | bbbbbbb xx |
| aaaa | bbbbbbbb xx |
| aaaa | bbbbbbbbb xx |
| aaaa | bbbbbbbbbb xx |
+------+--------------------+
}
do_test 8.3 {
set result "\n[db format -style table -esc off {
WITH c(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c WHERE n<15)
SELECT 'aaa' AS 'a',n, 'xx'||char(n)||'yy' AS xy FROM c
WHERE n NOT IN (8,10,13,14)}]"
} {
+-----+----+------------+
| a | n | xy |
+-----+----+------------+
| aaa | 1 | xx␁yy |
| aaa | 2 | xx␂yy |
| aaa | 3 | xx␃yy |
| aaa | 4 | xx␄yy |
| aaa | 5 | xx␅yy |
| aaa | 6 | xx␆yy |
| aaa | 7 | xx␇yy |
| aaa | 9 | xx yy |
| aaa | 11 | xx␋yy |
| aaa | 12 | xx␌yy |
| aaa | 15 | xx␏yy |
+-----+----+------------+
}
do_test 8.4 {
set result "\n[db format -style table -esc off {
WITH c(n) AS (VALUES(1) UNION ALL SELECT n+1 FROM c WHERE n<15)
SELECT 'aaa' AS 'a',n, 'xx'||char(n)||'yy'||char(9)||'zz' AS xyz FROM c
WHERE n NOT IN (8,10,13,14)}]"
} {
+-----+----+--------------------+
| a | n | xyz |
+-----+----+--------------------+
| aaa | 1 | xx␁yy zz |
| aaa | 2 | xx␂yy zz |
| aaa | 3 | xx␃yy zz |
| aaa | 4 | xx␄yy zz |
| aaa | 5 | xx␅yy zz |
| aaa | 6 | xx␆yy zz |
| aaa | 7 | xx␇yy zz |
| aaa | 9 | xx yy zz |
| aaa | 11 | xx␋yy zz |
| aaa | 12 | xx␌yy zz |
| aaa | 15 | xx␏yy zz |
+-----+----+--------------------+
}
do_test 9.1 {
db eval {
CREATE TABLE t9(x);
INSERT INTO t9 VALUES
(x'4331323334'),
(x'c30431323334'),
(x'd3000431323334'),
(x'e30000000431323334'),
(x'f3000000000000000431323334');
}
db format -style list -text plain -rowsep , -textjsonb on \
{SELECT * FROM t9}
} {1234,1234,1234,1234,1234,}
do_test 9.2 {
db format -style list -text sql -rowsep , -textjsonb on \
{SELECT * FROM t9}
} {jsonb('1234'),jsonb('1234'),jsonb('1234'),jsonb('1234'),jsonb('1234'),}
db close
finish_test

47
test/qrf02.test Normal file
View File

@@ -0,0 +1,47 @@
# 2025-11-05
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
#
# Test cases for the Query Result Formatter (QRF)
#
# These tests are for EXPLAIN and EXPLAIN QUERY PLAN formatting, the
# output of which can change when enhancments are made to the query
# planner. So expect to have to modify the expected results of these
# test cases in the future.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix qrf02
do_execsql_test 1.0 {
CREATE TABLE t1(a);
INSERT INTO t1 VALUES(1);
}
set result [db format {EXPLAIN SELECT * FROM t1}]
do_test 1.10 {
set result
} {/*addr opcode p1 p2 p3 p4 p5 comment
---- ------------- ---- ---- ---- ------------- -- -------------
0 Init */}
regsub -all {\d+} $result {N} result2
do_test 1.11 {
set result2
} "/.*\nN Rewind .*\nN Column .*/"
do_test 1.20 {
set result "\n[db format {EXPLAIN QUERY PLAN SELECT * FROM t1}]"
} {
QUERY PLAN
`--SCAN t1
}
finish_test

176
test/qrf03.test Normal file
View File

@@ -0,0 +1,176 @@
# 2025-11-15
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
#
# Test cases for the Query Result Formatter (QRF)
#
# Format narrowing due to nScreenWidth
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix qrf03
do_execsql_test 1.0 {
CREATE TABLE mlink(
mid INTEGER,
fid INTEGER,
pmid INTEGER,
pid INTEGER,
fnid INTEGER REFERENCES filename,
pfnid INTEGER,
mperm INTEGER,
isaux BOOLEAN DEFAULT 0
);
INSERT INTO mlink VALUES(28775,28774,28773,28706,1,0,0,0);
INSERT INTO mlink VALUES(28773,28706,28770,28685,1,0,0,0);
INSERT INTO mlink VALUES(28770,28736,28769,28695,2,0,0,0);
INSERT INTO mlink VALUES(28770,28697,28769,28698,3,0,0,0);
INSERT INTO mlink VALUES(28767,28768,28759,28746,4,0,0,0);
CREATE TABLE event(
type TEXT,
mtime DATETIME,
objid INTEGER PRIMARY KEY,
tagid INTEGER,
uid INTEGER REFERENCES user,
bgcolor TEXT,
euser TEXT,
user TEXT,
ecomment TEXT,
comment TEXT,
brief TEXT,
omtime DATETIME
);
INSERT INTO event VALUES('ci',2460994.978048461023,126223,NULL,NULL,NULL,NULL,'drh',NULL,unistr('Data structure improvements on columnar layout. Prep work for getting\u000acolumnar layouts to respond to nScreenWidth.'),NULL,2460994.978048461023);
INSERT INTO event VALUES('ci',2460994.836955601816,126218,NULL,NULL,NULL,NULL,'stephan',NULL,'API doc typo fix.',NULL,2460994.836955601816);
INSERT INTO event VALUES('ci',2460994.88823369192,126212,NULL,NULL,NULL,NULL,'stephan',NULL,'Move sqlite3-api-cleanup.js into post-js-footer.js to remove the final direct Emscripten dependency from the intermediary build product sqlite3-api.js (the whole library, waiting to be bootstrapped). This is partly in response to [forum:4b7d45433731d2e0|forum post 4b7d45433731d2e0], which demonstrates a potential use case for a standalone sqlite3-api.js. This is a build/doc change, not a functional one.',NULL,2460994.88823369192);
INSERT INTO event VALUES('ci',2460994.516081551089,126211,NULL,NULL,NULL,NULL,'drh',NULL,unistr('Improve columnar layout in QRF so that it correctly deals with control\u000acharacters, and especially tabs.'),NULL,2460994.516081551089);
INSERT INTO event VALUES('ci',2460994.409343171865,126208,NULL,NULL,NULL,NULL,'drh',NULL,'Make use of the new sqlite3_str_free() interface in the CLI.',NULL,2460994.409343171865);
}
do_test 1.10 {
set x "\n[db format -style box -screenwidth 68 \
{SELECT * FROM mlink ORDER BY rowid}]"
} {
┌───────┬───────┬───────┬───────┬──────┬───────┬───────┬───────┐
│ mid │ fid │ pmid │ pid │ fnid │ pfnid │ mperm │ isaux │
├───────┼───────┼───────┼───────┼──────┼───────┼───────┼───────┤
│ 28775 │ 28774 │ 28773 │ 28706 │ 1 │ 0 │ 0 │ 0 │
│ 28773 │ 28706 │ 28770 │ 28685 │ 1 │ 0 │ 0 │ 0 │
│ 28770 │ 28736 │ 28769 │ 28695 │ 2 │ 0 │ 0 │ 0 │
│ 28770 │ 28697 │ 28769 │ 28698 │ 3 │ 0 │ 0 │ 0 │
│ 28767 │ 28768 │ 28759 │ 28746 │ 4 │ 0 │ 0 │ 0 │
└───────┴───────┴───────┴───────┴──────┴───────┴───────┴───────┘
}
do_test 1.11 {
set x "\n[db format -style box -screenwidth 52 \
{SELECT * FROM mlink ORDER BY rowid}]"
} {
┌─────┬─────┬─────┬─────┬────┬─────┬─────┬─────┐
│ mid │ fid │pmid │ pid │fnid│pfnid│mperm│isaux│
├─────┼─────┼─────┼─────┼────┼─────┼─────┼─────┤
│28775│28774│28773│28706│1 │0 │0 │0 │
│28773│28706│28770│28685│1 │0 │0 │0 │
│28770│28736│28769│28695│2 │0 │0 │0 │
│28770│28697│28769│28698│3 │0 │0 │0 │
│28767│28768│28759│28746│4 │0 │0 │0 │
└─────┴─────┴─────┴─────┴────┴─────┴─────┴─────┘
}
do_test 1.20 {
set x "\n[db format -style table -screenwidth 68 \
{SELECT * FROM mlink ORDER BY rowid}]"
} {
+-------+-------+-------+-------+------+-------+-------+-------+
| mid | fid | pmid | pid | fnid | pfnid | mperm | isaux |
+-------+-------+-------+-------+------+-------+-------+-------+
| 28775 | 28774 | 28773 | 28706 | 1 | 0 | 0 | 0 |
| 28773 | 28706 | 28770 | 28685 | 1 | 0 | 0 | 0 |
| 28770 | 28736 | 28769 | 28695 | 2 | 0 | 0 | 0 |
| 28770 | 28697 | 28769 | 28698 | 3 | 0 | 0 | 0 |
| 28767 | 28768 | 28759 | 28746 | 4 | 0 | 0 | 0 |
+-------+-------+-------+-------+------+-------+-------+-------+
}
do_test 1.21 {
set x "\n[db format -style table -screenwidth 52 \
{SELECT * FROM mlink ORDER BY rowid}]"
} {
+-----+-----+-----+-----+----+-----+-----+-----+
| mid | fid |pmid | pid |fnid|pfnid|mperm|isaux|
+-----+-----+-----+-----+----+-----+-----+-----+
|28775|28774|28773|28706|1 |0 |0 |0 |
|28773|28706|28770|28685|1 |0 |0 |0 |
|28770|28736|28769|28695|2 |0 |0 |0 |
|28770|28697|28769|28698|3 |0 |0 |0 |
|28767|28768|28759|28746|4 |0 |0 |0 |
+-----+-----+-----+-----+----+-----+-----+-----+
}
do_test 1.30 {
set x "\n[db format -style markdown -screenwidth 68 \
{SELECT * FROM mlink ORDER BY rowid}]"
} {
| mid | fid | pmid | pid | fnid | pfnid | mperm | isaux |
|-------|-------|-------|-------|------|-------|-------|-------|
| 28775 | 28774 | 28773 | 28706 | 1 | 0 | 0 | 0 |
| 28773 | 28706 | 28770 | 28685 | 1 | 0 | 0 | 0 |
| 28770 | 28736 | 28769 | 28695 | 2 | 0 | 0 | 0 |
| 28770 | 28697 | 28769 | 28698 | 3 | 0 | 0 | 0 |
| 28767 | 28768 | 28759 | 28746 | 4 | 0 | 0 | 0 |
}
do_test 1.31 {
set x "\n[db format -style markdown -screenwidth 52 \
{SELECT * FROM mlink ORDER BY rowid}]"
} {
| mid | fid |pmid | pid |fnid|pfnid|mperm|isaux|
|-----|-----|-----|-----|----|-----|-----|-----|
|28775|28774|28773|28706|1 |0 |0 |0 |
|28773|28706|28770|28685|1 |0 |0 |0 |
|28770|28736|28769|28695|2 |0 |0 |0 |
|28770|28697|28769|28698|3 |0 |0 |0 |
|28767|28768|28759|28746|4 |0 |0 |0 |
}
do_test 1.40 {
set x "\n[db format -style column -screenwidth 68 \
{SELECT * FROM mlink ORDER BY rowid}]"
} {
mid fid pmid pid fnid pfnid mperm isaux
----- ----- ----- ----- ---- ----- ----- -----
28775 28774 28773 28706 1 0 0 0
28773 28706 28770 28685 1 0 0 0
28770 28736 28769 28695 2 0 0 0
28770 28697 28769 28698 3 0 0 0
28767 28768 28759 28746 4 0 0 0
}
do_test 1.41 {
set x "\n[db format -style column -screenwidth 52 \
{SELECT * FROM mlink ORDER BY rowid}]"
} {
mid fid pmid pid fnid pfnid mperm isaux
----- ----- ----- ----- ---- ----- ----- -----
28775 28774 28773 28706 1 0 0 0
28773 28706 28770 28685 1 0 0 0
28770 28736 28769 28695 2 0 0 0
28770 28697 28769 28698 3 0 0 0
28767 28768 28759 28746 4 0 0 0
}
finish_test

View File

@@ -42,7 +42,7 @@ proc compare_dbs {db1 db2} {
proc recover_with_opts {opts} {
set cmd ".recover $opts"
set fd [open [list |$::CLI test.db $cmd]]
set fd [open [list |$::CLI -noinit test.db $cmd]]
fconfigure $fd -translation binary
set sql [read $fd]
close $fd

View File

@@ -48,7 +48,7 @@ do_test shell1-1.1.1b {
} {1 1}
# error on extra options
do_test shell1-1.1.2 {
catchcmd "test.db \"select+3\" \"select+4\"" ""
catchcmd "-compat 20250101 test.db \"select+3\" \"select+4\"" ""
} {0 {3
4}}
# error on extra options
@@ -182,7 +182,7 @@ do_test shell1-1.17.1 {
catchcmd {-- --db "CREATE TABLE T(c1);"}
} {0 {}}
do_test shell1-1.17.2 {
catchcmd {-- --db "SELECT name from sqlite_schema;"}
catchcmd {-compat 20250101 -- --db "SELECT name from sqlite_schema;"}
} {0 T}
forcedelete ./--db
@@ -216,10 +216,14 @@ do_test shell1-2.2.4 {
} {0 {}}
do_test shell1-2.2.5 {
catchcmd "test.db" ".mode \"insert FOO"
} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown qbox quote table tabs tcl}}
} {1 {line 1: .mode "insert FOO
line 1: ^--- unknown mode
line 1: Use ".help .mode" for more info}}
do_test shell1-2.2.6 {
catchcmd "test.db" ".mode \'insert FOO"
} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown qbox quote table tabs tcl}}
} {1 {line 1: .mode 'insert FOO
line 1: ^--- unknown mode
line 1: Use ".help .mode" for more info}}
# check multiple tokens, and quoted tokens
do_test shell1-2.3.1 {
@@ -247,7 +251,9 @@ do_test shell1-2.3.7 {
# check quoted args are unquoted
do_test shell1-2.4.1 {
catchcmd "test.db" ".mode FOO"
} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown qbox quote table tabs tcl}}
} {1 {line 1: .mode FOO
line 1: ^--- unknown mode
line 1: Use ".help .mode" for more info}}
do_test shell1-2.4.2 {
catchcmd "test.db" ".mode csv"
} {0 {}}
@@ -364,7 +370,6 @@ do_test shell1-3.7.4 {
catchcmd "test.db" ".explain OFF BAD"
} {0 {}}
# .header(s) ON|OFF Turn display of headers on or off
do_test shell1-3.9.1 {
catchcmd "test.db" ".header"
@@ -400,7 +405,7 @@ do_test shell1-3.10.1 {
# look for a few of the possible help commands
list [regexp {.help} $res] \
[regexp {.quit} $res] \
[regexp {.show} $res]
[regexp {.mode} $res]
} {1 1 1}
do_test shell1-3.10.2 {
# we allow .help to take extra args (it is help after all)
@@ -408,7 +413,7 @@ do_test shell1-3.10.2 {
# look for a few of the possible help commands
list [regexp {.help} $res] \
[regexp {.quit} $res] \
[regexp {.show} $res]
[regexp {.mode} $res]
} {1 1 1}
# .import FILE TABLE Import data from FILE into TABLE
@@ -421,7 +426,8 @@ do_test shell1-3.11.2 {
do_test shell1-3.11.3 {
# too many arguments
catchcmd "test.db" ".import FOO BAR BAD"
} {/1 .ERROR: extra argument: "BAD".*./}
} {1 {line 1: .import FOO BAR BAD
line 1: ^--- unknown argument}}
# .indexes ?TABLE? Show names of all indexes
# If TABLE specified, only show indexes for tables
@@ -451,11 +457,13 @@ do_test shell1-3.12.3 {
# tabs Tab-separated values
# tcl TCL list elements
do_test shell1-3.13.1 {
catchcmd "test.db" ".mode"
} {0 {current output mode: list --escape ascii}}
catchcmd "test.db" ".mode batch\n.mode"
} {0 {current output mode: list}}
do_test shell1-3.13.2 {
catchcmd "test.db" ".mode FOO"
} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown qbox quote table tabs tcl}}
} {1 {line 1: .mode FOO
line 1: ^--- unknown mode
line 1: Use ".help .mode" for more info}}
do_test shell1-3.13.3 {
catchcmd "test.db" ".mode csv"
} {0 {}}
@@ -503,7 +511,7 @@ do_test shell1-3.15.1 {
.print x"
} {0 x}
do_test shell1-3.15.2 {
catchcmd "test.db" ".output FOO
catchcmd "test.db" ".mode batch\n.output FOO
.print x
.output
SELECT readfile('FOO');"
@@ -512,17 +520,8 @@ SELECT readfile('FOO');"
do_test shell1-3.15.3 {
# too many arguments
catchcmd "test.db" ".output FOO BAD"
} {1 {ERROR: extra parameter: "BAD". Usage:
.output ?FILE? Send output to FILE or stdout if FILE is omitted
If FILE begins with '|' then open it as a pipe.
If FILE is 'off' then output is disabled.
Options:
--bom Prefix output with a UTF8 byte-order mark
-e Send output to the system text editor
--plain Use text/plain for -w option
-w Send output to a web browser
-x Send output as CSV to a spreadsheet
child process exited abnormally}}
} {1 {line 1: .output FOO BAD
line 1: ^--- surplus argument}}
# .output stdout Send output to the screen
do_test shell1-3.16.1 {
@@ -531,17 +530,8 @@ do_test shell1-3.16.1 {
do_test shell1-3.16.2 {
# too many arguments
catchcmd "test.db" ".output stdout BAD"
} {1 {ERROR: extra parameter: "BAD". Usage:
.output ?FILE? Send output to FILE or stdout if FILE is omitted
If FILE begins with '|' then open it as a pipe.
If FILE is 'off' then output is disabled.
Options:
--bom Prefix output with a UTF8 byte-order mark
-e Send output to the system text editor
--plain Use text/plain for -w option
-w Send output to a web browser
-x Send output as CSV to a spreadsheet
child process exited abnormally}}
} {1 {line 1: .output stdout BAD
line 1: ^--- surplus argument}}
# .prompt MAIN CONTINUE Replace the standard prompts
do_test shell1-3.17.1 {
@@ -643,7 +633,7 @@ do_test shell1-3.22.4 {
# .show Show the current values for various settings
do_test shell1-3.23.1 {
set res [catchcmd "test.db" ".show"]
set res [catchcmd "test.db" ".mode batch\n.show"]
list [regexp {echo:} $res] \
[regexp {explain:} $res] \
[regexp {headers:} $res] \
@@ -679,7 +669,7 @@ do_test shell1-3.23b.4 {
# Adverse interaction between .stats and .eqp
#
do_test shell1-3.23b.5 {
catchcmd "test.db" [string map {"\n " "\n"} {
catchcmd "test.db" [string map {"\n " "\n"} {.mode batch
CREATE TEMP TABLE t1(x);
INSERT INTO t1 VALUES(1),(2);
.stats on
@@ -762,9 +752,9 @@ do_test shell1-3.27.4 {
catchcmd "test.db" ".timer OFF BAD"
} {1 {Usage: .timer on|off}}
do_test shell1-3-28.1 {
do_test shell1-3.28.1 {
catchcmd test.db \
".log stdout\nSELECT coalesce(sqlite_log(123,'hello'),'456');"
".mode batch\n.log stdout\nSELECT coalesce(sqlite_log(123,'hello'),'456');"
} "0 {(123) hello\n456}"
do_test shell1-3-29.1 {
@@ -803,14 +793,14 @@ INSERT INTO t1 VALUES('');
INSERT INTO t1 VALUES(1);
INSERT INTO t1 VALUES(2.25);
INSERT INTO t1 VALUES('hello');
INSERT INTO t1 VALUES(X'807f');
INSERT INTO t1 VALUES(x'807f');
CREATE TABLE t3(x,y);
INSERT INTO t3 VALUES(1,NULL);
INSERT INTO t3 VALUES(2,'');
INSERT INTO t3 VALUES(3,1);
INSERT INTO t3 VALUES(4,2.25);
INSERT INTO t3 VALUES(5,'hello');
INSERT INTO t3 VALUES(6,X'807f');
INSERT INTO t3 VALUES(6,x'807f');
COMMIT;}}
@@ -828,14 +818,14 @@ INSERT INTO t1(rowid,x) VALUES(2,'');
INSERT INTO t1(rowid,x) VALUES(3,1);
INSERT INTO t1(rowid,x) VALUES(4,2.25);
INSERT INTO t1(rowid,x) VALUES(5,'hello');
INSERT INTO t1(rowid,x) VALUES(6,X'807f');
INSERT INTO t1(rowid,x) VALUES(6,x'807f');
CREATE TABLE t3(x,y);
INSERT INTO t3(rowid,x,y) VALUES(1,1,NULL);
INSERT INTO t3(rowid,x,y) VALUES(2,2,'');
INSERT INTO t3(rowid,x,y) VALUES(3,3,1);
INSERT INTO t3(rowid,x,y) VALUES(4,4,2.25);
INSERT INTO t3(rowid,x,y) VALUES(5,5,'hello');
INSERT INTO t3(rowid,x,y) VALUES(6,6,X'807f');
INSERT INTO t3(rowid,x,y) VALUES(6,6,x'807f');
COMMIT;}}
# If the table contains an INTEGER PRIMARY KEY, do not record a separate
@@ -854,12 +844,12 @@ do_test shell1-4.1.2 {
} {0 {PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE t1(x INTEGER PRIMARY KEY, y);
INSERT INTO t1 VALUES(1,NULL);
INSERT INTO t1 VALUES(2,'');
INSERT INTO t1 VALUES(3,1);
INSERT INTO t1 VALUES(4,2.25);
INSERT INTO t1 VALUES(5,'hello');
INSERT INTO t1 VALUES(6,X'807f');
INSERT INTO t1(x,y) VALUES(1,NULL);
INSERT INTO t1(x,y) VALUES(2,'');
INSERT INTO t1(x,y) VALUES(3,1);
INSERT INTO t1(x,y) VALUES(4,2.25);
INSERT INTO t1(x,y) VALUES(5,'hello');
INSERT INTO t1(x,y) VALUES(6,x'807f');
COMMIT;}}
# Verify that the table named [table] is correctly quoted and that
@@ -883,7 +873,7 @@ INSERT INTO "table"(rowid,x,y) VALUES(2,12,'');
INSERT INTO "table"(rowid,x,y) VALUES(3,23,1);
INSERT INTO "table"(rowid,x,y) VALUES(4,34,2.25);
INSERT INTO "table"(rowid,x,y) VALUES(5,45,'hello');
INSERT INTO "table"(rowid,x,y) VALUES(6,56,X'807f');
INSERT INTO "table"(rowid,x,y) VALUES(6,56,x'807f');
COMMIT;}}
# Do not record rowids for a WITHOUT ROWID table. Also check correct quoting
@@ -902,12 +892,12 @@ do_test shell1-4.1.4 {
} {0 {PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE [ta<>ble](x INTEGER PRIMARY KEY, y) WITHOUT ROWID;
INSERT INTO "ta<>ble" VALUES(1,NULL);
INSERT INTO "ta<>ble" VALUES(12,'');
INSERT INTO "ta<>ble" VALUES(23,1);
INSERT INTO "ta<>ble" VALUES(34,2.25);
INSERT INTO "ta<>ble" VALUES(45,'hello');
INSERT INTO "ta<>ble" VALUES(56,X'807f');
INSERT INTO "ta<>ble"(x,y) VALUES(1,NULL);
INSERT INTO "ta<>ble"(x,y) VALUES(12,'');
INSERT INTO "ta<>ble"(x,y) VALUES(23,1);
INSERT INTO "ta<>ble"(x,y) VALUES(34,2.25);
INSERT INTO "ta<>ble"(x,y) VALUES(45,'hello');
INSERT INTO "ta<>ble"(x,y) VALUES(56,x'807f');
COMMIT;}}
# Do not record rowids if the rowid is inaccessible
@@ -924,9 +914,9 @@ do_test shell1-4.1.5 {
} {0 {PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE t1(_ROWID_,rowid,oid);
INSERT INTO t1 VALUES(1,NULL,'alpha');
INSERT INTO t1 VALUES(12,'',99);
INSERT INTO t1 VALUES(23,1,X'b0b1b2');
INSERT INTO t1(_ROWID_,rowid,oid) VALUES(1,NULL,'alpha');
INSERT INTO t1(_ROWID_,rowid,oid) VALUES(12,'',99);
INSERT INTO t1(_ROWID_,rowid,oid) VALUES(23,1,x'b0b1b2');
COMMIT;}}
} else {
@@ -1020,7 +1010,7 @@ INSERT INTO t1 VALUES('');
INSERT INTO t1 VALUES(1);
INSERT INTO t1 VALUES(2.25);
INSERT INTO t1 VALUES('hello');
INSERT INTO t1 VALUES(X'807f');}}
INSERT INTO t1 VALUES(x'807f');}}
# Test the output of ".mode insert" with headers
#
@@ -1031,7 +1021,7 @@ INSERT INTO t1(x) VALUES('');
INSERT INTO t1(x) VALUES(1);
INSERT INTO t1(x) VALUES(2.25);
INSERT INTO t1(x) VALUES('hello');
INSERT INTO t1(x) VALUES(X'807f');}}
INSERT INTO t1(x) VALUES(x'807f');}}
# Test the output of ".mode insert"
#
@@ -1042,7 +1032,7 @@ INSERT INTO t3 VALUES(2,'');
INSERT INTO t3 VALUES(3,1);
INSERT INTO t3 VALUES(4,2.25);
INSERT INTO t3 VALUES(5,'hello');
INSERT INTO t3 VALUES(6,X'807f');}}
INSERT INTO t3 VALUES(6,x'807f');}}
# Test the output of ".mode insert" with headers
#
@@ -1053,7 +1043,7 @@ INSERT INTO t3(x,y) VALUES(2,'');
INSERT INTO t3(x,y) VALUES(3,1);
INSERT INTO t3(x,y) VALUES(4,2.25);
INSERT INTO t3(x,y) VALUES(5,'hello');
INSERT INTO t3(x,y) VALUES(6,X'807f');}}
INSERT INTO t3(x,y) VALUES(6,x'807f');}}
# Test the output of ".mode tcl"
#
@@ -1069,8 +1059,8 @@ do_test shell1-4.3 {
catchcmd test.db ".mode tcl\nselect * from t1;"
} {0 {""
""
"1"
"2.25"
1
2.25
"hello"
"\200\177"}}
@@ -1083,15 +1073,15 @@ do_test shell1-4.4 {
}
catchcmd test.db ".mode tcl\nselect * from t2;"
} {0 {"" ""
"1" "2.25"
1 2.25
"hello" "\200\177"}}
# Test the output of ".mode tcl" with ".nullvalue"
#
do_test shell1-4.5 {
catchcmd test.db ".mode tcl\n.nullvalue NULL\nselect * from t2;"
} {0 {"NULL" ""
"1" "2.25"
} {0 {NULL ""
1 2.25
"hello" "\200\177"}}
# Test the output of ".mode tcl" with Tcl reserved characters
@@ -1115,7 +1105,7 @@ do_test shell1-4.6 {
#
do_test shell1-4.7 {
catchcmd test.db ".mode quote\nselect x'0123456789ABCDEF';"
} {0 X'0123456789abcdef'}
} {0 x'0123456789abcdef'}
# Test using arbitrary byte data with the shell via standard input/output.
#
@@ -1274,7 +1264,7 @@ do_test shell1-7.1.7 {
# information.
#
do_test shell1-8.1 {
catchcmd ":memory:" {
catchcmd ":memory:" {.mode batch
-- The pow2 table will hold all the necessary powers of two.
CREATE TABLE pow2(x INTEGER PRIMARY KEY, v TEXT);
WITH RECURSIVE c(x,v) AS (
@@ -1326,6 +1316,7 @@ do_test_with_ansi_output shell1-8.5 {
create table t(a text, b int);
insert into t values ('too long for one line', 1), ('shorter', NULL);
.header on
.mode box --wordwrap off
.width 10 10
.nullvalue NADA
select * from t;}
@@ -1344,12 +1335,12 @@ select * from t;}
#
do_test shell1-9.1 {
catchcmd :memory: {
.mode csv
.mode csv --rowsep "\n"
/*
x */ select 1,2; --x
-- .nada
;
.mode csv
.mode csv --rowsep "\n"
--x
select 2,1; select 3,4;
}
@@ -1387,4 +1378,30 @@ select base85(zeroblob(2000000000));
}
} {/1.*too big.*/}
#----------------------------------------------------------------------------
# As of 2025-11-17, the default mode is:
#
# qbox --screenwidth auto --linelimit 5 --charlimit 300 --textjsonb on
#
do_test shell1-12.1 {
catchcmd :memory: {.mode tty
.print
SELECT jsonb(1234) AS x;}
} {0 {
┌───────────────┐
│ x │
├───────────────┤
│ jsonb('1234') │
└───────────────┘}}
do_test shell1-12.2 {
catchcmd :memory: {.mode box --textjsonb on
.print
SELECT jsonb(1234) AS x;}
} {0 {
┌──────┐
│ x │
├──────┤
│ 1234 │
└──────┘}}
finish_test

View File

@@ -44,7 +44,7 @@ do_test shell2-1.1.1 {
# Shell silently ignores extra parameters.
# Ticket [f5cb008a65].
do_test shell2-1.2.1 {
catchcmdex {:memory: "select+3" "select+4"}
catchcmdex {:memory: -list "select+3" "select+4"}
} {0 {3
4
}}
@@ -64,7 +64,7 @@ do_test shell2-1.3 {
UPDATE OR REPLACE t5 SET a = 4 WHERE a = 1;
}
} {1 {Runtime error near line 9: too many levels of trigger recursion}}
} {1 {Error near line 9: too many levels of trigger recursion}}
@@ -75,7 +75,8 @@ do_test shell2-1.3 {
# NB. whitespace is important
do_test shell2-1.4.1 {
forcedelete foo.db
catchcmd "foo.db" {CREATE TABLE foo(a);
catchcmd "foo.db" {.mode batch
CREATE TABLE foo(a);
INSERT INTO foo(a) VALUES(1);
SELECT * FROM foo;}
} {0 1}
@@ -84,7 +85,7 @@ SELECT * FROM foo;}
# NB. whitespace is important
do_test shell2-1.4.2 {
forcedelete foo.db
catchcmd "-echo foo.db" {CREATE TABLE foo(a);
catchcmd "-compat 20250101 -echo foo.db" {CREATE TABLE foo(a);
INSERT INTO foo(a) VALUES(1);
SELECT * FROM foo;}
} {0 {CREATE TABLE foo(a);
@@ -96,7 +97,9 @@ SELECT * FROM foo;
# NB. whitespace is important
do_test shell2-1.4.3 {
forcedelete foo.db
catchcmd "foo.db" {.echo ON
catchcmd "foo.db" {
.mode batch
.echo ON
CREATE TABLE foo(a);
INSERT INTO foo(a) VALUES(1);
SELECT * FROM foo;}
@@ -110,7 +113,9 @@ SELECT * FROM foo;
# NB. whitespace is important
do_test shell2-1.4.4 {
forcedelete foo.db
catchcmd "foo.db" {.echo ON
catchcmd "foo.db" {
.mode batch
.echo ON
CREATE TABLE foo(a);
.echo OFF
INSERT INTO foo(a) VALUES(1);
@@ -124,7 +129,9 @@ SELECT * FROM foo;}
# NB. whitespace is important
do_test shell2-1.4.5 {
forcedelete foo.db
catchcmdex "foo.db" {.echo ON
catchcmdex "foo.db" {
.mode batch
.echo ON
CREATE TABLE foo1(a);
INSERT INTO foo1(a) VALUES(1);
CREATE TABLE foo2(b);
@@ -153,7 +160,9 @@ SELECT * FROM foo1; SELECT * FROM foo2;
# NB. whitespace is important
do_test shell2-1.4.6 {
forcedelete foo.db
catchcmdex "foo.db" {.echo ON
catchcmdex "foo.db" {
.mode batch
.echo ON
.headers ON
CREATE TABLE foo1(a);
INSERT INTO foo1(a) VALUES(1);
@@ -208,6 +217,7 @@ do_test shell2-1.4.9 {
do_test shell2-1.4.9 {
forcedelete clone.db
set res [catchcmd :memory: [string trim {
.mode batch
CREATE TABLE t(id INTEGER PRIMARY KEY AUTOINCREMENT);
INSERT INTO t VALUES (1),(2);
.clone clone.db
@@ -222,6 +232,7 @@ ifcapable vtab {
# See overflow report at https://sqlite.org/forum/forumpost/5d34ce5280
do_test shell2-1.4.10 {
set res [catchcmd :memory: [string trim {
.mode batch
SELECT * FROM generate_series(9223372036854775807,9223372036854775807,1);
SELECT * FROM generate_series(9223372036854775807,9223372036854775807,-1);
SELECT avg(value),min(value),max(value) FROM generate_series(
@@ -248,6 +259,65 @@ do_test shell2-1.4.10 {
0
1
2}}
do_test shell2-1.4.10b {
set res [catchcmd :memory: [string trim {
.mode tty
.print
SELECT * FROM generate_series(9223372036854775807,9223372036854775807,1);
SELECT * FROM generate_series(9223372036854775807,9223372036854775807,-1);
SELECT avg(value),min(value),max(value) FROM generate_series(
-9223372036854775808,9223372036854775807,1085102592571150095);
SELECT * FROM generate_series(-9223372036854775808,9223372036854775807,
9223372036854775807);
SELECT value FROM generate_series(-4611686018427387904,
4611686018427387904, 4611686018427387904) ORDER BY value DESC;
SELECT * FROM generate_series(0,-2,-1);
SELECT * FROM generate_series(0,-2);
SELECT * FROM generate_series(0,2) LIMIT 3;}]]
} {0 {
┌─────────────────────┐
│ value │
├─────────────────────┤
│ 9223372036854775807 │
└─────────────────────┘
┌─────────────────────┐
│ value │
├─────────────────────┤
│ 9223372036854775807 │
└─────────────────────┘
┌────────────┬──────────────────────┬─────────────────────┐
│ avg(value) │ min(value) │ max(value) │
├────────────┼──────────────────────┼─────────────────────┤
│ -0.5 │ -9223372036854775808 │ 9223372036854775807 │
└────────────┴──────────────────────┴─────────────────────┘
┌──────────────────────┐
│ value │
├──────────────────────┤
│ -9223372036854775808 │
│ -1 │
│ 9223372036854775806 │
└──────────────────────┘
┌──────────────────────┐
│ value │
├──────────────────────┤
│ 4611686018427387904 │
│ 0 │
│ -4611686018427387904 │
└──────────────────────┘
┌───────┐
│ value │
├───────┤
│ 0 │
│ -1 │
│ -2 │
└───────┘
┌───────┐
│ value │
├───────┤
│ 0 │
│ 1 │
│ 2 │
└───────┘}}
} ;# ifcapable vtab
ifcapable vtab {
@@ -270,7 +340,7 @@ do_test shell2-1.4.11 {
# Bug from forum post 7cbe081746dd3803
# Keywords as column names were producing an error message.
do_test shell2-1.4.12 {
set res [catchcmd :memory: [string trim {
set res [catchcmd :memory: [string trim {.mode batch
CREATE TABLE "group"("order" text);
INSERT INTO "group" VALUES ('ABC');
.sha3sum}]]

View File

@@ -109,7 +109,7 @@ do_test shell3-2.7 {
# Run combinations of odd identifiers, comments, semicolon placement
do_test shell3-3.1 {
forcedelete foo.db
set rc [ catchcmd "foo.db" {CREATE TABLE t1("
set rc [ catchcmd "foo.db -compat 20250101" {CREATE TABLE t1("
a--.
" --x
); CREATE TABLE t2("a[""b""]");

View File

@@ -136,15 +136,15 @@ SELECT * FROM t1;}
do_test shell4-3.1 {
set fd [open t1.txt wb]
puts $fd "SELECT 'squirrel';"
puts $fd ".mode list\nSELECT 'squirrel';"
close $fd
exec $::CLI_ONLY :memory: --interactive ".read t1.txt"
exec $::CLI_ONLY --noinit :memory: --interactive ".read t1.txt"
} {squirrel}
do_test_with_ansi_output shell4-3.2 {
set fd [open t1.txt wb]
puts $fd "SELECT 'pound: \302\243';"
puts $fd ".mode list\nSELECT 'pound: \302\243';"
close $fd
exec $::CLI_ONLY :memory: --interactive ".read t1.txt"
exec $::CLI_ONLY --noinit :memory: --interactive ".read t1.txt"
} {pound: £}
do_test shell4-4.1 {

View File

@@ -40,7 +40,8 @@ do_test shell5-1.1.2 {
do_test shell5-1.1.3 {
# too many arguments
catchcmd "test.db" ".import FOO BAR BAD"
} {/1 .ERROR: extra argument.*/}
} {1 {line 1: .import FOO BAR BAD
line 1: ^--- unknown argument}}
# .separator STRING Change separator used by output mode and .import
do_test shell5-1.2.1 {
@@ -59,13 +60,13 @@ do_test shell5-1.2.4 {
# column separator should default to "|"
do_test shell5-1.3.1.1 {
set res [catchcmd "test.db" ".show"]
set res [catchcmd "test.db" ".mode list\n.show"]
list [regexp {colseparator: \"\|\"} $res]
} {1}
# row separator should default to "\n"
do_test shell5-1.3.1.2 {
set res [catchcmd "test.db" ".show"]
set res [catchcmd "test.db" ".mode list\n.show"]
list [regexp {rowseparator: \"\\n\"} $res]
} {1}
@@ -97,7 +98,9 @@ do_test shell5-1.4.2 {
forcedelete shell5.csv
set in [open shell5.csv w]
close $in
set res [catchcmd ":memory:" {ATTACH 'test.db' AS test;
set res [catchcmd ":memory:" {
.mode list
ATTACH 'test.db' AS test;
.import -schema test shell5.csv t1
SELECT COUNT(*) FROM test.t1;}]
} {0 0}
@@ -116,7 +119,7 @@ do_test shell5-1.4.4 {
set in [open shell5.csv w]
puts $in "1|2|3"
close $in
set res [catchcmd ":memory:" {ATTACH 'test.db' AS test;
set res [catchcmd ":memory: --list" {ATTACH 'test.db' AS test;
.import --schema test shell5.csv t1}]
} {1 {shell5.csv:1: expected 2 columns but found 3 - extras ignored}}
@@ -125,7 +128,7 @@ do_test shell5-1.4.5 {
set in [open shell5.csv w]
puts $in "1|2"
close $in
set res [catchcmd "test.db" {DELETE FROM t1;
set res [catchcmd "test.db -list" {DELETE FROM t1;
.import shell5.csv t1
SELECT COUNT(*) FROM t1;}]
} {0 1}
@@ -138,7 +141,9 @@ do_test shell5-1.4.6 {
puts $in "2|3"
puts $in "3|4"
close $in
set res [catchcmd ":memory:" {ATTACH 'test.db' AS test;
set res [catchcmd ":memory:" {
.mode list
ATTACH 'test.db' AS test;
.import -schema test shell5.csv t1
SELECT COUNT(*) FROM test.t1;}]
} {0 3}
@@ -148,7 +153,9 @@ do_test shell5-1.4.7 {
set in [open shell5.csv w]
puts $in "4,5"
close $in
set res [catchcmd ":memory:" {ATTACH 'test.db' AS test;
set res [catchcmd ":memory:" {
.mode list
ATTACH 'test.db' AS test;
.separator ,
.import --schema test shell5.csv t1
SELECT COUNT(*) FROM test.t1;}]
@@ -159,12 +166,13 @@ do_test shell5-1.4.8.1 {
set in [open shell5.csv w]
puts $in "5|Now is the time for all good men to come to the aid of their country."
close $in
set res [catchcmd "test.db" {.import shell5.csv t1
set res [catchcmd "test.db" {.mode list
.import shell5.csv t1
SELECT COUNT(*) FROM t1;}]
} {0 5}
do_test shell5-1.4.8.2 {
catchcmd "test.db" {SELECT b FROM t1 WHERE a='5';}
catchcmd "test.db -list" {SELECT b FROM t1 WHERE a='5';}
} {0 {Now is the time for all good men to come to the aid of their country.}}
# import file with 1 row, 2 columns, quoted text data
@@ -174,12 +182,12 @@ do_test shell5-1.4.9.1 {
set in [open shell5.csv w]
puts $in "6|'Now is the time for all good men to come to the aid of their country.'"
close $in
set res [catchcmd "test.db" {.import shell5.csv t1
set res [catchcmd "test.db -list" {.import shell5.csv t1
SELECT COUNT(*) FROM t1;}]
} {0 6}
do_test shell5-1.4.9.2 {
catchcmd "test.db" {SELECT b FROM t1 WHERE a='6';}
catchcmd "test.db -list" {SELECT b FROM t1 WHERE a='6';}
} {0 {'Now is the time for all good men to come to the aid of their country.'}}
# import file with 1 row, 2 columns, quoted text data
@@ -187,12 +195,12 @@ do_test shell5-1.4.10.1 {
set in [open shell5.csv w]
puts $in "7|\"Now is the time for all good men to come to the aid of their country.\""
close $in
set res [catchcmd "test.db" {.import shell5.csv t1
set res [catchcmd "test.db -list" {.import shell5.csv t1
SELECT COUNT(*) FROM t1;}]
} {0 7}
do_test shell5-1.4.10.2 {
catchcmd "test.db" {SELECT b FROM t1 WHERE a='7';}
catchcmd "test.db -list" {SELECT b FROM t1 WHERE a='7';}
} {0 {Now is the time for all good men to come to the aid of their country.}}
# import file with 2 rows, 2 columns and an initial BOM
@@ -203,7 +211,7 @@ do_test shell5-1.4.11 {
puts $in "2|3"
puts $in "4|5"
close $in
set res [catchcmd "test.db" {CREATE TABLE t2(x INT, y INT);
set res [catchcmd "test.db -list" {CREATE TABLE t2(x INT, y INT);
.import shell5.csv t2
.mode quote
.header on
@@ -218,7 +226,7 @@ do_test shell5-1.4.12 {
puts $in "\xef\xbb\xbf\"two\"|3"
puts $in "4|5"
close $in
set res [catchcmd "test.db" {DELETE FROM t2;
set res [catchcmd "test.db -list" {DELETE FROM t2;
.import shell5.csv t2
.mode quote
.header on
@@ -232,7 +240,7 @@ do_test shell5-1.5.1 {
set in [open shell5.csv w]
puts $in "8|$str"
close $in
set res [catchcmd "test.db" {.import shell5.csv t1
set res [catchcmd "test.db -list" {.import shell5.csv t1
SELECT length(b) FROM t1 WHERE a='8';}]
} {0 999}
@@ -252,7 +260,7 @@ do_test shell5-1.6.1 {
set in [open shell5.csv w]
puts $in $data
close $in
set res [catchcmd "test.db" {DROP TABLE IF EXISTS t2;
set res [catchcmd "test.db -list" {DROP TABLE IF EXISTS t2;
.import shell5.csv t2
SELECT COUNT(*) FROM t2;}]
} {0 1}
@@ -576,8 +584,8 @@ do_test_with_ansi_output shell5-6.2 {
catchcmd test.db {.import -csv shell5.csv t1
.mode line
SELECT * FROM t1;}
} {0 { 1 = あい
2 = うえお}}
} {0 {1 = あい
2 = うえお}}
# 2024-03-11 https://sqlite.org/forum/forumpost/ca014d7358
# Import into a table that contains computed columns.
@@ -588,7 +596,7 @@ do_test shell5-7.1 {
puts $out {aaa|bbb}
close $out
forcedelete test.db
catchcmd :memory: {CREATE TABLE t1(a TEXT, b TEXT, c AS (a||b));
catchcmd ":memory: -list" {CREATE TABLE t1(a TEXT, b TEXT, c AS (a||b));
.import shell5.csv t1
SELECT * FROM t1;}
} {0 aaa|bbb|aaabbb}

View File

@@ -36,7 +36,7 @@ do_execsql_test shellA-1.0 {
# and that our calls to the CLI are working.
#
do_test_with_ansi_output shellA-1.2 {
exec {*}$CLI test.db {.mode box --escape symbol} {SELECT * FROM t1;}
exec {*}$CLI -noinit test.db {.mode box -quote off --escape symbol} {SELECT * FROM t1;}
} {
┌───┬──────────────────────────┐
│ a │ x │
@@ -63,33 +63,33 @@ do_test_with_ansi_output shellA-1.2 {
# ".mode list"
#
do_test shellA-1.3 {
exec {*}$CLI test.db {SELECT x FROM t1 WHERE a=2;}
exec {*}$CLI -noinit -list test.db {SELECT x FROM t1 WHERE a=2;}
} {
^[[31mVT-100 codes^[[0m
}
do_test_with_ansi_output shellA-1.4 {
exec {*}$CLI test.db --escape symbol {SELECT x FROM t1 WHERE a=2;}
exec {*}$CLI -noinit -list test.db --escape symbol {SELECT x FROM t1 WHERE a=2;}
} {
␛[31mVT-100 codes␛[0m
}
do_test shellA-1.5 {
exec {*}$CLI test.db --escape ascii {SELECT x FROM t1 WHERE a=2;}
exec {*}$CLI -noinit -list test.db --escape ascii {SELECT x FROM t1 WHERE a=2;}
} {
^[[31mVT-100 codes^[[0m
}
do_test_with_ansi_output shellA-1.6 {
exec {*}$CLI test.db {.mode list --escape symbol} {SELECT x FROM t1 WHERE a=2;}
exec {*}$CLI -noinit test.db {.mode list --escape symbol} {SELECT x FROM t1 WHERE a=2;}
} {
␛[31mVT-100 codes␛[0m
}
do_test shellA-1.7 {
exec {*}$CLI test.db {.mode list --escape ascii} {SELECT x FROM t1 WHERE a=2;}
exec {*}$CLI -noinit test.db {.mode list --escape ascii} {SELECT x FROM t1 WHERE a=2;}
} {
^[[31mVT-100 codes^[[0m
}
do_test shellA-1.8 {
file delete -force out.txt
exec {*}$CLI test.db {.mode list --escape off} {SELECT x FROM t1 WHERE a=7;} \
exec {*}$CLI -noinit test.db {.mode list --escape off} {SELECT x FROM t1 WHERE a=7;} \
>out.txt
set fd [open out.txt rb]
set res [read $fd]
@@ -98,44 +98,45 @@ do_test shellA-1.8 {
} "carriage\rreturn"
do_test shellA-1.9 {
set rc [catch {
exec {*}$CLI test.db {.mode test --escape xyz}
exec {*}$CLI -noinit test.db {.mode test --escape xyz}
} msg]
lappend rc $msg
} {1 {unknown control character escape mode "xyz" - choices: ascii symbol off}}
} {1 {argv[3]: .mode test --escape xyz
argv[3]: ^--- unknown mode
argv[3]: Use ".help .mode" for more info}}
do_test shellA-1.10 {
set rc [catch {
exec {*}$CLI --escape abc test.db .q
exec {*}$CLI --noinit --escape abc test.db .q
} msg]
lappend rc $msg
} {1 {unknown control character escape mode "abc" - choices: ascii symbol off}}
} {1 {unknown control character escape mode "abc" - choices: auto off ascii symbol}}
# ".mode quote"
#
do_test shellA-2.1 {
exec {*}$CLI test.db --quote {SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)}
exec {*}$CLI -noinit test.db --quote {SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)}
} {
1,'line with '' single quote'
2,unistr('\u001b[31mVT-100 codes\u001b[0m')
6,'new
line'
6,unistr('new\u000aline')
7,unistr('carriage\u000dreturn')
8,'last line'
}
do_test shellA-2.2 {
exec {*}$CLI test.db --quote {.mode}
} {current output mode: quote --escape ascii}
exec {*}$CLI -noinit test.db --quote {.mode -v}
} {/*current output mode: quote* --escape auto*/}
do_test shellA-2.3 {
exec {*}$CLI test.db --quote --escape SYMBOL {.mode}
exec {*}$CLI -noinit -compat 20250101 test.db --quote --escape SYMBOL {.mode}
} {current output mode: quote --escape symbol}
do_test shellA-2.4 {
exec {*}$CLI test.db --quote --escape OFF {.mode}
exec {*}$CLI -noinit -compat 20250101 test.db --quote --escape OFF {.mode}
} {current output mode: quote --escape off}
# ".mode line"
#
do_test_with_ansi_output shellA-3.1 {
exec {*}$CLI test.db --line --escape symbol \
exec {*}$CLI -noinit test.db --line --escape symbol \
{SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)}
} {
a = 1
@@ -155,7 +156,7 @@ line
x = last line
}
do_test shellA-3.2 {
exec {*}$CLI test.db --line --escape ascii \
exec {*}$CLI -noinit test.db --line --escape ascii \
{SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)}
} {
a = 1
@@ -178,7 +179,7 @@ line
# ".mode box"
#
do_test_with_ansi_output shellA-4.1 {
exec {*}$CLI test.db --box --escape ascii \
exec {*}$CLI -noinit test.db --box --escape ascii \
{SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)}
} {
┌───┬──────────────────────────┐
@@ -197,20 +198,15 @@ do_test_with_ansi_output shellA-4.1 {
└───┴──────────────────────────┘
}
do_test_with_ansi_output shellA-4.2 {
exec {*}$CLI test.db {.mode qbox} {SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)}
exec {*}$CLI -noinit test.db {.mode qbox} {SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)}
} {
┌───┬───────────────────────────────────────────┐
│ a │ x │
├───┼───────────────────────────────────────────┤
│ 1 │ 'line with '' single quote' │
├───┼───────────────────────────────────────────┤
│ 2 │ unistr('\u001b[31mVT-100 codes\u001b[0m') │
├───┼───────────────────────────────────────────┤
│ 6 │ 'new │
│ │ line' │
├───┼───────────────────────────────────────────┤
│ 6 │ unistr('new\u000aline') │
│ 7 │ unistr('carriage\u000dreturn') │
├───┼───────────────────────────────────────────┤
│ 8 │ 'last line' │
└───┴───────────────────────────────────────────┘
}
@@ -218,7 +214,7 @@ do_test_with_ansi_output shellA-4.2 {
# ".mode insert"
#
do_test shellA-5.1 {
exec {*}$CLI test.db {.mode insert t1 --escape ascii} \
exec {*}$CLI -noinit test.db {.mode insert t1 --escape ascii} \
{SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)}
} {
INSERT INTO t1 VALUES(1,'line with '' single quote');
@@ -228,7 +224,7 @@ INSERT INTO t1 VALUES(7,unistr('carriage\u000dreturn'));
INSERT INTO t1 VALUES(8,'last line');
}
do_test shellA-5.2 {
exec {*}$CLI test.db {.mode insert t1 --escape symbol} \
exec {*}$CLI -noinit test.db {.mode insert t1 --escape symbol} \
{SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)}
} {
INSERT INTO t1 VALUES(1,'line with '' single quote');
@@ -239,7 +235,7 @@ INSERT INTO t1 VALUES(8,'last line');
}
do_test shellA-5.3 {
file delete -force out.txt
exec {*}$CLI test.db {.mode insert t1 --escape off} \
exec {*}$CLI -noinit test.db {.mode insert t1 --escape off} \
{SELECT a, x FROM t1 WHERE a IN (1,2,6,7,8)} >out.txt
set fd [open out.txt rb]
set res [read $fd]

44
test/shellB.test Normal file
View File

@@ -0,0 +1,44 @@
# 2025-11-12
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
# TESTRUNNER: shell
#
# Test cases for the command-line shell using the new ".output memory"
# feature.
#
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set CLI [test_cli_invocation]
# Run an instance of the CLI on the file $name.
# Capture the number of test cases and the number of
# errors and increment the counts.
#
proc do_clitest {name} {
set mapping [list <NAME> $::testdir/$name <CLI> $::CLI]
set script [string map $mapping {
catch {exec <CLI> :memory: ".read <NAME>" 2>@stdout} res
set ntest 0
set nerr 999
regexp {.*(\d+) tests? run with (\d+) errors?} $res all ntest nerr
set_test_counter count [expr {[set_test_counter count]+$ntest-1}]
set_test_counter errors [expr {[set_test_counter errors]+$nerr}]
if {$nerr==0} {set res "error count: 0"}
set res
}]
# puts $script
do_test shellB-$name $script {error count: 0}
}
do_clitest modeA.clitest
finish_test

View File

@@ -42,7 +42,7 @@ do_test tcl-1.1.1 {
do_test tcl-1.2 {
set v [catch {db bogus} msg]
lappend v $msg
} {1 {bad option "bogus": must be authorizer, backup, bind_fallback, busy, cache, changes, close, collate, collation_needed, commit_hook, complete, config, copy, deserialize, enable_load_extension, errorcode, erroroffset, eval, exists, function, incrblob, interrupt, last_insert_rowid, nullvalue, onecolumn, preupdate, profile, progress, rekey, restore, rollback_hook, serialize, status, timeout, total_changes, trace, trace_v2, transaction, unlock_notify, update_hook, version, or wal_hook}}
} {1 {bad option "bogus": must be authorizer, backup, bind_fallback, busy, cache, changes, close, collate, collation_needed, commit_hook, complete, config, copy, deserialize, enable_load_extension, errorcode, erroroffset, eval, exists, format, function, incrblob, interrupt, last_insert_rowid, nullvalue, onecolumn, preupdate, profile, progress, rekey, restore, rollback_hook, serialize, status, timeout, total_changes, trace, trace_v2, transaction, unlock_notify, update_hook, version, or wal_hook}}
do_test tcl-1.2.1 {
set v [catch {db cache bogus} msg]
lappend v $msg

View File

@@ -364,6 +364,7 @@ set estwork(shell6.test) 3
set estwork(shell8.test) 104
set estwork(shell9.test) 3
set estwork(shellA.test) 2
set estwork(shellB.test) 2
set estwork(shmlock.test) 27
set estwork(sidedelete.test) 10
set estwork(skipscan1.test) 7

106
tool/mkcombo.tcl Normal file
View File

@@ -0,0 +1,106 @@
#!/usr/bin/tclsh
#
# Use this script to combine multiple source code files into a single
# file. Example:
#
# tclsh mkcombo.tcl file1.c file2.c file3.c -o file123.c
#
set help {Usage: tclsh mkcombo.tcl [OPTIONS] [FILELIST]
where OPTIONS is zero or more of the following with these effects:
--linemacros=? => Emit #line directives into output or not. (? = 1 or 0)
--o FILE => write to alternative output file named FILE
--help => See this.
}
set linemacros 0
set fname {}
set src [list]
for {set i 0} {$i<[llength $argv]} {incr i} {
set x [lindex $argv $i]
if {[regexp {^-?-linemacros(?:=([01]))?$} $x ma ulm]} {
if {$ulm == ""} {set ulm 1}
set linemacros $ulm
} elseif {[regexp {^-o$} $x]} {
incr i
if {$i==[llength $argv]} {
error "No argument following $x"
}
set fname [lindex $argv $i]
} elseif {[regexp {^-?-((help)|\?)$} $x]} {
puts $help
exit 0
} elseif {[regexp {^-?-} $x]} {
error "unknown command-line option: $x"
} else {
lappend src $x
}
}
# Open the output file and write a header comment at the beginning
# of the file.
#
if {![info exists fname]} {
set fname sqlite3.c
if {$enable_recover} { set fname sqlite3r.c }
}
set out [open $fname wb]
# Return a string consisting of N "*" characters.
#
proc star N {
set r {}
for {set i 0} {$i<$N} {incr i} {append r *}
return $r
}
# Force the output to use unix line endings, even on Windows.
fconfigure $out -translation binary
puts $out "/[star 78]"
puts $out {** The following is an amalgamation of these source code files:}
puts $out {**}
foreach s $src {
regsub {^.*/(src|ext)/} $s {\1/} s2
puts $out "** $s2"
}
puts $out {**}
puts $out "[star 78]/"
# Insert a comment into the code
#
proc section_comment {text} {
global out s78
set n [string length $text]
set nstar [expr {60 - $n}]
puts $out "/************** $text [star $nstar]/"
}
# Read the source file named $filename and write it into the
# sqlite3.c output file. The only transformation is the trimming
# of EOL whitespace.
#
proc copy_file_verbatim {filename} {
global out
set in [open $filename rb]
set tail [file tail $filename]
section_comment "Begin file $tail"
while {![eof $in]} {
set line [string trimright [gets $in]]
puts $out $line
}
section_comment "End of $tail"
}
set taillist ""
foreach file $src {
copy_file_verbatim $file
append taillist ", [file tail $file]"
}
set taillist "End of the amalgamation of [string range $taillist 2 end]"
set n [string length $taillist]
set ns [expr {(75-$n)/2}]
if {$ns<3} {set ns 3}
puts $out "/[star $ns] $taillist [star $ns]/"
close $out

View File

@@ -13,28 +13,90 @@ set topdir [file dir [file dir [file normal $argv0]]]
set out stdout
fconfigure stdout -translation binary
if {[lindex $argv 0]!=""} {
set out [open [lindex $argv 0] wb]
set output_file [lindex $argv 0]
file delete -force $output_file
set out [open $output_file wb]
} else {
set output_file {}
}
puts $out {/* DO NOT EDIT!
** This file is automatically generated by the script in the canonical
** SQLite source tree at tool/mkshellc.tcl. That script combines source
** code from various constituent source files of SQLite into this single
** "shell.c" file used to implement the SQLite command-line shell.
**
** Most of the code found below comes from the "src/shell.c.in" file in
** the canonical SQLite source tree. That main file contains "INCLUDE"
** lines that specify other files in the canonical source tree that are
** inserted to getnerate this complete program source file.
**
** The code from multiple files is combined into this single "shell.c"
** source file to help make the command-line program easier to compile.
**
** To modify this program, get a copy of the canonical SQLite source tree,
** edit the src/shell.c.in" and/or some of the other files that are included
** by "src/shell.c.in", then rerun the tool/mkshellc.tcl script.
*/}
############################## FIRST PASS ################################
# Read through the shell.c.in source file to gather information. Do not
# yet generate any code
#
set in [open $topdir/src/shell.c.in]
fconfigure $in -translation binary
set allSource(src/shell.c.in) 1
set inUsage 0
set dotcmd {}
while {1} {
set lx [gets $in]
if {[eof $in]} break;
if {[regexp {^INCLUDE } $lx]} {
set cfile [lindex $lx 1]
if {[string match ../* $cfile]} {
set xfile [string range $cfile 3 end]
} else {
set xfile "src/$cfile"
}
set allSource($xfile) 1
} elseif {[regexp {^\*\* USAGE:\s+([^\s]+)} $lx all dotcmd]} {
set inUsage 1
set details [string trim [string range $lx 2 end]]
} elseif {$inUsage} {
if {![regexp {^\*\*} $lx] || [regexp { DOT-COMMAND: } $lx]} {
set inUsage 0
set Usage($dotcmd) [string trim $details]
} else {
append details \n
append details [string range [string trimright $lx] 3 end]
}
}
}
# Generate dot-command usage text based on the data accumulated in
# the Usage() array.
#
proc generate_usage {out} {
global Usage
puts $out "/**************************************************************"
puts $out "** \"Usage\" help text automatically generated from comments */"
puts $out "static const struct \173"
puts $out " const char *zCmd; /* Name of the dot-command */"
puts $out " const char *zUsage; /* Documentation */"
puts $out "\175 aUsage\[\] = \173"
foreach dotcmd [array names Usage] {
puts $out " \173 \"$dotcmd\","
foreach line [split $Usage($dotcmd) \n] {
set x [string map [list \\ \\\\ \" \\\"] $line]
puts $out "\"$x\\n\""
}
puts $out " \175,"
}
puts $out "\175;"
}
# generate_usage stderr
###### SECOND PASS #######
# Make a second pass through shell.c.in to generate the the final
# output, based on data gathered during the first pass.
#
puts $out {/*
** This is the amalgamated source code to the "sqlite3" or "sqlite3.exe"
** command-line shell (CLI) for SQLite. This file is automatically
** generated by the tool/mkshellc.tcl script from the following sources:
**}
foreach fn [lsort [array names allSource]] {
puts $out "** $fn"
}
puts $out {**
** To modify this program, get a copy of the canonical SQLite source tree,
** edit the src/shell.c.in file and/or some of the other files that are
** listed above, then rerun the rerun "make shell.c".
*/}
seek $in 0 start
puts $out "/************************* Begin src/shell.c.in ******************/"
proc omit_redundant_typedefs {line} {
global typedef_seen
if {[regexp {^typedef .* ([a-zA-Z0-9_]+);} $line all typename]} {
@@ -53,9 +115,14 @@ while {1} {
incr iLine
if {[regexp {^INCLUDE } $lx]} {
set cfile [lindex $lx 1]
puts $out "/************************* Begin $cfile ******************/"
# puts $out "#line 1 \"$cfile\""
set in2 [open $topdir/src/$cfile]
if {[string match ../* $cfile]} {
set xfile [string range $cfile 3 end]
} else {
set xfile "src/$cfile"
}
puts $out "/************************* Begin $xfile ******************/"
# puts $out "#line 1 \"$xfile\""
set in2 [open $topdir/$xfile]
fconfigure $in2 -translation binary
while {![eof $in2]} {
set lx [omit_redundant_typedefs [gets $in2]]
@@ -69,11 +136,23 @@ while {1} {
puts $out $lx
}
close $in2
puts $out "/************************* End $cfile ********************/"
puts $out "/************************* End $xfile ********************/"
# puts $out "#line [expr $iLine+1] \"shell.c.in\""
continue
} elseif {[regexp {^INSERT-USAGE-TEXT-HERE} $lx]} {
generate_usage $out
} else {
puts $out $lx
}
puts $out $lx
}
puts $out "/************************* End src/shell.c.in ******************/"
close $in
close $out
# Try to disable write permissions on the generate file, to prevent
# accidentally editing the generated file rather than source files.
#
if {$output_file ne ""} {
catch {file attributes $output_file -readonly 1}
catch {file attributes $output_file -permissions -w}
catch {exec chmod -w $output_file}
}

View File

@@ -33,7 +33,7 @@ INCLUDE $ROOT/ext/misc/appendvfs.c
INCLUDE $ROOT/ext/misc/zipfile.c
INCLUDE $ROOT/ext/misc/sqlar.c
#endif
INCLUDE $ROOT/src/tclsqlite.c
INCLUDE tclsqlite-ex.c
const char *sqlite3_tclapp_init_proc(Tcl_Interp *interp){
(void)interp;