From c02f8fdfe2b7c84bb49f2f0d496f4d4ecec96f53 Mon Sep 17 00:00:00 2001 From: drh <> Date: Tue, 7 Oct 2025 20:11:19 +0000 Subject: [PATCH] Initial prototype spec for the SQLite Query Result Formatter. FossilOrigin-Name: 87b5e41b999877a1d0b4bb049642909c1698dc1b24e4e45631eb13d02818f0ec --- ext/misc/resfmt.md | 247 +++++++++++++++++++++++++++++++++++++++++++++ manifest | 14 ++- manifest.tags | 4 +- manifest.uuid | 2 +- 4 files changed, 259 insertions(+), 8 deletions(-) create mode 100644 ext/misc/resfmt.md diff --git a/ext/misc/resfmt.md b/ext/misc/resfmt.md new file mode 100644 index 0000000000..edcdbb2968 --- /dev/null +++ b/ext/misc/resfmt.md @@ -0,0 +1,247 @@ +# SQLite Result Formatting Subsystem + +The "resfmt" subsystem is a set of C-language subroutines that work +together to format the output from an SQLite query. The output format +is configurable. The application can request CSV, or a table, or +any of several other formats, according to needs. + +## 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: + +> ~~~ +ResfmtSpec spec; /* Formatter spec */ +Resfmt *pFmt; /* Formatter object */ +int errCode; /* Error code */ +char *zErrMsg; /* Text error message (optional) */ + +memset(&spec, 0, sizeof(spec)); +// Additional spec initialization here +pFmt = sqlite3_resfmt_begin(pStmt, &spec); +while( SQLITE_ROW==sqlite3_step(pStmt) ){ + sqlite3_resfmt_row(pFmt, pStmt); +} +sqlite3_resfmt_finish(pFmt, &errCode, &zErrMsg); +// Do something with errcode and zErrMsg +sqlite3_free(zErrMsg); +~~~ + +The `ResfmtSpec` structure (defined below) describes the desired +output format. The `pFmt` variable is a pointer to an opaque Resfmt +object that maintains the statement of the formatter. +The pFmt object is used as the first parameter to two other +routines, `sqlite3_resfmt_row()` and `sqlite3_resfmt_finish()`, and +is not usable for any other purpose by the caller. The +`sqlite3_resfmt_finish()` interface serves as a destructor for +the pFmt object. + +## 2.0 The `ResfmtSpec` object + +A pointer to an instance of the following structure is the second +parameter to the `sqlite3_resfmt_begin()` interface. This structure +defines how the rules of the statement are to be formatted. + +> ~~~ +typedef struct ResfmtSpec ResfmtSpec; +struct ResfmtSpec { + int iVersion; /* Version number of this structure */ + int eFormat; /* Output format */ + unsigned char bShowCNames; /* True to show column names */ + unsigned char eEscMode; /* How to deal with control characters */ + unsigned char bQuote; /* Quote output values as SQL literals */ + unsigned char bWordWrap; /* Try to wrap on word boundaries */ + int mxWidth; /* Maximum column width in columnar modes */ + const char *zColumnSep; /* Alternative column separator */ + const char *zRowSep; /* Alternative row separator */ + const char *zTableName; /* Output table name */ + int nWidth; /* Number of column width parameters */ + int *aWidth; /* Column widths */ + ssize_t (*pWrite)(void*,const unsigned char*,ssize_t); /* Write callback */ + void *pWriteArg; /* First argument to write callback */ + char **pzOutput; /* Storage location for output string */ + /* Additional fields may be added in the future */ +}; +~~~ + +The ResfmtSpec object must be fully initialized prior +to calling `sqlite3_resfmt_begin()` and its value must not change +by the application until after the corresponding call to +`sqlite3_resfmt_finish()`. Note that the result formatter itself +might change values in the ResfmtSpec object as it runs. +But the application should not try to change or use any fields of +the ResfmtSpec object while the formatter is running. + +### 2.1 Structure Version Number + +The ResfmtSpec.iVersion field must be 1. Future enhancements to this +subsystem might add new fields onto the bottom of the ResfmtSpec 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 + +The formatted output can either be sent to a callback function +or accumulated into an output buffer in memory obtained +from system malloc(). If the ResfmtSpec.pWrite column is not NULL, +then that function is invoked (using ResfmtSpec.pWriteArg as its +first argument) to transmit the formatted output. Or, if +ResfmtSpec.pzOutput points to a pointer to a character, then that +pointer is made to point to memory obtained from malloc() that +contains the complete text of the formatted output. + +When `sqlite3_resfmt_begin()` is called, +one of ResfmtSpec.pWrite and ResfmtSpec.pzOutput must be non-NULL +and the other must be NULL. + +Output might be generated row by row, on each call to +`sqlite3_resfmt_row()` or it might be written all at once +on the final call to `sqlite3_resfmt_finish()`, depending +on the output format. + +### 2.3 Output Format + +The ResfmtSpec.eFormat field is an integer code that defines the +specific output format that will be generated. See the +output format describes below for additional detail. + +### 2.4 Show Column Names + +The ResfmtSpec.bShowCNames field is a boolean. If true, then column +names appear in the output. If false, column names are omitted. + +### 2.5 Control Character Escapes + +The ResfmtSpec.eEscMode determines how ASCII control characters are +formatted in the output. If this value is zero, 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". This is the default. +If eEscMode is one, then control characters in the range of U+0001 +through U+001f are mapped into U+2401 through U+241f, respectively. +If eEscMode is 2, then control characters are output directly, with +no translation. + +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 Word Wrapping In Columnar Modes + +For output modes that attempt to display equal-width columns, the +ResfmtSpec.bWordWrap boolean determines whether long values are broken +at word boundaries, or at arbitrary characters. The ResfmtSpec.mxWidth +determines the maximum width of an output column. + +### 2.7 Row and Column Separator Strings + +The ResfmtSpec.zColumnSep and ResfmtSpec.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. + +### 2.8 The Output Table Name + +The ResfmtSpec.zTableName value is the name of the output table +when the MODE_Insert output mode is used. + +### 2.9 Column Widths And Alignments + +The ResfmtSpec.aWidth[] array, if specified, is an array of integers +that specify the minimum column width and the alignment for all columns +in columnar output modes. Negative values mean right-justify. The +absolute value is the minimum of the corresponding column. + +The ResfmtSpec.nWidth field is the number of values in the aWidth[] +array. Any column beyond the nWidth-th column are assumed to have +a minimum width of 0. + +## 3.0 The `sqlite3_resfmt_begin()` Interface + +Invoke the `sqlite3_resfmt_begin(P,S)` interface to begin formatting +the output of prepared statement P using format specification S. +This routine returns a pointer to an opaque Resfmt object that is +the current state of the formatter. The `sqlite3_resfmt_finish()` +routine is the destructor for the Resfmt object and must be called +to prevent a memory leak. + +If an out-of-memory fault occurs while allocating space for the +Resfmt object, then `sqlite3_resfmt_begin()` will return a NULL +pointer. The application need not check for this case as the +other routines that use a pointer to the Resfmt object all +interpret a NULL parameter in place of the Resfmt pointer as +a harmless no-op. + +## 4.0 The `sqlite3_resfmt_step()` Interface + +Invoke the `sqlite3_resfmt_step(F,P)` interface for each row +in the prepared statement that is to be output. The prepared +statement pointer P must be the same as the P argument passed +into `sqlite3_resfmt_begin()`, or unpredictable things can happen. + +The formatter might choose to output some content as each row +is processed, or it might accumulate the output and send it all +at once when `sqlite3_resfmt_finish()` is called. This is at +the discretion of the output formatter. Generally, rows are +output one-by-one on each call to `sqlite3_resfmt_row()` when the +output format is such that the row can be computed without knowing +the value of subsequence, such as in CSV output mode, and +the output is accumulated and sent all at once in columnar output +modes where the complete output content is needed to compute column +widths. + +### 5.0 The `sqlite3_resfmt_finish()` Interface + +Invoke the `sqlite3_resfmt_finish(F,C,E)` interface to finish +the formatting. C is an optional pointer to an integer. If C +is not a NULL pointer, then any error code associated with the +formatting operation is written into *C. E is an optional pointer +to a human-readable text error message. If E is not a NULL pointer, +then an error message held in memory obtained from sqlite3_malloc() +is written into *E. It is the responsibility of the calling +application to invoke sqlite3_free() on this error message to +reclaim the space. + +### 6.0 Output Modes + +The result formatter supports a variety of output modes. The +set of supported output modes might increase in future versions. +The following output modes are currently defined: + +> ~~~ +#define MODE_Line 0 /* One column per line. */ +#define MODE_Column 1 /* One record per line in neat columns */ +#define MODE_List 2 /* One record per line with a separator */ +#define MODE_Semi 3 /* Same as MODE_List but append ";" to each line */ +#define MODE_Html 4 /* Generate an XHTML table */ +#define MODE_Insert 5 /* Generate SQL "insert" statements */ +#define MODE_Quote 6 /* Quote values as for SQL */ +#define MODE_Tcl 7 /* Generate ANSI-C or TCL quoted elements */ +#define MODE_Csv 8 /* Quote strings, numbers are plain */ +#define MODE_Explain 9 /* Like MODE_Column, but do not truncate data */ +#define MODE_Ascii 10 /* Use ASCII unit and record separators (0x1F/0x1E) */ +#define MODE_Pretty 11 /* Pretty-print schemas */ +#define MODE_EQP 12 /* Converts EXPLAIN QUERY PLAN output into a graph */ +#define MODE_Json 13 /* Output JSON */ +#define MODE_Markdown 14 /* Markdown formatting */ +#define MODE_Table 15 /* MySQL-style table formatting */ +#define MODE_Box 16 /* Unicode box-drawing characters */ +#define MODE_Count 17 /* Output only a count of the rows of output */ +#define MODE_Off 18 /* No query output shown */ +#define MODE_ScanExp 19 /* Like MODE_Explain, but for ".scanstats vm" */ +#define MODE_Www 20 /* Full web-page output */ +~~~ + +Additional detail about the meaning of each of these output modes +is pending. + +### 7.0 Source Code Files + +The SQLite result formatter is implemented in three source code files: + + * `resfmt.c` → The implementation, written in portable C99 + * `resfmt.h` → A header file defining interfaces + * `resfmt.md` → This documentation, in Markdown + +To use the SQLite result formatter, include the "`resfmt.h`" header file +and link the application against the "`resfmt.c`" source file. diff --git a/manifest b/manifest index 56446e1771..7a54d48d87 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Improve\sthe\sinvariant\schecker\smodule\sso\sthat\sso\sthat\sit\sadded\s"+"\sbefore\n"column\sISNULL"\sin\squeries\swhere\sthe\sbase\squery\scontains\sa\sGROUP\sBY,\sto\nprevent\sthe\sISNULL\sterm\sfrom\sbeing\spushed\sdown\sinto\sthe\ssubquery,\ssince\nthat\scan\scause\sambiguities\sif\scolumn\sis\sUNIQUE. -D 2025-10-07T18:06:05.110 +C Initial\sprototype\sspec\sfor\sthe\sSQLite\sQuery\sResult\sFormatter. +D 2025-10-07T20:11:19.383 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -390,6 +390,7 @@ F ext/misc/qpvtab.c fc189e127f68f791af90a487f4460ec91539a716daf45a0c357e963fd47c F ext/misc/randomjson.c ef835fc64289e76ac4873b85fe12f9463a036168d7683cf2b773e36e6262c4ed F ext/misc/regexp.c 548151f3e57506fda678e6a65e85a763f4eece653287e1ad44e167f9485e0c6b F ext/misc/remember.c add730f0f7e7436cd15ea3fd6a90fd83c3f706ab44169f7f048438b7d6baa69c +F ext/misc/resfmt.md 33cf7edf5860e13e0fc17445e6b53e2f2a009f24d20a1d8d653066bfd1af8616 F ext/misc/rot13.c 51ac5f51e9d5fd811db58a9c23c628ad5f333c173f1fc53c8491a3603d38556c F ext/misc/scrub.c 2a44b0d44c69584c0580ad2553f6290a307a49df4668941d2812135bfb96a946 F ext/misc/series.c cbbec483aa0246661e7656971ce4de8e62ecc3151be94218306206fe4f5b7a9e @@ -2168,8 +2169,11 @@ F tool/version-info.c 33d0390ef484b3b1cb685d59362be891ea162123cea181cb8e6d2cf6dd F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 F tool/warnings.sh 1ad0169b022b280bcaaf94a7fa231591be96b514230ab5c98fbf15cd7df842dd F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 06b4bd2aba22c57f5a5fed606c3bee225dee6fdc13bb16cc58194040ef0d7d85 -R 453915b0b72d47b5291d37cc0a19ae2f +P b4ff920fbeef9a8590219596d73c09976da3da53c08a685be56f6b2cd2cdc70c +R 51927445b0466af2ada0962ee88c55c2 +T *branch * resfmt +T *sym-resfmt * +T -sym-trunk * U drh -Z 1eb68a79b24ce28ad8c3cdcce7ce295e +Z eb3a746c4a23932f9916a7ae201a8054 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.tags b/manifest.tags index bec971799f..1921677300 100644 --- a/manifest.tags +++ b/manifest.tags @@ -1,2 +1,2 @@ -branch trunk -tag trunk +branch resfmt +tag resfmt diff --git a/manifest.uuid b/manifest.uuid index 3cdeabada1..f9d0c62afe 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -b4ff920fbeef9a8590219596d73c09976da3da53c08a685be56f6b2cd2cdc70c +87b5e41b999877a1d0b4bb049642909c1698dc1b24e4e45631eb13d02818f0ec