]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Separate column width specification from cell alignment.
authordrh <>
Sat, 8 Nov 2025 14:04:29 +0000 (14:04 +0000)
committerdrh <>
Sat, 8 Nov 2025 14:04:29 +0000 (14:04 +0000)
FossilOrigin-Name: 5dadfd7f41764ddfed39bb49bbde3a54d8f282bb494c9c2abbba37d0995a7f8e

ext/qrf/README.md
ext/qrf/qrf.c
ext/qrf/qrf.h
manifest
manifest.uuid
src/shell.c.in
src/tclsqlite.c
test/qrf01.test

index a379103e146054eb017ebe6b112fee940a7f1c5e..2b8709174d0acc44e02371d2172f11fa26a92f1b 100644 (file)
@@ -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
index 0370ac5d838a57db4e49f38069a929495f7ef059..c964dcf4c2dbc4e87c59e5ba1de57299c697be51 100644 (file)
@@ -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; i<nColumn; i++){
       const char *z = (const char*)sqlite3_column_name(p->pStmt,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; i<nColumn && i<p->spec.nAlign; i++){
+    data.aAlign[i] = p->spec.aAlign[i];
+  }
   for(i=0; i<nColumn; i++){
     int w = 0;
     if( i<p->spec.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; i<data.n; i+=nColumn){
     int bMore;
     for(j=0; j<nColumn; j++){ data.azThis[j] = data.az[i+j]; }
@@ -1376,12 +1388,12 @@ static void qrfColumnar(Qrf *p){
         int nWS;
         qrfWrapLine(data.azThis[j], data.aiCol[j], bWW, &nThis, &nWide, &iNext);
         nWS = data.aiCol[j] - nWide;
-        if( bCenter ){
+        if( bCenter || (data.aAlign[j] & QRF_ALIGN_HMASK)==QRF_ALIGN_Center ){
           /* Center the text */
           sqlite3_str_appendchar(p->pOut, nWS/2, ' ');
           sqlite3_str_append(p->pOut, data.azThis[j], nThis);
           sqlite3_str_appendchar(p->pOut, nWS - nWS/2, ' ');
-        }else if( j<p->spec.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+nColumn<data.n ){
+    if( (i==0 || data.bMultiRow) && i+nColumn<data.n ){
       switch( p->spec.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; j<nColumn; j++){
               sqlite3_str_appendchar(p->pOut, data.aiCol[j], '-');
               if( j<nColumn-1 ){
@@ -1693,7 +1705,7 @@ static void qrfInitialize(
   if( p->spec.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, "<TR>", 4);
         for(i=0; i<p->nCol; 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; i<p->nCol; 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; i<p->nCol; i++){
index 79db316387d57e6c0be3353a65477f16ebf08458..8882389f763fde7078cfaa2122549e33ebea6437 100644 (file)
@@ -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
index 23e65ddb6a98c4a95ace0877409f1aa8fd3eab54..5fc156e9267b178b85a34c377e8d44aa98a93dda 100644 (file)
--- 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.
index 489308fe73a427549d1e246486b2176257e05ec3..473b80e939ced78d8c372db17773437a689bfb5b 100644 (file)
@@ -1 +1 @@
-b429e7882fd630a490350efa38bfce8686d6727f324a60191337a59d5436828b
+5dadfd7f41764ddfed39bb49bbde3a54d8f282bb494c9c2abbba37d0995a7f8e
index 694167f161bc849e1ec9b7c96396eb584db14ab2..a604babccb2698a9bd79a32ebe6724cf10f65cb0 100644 (file)
@@ -3831,7 +3831,7 @@ static void exec_prepared_stmt_columnar(
   memset(p->actualWidth, 0, nColumn*sizeof(short int));
   for(i=0; i<nColumn; i++){
     w = p->colWidth[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<nArg; j++){
       i64 w = integerValue(azArg[j]);
-      if( w < QRF_MN_WIDTH ) w = QRF_MN_WIDTH;
-      if( w > 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
index 8fd252462c12d6b591189a9fd2c7a9465c225b69..aa6e80deb83b638177f027ad92fa04435a30011c 100644 (file)
@@ -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; i<objc; i++){
     const char *zArg = Tcl_GetString(objv[i]);
-    const char *azBool[] = { "auto", "off", "on", 0 };
+    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);
@@ -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( v<QRF_MIN_WIDTH ){
+        v = QRF_MIN_WIDTH;
+      }else if( v>QRF_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; 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 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; jj<n; jj++){
         Tcl_Obj *pTerm;
-        const char *zTerm;
+        int v;
         rc = Tcl_ListObjIndex(pDb->interp, 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( v<QRF_MN_WIDTH ){
-            v = QRF_MN_WIDTH;
-          }else if( v>QRF_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;
 
index 49dee2e521634e243e0eb569ad0c6d48370208d4..44fa2e37da88efed08e04d177fece9e944a28c3d 100644 (file)
@@ -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 {