From: drh <> Date: Sat, 8 Nov 2025 14:04:29 +0000 (+0000) Subject: Separate column width specification from cell alignment. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=22d6a48ba782d3ccad21e5b816671d27f440139a;p=thirdparty%2Fsqlite.git Separate column width specification from cell alignment. FossilOrigin-Name: 5dadfd7f41764ddfed39bb49bbde3a54d8f282bb494c9c2abbba37d0995a7f8e --- diff --git a/ext/qrf/README.md b/ext/qrf/README.md index a379103e14..2b8709174d 100644 --- a/ext/qrf/README.md +++ b/ext/qrf/README.md @@ -1,9 +1,12 @@ # 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. The -output format is configurable. The application can request CSV, -or a table, or any of several other formats, according to needs. +subroutine that formats the output from an SQLite query for display +using a fix-width font, for example on a TTY or 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) @@ -50,7 +53,14 @@ the QRF involves filling out the sqlite3_qrf_spec. 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 as follows: +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; @@ -59,15 +69,21 @@ struct sqlite3_qrf_spec { 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 eDfltAlign; /* Default alignment, no covered by aAlignment */ + unsigned char eTitleAlign; /* Alignment for column headers */ unsigned char bColumnNames; /* True to show column names */ unsigned char bWordWrap; /* Try to wrap on word boundaries */ unsigned char bTextJsonb; /* Render JSONB blobs as JSON text */ short int mxColWidth; /* Maximum width of any individual column */ short int mxTotalWidth; /* Maximum overall table width */ + short int mxRowHeight; /* Maximum number of lines for any row */ int mxLength; /* Maximum content to display per element */ - int nWidth; /* Number of column width parameters */ + int nWidth; /* Number of entries in aWidth[] */ + int nAlignment; /* Number of entries in aAlignment[] short int *aWidth; /* Column widths */ + unsigned char *aAlignment; /* Column alignments */ const char *zColumnSep; /* Alternative column separator */ const char *zRowSep; /* Alternative row separator */ const char *zTableName; /* Output table name */ @@ -81,13 +97,16 @@ struct sqlite3_qrf_spec { }; ~~~ -The `sqlite3_qrf_spec` object must be fully initialized prior -to calling `sqlite3_format_query_result()`. Initialization can be mostly -accomplished using memset() to zero the spec object, as most fields -use a zero value as a reasonable default. However, the iVersion -field must be set to 1 and one of either the pzOutput or xWrite fields -must be initialized to a non-NULL value to tell QRF where to send -its output. +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 @@ -129,17 +148,22 @@ ignored, depending on the value of eStyle. ### 2.4 Show Column Names (bColumnNames) The sqlite3_qrf_spec.bColumnNames field can be either QRF_SW_Auto, -QRF_SW_On, or QRF_SW_Off. +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_SW_On, then column names appear in the output. -If the value is QRF_SW_Off, column names are omitted. If the -value is QRF_SW_Auto, then an appropriate default is chosen. +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) @@ -175,9 +199,11 @@ 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) +### 2.6 Display of TEXT values (eText, eTitle) -The sqlite3_qrf_spec.eText field can have one of the following values: +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 */ @@ -280,7 +306,7 @@ mxLength setting. As for 2025-11-07, the mxLength constraint is not yet implemented. The current behavior is always as if mxLength where zero.* -### 2.9 Word Wrapping In Columnar Modes (mxColWidth, mxTotalWidth, bWordWrap) +### 2.9 Word Wrapping In Columnar Styles (mxColWidth, mxTotalWidth, bWordWrap) When using columnar formatting modes (QRF_STYLE_Box, QRF_STYLE_Column, QRF_STYLE_Markdown, or QRF_STYLE_Table), the formatter attempts to limit @@ -310,47 +336,123 @@ a column in the middle of a word, even when bWordWrap is QRF_SW_On. As for 2025-11-07, the mxTotalWidth constraint is not yet implemented. The current behavior is always as if mxTotalWidth where zero.* - -### 2.10 Column width and alignment (nWidth and aWidth) +### 2.10 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 column widths and alignments +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/alignment -setting for all columns past then end of the aWidth array. +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. In fact, the interface defines several key width values: +column widths. The range if further restricted as follows: > ~~~ -#define QRF_MX_WIDTH (10000) -#define QRF_MN_WIDTH (-10000) -#define QRF_MINUS_ZERO (-32768) /* Special value meaning -0 */ +#define QRF_MAX_WIDTH 10000 /* Maximum column width */ +#define QRF_MIN_WIDTH 0 /* Minimum column width */ ~~~ -A width less then QRF_MN_WIDTH is interpreted as QRF_MN_WIDTH and a width -larger than QRF_MX_WIDTH is interpreted as QRF_MX_WIDTH. Thus the maximum -width of a column is 10000 characters, which is far wider than any -human-readable column should be. +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, and the all rows should be left-justifed. -The special value of QRF_MINUS_ZERO is interpreted as "-0" and work like -zero, except that shorter rows are right-justifed instead of left-justifed. +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.11 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). -Values of aWidth that are not zero (and not QRF_MINUS_ZERO) determine the -width of the corresponding column. Negative values are used for -right-justified columns and positive values are used for left-justified -columns. +*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.11 Row and Column Separator Strings +### 2.12 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 @@ -358,18 +460,18 @@ 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.12 The Output Table Name +### 2.13 The Output Table Name The sqlite3_qrf_spec.zTableName value is the name of the output table when eStyle is QRF_STYLE_Insert. -### 2.13 The Rendering Of NULL +### 2.14 The Rendering Of NULL 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. -### 2.14 Optional Value Rendering Callback +### 2.15 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 diff --git a/ext/qrf/qrf.c b/ext/qrf/qrf.c index 0370ac5d83..c964dcf4c2 100644 --- a/ext/qrf/qrf.c +++ b/ext/qrf/qrf.c @@ -845,7 +845,7 @@ static void qrfRenderValue(Qrf *p, sqlite3_str *pOut, int iCol){ break; } case SQLITE_BLOB: { - if( p->spec.bTextJsonb==QRF_SW_On ){ + if( p->spec.bTextJsonb==QRF_Yes ){ const char *zJson = qrfJsonbToJson(p, iCol); if( zJson ){ qrfEncodeText(p, pOut, zJson); @@ -997,6 +997,7 @@ struct qrfColData { char **az; /* Content of all cells */ int *aiWth; /* Width of each cell */ int *aiCol; /* Width of each column */ + unsigned char *aAlign; /* Alignment for each column */ }; /* @@ -1240,12 +1241,13 @@ static void qrfColumnar(Qrf *p){ /* Initialize the data container */ memset(&data, 0, sizeof(data)); data.nCol = p->nCol; - data.azThis = sqlite3_malloc64( nColumn*(sizeof(char*) + sizeof(int)) ); + data.azThis = sqlite3_malloc64( nColumn*(sizeof(char*) + sizeof(int) + 1) ); if( data.azThis==0 ){ qrfOom(p); return; } data.aiCol = (int*)&data.azThis[nColumn]; + data.aAlign = (unsigned char*)&data.aiCol[nColumn]; qrfColDataEnlarge(&data); if( p->iErr ){ qrfColDataFree(&data); @@ -1253,9 +1255,9 @@ static void qrfColumnar(Qrf *p){ } /* Load the column header names and all cell content into data */ - if( p->spec.bColumnNames==QRF_SW_On ){ + if( p->spec.bColumnNames==QRF_Yes ){ unsigned char saved_eText = p->spec.eText; - p->spec.eText = QRF_TEXT_Off; + p->spec.eText = p->spec.eTitle; for(i=0; ipStmt,i); int nNL = 0; @@ -1298,15 +1300,25 @@ static void qrfColumnar(Qrf *p){ return; } - /* Compute the width of every column */ + /* Compute the width and alignment of every column */ + memset(data.aAlign, p->spec.eDfltAlign, nColumn); + for(i=0; ispec.nAlign; i++){ + data.aAlign[i] = p->spec.aAlign[i]; + } for(i=0; ispec.nWidth ){ w = p->spec.aWidth[i]; - if( w==QRF_MINUS_ZERO ){ + if( w==(-32768) ){ w = 0; + if( p->spec.nAlign>i && (p->spec.aAlign[i] & QRF_ALIGN_HMASK)==0 ){ + data.aAlign[i] |= QRF_ALIGN_Right; + } }else if( w<0 ){ w = -w; + if( p->spec.nAlign>i && (p->spec.aAlign[i] & QRF_ALIGN_HMASK)==0 ){ + data.aAlign[i] |= QRF_ALIGN_Right; + } } } if( w==0 ){ @@ -1362,7 +1374,7 @@ static void qrfColumnar(Qrf *p){ szRowSep = (int)strlen(rowSep); szColSep = (int)strlen(colSep); - bWW = (p->spec.bWordWrap==QRF_SW_On && data.bMultiRow); + bWW = (p->spec.bWordWrap==QRF_Yes && data.bMultiRow); for(i=0; ipOut, nWS/2, ' '); sqlite3_str_append(p->pOut, data.azThis[j], nThis); sqlite3_str_appendchar(p->pOut, nWS - nWS/2, ' '); - }else if( jspec.nWidth && p->spec.aWidth[j]<0 ){ + }else if( (data.aAlign[j] & QRF_ALIGN_HMASK)==QRF_ALIGN_Right){ /* Right justify the text */ sqlite3_str_appendchar(p->pOut, nWS, ' '); sqlite3_str_append(p->pOut, data.azThis[j], nThis); @@ -1400,28 +1412,28 @@ static void qrfColumnar(Qrf *p){ } }while( bMore ); bCenter = 0; - if( i+nColumnspec.eStyle ){ case QRF_STYLE_Table: { - if( (i==0 && p->spec.bColumnNames==QRF_SW_On) || data.bMultiRow ){ + if( (i==0 && p->spec.bColumnNames==QRF_Yes) || data.bMultiRow ){ qrfRowSeparator(p->pOut, &data, '+'); } break; } case QRF_STYLE_Box: { - if( (i==0 && p->spec.bColumnNames==QRF_SW_On) || data.bMultiRow ){ + if( (i==0 && p->spec.bColumnNames==QRF_Yes) || data.bMultiRow ){ qrfBoxSeparator(p->pOut, &data, BOX_123, BOX_1234, BOX_134); } break; } case QRF_STYLE_Markdown: { - if( i==0 && p->spec.bColumnNames==QRF_SW_On ){ + if( i==0 && p->spec.bColumnNames==QRF_Yes ){ qrfRowSeparator(p->pOut, &data, '|'); } break; } case QRF_STYLE_Column: { - if( i==0 && p->spec.bColumnNames==QRF_SW_On ){ + if( i==0 && p->spec.bColumnNames==QRF_Yes ){ for(j=0; jpOut, data.aiCol[j], '-'); if( jspec.zNull==0 ) p->spec.zNull = ""; qrf_reinit: switch( p->spec.eStyle ){ - case QRF_STYLE_Auto: { + case QRF_Auto: { switch( sqlite3_stmt_isexplain(pStmt) ){ case 0: p->spec.eStyle = QRF_STYLE_Box; break; case 1: p->spec.eStyle = QRF_STYLE_Explain; break; @@ -1760,13 +1772,25 @@ qrf_reinit: break; } } - if( p->spec.eEsc==QRF_ESC_Auto ){ + if( p->spec.eEsc==QRF_Auto ){ p->spec.eEsc = QRF_ESC_Ascii; } - if( p->spec.eText==QRF_TEXT_Auto ){ + if( p->spec.eText==QRF_Auto ){ p->spec.eText = QRF_TEXT_Off; } - if( p->spec.eBlob==QRF_BLOB_Auto ){ + if( p->spec.eTitle==QRF_Auto ){ + switch( p->spec.eStyle ){ + case QRF_STYLE_Box: + case QRF_STYLE_Column: + case QRF_STYLE_Table: + p->spec.eTitle = QRF_TEXT_Off; + break; + default: + p->spec.eTitle = p->spec.eText; + break; + } + } + if( p->spec.eBlob==QRF_Auto ){ switch( p->spec.eText ){ case QRF_TEXT_Sql: p->spec.eBlob = QRF_BLOB_Sql; break; case QRF_TEXT_Csv: p->spec.eBlob = QRF_BLOB_Tcl; break; @@ -1775,25 +1799,25 @@ qrf_reinit: default: p->spec.eBlob = QRF_BLOB_Text; break; } } - if( p->spec.bColumnNames==QRF_SW_Auto ){ + if( p->spec.bColumnNames==QRF_Auto ){ switch( p->spec.eStyle ){ case QRF_STYLE_Box: case QRF_STYLE_Csv: case QRF_STYLE_Column: case QRF_STYLE_Table: case QRF_STYLE_Markdown: - p->spec.bColumnNames = QRF_SW_On; + p->spec.bColumnNames = QRF_Yes; break; default: - p->spec.bColumnNames = QRF_SW_Off; + p->spec.bColumnNames = QRF_No; break; } } - if( p->spec.bWordWrap==QRF_SW_Auto ){ - p->spec.bWordWrap = QRF_SW_On; + if( p->spec.bWordWrap==QRF_Auto ){ + p->spec.bWordWrap = QRF_Yes; } - if( p->spec.bTextJsonb==QRF_SW_Auto ){ - p->spec.bTextJsonb = QRF_SW_Off; + if( p->spec.bTextJsonb==QRF_Auto ){ + p->spec.bTextJsonb = QRF_No; } if( p->spec.zColumnSep==0 ) p->spec.zColumnSep = ","; if( p->spec.zRowSep==0 ) p->spec.zRowSep = "\n"; @@ -1869,7 +1893,7 @@ static void qrfOneSimpleRow(Qrf *p){ break; } case QRF_STYLE_Html: { - if( p->nRow==0 && p->spec.bColumnNames==QRF_SW_On ){ + if( p->nRow==0 && p->spec.bColumnNames==QRF_Yes ){ sqlite3_str_append(p->pOut, "", 4); for(i=0; inCol; i++){ const char *zCName = sqlite3_column_name(p->pStmt, i); @@ -1893,7 +1917,7 @@ static void qrfOneSimpleRow(Qrf *p){ }else{ sqlite3_str_appendf(p->pOut,"INSERT INTO %s",p->spec.zTableName); } - if( p->spec.bColumnNames==QRF_SW_On ){ + if( p->spec.bColumnNames==QRF_Yes ){ for(i=0; inCol; i++){ const char *zCName = sqlite3_column_name(p->pStmt, i); if( qrf_need_quote(zCName) ){ @@ -1951,7 +1975,7 @@ static void qrfOneSimpleRow(Qrf *p){ break; } default: { /* QRF_STYLE_List */ - if( p->nRow==0 && p->spec.bColumnNames==QRF_SW_On ){ + if( p->nRow==0 && p->spec.bColumnNames==QRF_Yes ){ int saved_eText = p->spec.eText; if( p->spec.eText!=QRF_TEXT_Csv ) p->spec.eText = QRF_TEXT_Off; for(i=0; inCol; i++){ diff --git a/ext/qrf/qrf.h b/ext/qrf/qrf.h index 79db316387..8882389f76 100644 --- a/ext/qrf/qrf.h +++ b/ext/qrf/qrf.h @@ -26,15 +26,21 @@ struct sqlite3_qrf_spec { 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 bColumnNames; /* 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 eDfltAlign; /* Default alignment, no covered by aAlignment */ + unsigned char eTitleAlign; /* Alignment for column headers */ short int mxColWidth; /* Maximum width of any individual column */ short int mxTotalWidth; /* Maximum overall table width */ + short int mxRowHeight; /* Maximum number of lines for any row */ int mxLength; /* Maximum content to display per element */ - int nWidth; /* Number of column width parameters */ + int nWidth; /* Number of entries in aWidth[] */ + int nAlign; /* Number of entries in aAlignment[] */ short int *aWidth; /* Column widths */ + unsigned char *aAlign; /* Column alignments */ const char *zColumnSep; /* Alternative column separator */ const char *zRowSep; /* Alternative row separator */ const char *zTableName; /* Output table name */ @@ -47,14 +53,6 @@ struct sqlite3_qrf_spec { /* Additional fields may be added in the future */ }; -/* -** Range of values for sqlite3_qrf_spec.aWidth[] entries and for -** sqlite3_qrf_spec.mxColWidth and .mxTotalWidth -*/ -#define QRF_MX_WIDTH (10000) -#define QRF_MN_WIDTH (-10000) -#define QRF_MINUS_ZERO (-32768) /* Special value meaning -0 */ - /* ** Interfaces */ @@ -64,6 +62,13 @@ int sqlite3_format_query_result( char **pzErr /* OUT: Write error message here */ ); +/* +** Range of values for sqlite3_qrf_spec.aWidth[] entries and for +** sqlite3_qrf_spec.mxColWidth and .mxTotalWidth +*/ +#define QRF_MAX_WIDTH 10000 +#define QRF_MIN_WIDTH 0 + /* ** Output styles: */ @@ -128,6 +133,33 @@ int sqlite3_format_query_result( #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 diff --git a/manifest b/manifest index 23e65ddb6a..5fc156e926 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Minor\schanges\sto\sfix\sa\sfew\slegacy\stest\scases.\s\sSnapshot\sprior\sto\syet\sanother\nrefactor. -D 2025-11-08T10:48:55.724 +C Separate\scolumn\swidth\sspecification\sfrom\scell\salignment. +D 2025-11-08T14:04:29.620 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -416,9 +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 550c99ea5f9db2580ec66c12260b28feddc1c8a9b22396bb01bd9c3faaf720b6 -F ext/qrf/qrf.c 1d30c5c4d00e568ed72cbf71e3182d9f6b9912ce31180070d6d9501f145b6580 -F ext/qrf/qrf.h 642c717aa5a88074f17712a59763f80c9d83e356c3c165953c3f9ba648511087 +F ext/qrf/README.md 7c32e08e1cc5bb704f243a641ddfaf2f00152bbeb62c3a621efcf9371e6b156b +F ext/qrf/qrf.c 2aed044a15f9776f5e142021a0cced555c510b84ff99cc148ea058bbd927960f +F ext/qrf/qrf.h 3917767fdd7547bded156327f2b6bf3e1dbf6c7ea787f67f31453815986fe780 F ext/rbu/rbu.c 801450b24eaf14440d8fd20385aacc751d5c9d6123398df41b1b5aa804bf4ce8 F ext/rbu/rbu1.test 25870dd7db7eb5597e2b4d6e29e7a7e095abf332660f67d89959552ce8f8f255 F ext/rbu/rbu10.test 7c22caa32c2ff26983ca8320779a31495a6555737684af7aba3daaf762ef3363 @@ -735,7 +735,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 08e9032b9069ba0d08fccc012962a9fb859830ace7a73b78acf85979380542e7 +F src/shell.c.in 2a8ff6f666823459a9e65dc28c6f9825ec3625d77865c2d8db3e419a355baa3e F src/sqlite.h.in 43f60117ce68847b9d4e7fa43c2ac42bb324185e66b924d3114b24d4037fc263 F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479 F src/sqlite3ext.h 7f236ca1b175ffe03316d974ef57df79b3938466c28d2f95caef5e08c57f3a52 @@ -743,7 +743,7 @@ F src/sqliteInt.h 88f7fc9ce1630d9a5f7e0a8e1f3287cdc63882fba985c18e7eee1b9f457f59 F src/sqliteLimit.h fe70bd8983e5d317a264f2ea97473b359faf3ebb0827877a76813f5cf0cdc364 F src/status.c 7565d63a79aa2f326339a24a0461a60096d0bd2bce711fefb50b5c89335f3592 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 -F src/tclsqlite.c cb5e3f5de07367b5b83af046c4ee4abae6b12b61bcbcf0d5df5049c17e9131f8 +F src/tclsqlite.c be06a88784bea622f3ceacdfadc69ca5bf62dee350f8b0ceea111d2944217364 F src/tclsqlite.h 614b3780a62522bc9f8f2b9fb22689e8009958e7aa77e572d0f3149050af348a F src/test1.c f880ab766eeedf2c063662bd9538b923fd42c4341b7bfc2150a6d93ab8b9341c F src/test2.c 62f0830958f9075692c29c6de51b495ae8969e1bef85f239ffcd9ba5fb44a5ff @@ -1506,7 +1506,7 @@ F test/printf2.test 3f55c1871a5a65507416076f6eb97e738d5210aeda7595a74ee895f2224c F test/progress.test ebab27f670bd0d4eb9d20d49cef96e68141d92fb F test/ptrchng.test ef1aa72d6cf35a2bbd0869a649b744e9d84977fc F test/pushdown.test 46a626ef1c0ca79b85296ff2e078b9da20a50e9b804b38f441590c3987580ddd -F test/qrf01.test 68481b70feab9dee97481daf23e3b63c3822542d4f8bc7ea805db93db3b0feaa +F test/qrf01.test d0f655dbd4071d475d878e47a7c17693784bc4653df198df431b1dd3b30d8ea0 F test/qrf02.test 39b4afdc000bedccdafc0aecf17638df67a67aaa2d2942865ae6abcc48ba0e92 F test/queryonly.test 5f653159e0f552f0552d43259890c1089391dcca F test/quick.test 1681febc928d686362d50057c642f77a02c62e57 @@ -2173,8 +2173,8 @@ F tool/version-info.c 33d0390ef484b3b1cb685d59362be891ea162123cea181cb8e6d2cf6dd F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 21084041196dd6908bb810d8fce80b7f6d5b12ebe474bf62b85128540ad020eb -R c8236e5308102b8245da0f16fe67a4b5 +P b429e7882fd630a490350efa38bfce8686d6727f324a60191337a59d5436828b +R 1329d55649e069457afc085261020c3a U drh -Z f0ed1a0a849e17623b9e171efdb9f424 +Z cebf3ea2c0f08016d910b76b9d9d0544 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 489308fe73..473b80e939 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -b429e7882fd630a490350efa38bfce8686d6727f324a60191337a59d5436828b +5dadfd7f41764ddfed39bb49bbde3a54d8f282bb494c9c2abbba37d0995a7f8e diff --git a/src/shell.c.in b/src/shell.c.in index 694167f161..a604babccb 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -3831,7 +3831,7 @@ static void exec_prepared_stmt_columnar( memset(p->actualWidth, 0, nColumn*sizeof(short int)); for(i=0; icolWidth[i]; - if( w==QRF_MINUS_ZERO ) w = 0; + if( w==(-32768) ) w = 0; if( w<0 ) w = -w; p->actualWidth[i] = w; } @@ -3892,8 +3892,8 @@ static void exec_prepared_stmt_columnar( n = strlenChar(z); j = i%nColumn; if( n>p->actualWidth[j] ){ - if( n>QRF_MX_WIDTH ){ - p->actualWidth[j] = QRF_MX_WIDTH; + if( n>QRF_MAX_WIDTH ){ + p->actualWidth[j] = QRF_MAX_WIDTH; }else { p->actualWidth[j] = n; } @@ -12026,8 +12026,8 @@ static int do_meta_command(char *zLine, ShellState *p){ if( p->nWidth ) p->actualWidth = &p->colWidth[p->nWidth]; for(j=1; j QRF_MX_WIDTH ) w = QRF_MX_WIDTH; + if( w < -QRF_MAX_WIDTH ) w = -QRF_MAX_WIDTH; + if( w > QRF_MAX_WIDTH ) w = QRF_MAX_WIDTH; p->colWidth[j-1] = (short int)w; } }else diff --git a/src/tclsqlite.c b/src/tclsqlite.c index 8fd252462c..aa6e80deb8 100644 --- a/src/tclsqlite.c +++ b/src/tclsqlite.c @@ -2058,13 +2058,18 @@ static void DbHookCmd( ** -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"|...) How to escape column names ** -blob ("auto"|"text"|"sql"|...) How to escape BLOB values ** -columnnames ("auto"|"off"|"on") Show column names? ** -wordwrap ("auto"|"off"|"on") Try to wrap at word boundry? ** -textjsonb ("auto"|"off"|"on") Auto-convert JSONB to text? +** -defaultalign ("auto"|"left"|...) Default alignment +** -titalalign ("auto"|"left"|"right"|...) Default column name alignment ** -maxcolwidth NUMBER Max width of any single column -** -maxwidth NUMBER Max width of the entire table +** -maxtotalwidth NUMBER Max width of the entire table +** -maxrowheight NUMBER Max height of a row in a table ** -maxlength 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 @@ -2079,13 +2084,18 @@ static void DbHookCmd( ** -style eStyle ** -esc eEsc ** -text eText +** -title eTitle ** -blob eBlob ** -columnnames bColumnNames ** -wordwrap bWordWrap ** -textjsonb bTextJsonb +** -defaultalign eDfltAlign +** -titlealign eTitleAlign ** -maxcolwidth mxColWidth -** -maxwidth mxTotalWidth +** -maxtotalwidth mxTotalWidth +** -maxrowheight mxRowHeight ** -maxlength mxLength +** -align nAlign, aAlign ** -widths nWidth, aWidth ** -columnsep zColumnSep ** -rowsep zRowSep @@ -2102,13 +2112,30 @@ static int dbQrf(SqliteDb *pDb, int objc, Tcl_Obj *const*objv){ 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; iinterp, "unknown argument: ", zArg, (char*)0); @@ -2158,7 +2185,7 @@ static int dbQrf(SqliteDb *pDb, int objc, Tcl_Obj *const*objv){ if( rc ) goto format_failed; qrf.eEsc = aEscMap[esc]; i++; - }else if( strcmp(zArg,"-text")==0 ){ + }else if( strcmp(zArg,"-text")==0 || strcmp(zArg, "-title")==0 ){ static const char *azText[] = { "auto", "csv", "html", "json", "off", "sql", @@ -2171,9 +2198,14 @@ static int dbQrf(SqliteDb *pDb, int objc, Tcl_Obj *const*objv){ }; int txt; rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azText, - "text encoding (-text)", 0, &txt); + zArg[2]=='e' ? "text encoding (-text)" : + "column-name encoding (-title)", 0, &txt); if( rc ) goto format_failed; - qrf.eText = aTextMap[txt]; + if( zArg[2]=='e' ){ + qrf.eText = aTextMap[txt]; + }else{ + qrf.eTitle = aTextMap[txt]; + } i++; }else if( strcmp(zArg,"-blob")==0 ){ static const char *azBlob[] = { @@ -2195,30 +2227,43 @@ static int dbQrf(SqliteDb *pDb, int objc, Tcl_Obj *const*objv){ rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azBool, "-columnnames", 0, &v); if( rc ) goto format_failed; - qrf.bColumnNames = v; + qrf.bColumnNames = aBoolMap[v]; 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 = v; + qrf.bWordWrap = aBoolMap[v]; i++; }else if( strcmp(zArg,"-textjsonb")==0 ){ int v = 0; rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azBool, "-testjsonb", 0, &v); if( rc ) goto format_failed; - qrf.bTextJsonb = v; + qrf.bTextJsonb = 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 (-dfltalign)" : + "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,"-maxcolwidth")==0 || strcmp(zArg,"-maxwidth")==0 ){ int v = 0; rc = Tcl_GetIntFromObj(pDb->interp, objv[i+1], &v); if( rc ) goto format_failed; - if( v<0 ){ - v = 0; - }else if( v>QRF_MX_WIDTH ){ - v = QRF_MX_WIDTH; + if( vQRF_MAX_WIDTH ){ + v = QRF_MAX_WIDTH; } if( zArg[4]=='c' ){ qrf.mxColWidth = v; @@ -2233,6 +2278,31 @@ static int dbQrf(SqliteDb *pDb, int objc, Tcl_Obj *const*objv){ if( v<0 ) v = 0; qrf.mxLength = 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; jjinterp, objv[i+1], jj, &pTerm); + if( rc ) goto format_failed; + rc = Tcl_GetIndexFromObj(pDb->interp, pTerm, azAlign, + "column alignement (-align)", 0, &x); + if( rc ) goto format_failed; + qrf.aAlign[jj] = x; + } + i++; }else if( strcmp(zArg,"-widths")==0 ){ Tcl_Size n = 0; int jj; @@ -2249,21 +2319,16 @@ static int dbQrf(SqliteDb *pDb, int objc, Tcl_Obj *const*objv){ qrf.nWidth = n; for(jj=0; jjinterp, objv[i+1], jj, &pTerm); if( rc ) goto format_failed; - zTerm = Tcl_GetString(pTerm); - if( strcmp(zTerm, "-0")==0 ){ - qrf.aWidth[jj] = QRF_MINUS_ZERO; - }else{ - int v = atoi(zTerm); - if( vQRF_MX_WIDTH ){ - v = QRF_MX_WIDTH; - } - qrf.aWidth[jj] = v; + 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 ){ @@ -2306,6 +2371,7 @@ static int dbQrf(SqliteDb *pDb, int objc, Tcl_Obj *const*objv){ format_failed: sqlite3_free(qrf.aWidth); + sqlite3_free(qrf.aAlign); sqlite3_free(zResult); return rc; diff --git a/test/qrf01.test b/test/qrf01.test index 49dee2e521..44fa2e37da 100644 --- a/test/qrf01.test +++ b/test/qrf01.test @@ -281,7 +281,7 @@ x'424c4f42'|NULL|'Ἀμήν' do_test 1.118 { set rc [catch {db format -style list -columnnames unk {SELECT * FROM t1}} res] lappend rc $res -} {1 {bad -columnnames "unk": must be auto, off, or on}} +} {1 {bad -columnnames "unk": must be auto, yes, no, on, or off}} do_test 1.120 { @@ -407,6 +407,19 @@ do_test 2.11 { │ │ │ σκυλί │ └───────┴───────┴────────────────────┘ } +do_test 2.12 { + 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 {