mirror of
https://github.com/sqlite/sqlite.git
synced 2025-12-20 01:22:32 +03:00
transforms the abNum[] array. FossilOrigin-Name: ea67d8b001d1be3b0410ca697ff7ddda71c957c8ecb6d76e75133ba39b754623
2736 lines
83 KiB
C
2736 lines
83 KiB
C
/*
|
|
** 2025-10-20
|
|
**
|
|
** The author disclaims copyright to this source code. In place of
|
|
** a legal notice, here is a blessing:
|
|
**
|
|
** May you do good and not evil.
|
|
** May you find forgiveness for yourself and forgive others.
|
|
** May you share freely, never taking more than you give.
|
|
**
|
|
*************************************************************************
|
|
** Implementation of the Result-Format or "qrf" utility library for SQLite.
|
|
** See the qrf.md documentation for additional information.
|
|
*/
|
|
#ifndef SQLITE_QRF_H
|
|
#include "qrf.h"
|
|
#endif
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
|
|
typedef sqlite3_int64 i64;
|
|
|
|
/* A single line in the EQP output */
|
|
typedef struct qrfEQPGraphRow qrfEQPGraphRow;
|
|
struct qrfEQPGraphRow {
|
|
int iEqpId; /* ID for this row */
|
|
int iParentId; /* ID of the parent row */
|
|
qrfEQPGraphRow *pNext; /* Next row in sequence */
|
|
char zText[1]; /* Text to display for this row */
|
|
};
|
|
|
|
/* All EQP output is collected into an instance of the following */
|
|
typedef struct qrfEQPGraph qrfEQPGraph;
|
|
struct qrfEQPGraph {
|
|
qrfEQPGraphRow *pRow; /* Linked list of all rows of the EQP output */
|
|
qrfEQPGraphRow *pLast; /* Last element of the pRow list */
|
|
char zPrefix[100]; /* Graph prefix */
|
|
};
|
|
|
|
/*
|
|
** Private state information. Subject to change from one release to the
|
|
** next.
|
|
*/
|
|
typedef struct Qrf Qrf;
|
|
struct Qrf {
|
|
sqlite3_stmt *pStmt; /* The statement whose output is to be rendered */
|
|
sqlite3 *db; /* The corresponding database connection */
|
|
sqlite3_stmt *pJTrans; /* JSONB to JSON translator statement */
|
|
char **pzErr; /* Write error message here, if not NULL */
|
|
sqlite3_str *pOut; /* Accumulated output */
|
|
int iErr; /* Error code */
|
|
int nCol; /* Number of output columns */
|
|
int expMode; /* Original sqlite3_stmt_isexplain() plus 1 */
|
|
int mxWidth; /* Screen width */
|
|
int mxHeight; /* nLineLimit */
|
|
union {
|
|
struct { /* Content for QRF_STYLE_Line */
|
|
int mxColWth; /* Maximum display width of any column */
|
|
const char **azCol; /* Names of output columns (MODE_Line) */
|
|
} sLine;
|
|
qrfEQPGraph *pGraph; /* EQP graph (Eqp, Stats, and StatsEst) */
|
|
struct { /* Content for QRF_STYLE_Explain */
|
|
int nIndent; /* Slots allocated for aiIndent */
|
|
int iIndent; /* Current slot */
|
|
int *aiIndent; /* Indentation for each opcode */
|
|
} sExpln;
|
|
} u;
|
|
sqlite3_int64 nRow; /* Number of rows handled so far */
|
|
int *actualWidth; /* Actual width of each column */
|
|
sqlite3_qrf_spec spec; /* Copy of the original spec */
|
|
};
|
|
|
|
/*
|
|
** Data for substitute ctype.h functions. Used for x-platform
|
|
** consistency and so that '_' is counted as an alphabetic
|
|
** character.
|
|
**
|
|
** 0x01 - space
|
|
** 0x02 - digit
|
|
** 0x04 - alphabetic, including '_'
|
|
*/
|
|
static const char qrfCType[] = {
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0,
|
|
0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
|
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 4,
|
|
0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
|
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
|
};
|
|
#define qrfSpace(x) ((qrfCType[(unsigned char)x]&1)!=0)
|
|
#define qrfDigit(x) ((qrfCType[(unsigned char)x]&2)!=0)
|
|
#define qrfAlpha(x) ((qrfCType[(unsigned char)x]&4)!=0)
|
|
#define qrfAlnum(x) ((qrfCType[(unsigned char)x]&6)!=0)
|
|
|
|
/*
|
|
** Set an error code and error message.
|
|
*/
|
|
static void qrfError(
|
|
Qrf *p, /* Query result state */
|
|
int iCode, /* Error code */
|
|
const char *zFormat, /* Message format (or NULL) */
|
|
...
|
|
){
|
|
p->iErr = iCode;
|
|
if( p->pzErr!=0 ){
|
|
sqlite3_free(*p->pzErr);
|
|
*p->pzErr = 0;
|
|
if( zFormat ){
|
|
va_list ap;
|
|
va_start(ap, zFormat);
|
|
*p->pzErr = sqlite3_vmprintf(zFormat, ap);
|
|
va_end(ap);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Out-of-memory error.
|
|
*/
|
|
static void qrfOom(Qrf *p){
|
|
qrfError(p, SQLITE_NOMEM, "out of memory");
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
** Add a new entry to the EXPLAIN QUERY PLAN data
|
|
*/
|
|
static void qrfEqpAppend(Qrf *p, int iEqpId, int p2, const char *zText){
|
|
qrfEQPGraphRow *pNew;
|
|
sqlite3_int64 nText;
|
|
if( zText==0 ) return;
|
|
if( p->u.pGraph==0 ){
|
|
p->u.pGraph = sqlite3_malloc64( sizeof(qrfEQPGraph) );
|
|
if( p->u.pGraph==0 ){
|
|
qrfOom(p);
|
|
return;
|
|
}
|
|
memset(p->u.pGraph, 0, sizeof(qrfEQPGraph) );
|
|
}
|
|
nText = strlen(zText);
|
|
pNew = sqlite3_malloc64( sizeof(*pNew) + nText );
|
|
if( pNew==0 ){
|
|
qrfOom(p);
|
|
return;
|
|
}
|
|
pNew->iEqpId = iEqpId;
|
|
pNew->iParentId = p2;
|
|
memcpy(pNew->zText, zText, nText+1);
|
|
pNew->pNext = 0;
|
|
if( p->u.pGraph->pLast ){
|
|
p->u.pGraph->pLast->pNext = pNew;
|
|
}else{
|
|
p->u.pGraph->pRow = pNew;
|
|
}
|
|
p->u.pGraph->pLast = pNew;
|
|
}
|
|
|
|
/*
|
|
** Free and reset the EXPLAIN QUERY PLAN data that has been collected
|
|
** in p->u.pGraph.
|
|
*/
|
|
static void qrfEqpReset(Qrf *p){
|
|
qrfEQPGraphRow *pRow, *pNext;
|
|
if( p->u.pGraph ){
|
|
for(pRow = p->u.pGraph->pRow; pRow; pRow = pNext){
|
|
pNext = pRow->pNext;
|
|
sqlite3_free(pRow);
|
|
}
|
|
sqlite3_free(p->u.pGraph);
|
|
p->u.pGraph = 0;
|
|
}
|
|
}
|
|
|
|
/* Return the next EXPLAIN QUERY PLAN line with iEqpId that occurs after
|
|
** pOld, or return the first such line if pOld is NULL
|
|
*/
|
|
static qrfEQPGraphRow *qrfEqpNextRow(Qrf *p, int iEqpId, qrfEQPGraphRow *pOld){
|
|
qrfEQPGraphRow *pRow = pOld ? pOld->pNext : p->u.pGraph->pRow;
|
|
while( pRow && pRow->iParentId!=iEqpId ) pRow = pRow->pNext;
|
|
return pRow;
|
|
}
|
|
|
|
/* Render a single level of the graph that has iEqpId as its parent. Called
|
|
** recursively to render sublevels.
|
|
*/
|
|
static void qrfEqpRenderLevel(Qrf *p, int iEqpId){
|
|
qrfEQPGraphRow *pRow, *pNext;
|
|
i64 n = strlen(p->u.pGraph->zPrefix);
|
|
char *z;
|
|
for(pRow = qrfEqpNextRow(p, iEqpId, 0); pRow; pRow = pNext){
|
|
pNext = qrfEqpNextRow(p, iEqpId, pRow);
|
|
z = pRow->zText;
|
|
sqlite3_str_appendf(p->pOut, "%s%s%s\n", p->u.pGraph->zPrefix,
|
|
pNext ? "|--" : "`--", z);
|
|
if( n<(i64)sizeof(p->u.pGraph->zPrefix)-7 ){
|
|
memcpy(&p->u.pGraph->zPrefix[n], pNext ? "| " : " ", 4);
|
|
qrfEqpRenderLevel(p, pRow->iEqpId);
|
|
p->u.pGraph->zPrefix[n] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Display and reset the EXPLAIN QUERY PLAN data
|
|
*/
|
|
static void qrfEqpRender(Qrf *p, i64 nCycle){
|
|
qrfEQPGraphRow *pRow;
|
|
if( p->u.pGraph!=0 && (pRow = p->u.pGraph->pRow)!=0 ){
|
|
if( pRow->zText[0]=='-' ){
|
|
if( pRow->pNext==0 ){
|
|
qrfEqpReset(p);
|
|
return;
|
|
}
|
|
sqlite3_str_appendf(p->pOut, "%s\n", pRow->zText+3);
|
|
p->u.pGraph->pRow = pRow->pNext;
|
|
sqlite3_free(pRow);
|
|
}else if( nCycle>0 ){
|
|
sqlite3_str_appendf(p->pOut, "QUERY PLAN (cycles=%lld [100%%])\n",nCycle);
|
|
}else{
|
|
sqlite3_str_appendall(p->pOut, "QUERY PLAN\n");
|
|
}
|
|
p->u.pGraph->zPrefix[0] = 0;
|
|
qrfEqpRenderLevel(p, 0);
|
|
qrfEqpReset(p);
|
|
}
|
|
}
|
|
|
|
#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
|
|
/*
|
|
** Helper function for qrfExpStats().
|
|
**
|
|
*/
|
|
static int qrfStatsHeight(sqlite3_stmt *p, int iEntry){
|
|
int iPid = 0;
|
|
int ret = 1;
|
|
sqlite3_stmt_scanstatus_v2(p, iEntry,
|
|
SQLITE_SCANSTAT_SELECTID, SQLITE_SCANSTAT_COMPLEX, (void*)&iPid
|
|
);
|
|
while( iPid!=0 ){
|
|
int ii;
|
|
for(ii=0; 1; ii++){
|
|
int iId;
|
|
int res;
|
|
res = sqlite3_stmt_scanstatus_v2(p, ii,
|
|
SQLITE_SCANSTAT_SELECTID, SQLITE_SCANSTAT_COMPLEX, (void*)&iId
|
|
);
|
|
if( res ) break;
|
|
if( iId==iPid ){
|
|
sqlite3_stmt_scanstatus_v2(p, ii,
|
|
SQLITE_SCANSTAT_PARENTID, SQLITE_SCANSTAT_COMPLEX, (void*)&iPid
|
|
);
|
|
}
|
|
}
|
|
ret++;
|
|
}
|
|
return ret;
|
|
}
|
|
#endif /* SQLITE_ENABLE_STMT_SCANSTATUS */
|
|
|
|
|
|
/*
|
|
** Generate ".scanstatus est" style of EQP output.
|
|
*/
|
|
static void qrfEqpStats(Qrf *p){
|
|
#ifndef SQLITE_ENABLE_STMT_SCANSTATUS
|
|
qrfError(p, SQLITE_ERROR, "not available in this build");
|
|
#else
|
|
static const int f = SQLITE_SCANSTAT_COMPLEX;
|
|
sqlite3_stmt *pS = p->pStmt;
|
|
int i = 0;
|
|
i64 nTotal = 0;
|
|
int nWidth = 0;
|
|
sqlite3_str *pLine = sqlite3_str_new(p->db);
|
|
sqlite3_str *pStats = sqlite3_str_new(p->db);
|
|
qrfEqpReset(p);
|
|
|
|
for(i=0; 1; i++){
|
|
const char *z = 0;
|
|
int n = 0;
|
|
if( sqlite3_stmt_scanstatus_v2(pS,i,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&z) ){
|
|
break;
|
|
}
|
|
n = (int)strlen(z) + qrfStatsHeight(pS,i)*3;
|
|
if( n>nWidth ) nWidth = n;
|
|
}
|
|
nWidth += 4;
|
|
|
|
sqlite3_stmt_scanstatus_v2(pS,-1, SQLITE_SCANSTAT_NCYCLE, f, (void*)&nTotal);
|
|
for(i=0; 1; i++){
|
|
i64 nLoop = 0;
|
|
i64 nRow = 0;
|
|
i64 nCycle = 0;
|
|
int iId = 0;
|
|
int iPid = 0;
|
|
const char *zo = 0;
|
|
const char *zName = 0;
|
|
double rEst = 0.0;
|
|
|
|
if( sqlite3_stmt_scanstatus_v2(pS,i,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&zo) ){
|
|
break;
|
|
}
|
|
sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_EST,f,(void*)&rEst);
|
|
sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_NLOOP,f,(void*)&nLoop);
|
|
sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_NVISIT,f,(void*)&nRow);
|
|
sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_NCYCLE,f,(void*)&nCycle);
|
|
sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_SELECTID,f,(void*)&iId);
|
|
sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_PARENTID,f,(void*)&iPid);
|
|
sqlite3_stmt_scanstatus_v2(pS,i, SQLITE_SCANSTAT_NAME,f,(void*)&zName);
|
|
|
|
if( nCycle>=0 || nLoop>=0 || nRow>=0 ){
|
|
const char *zSp = "";
|
|
double rpl;
|
|
sqlite3_str_reset(pStats);
|
|
if( nCycle>=0 && nTotal>0 ){
|
|
sqlite3_str_appendf(pStats, "cycles=%lld [%d%%]",
|
|
nCycle, ((nCycle*100)+nTotal/2) / nTotal
|
|
);
|
|
zSp = " ";
|
|
}
|
|
if( nLoop>=0 ){
|
|
sqlite3_str_appendf(pStats, "%sloops=%lld", zSp, nLoop);
|
|
zSp = " ";
|
|
}
|
|
if( nRow>=0 ){
|
|
sqlite3_str_appendf(pStats, "%srows=%lld", zSp, nRow);
|
|
zSp = " ";
|
|
}
|
|
|
|
if( p->spec.eStyle==QRF_STYLE_StatsEst ){
|
|
rpl = (double)nRow / (double)nLoop;
|
|
sqlite3_str_appendf(pStats, "%srpl=%.1f est=%.1f", zSp, rpl, rEst);
|
|
}
|
|
|
|
sqlite3_str_appendf(pLine,
|
|
"% *s (%s)", -1*(nWidth-qrfStatsHeight(pS,i)*3), zo,
|
|
sqlite3_str_value(pStats)
|
|
);
|
|
sqlite3_str_reset(pStats);
|
|
qrfEqpAppend(p, iId, iPid, sqlite3_str_value(pLine));
|
|
sqlite3_str_reset(pLine);
|
|
}else{
|
|
qrfEqpAppend(p, iId, iPid, zo);
|
|
}
|
|
}
|
|
sqlite3_free(sqlite3_str_finish(pLine));
|
|
sqlite3_free(sqlite3_str_finish(pStats));
|
|
#endif
|
|
}
|
|
|
|
|
|
/*
|
|
** Reset the prepared statement.
|
|
*/
|
|
static void qrfResetStmt(Qrf *p){
|
|
int rc = sqlite3_reset(p->pStmt);
|
|
if( rc!=SQLITE_OK && p->iErr==SQLITE_OK ){
|
|
qrfError(p, rc, "%s", sqlite3_errmsg(p->db));
|
|
}
|
|
}
|
|
|
|
/*
|
|
** If xWrite is defined, send all content of pOut to xWrite and
|
|
** reset pOut.
|
|
*/
|
|
static void qrfWrite(Qrf *p){
|
|
int n;
|
|
if( p->spec.xWrite && (n = sqlite3_str_length(p->pOut))>0 ){
|
|
int rc = p->spec.xWrite(p->spec.pWriteArg,
|
|
sqlite3_str_value(p->pOut),
|
|
(sqlite3_int64)n);
|
|
sqlite3_str_reset(p->pOut);
|
|
if( rc ){
|
|
qrfError(p, rc, "Failed to write %d bytes of output", n);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Lookup table to estimate the number of columns consumed by a Unicode
|
|
** character.
|
|
*/
|
|
static const struct {
|
|
unsigned char w; /* Width of the character in columns */
|
|
int iFirst; /* First character in a span having this width */
|
|
} aQrfUWidth[] = {
|
|
/* {1, 0x00000}, */
|
|
{0, 0x00300}, {1, 0x00370}, {0, 0x00483}, {1, 0x00487}, {0, 0x00488},
|
|
{1, 0x0048a}, {0, 0x00591}, {1, 0x005be}, {0, 0x005bf}, {1, 0x005c0},
|
|
{0, 0x005c1}, {1, 0x005c3}, {0, 0x005c4}, {1, 0x005c6}, {0, 0x005c7},
|
|
{1, 0x005c8}, {0, 0x00600}, {1, 0x00604}, {0, 0x00610}, {1, 0x00616},
|
|
{0, 0x0064b}, {1, 0x0065f}, {0, 0x00670}, {1, 0x00671}, {0, 0x006d6},
|
|
{1, 0x006e5}, {0, 0x006e7}, {1, 0x006e9}, {0, 0x006ea}, {1, 0x006ee},
|
|
{0, 0x0070f}, {1, 0x00710}, {0, 0x00711}, {1, 0x00712}, {0, 0x00730},
|
|
{1, 0x0074b}, {0, 0x007a6}, {1, 0x007b1}, {0, 0x007eb}, {1, 0x007f4},
|
|
{0, 0x00901}, {1, 0x00903}, {0, 0x0093c}, {1, 0x0093d}, {0, 0x00941},
|
|
{1, 0x00949}, {0, 0x0094d}, {1, 0x0094e}, {0, 0x00951}, {1, 0x00955},
|
|
{0, 0x00962}, {1, 0x00964}, {0, 0x00981}, {1, 0x00982}, {0, 0x009bc},
|
|
{1, 0x009bd}, {0, 0x009c1}, {1, 0x009c5}, {0, 0x009cd}, {1, 0x009ce},
|
|
{0, 0x009e2}, {1, 0x009e4}, {0, 0x00a01}, {1, 0x00a03}, {0, 0x00a3c},
|
|
{1, 0x00a3d}, {0, 0x00a41}, {1, 0x00a43}, {0, 0x00a47}, {1, 0x00a49},
|
|
{0, 0x00a4b}, {1, 0x00a4e}, {0, 0x00a70}, {1, 0x00a72}, {0, 0x00a81},
|
|
{1, 0x00a83}, {0, 0x00abc}, {1, 0x00abd}, {0, 0x00ac1}, {1, 0x00ac6},
|
|
{0, 0x00ac7}, {1, 0x00ac9}, {0, 0x00acd}, {1, 0x00ace}, {0, 0x00ae2},
|
|
{1, 0x00ae4}, {0, 0x00b01}, {1, 0x00b02}, {0, 0x00b3c}, {1, 0x00b3d},
|
|
{0, 0x00b3f}, {1, 0x00b40}, {0, 0x00b41}, {1, 0x00b44}, {0, 0x00b4d},
|
|
{1, 0x00b4e}, {0, 0x00b56}, {1, 0x00b57}, {0, 0x00b82}, {1, 0x00b83},
|
|
{0, 0x00bc0}, {1, 0x00bc1}, {0, 0x00bcd}, {1, 0x00bce}, {0, 0x00c3e},
|
|
{1, 0x00c41}, {0, 0x00c46}, {1, 0x00c49}, {0, 0x00c4a}, {1, 0x00c4e},
|
|
{0, 0x00c55}, {1, 0x00c57}, {0, 0x00cbc}, {1, 0x00cbd}, {0, 0x00cbf},
|
|
{1, 0x00cc0}, {0, 0x00cc6}, {1, 0x00cc7}, {0, 0x00ccc}, {1, 0x00cce},
|
|
{0, 0x00ce2}, {1, 0x00ce4}, {0, 0x00d41}, {1, 0x00d44}, {0, 0x00d4d},
|
|
{1, 0x00d4e}, {0, 0x00dca}, {1, 0x00dcb}, {0, 0x00dd2}, {1, 0x00dd5},
|
|
{0, 0x00dd6}, {1, 0x00dd7}, {0, 0x00e31}, {1, 0x00e32}, {0, 0x00e34},
|
|
{1, 0x00e3b}, {0, 0x00e47}, {1, 0x00e4f}, {0, 0x00eb1}, {1, 0x00eb2},
|
|
{0, 0x00eb4}, {1, 0x00eba}, {0, 0x00ebb}, {1, 0x00ebd}, {0, 0x00ec8},
|
|
{1, 0x00ece}, {0, 0x00f18}, {1, 0x00f1a}, {0, 0x00f35}, {1, 0x00f36},
|
|
{0, 0x00f37}, {1, 0x00f38}, {0, 0x00f39}, {1, 0x00f3a}, {0, 0x00f71},
|
|
{1, 0x00f7f}, {0, 0x00f80}, {1, 0x00f85}, {0, 0x00f86}, {1, 0x00f88},
|
|
{0, 0x00f90}, {1, 0x00f98}, {0, 0x00f99}, {1, 0x00fbd}, {0, 0x00fc6},
|
|
{1, 0x00fc7}, {0, 0x0102d}, {1, 0x01031}, {0, 0x01032}, {1, 0x01033},
|
|
{0, 0x01036}, {1, 0x01038}, {0, 0x01039}, {1, 0x0103a}, {0, 0x01058},
|
|
{1, 0x0105a}, {2, 0x01100}, {0, 0x01160}, {1, 0x01200}, {0, 0x0135f},
|
|
{1, 0x01360}, {0, 0x01712}, {1, 0x01715}, {0, 0x01732}, {1, 0x01735},
|
|
{0, 0x01752}, {1, 0x01754}, {0, 0x01772}, {1, 0x01774}, {0, 0x017b4},
|
|
{1, 0x017b6}, {0, 0x017b7}, {1, 0x017be}, {0, 0x017c6}, {1, 0x017c7},
|
|
{0, 0x017c9}, {1, 0x017d4}, {0, 0x017dd}, {1, 0x017de}, {0, 0x0180b},
|
|
{1, 0x0180e}, {0, 0x018a9}, {1, 0x018aa}, {0, 0x01920}, {1, 0x01923},
|
|
{0, 0x01927}, {1, 0x01929}, {0, 0x01932}, {1, 0x01933}, {0, 0x01939},
|
|
{1, 0x0193c}, {0, 0x01a17}, {1, 0x01a19}, {0, 0x01b00}, {1, 0x01b04},
|
|
{0, 0x01b34}, {1, 0x01b35}, {0, 0x01b36}, {1, 0x01b3b}, {0, 0x01b3c},
|
|
{1, 0x01b3d}, {0, 0x01b42}, {1, 0x01b43}, {0, 0x01b6b}, {1, 0x01b74},
|
|
{0, 0x01dc0}, {1, 0x01dcb}, {0, 0x01dfe}, {1, 0x01e00}, {0, 0x0200b},
|
|
{1, 0x02010}, {0, 0x0202a}, {1, 0x0202f}, {0, 0x02060}, {1, 0x02064},
|
|
{0, 0x0206a}, {1, 0x02070}, {0, 0x020d0}, {1, 0x020f0}, {2, 0x02329},
|
|
{1, 0x0232b}, {2, 0x02e80}, {0, 0x0302a}, {2, 0x03030}, {1, 0x0303f},
|
|
{2, 0x03040}, {0, 0x03099}, {2, 0x0309b}, {1, 0x0a4d0}, {0, 0x0a806},
|
|
{1, 0x0a807}, {0, 0x0a80b}, {1, 0x0a80c}, {0, 0x0a825}, {1, 0x0a827},
|
|
{2, 0x0ac00}, {1, 0x0d7a4}, {2, 0x0f900}, {1, 0x0fb00}, {0, 0x0fb1e},
|
|
{1, 0x0fb1f}, {0, 0x0fe00}, {2, 0x0fe10}, {1, 0x0fe1a}, {0, 0x0fe20},
|
|
{1, 0x0fe24}, {2, 0x0fe30}, {1, 0x0fe70}, {0, 0x0feff}, {2, 0x0ff00},
|
|
{1, 0x0ff61}, {2, 0x0ffe0}, {1, 0x0ffe7}, {0, 0x0fff9}, {1, 0x0fffc},
|
|
{0, 0x10a01}, {1, 0x10a04}, {0, 0x10a05}, {1, 0x10a07}, {0, 0x10a0c},
|
|
{1, 0x10a10}, {0, 0x10a38}, {1, 0x10a3b}, {0, 0x10a3f}, {1, 0x10a40},
|
|
{0, 0x1d167}, {1, 0x1d16a}, {0, 0x1d173}, {1, 0x1d183}, {0, 0x1d185},
|
|
{1, 0x1d18c}, {0, 0x1d1aa}, {1, 0x1d1ae}, {0, 0x1d242}, {1, 0x1d245},
|
|
{2, 0x20000}, {1, 0x2fffe}, {2, 0x30000}, {1, 0x3fffe}, {0, 0xe0001},
|
|
{1, 0xe0002}, {0, 0xe0020}, {1, 0xe0080}, {0, 0xe0100}, {1, 0xe01f0}
|
|
};
|
|
|
|
/*
|
|
** Return an estimate of the width, in columns, for the single Unicode
|
|
** character c. For normal characters, the answer is always 1. But the
|
|
** estimate might be 0 or 2 for zero-width and double-width characters.
|
|
**
|
|
** Different display devices display unicode using different widths. So
|
|
** it is impossible to know that true display width with 100% accuracy.
|
|
** Inaccuracies in the width estimates might cause columns to be misaligned.
|
|
** Unfortunately, there is nothing we can do about that.
|
|
*/
|
|
int sqlite3_qrf_wcwidth(int c){
|
|
int iFirst, iLast;
|
|
|
|
/* Fast path for common characters */
|
|
if( c<=0x300 ) return 1;
|
|
|
|
/* The general case */
|
|
iFirst = 0;
|
|
iLast = sizeof(aQrfUWidth)/sizeof(aQrfUWidth[0]) - 1;
|
|
while( iFirst<iLast-1 ){
|
|
int iMid = (iFirst+iLast)/2;
|
|
int cMid = aQrfUWidth[iMid].iFirst;
|
|
if( cMid < c ){
|
|
iFirst = iMid;
|
|
}else if( cMid > c ){
|
|
iLast = iMid - 1;
|
|
}else{
|
|
return aQrfUWidth[iMid].w;
|
|
}
|
|
}
|
|
if( aQrfUWidth[iLast].iFirst > c ) return aQrfUWidth[iFirst].w;
|
|
return aQrfUWidth[iLast].w;
|
|
}
|
|
|
|
/*
|
|
** Compute the value and length of a multi-byte UTF-8 character that
|
|
** begins at z[0]. Return the length. Write the Unicode value into *pU.
|
|
**
|
|
** This routine only works for *multi-byte* UTF-8 characters. It does
|
|
** not attempt to detect illegal characters.
|
|
*/
|
|
int sqlite3_qrf_decode_utf8(const unsigned char *z, int *pU){
|
|
if( (z[0] & 0xe0)==0xc0 && (z[1] & 0xc0)==0x80 ){
|
|
*pU = ((z[0] & 0x1f)<<6) | (z[1] & 0x3f);
|
|
return 2;
|
|
}
|
|
if( (z[0] & 0xf0)==0xe0 && (z[1] & 0xc0)==0x80 && (z[2] & 0xc0)==0x80 ){
|
|
*pU = ((z[0] & 0x0f)<<12) | ((z[1] & 0x3f)<<6) | (z[2] & 0x3f);
|
|
return 3;
|
|
}
|
|
if( (z[0] & 0xf8)==0xf0 && (z[1] & 0xc0)==0x80 && (z[2] & 0xc0)==0x80
|
|
&& (z[3] & 0xc0)==0x80
|
|
){
|
|
*pU = ((z[0] & 0x0f)<<18) | ((z[1] & 0x3f)<<12) | ((z[2] & 0x3f))<<6
|
|
| (z[3] & 0x3f);
|
|
return 4;
|
|
}
|
|
*pU = 0;
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
** Check to see if z[] is a valid VT100 escape. If it is, then
|
|
** return the number of bytes in the escape sequence. Return 0 if
|
|
** z[] is not a VT100 escape.
|
|
**
|
|
** This routine assumes that z[0] is \033 (ESC).
|
|
*/
|
|
static int qrfIsVt100(const unsigned char *z){
|
|
int i;
|
|
if( z[1]!='[' ) return 0;
|
|
i = 2;
|
|
while( z[i]>=0x30 && z[i]<=0x3f ){ i++; }
|
|
while( z[i]>=0x20 && z[i]<=0x2f ){ i++; }
|
|
if( z[i]<0x40 || z[i]>0x7e ) return 0;
|
|
return i+1;
|
|
}
|
|
|
|
/*
|
|
** Return the length of a string in display characters.
|
|
** Multibyte UTF8 characters count as a single character
|
|
** for single-width characters, or as two characters for
|
|
** double-width characters.
|
|
*/
|
|
static int qrfDisplayLength(const char *zIn){
|
|
const unsigned char *z = (const unsigned char*)zIn;
|
|
int n = 0;
|
|
while( *z ){
|
|
if( z[0]<' ' ){
|
|
int k;
|
|
if( z[0]=='\033' && (k = qrfIsVt100(z))>0 ){
|
|
z += k;
|
|
}else{
|
|
z++;
|
|
}
|
|
}else if( (0x80&z[0])==0 ){
|
|
n++;
|
|
z++;
|
|
}else{
|
|
int u = 0;
|
|
int len = sqlite3_qrf_decode_utf8(z, &u);
|
|
z += len;
|
|
n += sqlite3_qrf_wcwidth(u);
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
/*
|
|
** Return the display width of the longest line of text
|
|
** in the (possibly) multi-line input string zIn[0..nByte].
|
|
** zIn[] is not necessarily zero-terminated. Take
|
|
** into account tab characters, zero- and double-width
|
|
** characters, CR and NL, and VT100 escape codes.
|
|
**
|
|
** Write the number of newlines into *pnNL. So, *pnNL will
|
|
** return 0 if everything fits on one line, or positive it
|
|
** it will need to be split.
|
|
*/
|
|
static int qrfDisplayWidth(const char *zIn, sqlite3_int64 nByte, int *pnNL){
|
|
const unsigned char *z = (const unsigned char*)zIn;
|
|
const unsigned char *zEnd = &z[nByte];
|
|
int mx = 0;
|
|
int n = 0;
|
|
int nNL = 0;
|
|
while( z<zEnd ){
|
|
if( z[0]<' ' ){
|
|
int k;
|
|
if( z[0]=='\033' && (k = qrfIsVt100(z))>0 ){
|
|
z += k;
|
|
}else{
|
|
if( z[0]=='\t' ){
|
|
n = (n+8)&~7;
|
|
}else if( z[0]=='\n' || z[0]=='\r' ){
|
|
nNL++;
|
|
if( n>mx ) mx = n;
|
|
n = 0;
|
|
}
|
|
z++;
|
|
}
|
|
}else if( (0x80&z[0])==0 ){
|
|
n++;
|
|
z++;
|
|
}else{
|
|
int u = 0;
|
|
int len = sqlite3_qrf_decode_utf8(z, &u);
|
|
z += len;
|
|
n += sqlite3_qrf_wcwidth(u);
|
|
}
|
|
}
|
|
if( mx>n ) n = mx;
|
|
if( pnNL ) *pnNL = nNL;
|
|
return n;
|
|
}
|
|
|
|
/*
|
|
** Escape the input string if it is needed and in accordance with
|
|
** eEsc, which is either QRF_ESC_Ascii or QRF_ESC_Symbol.
|
|
**
|
|
** Escaping is needed if the string contains any control characters
|
|
** other than \t, \n, and \r\n
|
|
**
|
|
** If no escaping is needed (the common case) then set *ppOut to NULL
|
|
** and return 0. If escaping is needed, write the escaped string into
|
|
** memory obtained from sqlite3_malloc64() and make *ppOut point to that
|
|
** memory and return 0. If an error occurs, return non-zero.
|
|
**
|
|
** The caller is responsible for freeing *ppFree if it is non-NULL in order
|
|
** to reclaim memory.
|
|
*/
|
|
static void qrfEscape(
|
|
int eEsc, /* QRF_ESC_Ascii or QRF_ESC_Symbol */
|
|
sqlite3_str *pStr, /* String to be escaped */
|
|
int iStart /* Begin escapding on this byte of pStr */
|
|
){
|
|
sqlite3_int64 i, j; /* Loop counters */
|
|
sqlite3_int64 sz; /* Size of the string prior to escaping */
|
|
sqlite3_int64 nCtrl = 0;/* Number of control characters to escape */
|
|
unsigned char *zIn; /* Text to be escaped */
|
|
unsigned char c; /* A single character of the text */
|
|
unsigned char *zOut; /* Where to write the results */
|
|
|
|
/* Find the text to be escaped */
|
|
zIn = (unsigned char*)sqlite3_str_value(pStr);
|
|
if( zIn==0 ) return;
|
|
zIn += iStart;
|
|
|
|
/* Count the control characters */
|
|
for(i=0; (c = zIn[i])!=0; i++){
|
|
if( c<=0x1f
|
|
&& c!='\t'
|
|
&& c!='\n'
|
|
&& (c!='\r' || zIn[i+1]!='\n')
|
|
){
|
|
nCtrl++;
|
|
}
|
|
}
|
|
if( nCtrl==0 ) return; /* Early out if no control characters */
|
|
|
|
/* Make space to hold the escapes. Copy the original text to the end
|
|
** of the available space. */
|
|
sz = sqlite3_str_length(pStr) - iStart;
|
|
if( eEsc==QRF_ESC_Symbol ) nCtrl *= 2;
|
|
sqlite3_str_appendchar(pStr, nCtrl, ' ');
|
|
zOut = (unsigned char*)sqlite3_str_value(pStr);
|
|
if( zOut==0 ) return;
|
|
zOut += iStart;
|
|
zIn = zOut + nCtrl;
|
|
memmove(zIn,zOut,sz);
|
|
|
|
/* Convert the control characters */
|
|
for(i=j=0; (c = zIn[i])!=0; i++){
|
|
if( c>0x1f
|
|
|| c=='\t'
|
|
|| c=='\n'
|
|
|| (c=='\r' && zIn[i+1]=='\n')
|
|
){
|
|
continue;
|
|
}
|
|
if( i>0 ){
|
|
memmove(&zOut[j], zIn, i);
|
|
j += i;
|
|
}
|
|
zIn += i+1;
|
|
i = -1;
|
|
if( eEsc==QRF_ESC_Symbol ){
|
|
zOut[j++] = 0xe2;
|
|
zOut[j++] = 0x90;
|
|
zOut[j++] = 0x80+c;
|
|
}else{
|
|
zOut[j++] = '^';
|
|
zOut[j++] = 0x40+c;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
** If a field contains any character identified by a 1 in the following
|
|
** array, then the string must be quoted for CSV.
|
|
*/
|
|
static const char qrfCsvQuote[] = {
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
};
|
|
|
|
/*
|
|
** Encode text appropriately and append it to pOut.
|
|
*/
|
|
static void qrfEncodeText(Qrf *p, sqlite3_str *pOut, const char *zTxt){
|
|
int iStart = sqlite3_str_length(pOut);
|
|
switch( p->spec.eText ){
|
|
case QRF_TEXT_Sql: {
|
|
if( p->spec.eEsc==QRF_ESC_Off ){
|
|
sqlite3_str_appendf(pOut, "%Q", zTxt);
|
|
}else{
|
|
sqlite3_str_appendf(pOut, "%#Q", zTxt);
|
|
}
|
|
break;
|
|
}
|
|
case QRF_TEXT_Csv: {
|
|
unsigned int i;
|
|
for(i=0; zTxt[i]; i++){
|
|
if( qrfCsvQuote[((const unsigned char*)zTxt)[i]] ){
|
|
i = 0;
|
|
break;
|
|
}
|
|
}
|
|
if( i==0 || strstr(zTxt, p->spec.zColumnSep)!=0 ){
|
|
sqlite3_str_appendf(pOut, "\"%w\"", zTxt);
|
|
}else{
|
|
sqlite3_str_appendall(pOut, zTxt);
|
|
}
|
|
break;
|
|
}
|
|
case QRF_TEXT_Html: {
|
|
const unsigned char *z = (const unsigned char*)zTxt;
|
|
while( *z ){
|
|
unsigned int i = 0;
|
|
unsigned char c;
|
|
while( (c=z[i])>'>'
|
|
|| (c && c!='<' && c!='>' && c!='&' && c!='\"' && c!='\'')
|
|
){
|
|
i++;
|
|
}
|
|
if( i>0 ){
|
|
sqlite3_str_append(pOut, (const char*)z, i);
|
|
}
|
|
switch( z[i] ){
|
|
case '>': sqlite3_str_append(pOut, "<", 4); break;
|
|
case '&': sqlite3_str_append(pOut, "&", 5); break;
|
|
case '<': sqlite3_str_append(pOut, "<", 4); break;
|
|
case '"': sqlite3_str_append(pOut, """, 6); break;
|
|
case '\'': sqlite3_str_append(pOut, "'", 5); break;
|
|
default: i--;
|
|
}
|
|
z += i + 1;
|
|
}
|
|
break;
|
|
}
|
|
case QRF_TEXT_Tcl:
|
|
case QRF_TEXT_Json: {
|
|
const unsigned char *z = (const unsigned char*)zTxt;
|
|
sqlite3_str_append(pOut, "\"", 1);
|
|
while( *z ){
|
|
unsigned int i;
|
|
for(i=0; z[i]>=0x20 && z[i]!='\\' && z[i]!='"'; i++){}
|
|
if( i>0 ){
|
|
sqlite3_str_append(pOut, (const char*)z, i);
|
|
}
|
|
if( z[i]==0 ) break;
|
|
switch( z[i] ){
|
|
case '"': sqlite3_str_append(pOut, "\\\"", 2); break;
|
|
case '\\': sqlite3_str_append(pOut, "\\\\", 2); break;
|
|
case '\b': sqlite3_str_append(pOut, "\\b", 2); break;
|
|
case '\f': sqlite3_str_append(pOut, "\\f", 2); break;
|
|
case '\n': sqlite3_str_append(pOut, "\\n", 2); break;
|
|
case '\r': sqlite3_str_append(pOut, "\\r", 2); break;
|
|
case '\t': sqlite3_str_append(pOut, "\\t", 2); break;
|
|
default: {
|
|
if( p->spec.eText==QRF_TEXT_Json ){
|
|
sqlite3_str_appendf(pOut, "\\u%04x", z[i]);
|
|
}else{
|
|
sqlite3_str_appendf(pOut, "\\%03o", z[i]);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
z += i + 1;
|
|
}
|
|
sqlite3_str_append(pOut, "\"", 1);
|
|
break;
|
|
}
|
|
default: {
|
|
sqlite3_str_appendall(pOut, zTxt);
|
|
break;
|
|
}
|
|
}
|
|
if( p->spec.eEsc!=QRF_ESC_Off ){
|
|
qrfEscape(p->spec.eEsc, pOut, iStart);
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Do a quick sanity check to see aBlob[0..nBlob-1] is valid JSONB
|
|
** return true if it is and false if it is not.
|
|
**
|
|
** False positives are possible, but not false negatives.
|
|
*/
|
|
static int qrfJsonbQuickCheck(unsigned char *aBlob, int nBlob){
|
|
unsigned char x; /* Payload size half-byte */
|
|
int i; /* Loop counter */
|
|
int n; /* Bytes in the payload size integer */
|
|
sqlite3_uint64 sz; /* value of the payload size integer */
|
|
|
|
if( nBlob==0 ) return 0;
|
|
x = aBlob[0]>>4;
|
|
if( x<=11 ) return nBlob==(1+x);
|
|
n = x<14 ? x-11 : 4*(x-13);
|
|
if( nBlob<1+n ) return 0;
|
|
sz = aBlob[1];
|
|
for(i=1; i<n; i++) sz = (sz<<8) + aBlob[i+1];
|
|
return sz+n+1==(sqlite3_uint64)nBlob;
|
|
}
|
|
|
|
/*
|
|
** The current iCol-th column of p->pStmt is known to be a BLOB. Check
|
|
** to see if that BLOB is really a JSONB blob. If it is, then translate
|
|
** it into a text JSON representation and return a pointer to that text JSON.
|
|
** If the BLOB is not JSONB, then return a NULL pointer.
|
|
**
|
|
** The memory used to hold the JSON text is managed internally by the
|
|
** "p" object and is overwritten and/or deallocated upon the next call
|
|
** to this routine (with the same p argument) or when the p object is
|
|
** finailized.
|
|
*/
|
|
static const char *qrfJsonbToJson(Qrf *p, int iCol){
|
|
int nByte;
|
|
const void *pBlob;
|
|
int rc;
|
|
nByte = sqlite3_column_bytes(p->pStmt, iCol);
|
|
pBlob = sqlite3_column_blob(p->pStmt, iCol);
|
|
if( qrfJsonbQuickCheck((unsigned char*)pBlob, nByte)==0 ){
|
|
return 0;
|
|
}
|
|
if( p->pJTrans==0 ){
|
|
sqlite3 *db;
|
|
rc = sqlite3_open(":memory:",&db);
|
|
if( rc ){
|
|
sqlite3_close(db);
|
|
return 0;
|
|
}
|
|
rc = sqlite3_prepare_v2(db, "SELECT json(?1)", -1, &p->pJTrans, 0);
|
|
if( rc ){
|
|
sqlite3_finalize(p->pJTrans);
|
|
p->pJTrans = 0;
|
|
sqlite3_close(db);
|
|
return 0;
|
|
}
|
|
}else{
|
|
sqlite3_reset(p->pJTrans);
|
|
}
|
|
sqlite3_bind_blob(p->pJTrans, 1, (void*)pBlob, nByte, SQLITE_STATIC);
|
|
rc = sqlite3_step(p->pJTrans);
|
|
if( rc==SQLITE_ROW ){
|
|
return (const char*)sqlite3_column_text(p->pJTrans, 0);
|
|
}else{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Render value pVal into pOut
|
|
*/
|
|
static void qrfRenderValue(Qrf *p, sqlite3_str *pOut, int iCol){
|
|
#if SQLITE_VERSION_NUMBER>=3052000
|
|
int iStartLen = sqlite3_str_length(pOut);
|
|
#endif
|
|
if( p->spec.xRender ){
|
|
sqlite3_value *pVal;
|
|
char *z;
|
|
pVal = sqlite3_value_dup(sqlite3_column_value(p->pStmt,iCol));
|
|
z = p->spec.xRender(p->spec.pRenderArg, pVal);
|
|
sqlite3_value_free(pVal);
|
|
if( z ){
|
|
sqlite3_str_appendall(pOut, z);
|
|
sqlite3_free(z);
|
|
return;
|
|
}
|
|
}
|
|
switch( sqlite3_column_type(p->pStmt,iCol) ){
|
|
case SQLITE_INTEGER: {
|
|
sqlite3_str_appendf(pOut, "%lld", sqlite3_column_int64(p->pStmt,iCol));
|
|
break;
|
|
}
|
|
case SQLITE_FLOAT: {
|
|
const char *zTxt = (const char*)sqlite3_column_text(p->pStmt,iCol);
|
|
sqlite3_str_appendall(pOut, zTxt);
|
|
break;
|
|
}
|
|
case SQLITE_BLOB: {
|
|
if( p->spec.bTextJsonb==QRF_Yes ){
|
|
const char *zJson = qrfJsonbToJson(p, iCol);
|
|
if( zJson ){
|
|
if( p->spec.eText==QRF_TEXT_Sql ){
|
|
sqlite3_str_append(pOut,"jsonb(",6);
|
|
qrfEncodeText(p, pOut, zJson);
|
|
sqlite3_str_append(pOut,")",1);
|
|
}else{
|
|
qrfEncodeText(p, pOut, zJson);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
switch( p->spec.eBlob ){
|
|
case QRF_BLOB_Hex:
|
|
case QRF_BLOB_Sql: {
|
|
int iStart;
|
|
int nBlob = sqlite3_column_bytes(p->pStmt,iCol);
|
|
int i, j;
|
|
char *zVal;
|
|
const unsigned char *a = sqlite3_column_blob(p->pStmt,iCol);
|
|
if( p->spec.eBlob==QRF_BLOB_Sql ){
|
|
sqlite3_str_append(pOut, "x'", 2);
|
|
}
|
|
iStart = sqlite3_str_length(pOut);
|
|
sqlite3_str_appendchar(pOut, nBlob, ' ');
|
|
sqlite3_str_appendchar(pOut, nBlob, ' ');
|
|
if( p->spec.eBlob==QRF_BLOB_Sql ){
|
|
sqlite3_str_appendchar(pOut, 1, '\'');
|
|
}
|
|
if( sqlite3_str_errcode(pOut) ) return;
|
|
zVal = sqlite3_str_value(pOut);
|
|
for(i=0, j=iStart; i<nBlob; i++, j+=2){
|
|
unsigned char c = a[i];
|
|
zVal[j] = "0123456789abcdef"[(c>>4)&0xf];
|
|
zVal[j+1] = "0123456789abcdef"[(c)&0xf];
|
|
}
|
|
break;
|
|
}
|
|
case QRF_BLOB_Tcl:
|
|
case QRF_BLOB_Json: {
|
|
int iStart;
|
|
int nBlob = sqlite3_column_bytes(p->pStmt,iCol);
|
|
int i, j;
|
|
char *zVal;
|
|
const unsigned char *a = sqlite3_column_blob(p->pStmt,iCol);
|
|
int szC = p->spec.eBlob==QRF_BLOB_Json ? 6 : 4;
|
|
sqlite3_str_append(pOut, "\"", 1);
|
|
iStart = sqlite3_str_length(pOut);
|
|
for(i=szC; i>0; i--){
|
|
sqlite3_str_appendchar(pOut, nBlob, ' ');
|
|
}
|
|
sqlite3_str_appendchar(pOut, 1, '"');
|
|
if( sqlite3_str_errcode(pOut) ) return;
|
|
zVal = sqlite3_str_value(pOut);
|
|
for(i=0, j=iStart; i<nBlob; i++, j+=szC){
|
|
unsigned char c = a[i];
|
|
zVal[j] = '\\';
|
|
if( szC==4 ){
|
|
zVal[j+1] = '0' + ((c>>6)&3);
|
|
zVal[j+2] = '0' + ((c>>3)&7);
|
|
zVal[j+3] = '0' + (c&7);
|
|
}else{
|
|
zVal[j+1] = 'u';
|
|
zVal[j+2] = '0';
|
|
zVal[j+3] = '0';
|
|
zVal[j+4] = "0123456789abcdef"[(c>>4)&0xf];
|
|
zVal[j+5] = "0123456789abcdef"[(c)&0xf];
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case QRF_BLOB_Size: {
|
|
int nBlob = sqlite3_column_bytes(p->pStmt,iCol);
|
|
sqlite3_str_appendf(pOut, "(%d-byte blob)", nBlob);
|
|
break;
|
|
}
|
|
default: {
|
|
const char *zTxt = (const char*)sqlite3_column_text(p->pStmt,iCol);
|
|
qrfEncodeText(p, pOut, zTxt);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SQLITE_NULL: {
|
|
sqlite3_str_appendall(pOut, p->spec.zNull);
|
|
break;
|
|
}
|
|
case SQLITE_TEXT: {
|
|
const char *zTxt = (const char*)sqlite3_column_text(p->pStmt,iCol);
|
|
qrfEncodeText(p, pOut, zTxt);
|
|
break;
|
|
}
|
|
}
|
|
#if SQLITE_VERSION_NUMBER>=3052000
|
|
if( p->spec.nCharLimit>0
|
|
&& (sqlite3_str_length(pOut) - iStartLen) > p->spec.nCharLimit
|
|
){
|
|
const unsigned char *z;
|
|
int ii = 0, w = 0, limit = p->spec.nCharLimit;
|
|
z = (const unsigned char*)sqlite3_str_value(pOut) + iStartLen;
|
|
if( limit<4 ) limit = 4;
|
|
while( 1 ){
|
|
if( z[ii]<' ' ){
|
|
int k;
|
|
if( z[ii]=='\033' && (k = qrfIsVt100(z+ii))>0 ){
|
|
ii += k;
|
|
}else if( z[ii]==0 ){
|
|
break;
|
|
}else{
|
|
ii++;
|
|
}
|
|
}else if( (0x80&z[ii])==0 ){
|
|
w++;
|
|
if( w>limit ) break;
|
|
ii++;
|
|
}else{
|
|
int u = 0;
|
|
int len = sqlite3_qrf_decode_utf8(&z[ii], &u);
|
|
w += sqlite3_qrf_wcwidth(u);
|
|
if( w>limit ) break;
|
|
ii += len;
|
|
}
|
|
}
|
|
if( w>limit ){
|
|
sqlite3_str_truncate(pOut, iStartLen+ii);
|
|
sqlite3_str_append(pOut, "...", 3);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* Trim spaces of the end if pOut
|
|
*/
|
|
static void qrfRTrim(sqlite3_str *pOut){
|
|
#if SQLITE_VERSION_NUMBER>=3052000
|
|
int nByte = sqlite3_str_length(pOut);
|
|
const char *zOut = sqlite3_str_value(pOut);
|
|
while( nByte>0 && zOut[nByte-1]==' ' ){ nByte--; }
|
|
sqlite3_str_truncate(pOut, nByte);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
** Store string zUtf to pOut as w characters. If w is negative,
|
|
** then right-justify the text. W is the width in display characters, not
|
|
** in bytes. Double-width unicode characters count as two characters.
|
|
** VT100 escape sequences count as zero. And so forth.
|
|
*/
|
|
static void qrfWidthPrint(Qrf *p, sqlite3_str *pOut, int w, const char *zUtf){
|
|
const unsigned char *a = (const unsigned char*)zUtf;
|
|
static const int mxW = 10000000;
|
|
unsigned char c;
|
|
int i = 0;
|
|
int n = 0;
|
|
int k;
|
|
int aw;
|
|
(void)p;
|
|
if( w<-mxW ){
|
|
w = -mxW;
|
|
}else if( w>mxW ){
|
|
w= mxW;
|
|
}
|
|
aw = w<0 ? -w : w;
|
|
if( a==0 ) a = (const unsigned char*)"";
|
|
while( (c = a[i])!=0 ){
|
|
if( (c&0xc0)==0xc0 ){
|
|
int u;
|
|
int len = sqlite3_qrf_decode_utf8(a+i, &u);
|
|
int x = sqlite3_qrf_wcwidth(u);
|
|
if( x+n>aw ){
|
|
break;
|
|
}
|
|
i += len;
|
|
n += x;
|
|
}else if( c==0x1b && (k = qrfIsVt100(&a[i]))>0 ){
|
|
i += k;
|
|
}else if( n>=aw ){
|
|
break;
|
|
}else{
|
|
n++;
|
|
i++;
|
|
}
|
|
}
|
|
if( n>=aw ){
|
|
sqlite3_str_append(pOut, zUtf, i);
|
|
}else if( w<0 ){
|
|
if( aw>n ) sqlite3_str_appendchar(pOut, aw-n, ' ');
|
|
sqlite3_str_append(pOut, zUtf, i);
|
|
}else{
|
|
sqlite3_str_append(pOut, zUtf, i);
|
|
if( aw>n ) sqlite3_str_appendchar(pOut, aw-n, ' ');
|
|
}
|
|
}
|
|
|
|
/*
|
|
** (*pz)[] is a line of text that is to be displayed the box or table or
|
|
** similar tabular formats. z[] contain newlines or might be too wide
|
|
** to fit in the columns so will need to be split into multiple line.
|
|
**
|
|
** This routine determines:
|
|
**
|
|
** * How many bytes of z[] should be shown on the current line.
|
|
** * How many character positions those bytes will cover.
|
|
** * The byte offset to the start of the next line.
|
|
*/
|
|
static void qrfWrapLine(
|
|
const char *zIn, /* Input text to be displayed */
|
|
int w, /* Column width in characters (not bytes) */
|
|
int bWrap, /* True if we should do word-wrapping */
|
|
int *pnThis, /* OUT: How many bytes of z[] for the current line */
|
|
int *pnWide, /* OUT: How wide is the text of this line */
|
|
int *piNext /* OUT: Offset into z[] to start of the next line */
|
|
){
|
|
int i; /* Input bytes consumed */
|
|
int k; /* Bytes in a VT100 code */
|
|
int n; /* Output column number */
|
|
const unsigned char *z = (const unsigned char*)zIn;
|
|
unsigned char c = 0;
|
|
|
|
if( z[0]==0 ){
|
|
*pnThis = 0;
|
|
*pnWide = 0;
|
|
*piNext = 0;
|
|
return;
|
|
}
|
|
n = 0;
|
|
for(i=0; n<=w; i++){
|
|
c = z[i];
|
|
if( c>=0xc0 ){
|
|
int u;
|
|
int len = sqlite3_qrf_decode_utf8(&z[i], &u);
|
|
int wcw = sqlite3_qrf_wcwidth(u);
|
|
if( wcw+n>w ) break;
|
|
i += len-1;
|
|
n += wcw;
|
|
continue;
|
|
}
|
|
if( c>=' ' ){
|
|
if( n==w ) break;
|
|
n++;
|
|
continue;
|
|
}
|
|
if( c==0 || c=='\n' ) break;
|
|
if( c=='\r' && z[i+1]=='\n' ){ c = z[++i]; break; }
|
|
if( c=='\t' ){
|
|
int wcw = 8 - (n&7);
|
|
if( n+wcw>w ) break;
|
|
n += wcw;
|
|
continue;
|
|
}
|
|
if( c==0x1b && (k = qrfIsVt100(&z[i]))>0 ){
|
|
i += k-1;
|
|
}else if( n==w ){
|
|
break;
|
|
}else{
|
|
n++;
|
|
}
|
|
}
|
|
if( c==0 ){
|
|
*pnThis = i;
|
|
*pnWide = n;
|
|
*piNext = i;
|
|
return;
|
|
}
|
|
if( c=='\n' ){
|
|
*pnThis = i;
|
|
*pnWide = n;
|
|
*piNext = i+1;
|
|
return;
|
|
}
|
|
|
|
/* If we get this far, that means the current line will end at some
|
|
** point that is neither a "\n" or a 0x00. Figure out where that
|
|
** split should occur
|
|
*/
|
|
if( bWrap && z[i]!=0 && !qrfSpace(z[i]) && qrfAlnum(c)==qrfAlnum(z[i]) ){
|
|
/* Perhaps try to back up to a better place to break the line */
|
|
for(k=i-1; k>=i/2; k--){
|
|
if( qrfSpace(z[k]) ) break;
|
|
}
|
|
if( k<i/2 ){
|
|
for(k=i; k>=i/2; k--){
|
|
if( qrfAlnum(z[k-1])!=qrfAlnum(z[k]) && (z[k]&0xc0)!=0x80 ) break;
|
|
}
|
|
}
|
|
if( k>=i/2 ){
|
|
i = k;
|
|
n = qrfDisplayWidth((const char*)z, k, 0);
|
|
}
|
|
}
|
|
*pnThis = i;
|
|
*pnWide = n;
|
|
while( zIn[i]==' ' || zIn[i]=='\t' || zIn[i]=='\r' ){ i++; }
|
|
*piNext = i;
|
|
}
|
|
|
|
/*
|
|
** Append nVal bytes of text from zVal onto the end of pOut.
|
|
** Convert tab characters in zVal to the appropriate number of
|
|
** spaces.
|
|
*/
|
|
static void qrfAppendWithTabs(
|
|
sqlite3_str *pOut, /* Append text here */
|
|
const char *zVal, /* Text to append */
|
|
int nVal /* Use only the first nVal bytes of zVal[] */
|
|
){
|
|
int i = 0;
|
|
unsigned int col = 0;
|
|
unsigned char *z = (unsigned char *)zVal;
|
|
while( i<nVal ){
|
|
unsigned char c = z[i];
|
|
if( c<' ' ){
|
|
int k;
|
|
sqlite3_str_append(pOut, (const char*)z, i);
|
|
nVal -= i;
|
|
z += i;
|
|
i = 0;
|
|
if( c=='\033' && (k = qrfIsVt100(z))>0 ){
|
|
sqlite3_str_append(pOut, (const char*)z, k);
|
|
z += k;
|
|
nVal -= k;
|
|
}else if( c=='\t' ){
|
|
k = 8 - (col&7);
|
|
sqlite3_str_appendchar(pOut, k, ' ');
|
|
col += k;
|
|
z++;
|
|
nVal--;
|
|
}else if( c=='\r' && nVal==1 ){
|
|
z++;
|
|
nVal--;
|
|
}else{
|
|
char zCtrlPik[4];
|
|
col++;
|
|
zCtrlPik[0] = 0xe2;
|
|
zCtrlPik[1] = 0x90;
|
|
zCtrlPik[2] = 0x80+c;
|
|
sqlite3_str_append(pOut, zCtrlPik, 3);
|
|
z++;
|
|
nVal--;
|
|
}
|
|
}else if( (0x80&c)==0 ){
|
|
i++;
|
|
col++;
|
|
}else{
|
|
int u = 0;
|
|
int len = sqlite3_qrf_decode_utf8(&z[i], &u);
|
|
i += len;
|
|
col += sqlite3_qrf_wcwidth(u);
|
|
}
|
|
}
|
|
sqlite3_str_append(pOut, (const char*)z, i);
|
|
}
|
|
|
|
/*
|
|
** GCC does not define the offsetof() macro so we'll have to do it
|
|
** ourselves.
|
|
*/
|
|
#ifndef offsetof
|
|
# define offsetof(ST,M) ((size_t)((char*)&((ST*)0)->M - (char*)0))
|
|
#endif
|
|
|
|
/*
|
|
** Data for columnar layout, collected into a single object so
|
|
** that it can be more easily passed into subroutines.
|
|
*/
|
|
typedef struct qrfColData qrfColData;
|
|
struct qrfColData {
|
|
Qrf *p; /* The QRF instance */
|
|
int nCol; /* Number of columns in the table */
|
|
unsigned char bMultiRow; /* One or more cells will span multiple lines */
|
|
unsigned char nMargin; /* Width of column margins */
|
|
sqlite3_int64 nRow; /* Number of rows */
|
|
sqlite3_int64 nAlloc; /* Number of cells allocated */
|
|
sqlite3_int64 n; /* Number of cells. nCol*nRow */
|
|
char **az; /* Content of all cells */
|
|
int *aiWth; /* Width of each cell */
|
|
unsigned char *abNum; /* True for each numeric cell */
|
|
struct qrfPerCol { /* Per-column data */
|
|
char *z; /* Cache of text for current row */
|
|
int w; /* Computed width of this column */
|
|
int mxW; /* Maximum natural (unwrapped) width */
|
|
unsigned char e; /* Alignment */
|
|
unsigned char fx; /* Width is fixed */
|
|
unsigned char bNum; /* True if is numeric */
|
|
} *a; /* One per column */
|
|
};
|
|
|
|
/*
|
|
** Output horizontally justified text into pOut. The text is the
|
|
** first nVal bytes of zVal. Include nWS bytes of whitespace, either
|
|
** split between both sides, or on the left, or on the right, depending
|
|
** on eAlign.
|
|
*/
|
|
static void qrfPrintAligned(
|
|
sqlite3_str *pOut, /* Append text here */
|
|
struct qrfPerCol *pCol, /* Information about the text to print */
|
|
int nVal, /* Use only the first nVal bytes of zVal[] */
|
|
int nWS /* Whitespace for horizonal alignment */
|
|
){
|
|
unsigned char eAlign = pCol->e & QRF_ALIGN_HMASK;
|
|
if( eAlign==QRF_Auto && pCol->bNum ) eAlign = QRF_ALIGN_Right;
|
|
if( eAlign==QRF_ALIGN_Center ){
|
|
/* Center the text */
|
|
sqlite3_str_appendchar(pOut, nWS/2, ' ');
|
|
qrfAppendWithTabs(pOut, pCol->z, nVal);
|
|
sqlite3_str_appendchar(pOut, nWS - nWS/2, ' ');
|
|
}else if( eAlign==QRF_ALIGN_Right ){
|
|
/* Right justify the text */
|
|
sqlite3_str_appendchar(pOut, nWS, ' ');
|
|
qrfAppendWithTabs(pOut, pCol->z, nVal);
|
|
}else{
|
|
/* Left justify the text */
|
|
qrfAppendWithTabs(pOut, pCol->z, nVal);
|
|
sqlite3_str_appendchar(pOut, nWS, ' ');
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Free all the memory allocates in the qrfColData object
|
|
*/
|
|
static void qrfColDataFree(qrfColData *p){
|
|
sqlite3_int64 i;
|
|
for(i=0; i<p->n; i++) sqlite3_free(p->az[i]);
|
|
sqlite3_free(p->az);
|
|
sqlite3_free(p->aiWth);
|
|
sqlite3_free(p->abNum);
|
|
sqlite3_free(p->a);
|
|
memset(p, 0, sizeof(*p));
|
|
}
|
|
|
|
/*
|
|
** Allocate space for more cells in the qrfColData object.
|
|
** Return non-zero if a memory allocation fails.
|
|
*/
|
|
static int qrfColDataEnlarge(qrfColData *p){
|
|
char **azData;
|
|
int *aiWth;
|
|
unsigned char *abNum;
|
|
p->nAlloc = 2*p->nAlloc + 10*p->nCol;
|
|
azData = sqlite3_realloc64(p->az, p->nAlloc*sizeof(char*));
|
|
if( azData==0 ){
|
|
qrfOom(p->p);
|
|
qrfColDataFree(p);
|
|
return 1;
|
|
}
|
|
p->az = azData;
|
|
aiWth = sqlite3_realloc64(p->aiWth, p->nAlloc*sizeof(int));
|
|
if( aiWth==0 ){
|
|
qrfOom(p->p);
|
|
qrfColDataFree(p);
|
|
return 1;
|
|
}
|
|
p->aiWth = aiWth;
|
|
abNum = sqlite3_realloc64(p->abNum, p->nAlloc);
|
|
if( abNum==0 ){
|
|
qrfOom(p->p);
|
|
qrfColDataFree(p);
|
|
return 1;
|
|
}
|
|
p->abNum = abNum;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
** Print a markdown or table-style row separator using ascii-art
|
|
*/
|
|
static void qrfRowSeparator(sqlite3_str *pOut, qrfColData *p, char cSep){
|
|
int i;
|
|
if( p->nCol>0 ){
|
|
int useBorder = p->p->spec.bBorder!=QRF_No;
|
|
if( useBorder ){
|
|
sqlite3_str_append(pOut, &cSep, 1);
|
|
}
|
|
sqlite3_str_appendchar(pOut, p->a[0].w+p->nMargin, '-');
|
|
for(i=1; i<p->nCol; i++){
|
|
sqlite3_str_append(pOut, &cSep, 1);
|
|
sqlite3_str_appendchar(pOut, p->a[i].w+p->nMargin, '-');
|
|
}
|
|
if( useBorder ){
|
|
sqlite3_str_append(pOut, &cSep, 1);
|
|
}
|
|
}
|
|
sqlite3_str_append(pOut, "\n", 1);
|
|
}
|
|
|
|
/*
|
|
** UTF8 box-drawing characters. Imagine box lines like this:
|
|
**
|
|
** 1
|
|
** |
|
|
** 4 --+-- 2
|
|
** |
|
|
** 3
|
|
**
|
|
** Each box characters has between 2 and 4 of the lines leading from
|
|
** the center. The characters are here identified by the numbers of
|
|
** their corresponding lines.
|
|
*/
|
|
#define BOX_24 "\342\224\200" /* U+2500 --- */
|
|
#define BOX_13 "\342\224\202" /* U+2502 | */
|
|
#define BOX_23 "\342\224\214" /* U+250c ,- */
|
|
#define BOX_34 "\342\224\220" /* U+2510 -, */
|
|
#define BOX_12 "\342\224\224" /* U+2514 '- */
|
|
#define BOX_14 "\342\224\230" /* U+2518 -' */
|
|
#define BOX_123 "\342\224\234" /* U+251c |- */
|
|
#define BOX_134 "\342\224\244" /* U+2524 -| */
|
|
#define BOX_234 "\342\224\254" /* U+252c -,- */
|
|
#define BOX_124 "\342\224\264" /* U+2534 -'- */
|
|
#define BOX_1234 "\342\224\274" /* U+253c -|- */
|
|
|
|
/* Draw horizontal line N characters long using unicode box
|
|
** characters
|
|
*/
|
|
static void qrfBoxLine(sqlite3_str *pOut, int N){
|
|
const char zDash[] =
|
|
BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24
|
|
BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24;
|
|
const int nDash = sizeof(zDash) - 1;
|
|
N *= 3;
|
|
while( N>nDash ){
|
|
sqlite3_str_append(pOut, zDash, nDash);
|
|
N -= nDash;
|
|
}
|
|
sqlite3_str_append(pOut, zDash, N);
|
|
}
|
|
|
|
/*
|
|
** Draw a horizontal separator for a QRF_STYLE_Box table.
|
|
*/
|
|
static void qrfBoxSeparator(
|
|
sqlite3_str *pOut,
|
|
qrfColData *p,
|
|
const char *zSep1,
|
|
const char *zSep2,
|
|
const char *zSep3
|
|
){
|
|
int i;
|
|
if( p->nCol>0 ){
|
|
int useBorder = p->p->spec.bBorder!=QRF_No;
|
|
if( useBorder ){
|
|
sqlite3_str_appendall(pOut, zSep1);
|
|
}
|
|
qrfBoxLine(pOut, p->a[0].w+p->nMargin);
|
|
for(i=1; i<p->nCol; i++){
|
|
sqlite3_str_appendall(pOut, zSep2);
|
|
qrfBoxLine(pOut, p->a[i].w+p->nMargin);
|
|
}
|
|
if( useBorder ){
|
|
sqlite3_str_appendall(pOut, zSep3);
|
|
}
|
|
}
|
|
sqlite3_str_append(pOut, "\n", 1);
|
|
}
|
|
|
|
/*
|
|
** Load into pData the default alignment for the body of a table.
|
|
*/
|
|
static void qrfLoadAlignment(qrfColData *pData, Qrf *p){
|
|
sqlite3_int64 i;
|
|
for(i=0; i<pData->nCol; i++){
|
|
pData->a[i].e = p->spec.eDfltAlign;
|
|
if( i<p->spec.nAlign ){
|
|
unsigned char ax = p->spec.aAlign[i];
|
|
if( (ax & QRF_ALIGN_HMASK)!=0 ){
|
|
pData->a[i].e = (ax & QRF_ALIGN_HMASK) |
|
|
(pData->a[i].e & QRF_ALIGN_VMASK);
|
|
}
|
|
}else if( i<p->spec.nWidth ){
|
|
if( p->spec.aWidth[i]<0 ){
|
|
pData->a[i].e = QRF_ALIGN_Right |
|
|
(pData->a[i].e & QRF_ALIGN_VMASK);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
** If the single column in pData->a[] with pData->n entries can be
|
|
** laid out as nCol columns with a 2-space gap between each such
|
|
** that all columns fit within nSW, then return a pointer to an array
|
|
** of integers which is the width of each column from left to right.
|
|
**
|
|
** If the layout is not possible, return a NULL pointer.
|
|
**
|
|
** Space to hold the returned array is from sqlite_malloc64().
|
|
*/
|
|
static int *qrfValidLayout(
|
|
qrfColData *pData, /* Collected query results */
|
|
Qrf *p, /* On which to report an OOM */
|
|
int nCol, /* Attempt this many columns */
|
|
int nSW /* Screen width */
|
|
){
|
|
int i; /* Loop counter */
|
|
int nr; /* Number of rows */
|
|
int w = 0; /* Width of the current column */
|
|
int t; /* Total width of all columns */
|
|
int *aw; /* Array of individual column widths */
|
|
|
|
aw = sqlite3_malloc64( sizeof(int)*nCol );
|
|
if( aw==0 ){
|
|
qrfOom(p);
|
|
return 0;
|
|
}
|
|
nr = (pData->n + nCol - 1)/nCol;
|
|
for(i=0; i<pData->n; i++){
|
|
if( (i%nr)==0 ){
|
|
if( i>0 ) aw[i/nr-1] = w;
|
|
w = pData->aiWth[i];
|
|
}else if( pData->aiWth[i]>w ){
|
|
w = pData->aiWth[i];
|
|
}
|
|
}
|
|
aw[nCol-1] = w;
|
|
for(t=i=0; i<nCol; i++) t += aw[i];
|
|
t += 2*(nCol-1);
|
|
if( t>nSW ){
|
|
sqlite3_free(aw);
|
|
return 0;
|
|
}
|
|
return aw;
|
|
}
|
|
|
|
/*
|
|
** The output is single-column and the bSplitColumn flag is set.
|
|
** Check to see if the single-column output can be split into multiple
|
|
** columns that appear side-by-side. Adjust pData appropriately.
|
|
*/
|
|
static void qrfSplitColumn(qrfColData *pData, Qrf *p){
|
|
int nCol = 1;
|
|
int *aw = 0;
|
|
char **az = 0;
|
|
int *aiWth = 0;
|
|
unsigned char *abNum = 0;
|
|
int nColNext = 2;
|
|
int w;
|
|
struct qrfPerCol *a = 0;
|
|
sqlite3_int64 nRow = 1;
|
|
sqlite3_int64 i;
|
|
while( 1/*exit-by-break*/ ){
|
|
int *awNew = qrfValidLayout(pData, p, nColNext, p->spec.nScreenWidth);
|
|
if( awNew==0 ) break;
|
|
sqlite3_free(aw);
|
|
aw = awNew;
|
|
nCol = nColNext;
|
|
nRow = (pData->n + nCol - 1)/nCol;
|
|
if( nRow==1 ) break;
|
|
nColNext++;
|
|
while( (pData->n + nColNext - 1)/nColNext == nRow ) nColNext++;
|
|
}
|
|
if( nCol==1 ){
|
|
sqlite3_free(aw);
|
|
return; /* Cannot do better than 1 column */
|
|
}
|
|
az = sqlite3_malloc64( nRow*nCol*sizeof(char*) );
|
|
if( az==0 ){
|
|
qrfOom(p);
|
|
return;
|
|
}
|
|
aiWth = sqlite3_malloc64( nRow*nCol*sizeof(int) );
|
|
if( aiWth==0 ){
|
|
sqlite3_free(az);
|
|
qrfOom(p);
|
|
return;
|
|
}
|
|
a = sqlite3_malloc64( nCol*sizeof(struct qrfPerCol) );
|
|
if( a==0 ){
|
|
sqlite3_free(az);
|
|
sqlite3_free(aiWth);
|
|
qrfOom(p);
|
|
return;
|
|
}
|
|
abNum = sqlite3_malloc64( nRow*nCol );
|
|
if( abNum==0 ){
|
|
sqlite3_free(az);
|
|
sqlite3_free(aiWth);
|
|
sqlite3_free(a);
|
|
qrfOom(p);
|
|
return;
|
|
}
|
|
for(i=0; i<pData->n; i++){
|
|
sqlite3_int64 j = (i%nRow)*nCol + (i/nRow);
|
|
az[j] = pData->az[i];
|
|
abNum[j]= pData->abNum[i];
|
|
pData->az[i] = 0;
|
|
aiWth[j] = pData->aiWth[i];
|
|
}
|
|
while( i<nRow*nCol ){
|
|
sqlite3_int64 j = (i%nRow)*nCol + (i/nRow);
|
|
az[j] = sqlite3_mprintf("");
|
|
if( az[j]==0 ) qrfOom(p);
|
|
aiWth[j] = 0;
|
|
abNum[j] = 0;
|
|
i++;
|
|
}
|
|
for(i=0; i<nCol; i++){
|
|
a[i].fx = a[i].mxW = a[i].w = aw[i];
|
|
a[i].e = pData->a[0].e;
|
|
}
|
|
sqlite3_free(pData->az);
|
|
sqlite3_free(pData->aiWth);
|
|
sqlite3_free(pData->a);
|
|
sqlite3_free(pData->abNum);
|
|
sqlite3_free(aw);
|
|
pData->az = az;
|
|
pData->aiWth = aiWth;
|
|
pData->a = a;
|
|
pData->abNum = abNum;
|
|
pData->nCol = nCol;
|
|
pData->n = pData->nAlloc = nRow*nCol;
|
|
for(i=w=0; i<nCol; i++) w += a[i].w;
|
|
pData->nMargin = (p->spec.nScreenWidth - w)/(nCol - 1);
|
|
if( pData->nMargin>5 ) pData->nMargin = 5;
|
|
}
|
|
|
|
/*
|
|
** Adjust the layout for the screen width restriction
|
|
*/
|
|
static void qrfRestrictScreenWidth(qrfColData *pData, Qrf *p){
|
|
int sepW; /* Width of all box separators and margins */
|
|
int sumW; /* Total width of data area over all columns */
|
|
int targetW; /* Desired total data area */
|
|
int i; /* Loop counters */
|
|
int nCol; /* Number of columns */
|
|
|
|
pData->nMargin = 2; /* Default to normal margins */
|
|
if( p->spec.nScreenWidth==0 ) return;
|
|
if( p->spec.eStyle==QRF_STYLE_Column ){
|
|
sepW = pData->nCol*2 - 2;
|
|
}else{
|
|
sepW = pData->nCol*3 + 1;
|
|
if( p->spec.bBorder==QRF_No ) sepW -= 2;
|
|
}
|
|
nCol = pData->nCol;
|
|
for(i=sumW=0; i<nCol; i++) sumW += pData->a[i].w;
|
|
if( p->spec.nScreenWidth >= sumW+sepW ) return;
|
|
|
|
/* First thing to do is reduce the separation between columns */
|
|
pData->nMargin = 0;
|
|
if( p->spec.eStyle==QRF_STYLE_Column ){
|
|
sepW = pData->nCol - 1;
|
|
}else{
|
|
sepW = pData->nCol + 1;
|
|
if( p->spec.bBorder==QRF_No ) sepW -= 2;
|
|
}
|
|
targetW = p->spec.nScreenWidth - sepW;
|
|
|
|
#define MIN_SQUOZE 8
|
|
#define MIN_EX_SQUOZE 16
|
|
/* Reduce the width of the widest eligible column. A column is
|
|
** eligible for narrowing if:
|
|
**
|
|
** * It is not a fixed-width column (a[0].fx is false)
|
|
** * The current width is more than MIN_SQUOZE
|
|
** * Either:
|
|
** + The current width is more then MIN_EX_SQUOZE, or
|
|
** + The current width is more than half the max width (a[].mxW)
|
|
**
|
|
** Keep making reductions until either no more reductions are
|
|
** possible or until the size target is reached.
|
|
*/
|
|
while( sumW > targetW ){
|
|
int gain, w;
|
|
int ix = -1;
|
|
int mx = 0;
|
|
for(i=0; i<nCol; i++){
|
|
if( pData->a[i].fx==0
|
|
&& (w = pData->a[i].w)>mx
|
|
&& w>MIN_SQUOZE
|
|
&& (w>MIN_EX_SQUOZE || w*2>pData->a[i].mxW)
|
|
){
|
|
ix = i;
|
|
mx = w;
|
|
}
|
|
}
|
|
if( ix<0 ) break;
|
|
if( mx>=MIN_SQUOZE*2 ){
|
|
gain = mx/2;
|
|
}else{
|
|
gain = mx - MIN_SQUOZE;
|
|
}
|
|
if( sumW - gain < targetW ){
|
|
gain = sumW - targetW;
|
|
}
|
|
sumW -= gain;
|
|
pData->a[ix].w -= gain;
|
|
pData->bMultiRow = 1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Columnar modes require that the entire query be evaluated first, with
|
|
** results written into memory, so that we can compute appropriate column
|
|
** widths.
|
|
*/
|
|
static void qrfColumnar(Qrf *p){
|
|
sqlite3_int64 i, j; /* Loop counters */
|
|
const char *colSep = 0; /* Column separator text */
|
|
const char *rowSep = 0; /* Row terminator text */
|
|
const char *rowStart = 0; /* Row start text */
|
|
int szColSep, szRowSep, szRowStart; /* Size in bytes of previous 3 */
|
|
int rc; /* Result code */
|
|
int nColumn = p->nCol; /* Number of columns */
|
|
int bWW; /* True to do word-wrap */
|
|
sqlite3_str *pStr; /* Temporary rendering */
|
|
qrfColData data; /* Columnar layout data */
|
|
int bRTrim; /* Trim trailing space */
|
|
|
|
rc = sqlite3_step(p->pStmt);
|
|
if( rc!=SQLITE_ROW || nColumn==0 ){
|
|
return; /* No output */
|
|
}
|
|
|
|
/* Initialize the data container */
|
|
memset(&data, 0, sizeof(data));
|
|
data.nCol = p->nCol;
|
|
data.p = p;
|
|
data.a = sqlite3_malloc64( nColumn*sizeof(struct qrfPerCol) );
|
|
if( data.a==0 ){
|
|
qrfOom(p);
|
|
return;
|
|
}
|
|
memset(data.a, 0, nColumn*sizeof(struct qrfPerCol) );
|
|
if( qrfColDataEnlarge(&data) ) return;
|
|
assert( data.az!=0 );
|
|
|
|
/* Load the column header names and all cell content into data */
|
|
if( p->spec.bTitles==QRF_Yes ){
|
|
unsigned char saved_eText = p->spec.eText;
|
|
p->spec.eText = p->spec.eTitle;
|
|
memset(data.abNum, 0, nColumn);
|
|
for(i=0; i<nColumn; i++){
|
|
const char *z = (const char*)sqlite3_column_name(p->pStmt,i);
|
|
int nNL = 0;
|
|
int n, w;
|
|
pStr = sqlite3_str_new(p->db);
|
|
qrfEncodeText(p, pStr, z ? z : "");
|
|
n = sqlite3_str_length(pStr);
|
|
z = data.az[data.n] = sqlite3_str_finish(pStr);
|
|
data.aiWth[data.n] = w = qrfDisplayWidth(z, n, &nNL);
|
|
data.n++;
|
|
if( w>data.a[i].mxW ) data.a[i].mxW = w;
|
|
if( nNL ) data.bMultiRow = 1;
|
|
}
|
|
p->spec.eText = saved_eText;
|
|
p->nRow++;
|
|
}
|
|
do{
|
|
if( data.n+nColumn > data.nAlloc ){
|
|
if( qrfColDataEnlarge(&data) ) return;
|
|
}
|
|
for(i=0; i<nColumn; i++){
|
|
char *z;
|
|
int nNL = 0;
|
|
int n, w;
|
|
int eType = sqlite3_column_type(p->pStmt,i);
|
|
pStr = sqlite3_str_new(p->db);
|
|
qrfRenderValue(p, pStr, i);
|
|
n = sqlite3_str_length(pStr);
|
|
z = data.az[data.n] = sqlite3_str_finish(pStr);
|
|
data.abNum[data.n] = eType==SQLITE_INTEGER || eType==SQLITE_FLOAT;
|
|
data.aiWth[data.n] = w = qrfDisplayWidth(z, n, &nNL);
|
|
data.n++;
|
|
if( w>data.a[i].mxW ) data.a[i].mxW = w;
|
|
if( nNL ) data.bMultiRow = 1;
|
|
}
|
|
p->nRow++;
|
|
}while( sqlite3_step(p->pStmt)==SQLITE_ROW && p->iErr==SQLITE_OK );
|
|
if( p->iErr ){
|
|
qrfColDataFree(&data);
|
|
return;
|
|
}
|
|
|
|
/* Compute the width and alignment of every column */
|
|
if( p->spec.bTitles==QRF_No ){
|
|
qrfLoadAlignment(&data, p);
|
|
}else{
|
|
unsigned char e;
|
|
if( p->spec.eTitleAlign==QRF_Auto ){
|
|
e = QRF_ALIGN_Center;
|
|
}else{
|
|
e = p->spec.eTitleAlign;
|
|
}
|
|
for(i=0; i<nColumn; i++) data.a[i].e = e;
|
|
}
|
|
|
|
for(i=0; i<nColumn; i++){
|
|
int w = 0;
|
|
if( i<p->spec.nWidth ){
|
|
w = p->spec.aWidth[i];
|
|
if( w==(-32768) ){
|
|
w = 0;
|
|
if( p->spec.nAlign>i && (p->spec.aAlign[i] & QRF_ALIGN_HMASK)==0 ){
|
|
data.a[i].e |= QRF_ALIGN_Right;
|
|
}
|
|
}else if( w<0 ){
|
|
w = -w;
|
|
if( p->spec.nAlign>i && (p->spec.aAlign[i] & QRF_ALIGN_HMASK)==0 ){
|
|
data.a[i].e |= QRF_ALIGN_Right;
|
|
}
|
|
}
|
|
if( w ) data.a[i].fx = 1;
|
|
}
|
|
if( w==0 ){
|
|
w = data.a[i].mxW;
|
|
if( p->spec.nWrap>0 && w>p->spec.nWrap ){
|
|
w = p->spec.nWrap;
|
|
data.bMultiRow = 1;
|
|
}
|
|
}else if( (data.bMultiRow==0 || w==1) && data.a[i].mxW>w ){
|
|
data.bMultiRow = 1;
|
|
if( w==1 ){
|
|
/* If aiWth[j] is 2 or more, then there might be a double-wide
|
|
** character somewhere. So make the column width at least 2. */
|
|
w = 2;
|
|
}
|
|
}
|
|
data.a[i].w = w;
|
|
}
|
|
|
|
if( nColumn==1
|
|
&& data.n>1
|
|
&& p->spec.bSplitColumn==QRF_Yes
|
|
&& p->spec.eStyle==QRF_STYLE_Column
|
|
&& p->spec.bTitles==QRF_No
|
|
&& p->spec.nScreenWidth>data.a[0].w+3
|
|
){
|
|
/* Attempt to convert single-column tables into multi-column by
|
|
** verticle wrapping, if the screen is wide enough and if the
|
|
** bSplitColumn flag is set. */
|
|
qrfSplitColumn(&data, p);
|
|
nColumn = data.nCol;
|
|
}else{
|
|
/* Adjust the column widths due to screen width restrictions */
|
|
qrfRestrictScreenWidth(&data, p);
|
|
}
|
|
|
|
/* Draw the line across the top of the table. Also initialize
|
|
** the row boundary and column separator texts. */
|
|
switch( p->spec.eStyle ){
|
|
case QRF_STYLE_Box:
|
|
if( data.nMargin ){
|
|
rowStart = BOX_13 " ";
|
|
colSep = " " BOX_13 " ";
|
|
rowSep = " " BOX_13 "\n";
|
|
}else{
|
|
rowStart = BOX_13;
|
|
colSep = BOX_13;
|
|
rowSep = BOX_13 "\n";
|
|
}
|
|
if( p->spec.bBorder==QRF_No){
|
|
rowStart += 3;
|
|
rowSep = "\n";
|
|
}else{
|
|
qrfBoxSeparator(p->pOut, &data, BOX_23, BOX_234, BOX_34);
|
|
}
|
|
break;
|
|
case QRF_STYLE_Table:
|
|
if( data.nMargin ){
|
|
rowStart = "| ";
|
|
colSep = " | ";
|
|
rowSep = " |\n";
|
|
}else{
|
|
rowStart = "|";
|
|
colSep = "|";
|
|
rowSep = "|\n";
|
|
}
|
|
if( p->spec.bBorder==QRF_No ){
|
|
rowStart += 1;
|
|
rowSep = "\n";
|
|
}else{
|
|
qrfRowSeparator(p->pOut, &data, '+');
|
|
}
|
|
break;
|
|
case QRF_STYLE_Column: {
|
|
static const char zSpace[] = " ";
|
|
rowStart = "";
|
|
if( data.nMargin<2 ){
|
|
colSep = " ";
|
|
}else if( data.nMargin<=5 ){
|
|
colSep = &zSpace[5-data.nMargin];
|
|
}else{
|
|
colSep = zSpace;
|
|
}
|
|
rowSep = "\n";
|
|
break;
|
|
}
|
|
default: /*case QRF_STYLE_Markdown:*/
|
|
if( data.nMargin ){
|
|
rowStart = "| ";
|
|
colSep = " | ";
|
|
rowSep = " |\n";
|
|
}else{
|
|
rowStart = "|";
|
|
colSep = "|";
|
|
rowSep = "|\n";
|
|
}
|
|
break;
|
|
}
|
|
szRowStart = (int)strlen(rowStart);
|
|
szRowSep = (int)strlen(rowSep);
|
|
szColSep = (int)strlen(colSep);
|
|
|
|
bWW = (p->spec.bWordWrap==QRF_Yes && data.bMultiRow);
|
|
if( p->spec.eStyle==QRF_STYLE_Column
|
|
|| (p->spec.bBorder==QRF_No
|
|
&& (p->spec.eStyle==QRF_STYLE_Box || p->spec.eStyle==QRF_STYLE_Table)
|
|
)
|
|
){
|
|
bRTrim = 1;
|
|
}else{
|
|
bRTrim = 0;
|
|
}
|
|
for(i=0; i<data.n; i+=nColumn){
|
|
int bMore;
|
|
int nRow = 0;
|
|
|
|
/* Draw a single row of the table. This might be the title line
|
|
** (if there is a title line) or a row in the body of the table.
|
|
** The column number will be j. The row number is i/nColumn.
|
|
*/
|
|
for(j=0; j<nColumn; j++){
|
|
data.a[j].z = data.az[i+j];
|
|
data.a[j].bNum = data.abNum[i+j];
|
|
}
|
|
do{
|
|
sqlite3_str_append(p->pOut, rowStart, szRowStart);
|
|
bMore = 0;
|
|
for(j=0; j<nColumn; j++){
|
|
int nThis = 0;
|
|
int nWide = 0;
|
|
int iNext = 0;
|
|
int nWS;
|
|
qrfWrapLine(data.a[j].z, data.a[j].w, bWW, &nThis, &nWide, &iNext);
|
|
nWS = data.a[j].w - nWide;
|
|
qrfPrintAligned(p->pOut, &data.a[j], nThis, nWS);
|
|
data.a[j].z += iNext;
|
|
if( data.a[j].z[0]!=0 ){
|
|
bMore = 1;
|
|
}
|
|
if( j<nColumn-1 ){
|
|
sqlite3_str_append(p->pOut, colSep, szColSep);
|
|
}else{
|
|
if( bRTrim ) qrfRTrim(p->pOut);
|
|
sqlite3_str_append(p->pOut, rowSep, szRowSep);
|
|
}
|
|
}
|
|
}while( bMore && ++nRow < p->mxHeight );
|
|
if( bMore ){
|
|
/* This row was terminated by nLineLimit. Show ellipsis. */
|
|
sqlite3_str_append(p->pOut, rowStart, szRowStart);
|
|
for(j=0; j<nColumn; j++){
|
|
if( data.a[j].z[0]==0 ){
|
|
sqlite3_str_appendchar(p->pOut, data.a[j].w, ' ');
|
|
}else{
|
|
int nE = 3;
|
|
if( nE>data.a[j].w ) nE = data.a[j].w;
|
|
data.a[j].z = "...";
|
|
qrfPrintAligned(p->pOut, &data.a[j], nE, data.a[j].w-nE);
|
|
}
|
|
if( j<nColumn-1 ){
|
|
sqlite3_str_append(p->pOut, colSep, szColSep);
|
|
}else{
|
|
if( bRTrim ) qrfRTrim(p->pOut);
|
|
sqlite3_str_append(p->pOut, rowSep, szRowSep);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Draw either (1) the separator between the title line and the body
|
|
** of the table, or (2) separators between individual rows of the table
|
|
** body. isTitleDataSeparator will be true if we are doing (1).
|
|
*/
|
|
if( (i==0 || data.bMultiRow) && i+nColumn<data.n ){
|
|
int isTitleDataSeparator = (i==0 && p->spec.bTitles==QRF_Yes);
|
|
if( isTitleDataSeparator ){
|
|
qrfLoadAlignment(&data, p);
|
|
}
|
|
switch( p->spec.eStyle ){
|
|
case QRF_STYLE_Table: {
|
|
if( isTitleDataSeparator || data.bMultiRow ){
|
|
qrfRowSeparator(p->pOut, &data, '+');
|
|
}
|
|
break;
|
|
}
|
|
case QRF_STYLE_Box: {
|
|
if( isTitleDataSeparator || data.bMultiRow ){
|
|
qrfBoxSeparator(p->pOut, &data, BOX_123, BOX_1234, BOX_134);
|
|
}
|
|
break;
|
|
}
|
|
case QRF_STYLE_Markdown: {
|
|
if( isTitleDataSeparator ){
|
|
qrfRowSeparator(p->pOut, &data, '|');
|
|
}
|
|
break;
|
|
}
|
|
case QRF_STYLE_Column: {
|
|
if( isTitleDataSeparator ){
|
|
for(j=0; j<nColumn; j++){
|
|
sqlite3_str_appendchar(p->pOut, data.a[j].w, '-');
|
|
if( j<nColumn-1 ){
|
|
sqlite3_str_append(p->pOut, colSep, szColSep);
|
|
}else{
|
|
qrfRTrim(p->pOut);
|
|
sqlite3_str_append(p->pOut, rowSep, szRowSep);
|
|
}
|
|
}
|
|
}else if( data.bMultiRow ){
|
|
qrfRTrim(p->pOut);
|
|
sqlite3_str_append(p->pOut, "\n", 1);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Draw the line across the bottom of the table */
|
|
if( p->spec.bBorder!=QRF_No ){
|
|
switch( p->spec.eStyle ){
|
|
case QRF_STYLE_Box:
|
|
qrfBoxSeparator(p->pOut, &data, BOX_12, BOX_124, BOX_14);
|
|
break;
|
|
case QRF_STYLE_Table:
|
|
qrfRowSeparator(p->pOut, &data, '+');
|
|
break;
|
|
}
|
|
}
|
|
qrfWrite(p);
|
|
|
|
qrfColDataFree(&data);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
** Parameter azArray points to a zero-terminated array of strings. zStr
|
|
** points to a single nul-terminated string. Return non-zero if zStr
|
|
** is equal, according to strcmp(), to any of the strings in the array.
|
|
** Otherwise, return zero.
|
|
*/
|
|
static int qrfStringInArray(const char *zStr, const char **azArray){
|
|
int i;
|
|
if( zStr==0 ) return 0;
|
|
for(i=0; azArray[i]; i++){
|
|
if( 0==strcmp(zStr, azArray[i]) ) return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
** Print out an EXPLAIN with indentation. This is a two-pass algorithm.
|
|
**
|
|
** On the first pass, we compute aiIndent[iOp] which is the amount of
|
|
** indentation to apply to the iOp-th opcode. The output actually occurs
|
|
** on the second pass.
|
|
**
|
|
** The indenting rules are:
|
|
**
|
|
** * For each "Next", "Prev", "VNext" or "VPrev" instruction, indent
|
|
** all opcodes that occur between the p2 jump destination and the opcode
|
|
** itself by 2 spaces.
|
|
**
|
|
** * Do the previous for "Return" instructions for when P2 is positive.
|
|
** See tag-20220407a in wherecode.c and vdbe.c.
|
|
**
|
|
** * For each "Goto", if the jump destination is earlier in the program
|
|
** and ends on one of:
|
|
** Yield SeekGt SeekLt RowSetRead Rewind
|
|
** or if the P1 parameter is one instead of zero,
|
|
** then indent all opcodes between the earlier instruction
|
|
** and "Goto" by 2 spaces.
|
|
*/
|
|
static void qrfExplain(Qrf *p){
|
|
int *abYield = 0; /* abYield[iOp] is rue if opcode iOp is an OP_Yield */
|
|
int *aiIndent = 0; /* Indent the iOp-th opcode by aiIndent[iOp] */
|
|
i64 nAlloc = 0; /* Allocated size of aiIndent[], abYield */
|
|
int nIndent = 0; /* Number of entries in aiIndent[] */
|
|
int iOp; /* Opcode number */
|
|
int i; /* Column loop counter */
|
|
|
|
const char *azNext[] = { "Next", "Prev", "VPrev", "VNext", "SorterNext",
|
|
"Return", 0 };
|
|
const char *azYield[] = { "Yield", "SeekLT", "SeekGT", "RowSetRead",
|
|
"Rewind", 0 };
|
|
const char *azGoto[] = { "Goto", 0 };
|
|
|
|
/* The caller guarantees that the leftmost 4 columns of the statement
|
|
** passed to this function are equivalent to the leftmost 4 columns
|
|
** of EXPLAIN statement output. In practice the statement may be
|
|
** an EXPLAIN, or it may be a query on the bytecode() virtual table. */
|
|
assert( sqlite3_column_count(p->pStmt)>=4 );
|
|
assert( 0==sqlite3_stricmp( sqlite3_column_name(p->pStmt, 0), "addr" ) );
|
|
assert( 0==sqlite3_stricmp( sqlite3_column_name(p->pStmt, 1), "opcode" ) );
|
|
assert( 0==sqlite3_stricmp( sqlite3_column_name(p->pStmt, 2), "p1" ) );
|
|
assert( 0==sqlite3_stricmp( sqlite3_column_name(p->pStmt, 3), "p2" ) );
|
|
|
|
for(iOp=0; SQLITE_ROW==sqlite3_step(p->pStmt); iOp++){
|
|
int iAddr = sqlite3_column_int(p->pStmt, 0);
|
|
const char *zOp = (const char*)sqlite3_column_text(p->pStmt, 1);
|
|
int p1 = sqlite3_column_int(p->pStmt, 2);
|
|
int p2 = sqlite3_column_int(p->pStmt, 3);
|
|
|
|
/* Assuming that p2 is an instruction address, set variable p2op to the
|
|
** index of that instruction in the aiIndent[] array. p2 and p2op may be
|
|
** different if the current instruction is part of a sub-program generated
|
|
** by an SQL trigger or foreign key. */
|
|
int p2op = (p2 + (iOp-iAddr));
|
|
|
|
/* Grow the aiIndent array as required */
|
|
if( iOp>=nAlloc ){
|
|
nAlloc += 100;
|
|
aiIndent = (int*)sqlite3_realloc64(aiIndent, nAlloc*sizeof(int));
|
|
abYield = (int*)sqlite3_realloc64(abYield, nAlloc*sizeof(int));
|
|
if( aiIndent==0 || abYield==0 ){
|
|
qrfOom(p);
|
|
sqlite3_free(aiIndent);
|
|
sqlite3_free(abYield);
|
|
return;
|
|
}
|
|
}
|
|
|
|
abYield[iOp] = qrfStringInArray(zOp, azYield);
|
|
aiIndent[iOp] = 0;
|
|
nIndent = iOp+1;
|
|
if( qrfStringInArray(zOp, azNext) && p2op>0 ){
|
|
for(i=p2op; i<iOp; i++) aiIndent[i] += 2;
|
|
}
|
|
if( qrfStringInArray(zOp, azGoto) && p2op<iOp && (abYield[p2op] || p1) ){
|
|
for(i=p2op; i<iOp; i++) aiIndent[i] += 2;
|
|
}
|
|
}
|
|
sqlite3_free(abYield);
|
|
|
|
/* Second pass. Actually generate output */
|
|
sqlite3_reset(p->pStmt);
|
|
if( p->iErr==SQLITE_OK ){
|
|
static const int aExplainWidth[] = {4, 13, 4, 4, 4, 13, 2, 13};
|
|
static const int aExplainMap[] = {0, 1, 2, 3, 4, 5, 6, 7 };
|
|
static const int aScanExpWidth[] = {4,15, 6, 13, 4, 4, 4, 13, 2, 13};
|
|
static const int aScanExpMap[] = {0, 9, 8, 1, 2, 3, 4, 5, 6, 7 };
|
|
const int *aWidth = aExplainWidth;
|
|
const int *aMap = aExplainMap;
|
|
int nWidth = sizeof(aExplainWidth)/sizeof(int);
|
|
int iIndent = 1;
|
|
int nArg = p->nCol;
|
|
if( p->spec.eStyle==QRF_STYLE_StatsVm ){
|
|
aWidth = aScanExpWidth;
|
|
aMap = aScanExpMap;
|
|
nWidth = sizeof(aScanExpWidth)/sizeof(int);
|
|
iIndent = 3;
|
|
}
|
|
if( nArg>nWidth ) nArg = nWidth;
|
|
|
|
for(iOp=0; sqlite3_step(p->pStmt)==SQLITE_ROW; iOp++){
|
|
/* If this is the first row seen, print out the headers */
|
|
if( iOp==0 ){
|
|
for(i=0; i<nArg; i++){
|
|
const char *zCol = sqlite3_column_name(p->pStmt, aMap[i]);
|
|
qrfWidthPrint(p,p->pOut, aWidth[i], zCol);
|
|
if( i==nArg-1 ){
|
|
sqlite3_str_append(p->pOut, "\n", 1);
|
|
}else{
|
|
sqlite3_str_append(p->pOut, " ", 2);
|
|
}
|
|
}
|
|
for(i=0; i<nArg; i++){
|
|
sqlite3_str_appendf(p->pOut, "%.*c", aWidth[i], '-');
|
|
if( i==nArg-1 ){
|
|
sqlite3_str_append(p->pOut, "\n", 1);
|
|
}else{
|
|
sqlite3_str_append(p->pOut, " ", 2);
|
|
}
|
|
}
|
|
}
|
|
|
|
for(i=0; i<nArg; i++){
|
|
const char *zSep = " ";
|
|
int w = aWidth[i];
|
|
const char *zVal = (const char*)sqlite3_column_text(p->pStmt, aMap[i]);
|
|
int len;
|
|
if( i==nArg-1 ) w = 0;
|
|
if( zVal==0 ) zVal = "";
|
|
len = qrfDisplayLength(zVal);
|
|
if( len>w ){
|
|
w = len;
|
|
zSep = " ";
|
|
}
|
|
if( i==iIndent && aiIndent && iOp<nIndent ){
|
|
sqlite3_str_appendchar(p->pOut, aiIndent[iOp], ' ');
|
|
}
|
|
qrfWidthPrint(p, p->pOut, w, zVal);
|
|
if( i==nArg-1 ){
|
|
sqlite3_str_append(p->pOut, "\n", 1);
|
|
}else{
|
|
sqlite3_str_appendall(p->pOut, zSep);
|
|
}
|
|
}
|
|
p->nRow++;
|
|
}
|
|
qrfWrite(p);
|
|
}
|
|
sqlite3_free(aiIndent);
|
|
}
|
|
|
|
/*
|
|
** Do a "scanstatus vm" style EXPLAIN listing on p->pStmt.
|
|
**
|
|
** p->pStmt is probably not an EXPLAIN query. Instead, construct a
|
|
** new query that is a bytecode() rendering of p->pStmt with extra
|
|
** columns for the "scanstatus vm" outputs, and run the results of
|
|
** that new query through the normal EXPLAIN formatting.
|
|
*/
|
|
static void qrfScanStatusVm(Qrf *p){
|
|
sqlite3_stmt *pOrigStmt = p->pStmt;
|
|
sqlite3_stmt *pExplain;
|
|
int rc;
|
|
static const char *zSql =
|
|
" SELECT addr, opcode, p1, p2, p3, p4, p5, comment, nexec,"
|
|
" format('% 6s (%.2f%%)',"
|
|
" CASE WHEN ncycle<100_000 THEN ncycle || ' '"
|
|
" WHEN ncycle<100_000_000 THEN (ncycle/1_000) || 'K'"
|
|
" WHEN ncycle<100_000_000_000 THEN (ncycle/1_000_000) || 'M'"
|
|
" ELSE (ncycle/1000_000_000) || 'G' END,"
|
|
" ncycle*100.0/(sum(ncycle) OVER ())"
|
|
" ) AS cycles"
|
|
" FROM bytecode(?1)";
|
|
rc = sqlite3_prepare_v2(p->db, zSql, -1, &pExplain, 0);
|
|
if( rc ){
|
|
qrfError(p, rc, "%s", sqlite3_errmsg(p->db));
|
|
sqlite3_finalize(pExplain);
|
|
return;
|
|
}
|
|
sqlite3_bind_pointer(pExplain, 1, pOrigStmt, "stmt-pointer", 0);
|
|
p->pStmt = pExplain;
|
|
p->nCol = 10;
|
|
qrfExplain(p);
|
|
sqlite3_finalize(pExplain);
|
|
p->pStmt = pOrigStmt;
|
|
}
|
|
|
|
/*
|
|
** Attempt to determine if identifier zName needs to be quoted, either
|
|
** because it contains non-alphanumeric characters, or because it is an
|
|
** SQLite keyword. Be conservative in this estimate: When in doubt assume
|
|
** that quoting is required.
|
|
**
|
|
** Return 1 if quoting is required. Return 0 if no quoting is required.
|
|
*/
|
|
|
|
static int qrf_need_quote(const char *zName){
|
|
int i;
|
|
const unsigned char *z = (const unsigned char*)zName;
|
|
if( z==0 ) return 1;
|
|
if( !qrfAlpha(z[0]) ) return 1;
|
|
for(i=0; z[i]; i++){
|
|
if( !qrfAlnum(z[i]) ) return 1;
|
|
}
|
|
return sqlite3_keyword_check(zName, i)!=0;
|
|
}
|
|
|
|
/*
|
|
** Helper function for QRF_STYLE_Json and QRF_STYLE_JObject.
|
|
** The initial "{" for a JSON object that will contain row content
|
|
** has been output. Now output all the content.
|
|
*/
|
|
static void qrfOneJsonRow(Qrf *p){
|
|
int i, nItem;
|
|
for(nItem=i=0; i<p->nCol; i++){
|
|
const char *zCName;
|
|
zCName = sqlite3_column_name(p->pStmt, i);
|
|
if( nItem>0 ) sqlite3_str_append(p->pOut, ",", 1);
|
|
nItem++;
|
|
qrfEncodeText(p, p->pOut, zCName);
|
|
sqlite3_str_append(p->pOut, ":", 1);
|
|
qrfRenderValue(p, p->pOut, i);
|
|
}
|
|
qrfWrite(p);
|
|
}
|
|
|
|
/*
|
|
** Render a single row of output for non-columnar styles - any
|
|
** style that lets us render row by row as the content is received
|
|
** from the query.
|
|
*/
|
|
static void qrfOneSimpleRow(Qrf *p){
|
|
int i;
|
|
switch( p->spec.eStyle ){
|
|
case QRF_STYLE_Off:
|
|
case QRF_STYLE_Count: {
|
|
/* No-op */
|
|
break;
|
|
}
|
|
case QRF_STYLE_Json: {
|
|
if( p->nRow==0 ){
|
|
sqlite3_str_append(p->pOut, "[{", 2);
|
|
}else{
|
|
sqlite3_str_append(p->pOut, "},\n{", 4);
|
|
}
|
|
qrfOneJsonRow(p);
|
|
break;
|
|
}
|
|
case QRF_STYLE_JObject: {
|
|
if( p->nRow==0 ){
|
|
sqlite3_str_append(p->pOut, "{", 1);
|
|
}else{
|
|
sqlite3_str_append(p->pOut, "}\n{", 3);
|
|
}
|
|
qrfOneJsonRow(p);
|
|
break;
|
|
}
|
|
case QRF_STYLE_Html: {
|
|
if( p->nRow==0 && p->spec.bTitles==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);
|
|
sqlite3_str_append(p->pOut, "\n<TH>", 5);
|
|
qrfEncodeText(p, p->pOut, zCName);
|
|
}
|
|
sqlite3_str_append(p->pOut, "\n</TR>\n", 7);
|
|
}
|
|
sqlite3_str_append(p->pOut, "<TR>", 4);
|
|
for(i=0; i<p->nCol; i++){
|
|
sqlite3_str_append(p->pOut, "\n<TD>", 5);
|
|
qrfRenderValue(p, p->pOut, i);
|
|
}
|
|
sqlite3_str_append(p->pOut, "\n</TR>\n", 7);
|
|
qrfWrite(p);
|
|
break;
|
|
}
|
|
case QRF_STYLE_Insert: {
|
|
if( qrf_need_quote(p->spec.zTableName) ){
|
|
sqlite3_str_appendf(p->pOut,"INSERT INTO \"%w\"",p->spec.zTableName);
|
|
}else{
|
|
sqlite3_str_appendf(p->pOut,"INSERT INTO %s",p->spec.zTableName);
|
|
}
|
|
if( p->spec.bTitles==QRF_Yes ){
|
|
for(i=0; i<p->nCol; i++){
|
|
const char *zCName = sqlite3_column_name(p->pStmt, i);
|
|
if( qrf_need_quote(zCName) ){
|
|
sqlite3_str_appendf(p->pOut, "%c\"%w\"",
|
|
i==0 ? '(' : ',', zCName);
|
|
}else{
|
|
sqlite3_str_appendf(p->pOut, "%c%s",
|
|
i==0 ? '(' : ',', zCName);
|
|
}
|
|
}
|
|
sqlite3_str_append(p->pOut, ")", 1);
|
|
}
|
|
sqlite3_str_append(p->pOut," VALUES(", 8);
|
|
for(i=0; i<p->nCol; i++){
|
|
if( i>0 ) sqlite3_str_append(p->pOut, ",", 1);
|
|
qrfRenderValue(p, p->pOut, i);
|
|
}
|
|
sqlite3_str_append(p->pOut, ");\n", 3);
|
|
qrfWrite(p);
|
|
break;
|
|
}
|
|
case QRF_STYLE_Line: {
|
|
sqlite3_str *pVal;
|
|
int mxW;
|
|
int bWW;
|
|
if( p->u.sLine.azCol==0 ){
|
|
p->u.sLine.azCol = sqlite3_malloc64( p->nCol*sizeof(char*) );
|
|
if( p->u.sLine.azCol==0 ){
|
|
qrfOom(p);
|
|
break;
|
|
}
|
|
p->u.sLine.mxColWth = 0;
|
|
for(i=0; i<p->nCol; i++){
|
|
int sz;
|
|
p->u.sLine.azCol[i] = sqlite3_column_name(p->pStmt, i);
|
|
if( p->u.sLine.azCol[i]==0 ) p->u.sLine.azCol[i] = "unknown";
|
|
sz = qrfDisplayLength(p->u.sLine.azCol[i]);
|
|
if( sz > p->u.sLine.mxColWth ) p->u.sLine.mxColWth = sz;
|
|
}
|
|
}
|
|
if( p->nRow ) sqlite3_str_append(p->pOut, "\n", 1);
|
|
pVal = sqlite3_str_new(p->db);
|
|
mxW = p->mxWidth - (3 + p->u.sLine.mxColWth);
|
|
bWW = p->spec.bWordWrap==QRF_Yes;
|
|
for(i=0; i<p->nCol; i++){
|
|
const char *zVal;
|
|
int cnt = 0;
|
|
qrfWidthPrint(p, p->pOut, -p->u.sLine.mxColWth, p->u.sLine.azCol[i]);
|
|
sqlite3_str_append(p->pOut, " = ", 3);
|
|
qrfRenderValue(p, pVal, i);
|
|
zVal = sqlite3_str_value(pVal);
|
|
if( zVal==0 ) zVal = "";
|
|
do{
|
|
int nThis, nWide, iNext;
|
|
qrfWrapLine(zVal, mxW, bWW, &nThis, &nWide, &iNext);
|
|
if( cnt ) sqlite3_str_appendchar(p->pOut,p->u.sLine.mxColWth+3,' ');
|
|
cnt++;
|
|
if( cnt>p->mxHeight ){
|
|
zVal = "...";
|
|
nThis = iNext = 3;
|
|
}
|
|
sqlite3_str_append(p->pOut, zVal, nThis);
|
|
sqlite3_str_append(p->pOut, "\n", 1);
|
|
zVal += iNext;
|
|
}while( zVal[0] );
|
|
sqlite3_str_reset(pVal);
|
|
}
|
|
sqlite3_free(sqlite3_str_finish(pVal));
|
|
qrfWrite(p);
|
|
break;
|
|
}
|
|
case QRF_STYLE_Eqp: {
|
|
const char *zEqpLine = (const char*)sqlite3_column_text(p->pStmt,3);
|
|
int iEqpId = sqlite3_column_int(p->pStmt, 0);
|
|
int iParentId = sqlite3_column_int(p->pStmt, 1);
|
|
if( zEqpLine==0 ) zEqpLine = "";
|
|
if( zEqpLine[0]=='-' ) qrfEqpRender(p, 0);
|
|
qrfEqpAppend(p, iEqpId, iParentId, zEqpLine);
|
|
break;
|
|
}
|
|
default: { /* QRF_STYLE_List */
|
|
if( p->nRow==0 && p->spec.bTitles==QRF_Yes ){
|
|
int saved_eText = p->spec.eText;
|
|
p->spec.eText = p->spec.eTitle;
|
|
for(i=0; i<p->nCol; i++){
|
|
const char *zCName = sqlite3_column_name(p->pStmt, i);
|
|
if( i>0 ) sqlite3_str_appendall(p->pOut, p->spec.zColumnSep);
|
|
qrfEncodeText(p, p->pOut, zCName);
|
|
}
|
|
sqlite3_str_appendall(p->pOut, p->spec.zRowSep);
|
|
qrfWrite(p);
|
|
p->spec.eText = saved_eText;
|
|
}
|
|
for(i=0; i<p->nCol; i++){
|
|
if( i>0 ) sqlite3_str_appendall(p->pOut, p->spec.zColumnSep);
|
|
qrfRenderValue(p, p->pOut, i);
|
|
}
|
|
sqlite3_str_appendall(p->pOut, p->spec.zRowSep);
|
|
qrfWrite(p);
|
|
break;
|
|
}
|
|
}
|
|
p->nRow++;
|
|
}
|
|
|
|
/*
|
|
** Initialize the internal Qrf object.
|
|
*/
|
|
static void qrfInitialize(
|
|
Qrf *p, /* State object to be initialized */
|
|
sqlite3_stmt *pStmt, /* Query whose output to be formatted */
|
|
const sqlite3_qrf_spec *pSpec, /* Format specification */
|
|
char **pzErr /* Write errors here */
|
|
){
|
|
size_t sz; /* Size of pSpec[], based on pSpec->iVersion */
|
|
memset(p, 0, sizeof(*p));
|
|
p->pzErr = pzErr;
|
|
if( pSpec->iVersion!=1 ){
|
|
qrfError(p, SQLITE_ERROR,
|
|
"unusable sqlite3_qrf_spec.iVersion (%d)",
|
|
pSpec->iVersion);
|
|
return;
|
|
}
|
|
p->pStmt = pStmt;
|
|
p->db = sqlite3_db_handle(pStmt);
|
|
p->pOut = sqlite3_str_new(p->db);
|
|
if( p->pOut==0 ){
|
|
qrfOom(p);
|
|
return;
|
|
}
|
|
p->iErr = 0;
|
|
p->nCol = sqlite3_column_count(p->pStmt);
|
|
p->nRow = 0;
|
|
sz = sizeof(sqlite3_qrf_spec);
|
|
memcpy(&p->spec, pSpec, sz);
|
|
if( p->spec.zNull==0 ) p->spec.zNull = "";
|
|
p->mxWidth = p->spec.nScreenWidth;
|
|
if( p->mxWidth<=0 ) p->mxWidth = QRF_MAX_WIDTH;
|
|
p->mxHeight = p->spec.nLineLimit;
|
|
if( p->mxHeight<=0 ) p->mxHeight = 2147483647;
|
|
if( p->spec.eStyle>QRF_STYLE_Table ) p->spec.eStyle = QRF_Auto;
|
|
if( p->spec.eEsc>QRF_ESC_Symbol ) p->spec.eEsc = QRF_Auto;
|
|
if( p->spec.eText>QRF_TEXT_Json ) p->spec.eText = QRF_Auto;
|
|
if( p->spec.eTitle>QRF_TEXT_Json ) p->spec.eTitle = QRF_Auto;
|
|
if( p->spec.eBlob>QRF_BLOB_Size ) p->spec.eBlob = QRF_Auto;
|
|
qrf_reinit:
|
|
switch( p->spec.eStyle ){
|
|
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;
|
|
default: p->spec.eStyle = QRF_STYLE_Eqp; break;
|
|
}
|
|
goto qrf_reinit;
|
|
}
|
|
case QRF_STYLE_List: {
|
|
if( p->spec.zColumnSep==0 ) p->spec.zColumnSep = "|";
|
|
if( p->spec.zRowSep==0 ) p->spec.zRowSep = "\n";
|
|
break;
|
|
}
|
|
case QRF_STYLE_JObject:
|
|
case QRF_STYLE_Json: {
|
|
p->spec.eText = QRF_TEXT_Json;
|
|
p->spec.zNull = "null";
|
|
break;
|
|
}
|
|
case QRF_STYLE_Html: {
|
|
p->spec.eText = QRF_TEXT_Html;
|
|
p->spec.zNull = "null";
|
|
break;
|
|
}
|
|
case QRF_STYLE_Insert: {
|
|
p->spec.eText = QRF_TEXT_Sql;
|
|
p->spec.zNull = "NULL";
|
|
if( p->spec.zTableName==0 || p->spec.zTableName[0]==0 ){
|
|
p->spec.zTableName = "tab";
|
|
}
|
|
break;
|
|
}
|
|
case QRF_STYLE_Csv: {
|
|
p->spec.eStyle = QRF_STYLE_List;
|
|
p->spec.eText = QRF_TEXT_Csv;
|
|
p->spec.zColumnSep = ",";
|
|
p->spec.zRowSep = "\r\n";
|
|
p->spec.zNull = "";
|
|
break;
|
|
}
|
|
case QRF_STYLE_Quote: {
|
|
p->spec.eText = QRF_TEXT_Sql;
|
|
p->spec.zNull = "NULL";
|
|
p->spec.zColumnSep = ",";
|
|
p->spec.zRowSep = "\n";
|
|
break;
|
|
}
|
|
case QRF_STYLE_Eqp: {
|
|
int expMode = sqlite3_stmt_isexplain(p->pStmt);
|
|
if( expMode!=2 ){
|
|
sqlite3_stmt_explain(p->pStmt, 2);
|
|
p->expMode = expMode+1;
|
|
}
|
|
break;
|
|
}
|
|
case QRF_STYLE_Explain: {
|
|
int expMode = sqlite3_stmt_isexplain(p->pStmt);
|
|
if( expMode!=1 ){
|
|
sqlite3_stmt_explain(p->pStmt, 1);
|
|
p->expMode = expMode+1;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if( p->spec.eEsc==QRF_Auto ){
|
|
p->spec.eEsc = QRF_ESC_Ascii;
|
|
}
|
|
if( p->spec.eText==QRF_Auto ){
|
|
p->spec.eText = QRF_TEXT_Plain;
|
|
}
|
|
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_Plain;
|
|
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;
|
|
case QRF_TEXT_Tcl: p->spec.eBlob = QRF_BLOB_Tcl; break;
|
|
case QRF_TEXT_Json: p->spec.eBlob = QRF_BLOB_Json; break;
|
|
default: p->spec.eBlob = QRF_BLOB_Text; break;
|
|
}
|
|
}
|
|
if( p->spec.bTitles==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.bTitles = QRF_Yes;
|
|
break;
|
|
default:
|
|
p->spec.bTitles = QRF_No;
|
|
break;
|
|
}
|
|
}
|
|
if( p->spec.bWordWrap==QRF_Auto ){
|
|
p->spec.bWordWrap = QRF_Yes;
|
|
}
|
|
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";
|
|
}
|
|
|
|
/*
|
|
** Finish rendering the results
|
|
*/
|
|
static void qrfFinalize(Qrf *p){
|
|
switch( p->spec.eStyle ){
|
|
case QRF_STYLE_Count: {
|
|
sqlite3_str_appendf(p->pOut, "%lld\n", p->nRow);
|
|
qrfWrite(p);
|
|
break;
|
|
}
|
|
case QRF_STYLE_Json: {
|
|
if( p->nRow>0 ){
|
|
sqlite3_str_append(p->pOut, "}]\n", 3);
|
|
qrfWrite(p);
|
|
}
|
|
break;
|
|
}
|
|
case QRF_STYLE_JObject: {
|
|
if( p->nRow>0 ){
|
|
sqlite3_str_append(p->pOut, "}\n", 2);
|
|
qrfWrite(p);
|
|
}
|
|
break;
|
|
}
|
|
case QRF_STYLE_Line: {
|
|
if( p->u.sLine.azCol ) sqlite3_free(p->u.sLine.azCol);
|
|
break;
|
|
}
|
|
case QRF_STYLE_Stats:
|
|
case QRF_STYLE_StatsEst:
|
|
case QRF_STYLE_Eqp: {
|
|
qrfEqpRender(p, 0);
|
|
qrfWrite(p);
|
|
break;
|
|
}
|
|
}
|
|
if( p->spec.pzOutput ){
|
|
if( p->spec.pzOutput[0] ){
|
|
sqlite3_int64 n, sz;
|
|
char *zCombined;
|
|
sz = strlen(p->spec.pzOutput[0]);
|
|
n = sqlite3_str_length(p->pOut);
|
|
zCombined = sqlite3_realloc(p->spec.pzOutput[0], sz+n+1);
|
|
if( zCombined==0 ){
|
|
sqlite3_free(p->spec.pzOutput[0]);
|
|
p->spec.pzOutput[0] = 0;
|
|
qrfOom(p);
|
|
}else{
|
|
p->spec.pzOutput[0] = zCombined;
|
|
memcpy(zCombined+sz, sqlite3_str_value(p->pOut), n+1);
|
|
}
|
|
sqlite3_free(sqlite3_str_finish(p->pOut));
|
|
}else{
|
|
p->spec.pzOutput[0] = sqlite3_str_finish(p->pOut);
|
|
}
|
|
}else if( p->pOut ){
|
|
sqlite3_free(sqlite3_str_finish(p->pOut));
|
|
}
|
|
if( p->expMode>0 ){
|
|
sqlite3_stmt_explain(p->pStmt, p->expMode-1);
|
|
}
|
|
if( p->actualWidth ){
|
|
sqlite3_free(p->actualWidth);
|
|
}
|
|
if( p->pJTrans ){
|
|
sqlite3 *db = sqlite3_db_handle(p->pJTrans);
|
|
sqlite3_finalize(p->pJTrans);
|
|
sqlite3_close(db);
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Run the prepared statement pStmt and format the results according
|
|
** to the specification provided in pSpec. Return an error code.
|
|
** If pzErr is not NULL and if an error occurs, write an error message
|
|
** into *pzErr.
|
|
*/
|
|
int sqlite3_format_query_result(
|
|
sqlite3_stmt *pStmt, /* Statement to evaluate */
|
|
const sqlite3_qrf_spec *pSpec, /* Format specification */
|
|
char **pzErr /* Write error message here */
|
|
){
|
|
Qrf qrf; /* The new Qrf being created */
|
|
|
|
if( pStmt==0 ) return SQLITE_OK; /* No-op */
|
|
if( pSpec==0 ) return SQLITE_MISUSE;
|
|
qrfInitialize(&qrf, pStmt, pSpec, pzErr);
|
|
switch( qrf.spec.eStyle ){
|
|
case QRF_STYLE_Box:
|
|
case QRF_STYLE_Column:
|
|
case QRF_STYLE_Markdown:
|
|
case QRF_STYLE_Table: {
|
|
/* Columnar modes require that the entire query be evaluated and the
|
|
** results stored in memory, so that we can compute column widths */
|
|
qrfColumnar(&qrf);
|
|
break;
|
|
}
|
|
case QRF_STYLE_Explain: {
|
|
qrfExplain(&qrf);
|
|
break;
|
|
}
|
|
case QRF_STYLE_StatsVm: {
|
|
qrfScanStatusVm(&qrf);
|
|
break;
|
|
}
|
|
case QRF_STYLE_Stats:
|
|
case QRF_STYLE_StatsEst: {
|
|
qrfEqpStats(&qrf);
|
|
break;
|
|
}
|
|
default: {
|
|
/* Non-columnar modes where the output can occur after each row
|
|
** of result is received */
|
|
while( qrf.iErr==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
|
|
qrfOneSimpleRow(&qrf);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
qrfResetStmt(&qrf);
|
|
qrfFinalize(&qrf);
|
|
return qrf.iErr;
|
|
}
|