summaryrefslogtreecommitdiff
path: root/tea
diff options
context:
space:
mode:
authorCy Schubert <cy@FreeBSD.org>2026-05-25 17:05:16 +0000
committerCy Schubert <cy@FreeBSD.org>2026-05-25 17:05:16 +0000
commitb00eb376e3fb28e738f9370552dae9d92c1fdd76 (patch)
tree4675a4ad76d8d7d5746413670e0e86f84e968a39 /tea
parente7e917ee3cf2b3010b1c511c6ebaf8b65b983ad7 (diff)
Diffstat (limited to 'tea')
-rw-r--r--tea/Makefile.in84
-rw-r--r--tea/README.txt18
-rw-r--r--tea/_teaish.tester.tcl.in3
-rwxr-xr-xtea/configure17
-rw-r--r--tea/doc/sqlite3.n15
-rw-r--r--tea/generic/tclsqlite3.c562
-rw-r--r--tea/teaish.tcl10
7 files changed, 587 insertions, 122 deletions
diff --git a/tea/Makefile.in b/tea/Makefile.in
index 5b2ad4c69992..04c8f87f555b 100644
--- a/tea/Makefile.in
+++ b/tea/Makefile.in
@@ -119,7 +119,7 @@ TCLLIBDIR = @TCLLIBDIR@
# typically come from the ./configure command-line invocation).
#
CFLAGS.configure = @SH_CFLAGS@ @TEAISH_CFLAGS@ @CFLAGS@ @CPPFLAGS@ $(TCL_INCLUDE_SPEC)
-#CFLAGS.configure += -DUSE_TCL_STUBS=1
+CFLAGS.configure += -DUSE_TCL_STUBS=@TEAISH_USE_STUBS@
#
# LDFLAGS.configure = LDFLAGS as known at configure-time.
@@ -146,13 +146,14 @@ LDFLAGS.shlib = @SH_LDFLAGS@
# sources passed to [teaish-src-add], but may also be appended to by
# teaish.make.
#
-tx.src =@TEAISH_EXT_SRC@
+tx.src = @TEAISH_EXT_SRC@
#
# tx.CFLAGS is typically set by teaish.make, whereas TEAISH_CFLAGS
# gets set up via the configure script.
#
tx.CFLAGS =
+tx.CPPFLAGS =
#
# tx.LDFLAGS is typically set by teaish.make, whereas TEAISH_LDFLAGS
@@ -167,6 +168,11 @@ tx.LDFLAGS =
#
tx.dist.files = @TEAISH_DIST_FILES@
+#
+# The base name for a distribution tar/zip file.
+#
+tx.dist.basename = $(tx.name.dist)-$(tx.version)
+
# List of deps which may trigger an auto-reconfigure.
#
teaish__autogen.deps = \
@@ -199,16 +205,21 @@ $(teaish.makefile): $(teaish__auto.def) $(teaish.makefile.in) \
@AUTODEPS@
@if TEAISH_TESTER_TCL_IN
-@TEAISH_TESTER_TCL_IN@:
-@TEAISH_TESTER_TCL@: @TEAISH_TESTER_TCL_IN@
-config.log: @TEAISH_TESTER_TCL@
+@TEAISH_TESTER_TCL_IN@: $(teaish__autogen.deps)
+config.log: @TEAISH_TESTER_TCL_IN@
+@TEAISH_TESTER_TCL@: @TEAISH_TESTER_TCL_IN@
+@endif
+@if TEAISH_TEST_TCL_IN
+@TEAISH_TEST_TCL_IN@: $(teaish__autogen.deps)
+config.log: @TEAISH_TEST_TCL_IN@
+@TEAISH_TEST_TCL@: @TEAISH_TEST_TCL_IN@
@endif
#
# CC variant for compiling Tcl-using sources.
#
CC.tcl = \
- $(CC) -o $@ $(CFLAGS.configure) $(CFLAGS) $(tx.CFLAGS)
+ $(CC) -o $@ $(CFLAGS.configure) $(CFLAGS) $(tx.CFLAGS) $(tx.CPPFLAGS)
#
# CC variant for linking $(tx.src) into an extension DLL. Note that
@@ -217,7 +228,7 @@ CC.tcl = \
#
CC.dll = \
$(CC.tcl) $(tx.src) $(LDFLAGS.shlib) \
- $(LDFLAGS.configure) $(LDFLAGS) $(tx.LDFLAGS) $(TCL_STUB_LIB_SPEC)
+ $(tx.LDFLAGS) $(LDFLAGS.configure) $(LDFLAGS) $(TCL_STUB_LIB_SPEC)
@if TEAISH_ENABLE_DLL
#
@@ -248,16 +259,25 @@ test-extension: # this name is reserved for use by teaish.make[.in]
test-prepre: $(tx.dll)
@endif
@if TEAISH_TESTER_TCL
-test-core.args = @TEAISH_TESTER_TCL@
+teaish.tester.tcl = @TEAISH_TESTER_TCL@
+test-core.args = $(teaish.tester.tcl)
@if TEAISH_ENABLE_DLL
test-core.args += '$(tx.dll)' '$(tx.loadPrefix)'
@else
test-core.args += '' ''
@endif
test-core.args += @TEAISH_TESTUTIL_TCL@
+# Clients may pass additional args via test.args=...
+# and ::argv will be rewritten before the test script loads, to
+# remove $(test-core.args)
+test.args ?=
test-core: test-pre
- $(TCLSH) $(test-core.args)
-test-prepre: @TEAISH_TESTER_TCL@
+ $(TCLSH) $(test-core.args) $(test.args)
+test-gdb: $(teaish.tester.tcl)
+ gdb --args $(TCLSH) $(test-core.args) $(test.args)
+test-vg.flags ?= --leak-check=full -v --show-reachable=yes --track-origins=yes
+test-vg: $(teaish.tester.tcl)
+ valgrind $(test-vg.flags) $(TCLSH) $(test-core.args) $(test.args)
@else # !TEAISH_TESTER_TCL
test-prepre:
@endif # TEAISH_TESTER_TCL
@@ -288,7 +308,7 @@ distclean-core: distclean-pre
@endif
@endif
@if TEAISH_TESTER_TCL_IN
- rm -f @TEAISH_TESTER_TCL@
+ rm -f $(teaish.tester.tcl)
@endif
@if TEAISH_PKGINDEX_TCL_IN
rm -f @TEAISH_PKGINDEX_TCL@
@@ -355,10 +375,15 @@ install-core: install-pre
@endif
install-test: install-core
@echo "Post-install test of [package require $(tx.name.pkg) $(tx.version)]..."; \
+ set xtra=""; \
+ if [ x != "x$(DESTDIR)" ]; then \
+ xtra='set ::auto_path [linsert $$::auto_path 0 [file normalize $(DESTDIR)$(TCLLIBDIR)/..]];'; \
+ fi; \
if echo \
- 'set c 0; ' \
+ 'set c 0; ' $$xtra \
'@TEAISH_POSTINST_PREREQUIRE@' \
- 'if {[catch {package require $(tx.name.pkg) $(tx.version)}]} {incr c};' \
+ 'if {[catch {package require $(tx.name.pkg) $(tx.version)} xc]} {incr c};' \
+ 'if {$$c && "" ne $$xc} {puts $$xc; puts "auto_path=$$::auto_path"};' \
'exit $$c' \
| $(TCLSH) ; then \
echo "passed"; \
@@ -406,7 +431,7 @@ config.log: $(teaish.makefile.in)
# recognized when running in --teaish-install mode, causing
# the sub-configure to fail.
dist.flags = --with-tclsh=$(TCLSH)
-dist.reconfig = $(teaish.dir)/configure $(dist.flags)
+dist.reconfig = $(teaish.dir)/configure $(tx.dist.reconfig-flags) $(dist.flags)
# Temp dir for dist.zip. Must be different than dist.tgz or else
# parallel builds may hose the dist.
@@ -414,24 +439,23 @@ teaish__dist.tmp.zip = teaish__dist_zip
#
# Make a distribution zip file...
#
-dist.basename = $(tx.name.dist)-$(tx.version)
-dist.zip = $(dist.basename).zip
+dist.zip = $(tx.dist.basename).zip
.PHONY: dist.zip dist.zip-core dist.zip-post
#dist.zip-pre:
# We apparently can't add a pre-hook here, else "make dist" rebuilds
# the archive each time it's run.
$(dist.zip): $(tx.dist.files)
@rm -fr $(teaish__dist.tmp.zip)
- @mkdir -p $(teaish__dist.tmp.zip)/$(dist.basename)
+ @mkdir -p $(teaish__dist.tmp.zip)/$(tx.dist.basename)
@tar cf $(teaish__dist.tmp.zip)/tmp.tar $(tx.dist.files)
- @tar xf $(teaish__dist.tmp.zip)/tmp.tar -C $(teaish__dist.tmp.zip)/$(dist.basename)
+ @tar xf $(teaish__dist.tmp.zip)/tmp.tar -C $(teaish__dist.tmp.zip)/$(tx.dist.basename)
@if TEAISH_DIST_FULL
@$(dist.reconfig) \
- --teaish-install=$(teaish__dist.tmp.zip)/$(dist.basename) \
- --t-e-d=$(teaish__dist.tmp.zip)/$(dist.basename) >/dev/null
+ --teaish-install=$(teaish__dist.tmp.zip)/$(tx.dist.basename) \
+ --t-e-d=$(teaish__dist.tmp.zip)/$(tx.dist.basename) >/dev/null
@endif
- @rm -f $(dist.basename)/tmp.tar $(dist.zip)
- @cd $(teaish__dist.tmp.zip) && zip -q -r ../$(dist.zip) $(dist.basename)
+ @rm -f $(tx.dist.basename)/tmp.tar $(dist.zip)
+ @cd $(teaish__dist.tmp.zip) && zip -q -r ../$(dist.zip) $(tx.dist.basename)
@rm -fr $(teaish__dist.tmp.zip)
@ls -la $(dist.zip)
dist.zip-core: $(dist.zip)
@@ -447,23 +471,23 @@ undist: undist-zip
# Make a distribution tarball...
#
teaish__dist.tmp.tgz = teaish__dist_tgz
-dist.tgz = $(dist.basename).tar.gz
+dist.tgz = $(tx.dist.basename).tar.gz
.PHONY: dist.tgz dist.tgz-core dist.tgz-post
# dist.tgz-pre:
# see notes in dist.zip
$(dist.tgz): $(tx.dist.files)
@rm -fr $(teaish__dist.tmp.tgz)
- @mkdir -p $(teaish__dist.tmp.tgz)/$(dist.basename)
+ @mkdir -p $(teaish__dist.tmp.tgz)/$(tx.dist.basename)
@tar cf $(teaish__dist.tmp.tgz)/tmp.tar $(tx.dist.files)
- @tar xf $(teaish__dist.tmp.tgz)/tmp.tar -C $(teaish__dist.tmp.tgz)/$(dist.basename)
+ @tar xf $(teaish__dist.tmp.tgz)/tmp.tar -C $(teaish__dist.tmp.tgz)/$(tx.dist.basename)
@if TEAISH_DIST_FULL
- @rm -f $(teaish__dist.tmp.tgz)/$(dist.basename)/pkgIndex.tcl.in; # kludge
+ @rm -f $(teaish__dist.tmp.tgz)/$(tx.dist.basename)/pkgIndex.tcl.in; # kludge
@$(dist.reconfig) \
- --teaish-install=$(teaish__dist.tmp.tgz)/$(dist.basename) \
- --t-e-d=$(teaish__dist.tmp.zip)/$(dist.basename) >/dev/null
+ --teaish-install=$(teaish__dist.tmp.tgz)/$(tx.dist.basename) \
+ --t-e-d=$(teaish__dist.tmp.zip)/$(tx.dist.basename) >/dev/null
@endif
- @rm -f $(dist.basename)/tmp.tar $(dist.tgz)
- @cd $(teaish__dist.tmp.tgz) && tar czf ../$(dist.tgz) $(dist.basename)
+ @rm -f $(tx.dist.basename)/tmp.tar $(dist.tgz)
+ @cd $(teaish__dist.tmp.tgz) && tar czf ../$(dist.tgz) $(tx.dist.basename)
@rm -fr $(teaish__dist.tmp.tgz)
@ls -la $(dist.tgz)
dist.tgz-core: $(dist.tgz)
diff --git a/tea/README.txt b/tea/README.txt
index fb7cb1924854..122b08d32d0c 100644
--- a/tea/README.txt
+++ b/tea/README.txt
@@ -83,22 +83,12 @@ script and then run make. For example:
$ cd sqlite-*-tea
$ ./configure --with-tcl=/path/to/tcl/install/root
- $ make
+ $ make test
$ make install
WINDOWS BUILD
=============
-The recommended method to build extensions under windows is to use the
-Msys + Mingw build process. This provides a Unix-style build while
-generating native Windows binaries. Using the Msys + Mingw build tools
-means that you can use the same configure script as per the Unix build
-to create a Makefile. See the tcl/win/README file for the URL of
-the Msys + Mingw download.
-If you have VC++ then you may wish to use the files in the win
-subdirectory and build the extension using just VC++. These files have
-been designed to be as generic as possible but will require some
-additional maintenance by the project developer to synchronise with
-the TEA configure.in and Makefile.in files. Instructions for using the
-VC++ makefile are written in the first part of the Makefile.vc
-file.
+On Windows this build is known to work on Cygwin and some Msys2
+environments. We do not currently support Microsoft makefiles for
+native Windows builds.
diff --git a/tea/_teaish.tester.tcl.in b/tea/_teaish.tester.tcl.in
index 59d11f0a8f6e..e04d8e63e790 100644
--- a/tea/_teaish.tester.tcl.in
+++ b/tea/_teaish.tester.tcl.in
@@ -21,7 +21,8 @@ if {[llength [lindex $::argv 0]] > 0} {
# ----^^^^^^^ needed on Haiku when argv 0 is just a filename, else
# load cannot find the file.
}
-source -encoding utf-8 [lindex $::argv 2]; # teaish/tester.tcl
+set ::argv [lassign $argv - -]
+source -encoding utf-8 [lindex $::argv 0]; # teaish/tester.tcl
@if TEAISH_PKGINIT_TCL
apply {{file} {
set dir [file dirname $::argv0]
diff --git a/tea/configure b/tea/configure
index 47378126f5ab..01b3abcc2fba 100755
--- a/tea/configure
+++ b/tea/configure
@@ -1,7 +1,20 @@
#!/bin/sh
+# Look for and run autosetup...
dir0="`dirname "$0"`"
-dirA="$dir0/../autosetup"
-# This is the case ^^^^^^^^^^^^ in the SQLite "autoconf" bundle.
+dirA="$dir0"
+if [ -d $dirA/autosetup ]; then
+ # A local copy of autosetup
+ dirA=$dirA/autosetup
+elif [ -d $dirA/../autosetup ]; then
+ # SQLite "autoconf" bundle
+ dirA=$dirA/../autosetup
+elif [ -d $dirA/../../autosetup ]; then
+ # SQLite canonical source tree
+ dirA=$dirA/../../autosetup
+else
+ echo "$0: Cannot find autosetup" 1>&2
+ exit 1
+fi
WRAPPER="$0"; export WRAPPER; exec "`"$dirA/autosetup-find-tclsh"`" \
"$dirA/autosetup" --teaish-extension-dir="$dir0" \
"$@"
diff --git a/tea/doc/sqlite3.n b/tea/doc/sqlite3.n
deleted file mode 100644
index 3514046342da..000000000000
--- a/tea/doc/sqlite3.n
+++ /dev/null
@@ -1,15 +0,0 @@
-.TH sqlite3 n 4.1 "Tcl-Extensions"
-.HS sqlite3 tcl
-.BS
-.SH NAME
-sqlite3 \- an interface to the SQLite3 database engine
-.SH SYNOPSIS
-\fBsqlite3\fI command_name ?filename?\fR
-.br
-.SH DESCRIPTION
-SQLite3 is a self-contains, zero-configuration, transactional SQL database
-engine. This extension provides an easy to use interface for accessing
-SQLite database files from Tcl.
-.PP
-For full documentation see \fIhttps://sqlite.org/\fR and
-in particular \fIhttps://sqlite.org/tclsqlite.html\fR.
diff --git a/tea/generic/tclsqlite3.c b/tea/generic/tclsqlite3.c
index 197ce744836c..144d4399d8e5 100644
--- a/tea/generic/tclsqlite3.c
+++ b/tea/generic/tclsqlite3.c
@@ -54,6 +54,10 @@
# define CONST const
#elif !defined(Tcl_Size)
typedef int Tcl_Size;
+# ifndef Tcl_BounceRefCount
+# define Tcl_BounceRefCount(X) Tcl_IncrRefCount(X); Tcl_DecrRefCount(X)
+ /* https://www.tcl-lang.org/man/tcl9.0/TclLib/Object.html */
+# endif
#endif
/**** End copy of tclsqlite.h ****/
@@ -125,6 +129,15 @@
/* Forward declaration */
typedef struct SqliteDb SqliteDb;
+/* Add -DSQLITE_ENABLE_QRF_IN_TCL to add the Query Result Formatter (QRF)
+** into the build of the TCL extension, when building using separate
+** source files. The QRF is included automatically when building from
+** the tclsqlite3.c amalgamation.
+*/
+#if defined(SQLITE_ENABLE_QRF_IN_TCL)
+#include "qrf.h"
+#endif
+
/*
** New SQL functions can be created as TCL scripts. Each such function
** is described by an instance of the following structure.
@@ -1089,7 +1102,9 @@ static void tclSqlFunc(sqlite3_context *context, int argc, sqlite3_value**argv){
Tcl_DecrRefCount(pCmd);
}
- if( rc && rc!=TCL_RETURN ){
+ if( TCL_BREAK==rc ){
+ sqlite3_result_null(context);
+ }else if( rc && rc!=TCL_RETURN ){
sqlite3_result_error(context, Tcl_GetStringResult(p->interp), -1);
}else{
Tcl_Obj *pVar = Tcl_GetObjResult(p->interp);
@@ -1107,7 +1122,7 @@ static void tclSqlFunc(sqlite3_context *context, int argc, sqlite3_value**argv){
}else if( (c=='b' && pVar->bytes==0 && strcmp(zType,"boolean")==0 )
|| (c=='b' && pVar->bytes==0 && strcmp(zType,"booleanString")==0 )
|| (c=='w' && strcmp(zType,"wideInt")==0)
- || (c=='i' && strcmp(zType,"int")==0)
+ || (c=='i' && strcmp(zType,"int")==0)
){
eType = SQLITE_INTEGER;
}else if( c=='d' && strcmp(zType,"double")==0 ){
@@ -1621,11 +1636,12 @@ struct DbEvalContext {
SqlPreparedStmt *pPreStmt; /* Current statement */
int nCol; /* Number of columns returned by pStmt */
int evalFlags; /* Flags used */
- Tcl_Obj *pArray; /* Name of array variable */
+ Tcl_Obj *pVarName; /* Name of target array/dict variable */
Tcl_Obj **apColName; /* Array of column names */
};
#define SQLITE_EVAL_WITHOUTNULLS 0x00001 /* Unset array(*) for NULL */
+#define SQLITE_EVAL_ASDICT 0x00002 /* Use dict instead of array */
/*
** Release any cache of column names currently held as part of
@@ -1646,20 +1662,20 @@ static void dbReleaseColumnNames(DbEvalContext *p){
/*
** Initialize a DbEvalContext structure.
**
-** If pArray is not NULL, then it contains the name of a Tcl array
+** If pVarName is not NULL, then it contains the name of a Tcl array
** variable. The "*" member of this array is set to a list containing
** the names of the columns returned by the statement as part of each
** call to dbEvalStep(), in order from left to right. e.g. if the names
** of the returned columns are a, b and c, it does the equivalent of the
** tcl command:
**
-** set ${pArray}(*) {a b c}
+** set ${pVarName}(*) {a b c}
*/
static void dbEvalInit(
DbEvalContext *p, /* Pointer to structure to initialize */
SqliteDb *pDb, /* Database handle */
Tcl_Obj *pSql, /* Object containing SQL script */
- Tcl_Obj *pArray, /* Name of Tcl array to set (*) element of */
+ Tcl_Obj *pVarName, /* Name of Tcl array to set (*) element of */
int evalFlags /* Flags controlling evaluation */
){
memset(p, 0, sizeof(DbEvalContext));
@@ -1667,9 +1683,9 @@ static void dbEvalInit(
p->zSql = Tcl_GetString(pSql);
p->pSql = pSql;
Tcl_IncrRefCount(pSql);
- if( pArray ){
- p->pArray = pArray;
- Tcl_IncrRefCount(pArray);
+ if( pVarName ){
+ p->pVarName = pVarName;
+ Tcl_IncrRefCount(pVarName);
}
p->evalFlags = evalFlags;
addDatabaseRef(p->pDb);
@@ -1692,7 +1708,7 @@ static void dbEvalRowInfo(
Tcl_Obj **apColName = 0; /* Array of column names */
p->nCol = nCol = sqlite3_column_count(pStmt);
- if( nCol>0 && (papColName || p->pArray) ){
+ if( nCol>0 && (papColName || p->pVarName) ){
apColName = (Tcl_Obj**)Tcl_Alloc( sizeof(Tcl_Obj*)*nCol );
for(i=0; i<nCol; i++){
apColName[i] = Tcl_NewStringObj(sqlite3_column_name(pStmt,i), -1);
@@ -1701,20 +1717,35 @@ static void dbEvalRowInfo(
p->apColName = apColName;
}
- /* If results are being stored in an array variable, then create
- ** the array(*) entry for that array
+ /* If results are being stored in a variable then create the
+ ** array(*) or dict(*) entry for that variable.
*/
- if( p->pArray ){
+ if( p->pVarName ){
Tcl_Interp *interp = p->pDb->interp;
Tcl_Obj *pColList = Tcl_NewObj();
Tcl_Obj *pStar = Tcl_NewStringObj("*", -1);
+ Tcl_IncrRefCount(pColList);
+ Tcl_IncrRefCount(pStar);
for(i=0; i<nCol; i++){
Tcl_ListObjAppendElement(interp, pColList, apColName[i]);
}
- Tcl_IncrRefCount(pStar);
- Tcl_ObjSetVar2(interp, p->pArray, pStar, pColList, 0);
+ if( 0==(SQLITE_EVAL_ASDICT & p->evalFlags) ){
+ Tcl_ObjSetVar2(interp, p->pVarName, pStar, pColList, 0);
+ }else{
+ Tcl_Obj * pDict = Tcl_ObjGetVar2(interp, p->pVarName, NULL, 0);
+ if( !pDict ){
+ pDict = Tcl_NewDictObj();
+ }else if( Tcl_IsShared(pDict) ){
+ pDict = Tcl_DuplicateObj(pDict);
+ }
+ if( Tcl_DictObjPut(interp, pDict, pStar, pColList)==TCL_OK ){
+ Tcl_ObjSetVar2(interp, p->pVarName, NULL, pDict, 0);
+ }
+ Tcl_BounceRefCount(pDict);
+ }
Tcl_DecrRefCount(pStar);
+ Tcl_DecrRefCount(pColList);
}
}
@@ -1756,7 +1787,7 @@ static int dbEvalStep(DbEvalContext *p){
if( rcs==SQLITE_ROW ){
return TCL_OK;
}
- if( p->pArray ){
+ if( p->pVarName ){
dbEvalRowInfo(p, 0, 0);
}
rcs = sqlite3_reset(pStmt);
@@ -1807,9 +1838,9 @@ static void dbEvalFinalize(DbEvalContext *p){
dbReleaseStmt(p->pDb, p->pPreStmt, 0);
p->pPreStmt = 0;
}
- if( p->pArray ){
- Tcl_DecrRefCount(p->pArray);
- p->pArray = 0;
+ if( p->pVarName ){
+ Tcl_DecrRefCount(p->pVarName);
+ p->pVarName = 0;
}
Tcl_DecrRefCount(p->pSql);
dbReleaseColumnNames(p);
@@ -1884,7 +1915,7 @@ static int DbUseNre(void){
/*
** This function is part of the implementation of the command:
**
-** $db eval SQL ?ARRAYNAME? SCRIPT
+** $db eval SQL ?TGT-NAME? SCRIPT
*/
static int SQLITE_TCLAPI DbEvalNextCmd(
ClientData data[], /* data[0] is the (DbEvalContext*) */
@@ -1898,8 +1929,8 @@ static int SQLITE_TCLAPI DbEvalNextCmd(
** is a pointer to a Tcl_Obj containing the script to run for each row
** returned by the queries encapsulated in data[0]. */
DbEvalContext *p = (DbEvalContext *)data[0];
- Tcl_Obj *pScript = (Tcl_Obj *)data[1];
- Tcl_Obj *pArray = p->pArray;
+ Tcl_Obj * const pScript = (Tcl_Obj *)data[1];
+ Tcl_Obj * const pVarName = p->pVarName;
while( (rc==TCL_OK || rc==TCL_CONTINUE) && TCL_OK==(rc = dbEvalStep(p)) ){
int i;
@@ -1907,15 +1938,46 @@ static int SQLITE_TCLAPI DbEvalNextCmd(
Tcl_Obj **apColName;
dbEvalRowInfo(p, &nCol, &apColName);
for(i=0; i<nCol; i++){
- if( pArray==0 ){
+ if( pVarName==0 ){
Tcl_ObjSetVar2(interp, apColName[i], 0, dbEvalColumnValue(p,i), 0);
}else if( (p->evalFlags & SQLITE_EVAL_WITHOUTNULLS)!=0
- && sqlite3_column_type(p->pPreStmt->pStmt, i)==SQLITE_NULL
+ && sqlite3_column_type(p->pPreStmt->pStmt, i)==SQLITE_NULL
){
- Tcl_UnsetVar2(interp, Tcl_GetString(pArray),
- Tcl_GetString(apColName[i]), 0);
+ /* Remove NULL-containing column from the target container... */
+ if( 0==(SQLITE_EVAL_ASDICT & p->evalFlags) ){
+ /* Target is an array */
+ Tcl_UnsetVar2(interp, Tcl_GetString(pVarName),
+ Tcl_GetString(apColName[i]), 0);
+ }else{
+ /* Target is a dict */
+ Tcl_Obj *pDict = Tcl_ObjGetVar2(interp, pVarName, NULL, 0);
+ if( pDict ){
+ if( Tcl_IsShared(pDict) ){
+ pDict = Tcl_DuplicateObj(pDict);
+ }
+ if( Tcl_DictObjRemove(interp, pDict, apColName[i])==TCL_OK ){
+ Tcl_ObjSetVar2(interp, pVarName, NULL, pDict, 0);
+ }
+ Tcl_BounceRefCount(pDict);
+ }
+ }
+ }else if( 0==(SQLITE_EVAL_ASDICT & p->evalFlags) ){
+ /* Target is an array: set target(colName) = colValue */
+ Tcl_ObjSetVar2(interp, pVarName, apColName[i],
+ dbEvalColumnValue(p,i), 0);
}else{
- Tcl_ObjSetVar2(interp, pArray, apColName[i], dbEvalColumnValue(p,i), 0);
+ /* Target is a dict: set target(colName) = colValue */
+ Tcl_Obj *pDict = Tcl_ObjGetVar2(interp, pVarName, NULL, 0);
+ if( !pDict ){
+ pDict = Tcl_NewDictObj();
+ }else if( Tcl_IsShared(pDict) ){
+ pDict = Tcl_DuplicateObj(pDict);
+ }
+ if( Tcl_DictObjPut(interp, pDict, apColName[i],
+ dbEvalColumnValue(p,i))==TCL_OK ){
+ Tcl_ObjSetVar2(interp, pVarName, NULL, pDict, 0);
+ }
+ Tcl_BounceRefCount(pDict);
}
}
@@ -1988,6 +2050,376 @@ static void DbHookCmd(
}
/*
+** Implementation of the "db format" command.
+**
+** Based on provided options, format the results of the SQL statement(s)
+** provided into human-readable form using the Query Result Formatter (QRF)
+** and return the resuling text.
+**
+** Syntax: db format OPTIONS SQL
+**
+** OPTIONS may be:
+**
+** -style ("auto"|"box"|"column"|...) Output style
+** -esc ("auto"|"off"|"ascii"|"symbol") How to deal with ctrl chars
+** -text ("auto"|"off"|"sql"|"csv"|...) How to escape TEXT values
+** -title ("auto"|"off"|"sql"|...|"off") How to escape column names
+** -blob ("auto"|"text"|"sql"|...) How to escape BLOB values
+** -wordwrap ("auto"|"off"|"on") Try to wrap at word boundry?
+** -textjsonb ("auto"|"off"|"on") Auto-convert JSONB to text?
+** -splitcolumn ("auto"|"off"|"on") Enable split-column mode
+** -defaultalign ("auto"|"left"|...) Default alignment
+** -titalalign ("auto"|"left"|"right"|...) Default column name alignment
+** -border ("auto"|"off"|"on") Border for box and table styles
+** -wrap NUMBER Max width of any single column
+** -screenwidth NUMBER Width of the display TTY
+** -linelimit NUMBER Max lines for any cell
+** -charlimit NUMBER Content truncated to this size
+** -titlelimit NUMBER Max width of column titles
+** -multiinsert NUMBER Multi-row INSERT byte size
+** -align LIST-OF-ALIGNMENT Alignment of columns
+** -widths LIST-OF-NUMBERS Widths for individual columns
+** -columnsep TEXT Column separator text
+** -rowsep TEXT Row separator text
+** -tablename TEXT Table name for style "insert"
+** -null TEXT Text for NULL values
+**
+** A mapping from TCL "format" command options to sqlite3_qrf_spec fields
+** is below. Use this to reference the QRF documentation:
+**
+** TCL Option spec field
+** ---------- ----------
+** -style eStyle
+** -esc eEsc
+** -text eText
+** -title eTitle, bTitle
+** -blob eBlob
+** -wordwrap bWordWrap
+** -textjsonb bTextJsonb
+** -splitcolumn bSplitColumn
+** -defaultalign eDfltAlign
+** -titlealign eTitleAlign
+** -border bBorder
+** -wrap nWrap
+** -screenwidth nScreenWidth
+** -linelimit nLineLimit
+** -charlimit nCharLimit
+** -titlelimit nTitleLimit
+** -multiinsert nMultiInsert
+** -align nAlign, aAlign
+** -widths nWidth, aWidth
+** -columnsep zColumnSep
+** -rowsep zRowSep
+** -tablename zTableName
+** -null zNull
+*/
+static int dbQrf(SqliteDb *pDb, int objc, Tcl_Obj *const*objv){
+#ifndef SQLITE_QRF_H
+ Tcl_SetResult(pDb->interp, "QRF not available in this build", TCL_VOLATILE);
+ return TCL_ERROR;
+#else
+ char *zResult = 0; /* Result to be returned */
+ const char *zSql = 0; /* SQL to run */
+ int i; /* Loop counter */
+ int rc; /* Result code */
+ sqlite3_qrf_spec qrf; /* Formatting spec */
+ static const char *azAlign[] = {
+ "auto", "bottom", "c",
+ "center", "e", "left",
+ "middle", "n", "ne",
+ "nw", "right", "s",
+ "se", "sw", "top",
+ "w", 0
+ };
+ static const unsigned char aAlignMap[] = {
+ QRF_ALIGN_Auto, QRF_ALIGN_Bottom, QRF_ALIGN_C,
+ QRF_ALIGN_Center, QRF_ALIGN_E, QRF_ALIGN_Left,
+ QRF_ALIGN_Middle, QRF_ALIGN_N, QRF_ALIGN_NE,
+ QRF_ALIGN_NW, QRF_ALIGN_Right, QRF_ALIGN_S,
+ QRF_ALIGN_SE, QRF_ALIGN_SW, QRF_ALIGN_Top,
+ QRF_ALIGN_W
+ };
+
+ memset(&qrf, 0, sizeof(qrf));
+ qrf.iVersion = 1;
+ qrf.pzOutput = &zResult;
+ for(i=2; i<objc; i++){
+ const char *zArg = Tcl_GetString(objv[i]);
+ const char *azBool[] = { "auto", "yes", "no", "on", "off", 0 };
+ const unsigned char aBoolMap[] = { 0, 2, 1, 2, 1 };
+ if( zArg[0]!='-' ){
+ if( zSql ){
+ Tcl_AppendResult(pDb->interp, "unknown argument: ", zArg, (char*)0);
+ rc = TCL_ERROR;
+ goto format_failed;
+ }
+ zSql = zArg;
+ }else if( i==objc-1 ){
+ Tcl_AppendResult(pDb->interp, "option has no argument: ", zArg, (char*)0);
+ rc = TCL_ERROR;
+ goto format_failed;
+ }else if( strcmp(zArg,"-style")==0 ){
+ static const char *azStyles[] = {
+ "auto", "box", "column",
+ "count", "csv", "eqp",
+ "explain", "html", "insert",
+ "jobject", "json", "line",
+ "list", "markdown", "quote",
+ "stats", "stats-est", "stats-vm",
+ "table", 0
+ };
+ static unsigned char aStyleMap[] = {
+ QRF_STYLE_Auto, QRF_STYLE_Box, QRF_STYLE_Column,
+ QRF_STYLE_Count, QRF_STYLE_Csv, QRF_STYLE_Eqp,
+ QRF_STYLE_Explain, QRF_STYLE_Html, QRF_STYLE_Insert,
+ QRF_STYLE_JObject, QRF_STYLE_Json, QRF_STYLE_Line,
+ QRF_STYLE_List, QRF_STYLE_Markdown, QRF_STYLE_Quote,
+ QRF_STYLE_Stats, QRF_STYLE_StatsEst, QRF_STYLE_StatsVm,
+ QRF_STYLE_Table
+ };
+ int style;
+ rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azStyles,
+ "format style (-style)", 0, &style);
+ if( rc ) goto format_failed;
+ qrf.eStyle = aStyleMap[style];
+ i++;
+ }else if( strcmp(zArg,"-esc")==0 ){
+ static const char *azEsc[] = {
+ "ascii", "auto", "off", "symbol", 0
+ };
+ static unsigned char aEscMap[] = {
+ QRF_ESC_Ascii, QRF_ESC_Auto, QRF_ESC_Off, QRF_ESC_Symbol
+ };
+ int esc;
+ rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azEsc,
+ "control character escape (-esc)", 0, &esc);
+ if( rc ) goto format_failed;
+ qrf.eEsc = aEscMap[esc];
+ i++;
+ }else if( strcmp(zArg,"-text")==0 || strcmp(zArg, "-title")==0 ){
+ /* NB: --title can be "off" or "on but --text may not be. Thus we put
+ ** the "off" and "on" choices first and start the search on the
+ ** thrid element of the array when processing --text */
+ static const char *azText[] = { "off", "on",
+ "auto", "csv", "html",
+ "json", "plain", "relaxed",
+ "sql", "tcl", 0
+ };
+ static unsigned char aTextMap[] = {
+ QRF_TEXT_Auto, QRF_TEXT_Csv, QRF_TEXT_Html,
+ QRF_TEXT_Json, QRF_TEXT_Plain, QRF_TEXT_Relaxed,
+ QRF_TEXT_Sql, QRF_TEXT_Tcl
+ };
+ int txt;
+ int k = zArg[2]=='e';
+ rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], &azText[k*2], zArg,
+ 0, &txt);
+ if( rc ) goto format_failed;
+ if( k ){
+ qrf.eText = aTextMap[txt];
+ }else if( txt<=1 ){
+ qrf.bTitles = txt ? QRF_Yes : QRF_No;
+ qrf.eTitle = QRF_TEXT_Auto;
+ }else{
+ qrf.bTitles = QRF_Yes;
+ qrf.eTitle = aTextMap[txt-2];
+ }
+ i++;
+ }else if( strcmp(zArg,"-blob")==0 ){
+ static const char *azBlob[] = {
+ "auto", "hex", "json",
+ "tcl", "text", "sql",
+ "size", 0
+ };
+ static unsigned char aBlobMap[] = {
+ QRF_BLOB_Auto, QRF_BLOB_Hex, QRF_BLOB_Json,
+ QRF_BLOB_Tcl, QRF_BLOB_Text, QRF_BLOB_Sql,
+ QRF_BLOB_Size
+ };
+ int blob;
+ rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azBlob,
+ "BLOB encoding (-blob)", 0, &blob);
+ if( rc ) goto format_failed;
+ qrf.eBlob = aBlobMap[blob];
+ i++;
+ }else if( strcmp(zArg,"-wordwrap")==0 ){
+ int v = 0;
+ rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azBool,
+ "-wordwrap", 0, &v);
+ if( rc ) goto format_failed;
+ qrf.bWordWrap = aBoolMap[v];
+ i++;
+ }else if( strcmp(zArg,"-textjsonb")==0
+ || strcmp(zArg,"-splitcolumn")==0
+ || strcmp(zArg,"-border")==0
+ ){
+ int v = 0;
+ rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azBool,
+ zArg, 0, &v);
+ if( rc ) goto format_failed;
+ if( zArg[1]=='t' ){
+ qrf.bTextJsonb = aBoolMap[v];
+ }else if( zArg[1]=='b' ){
+ qrf.bBorder = aBoolMap[v];
+ }else{
+ qrf.bSplitColumn = aBoolMap[v];
+ }
+ i++;
+ }else if( strcmp(zArg,"-defaultalign")==0 || strcmp(zArg,"-titlealign")==0){
+ int ax = 0;
+ rc = Tcl_GetIndexFromObj(pDb->interp, objv[i+1], azAlign,
+ zArg[1]=='d' ? "default alignment (-defaultalign)" :
+ "title alignment (-titlealign)",
+ 0, &ax);
+ if( rc ) goto format_failed;
+ if( zArg[1]=='d' ){
+ qrf.eDfltAlign = aAlignMap[ax];
+ }else{
+ qrf.eTitleAlign = aAlignMap[ax];
+ }
+ i++;
+ }else if( strcmp(zArg,"-wrap")==0
+ || strcmp(zArg,"-screenwidth")==0
+ || strcmp(zArg,"-linelimit")==0
+ || strcmp(zArg,"-titlelimit")==0
+ ){
+ int v = 0;
+ rc = Tcl_GetIntFromObj(pDb->interp, objv[i+1], &v);
+ if( rc ) goto format_failed;
+ if( v<QRF_MIN_WIDTH ){
+ v = QRF_MIN_WIDTH;
+ }else if( v>QRF_MAX_WIDTH ){
+ v = QRF_MAX_WIDTH;
+ }
+ if( zArg[1]=='w' ){
+ qrf.nWrap = v;
+ }else if( zArg[1]=='s' ){
+ qrf.nScreenWidth = v;
+ }else if( zArg[1]=='t' ){
+ qrf.nTitleLimit = v;
+ }else{
+ qrf.nLineLimit = v;
+ }
+ i++;
+ }else if( strcmp(zArg,"-charlimit")==0 ){
+ int v = 0;
+ rc = Tcl_GetIntFromObj(pDb->interp, objv[i+1], &v);
+ if( rc ) goto format_failed;
+ if( v<0 ) v = 0;
+ qrf.nCharLimit = v;
+ i++;
+ }else if( strcmp(zArg,"-multiinsert")==0 ){
+ int v = 0;
+ rc = Tcl_GetIntFromObj(pDb->interp, objv[i+1], &v);
+ if( rc ) goto format_failed;
+ if( v<0 ) v = 0;
+ qrf.nMultiInsert = v;
+ i++;
+ }else if( strcmp(zArg,"-align")==0 ){
+ Tcl_Size n = 0;
+ int jj;
+ rc = Tcl_ListObjLength(pDb->interp, objv[i+1], &n);
+ if( rc ) goto format_failed;
+ sqlite3_free(qrf.aAlign);
+ qrf.aAlign = sqlite3_malloc64( (n+1)*sizeof(qrf.aAlign[0]) );
+ if( qrf.aAlign==0 ){
+ Tcl_AppendResult(pDb->interp, "out of memory", (char*)0);
+ rc = TCL_ERROR;
+ goto format_failed;
+ }
+ memset(qrf.aAlign, 0, (n+1)*sizeof(qrf.aAlign[0]));
+ qrf.nAlign = n;
+ for(jj=0; jj<n; jj++){
+ int x;
+ Tcl_Obj *pTerm;
+ rc = Tcl_ListObjIndex(pDb->interp, objv[i+1], jj, &pTerm);
+ if( rc ) goto format_failed;
+ rc = Tcl_GetIndexFromObj(pDb->interp, pTerm, azAlign,
+ "column alignment (-align)", 0, &x);
+ if( rc ) goto format_failed;
+ qrf.aAlign[jj] = aAlignMap[x];
+ }
+ i++;
+ }else if( strcmp(zArg,"-widths")==0 ){
+ Tcl_Size n = 0;
+ int jj;
+ rc = Tcl_ListObjLength(pDb->interp, objv[i+1], &n);
+ if( rc ) goto format_failed;
+ sqlite3_free(qrf.aWidth);
+ qrf.aWidth = sqlite3_malloc64( (n+1)*sizeof(qrf.aWidth[0]) );
+ if( qrf.aWidth==0 ){
+ Tcl_AppendResult(pDb->interp, "out of memory", (char*)0);
+ rc = TCL_ERROR;
+ goto format_failed;
+ }
+ memset(qrf.aWidth, 0, (n+1)*sizeof(qrf.aWidth[0]));
+ qrf.nWidth = n;
+ for(jj=0; jj<n; jj++){
+ Tcl_Obj *pTerm;
+ int v;
+ rc = Tcl_ListObjIndex(pDb->interp, objv[i+1], jj, &pTerm);
+ if( rc ) goto format_failed;
+ rc = Tcl_GetIntFromObj(pDb->interp, pTerm, &v);
+ if( v<(-QRF_MAX_WIDTH) ){
+ v = -QRF_MAX_WIDTH;
+ }else if( v>QRF_MAX_WIDTH ){
+ v = QRF_MAX_WIDTH;
+ }
+ qrf.aWidth[jj] = (short int)v;
+ }
+ i++;
+ }else if( strcmp(zArg,"-columnsep")==0 ){
+ qrf.zColumnSep = Tcl_GetString(objv[i+1]);
+ i++;
+ }else if( strcmp(zArg,"-rowsep")==0 ){
+ qrf.zRowSep = Tcl_GetString(objv[i+1]);
+ i++;
+ }else if( strcmp(zArg,"-tablename")==0 ){
+ qrf.zTableName = Tcl_GetString(objv[i+1]);
+ i++;
+ }else if( strcmp(zArg,"-null")==0 ){
+ qrf.zNull = Tcl_GetString(objv[i+1]);
+ i++;
+ }else if( strcmp(zArg,"-version")==0 ){
+ /* Undocumented. Testing use only */
+ qrf.iVersion = atoi(Tcl_GetString(objv[i+1]));
+ i++;
+ }else{
+ Tcl_AppendResult(pDb->interp, "unknown option: ", zArg, (char*)0);
+ rc = TCL_ERROR;
+ goto format_failed;
+ }
+ }
+ while( zSql && zSql[0] ){
+ SqlPreparedStmt *pStmt = 0; /* Next statement to run */
+ char *zErr = 0; /* Error message from QRF */
+
+ rc = dbPrepareAndBind(pDb, zSql, &zSql, &pStmt);
+ if( rc ) goto format_failed;
+ if( pStmt==0 ) continue;
+ rc = sqlite3_format_query_result(pStmt->pStmt, &qrf, &zErr);
+ dbReleaseStmt(pDb, pStmt, 0);
+ if( rc ){
+ Tcl_SetResult(pDb->interp, zErr, TCL_VOLATILE);
+ sqlite3_free(zErr);
+ rc = TCL_ERROR;
+ goto format_failed;
+ }
+ }
+ Tcl_SetResult(pDb->interp, zResult, TCL_VOLATILE);
+ rc = TCL_OK;
+ /* Fall through...*/
+
+format_failed:
+ sqlite3_free(qrf.aWidth);
+ sqlite3_free(qrf.aAlign);
+ sqlite3_free(zResult);
+ return rc;
+
+#endif
+}
+
+/*
** The "sqlite" command below creates a new Tcl command for each
** connection it opens to an SQLite database. This routine is invoked
** whenever one of those connection-specific commands is executed
@@ -2016,15 +2448,15 @@ static int SQLITE_TCLAPI DbObjCmd(
"commit_hook", "complete", "config",
"copy", "deserialize", "enable_load_extension",
"errorcode", "erroroffset", "eval",
- "exists", "function", "incrblob",
- "interrupt", "last_insert_rowid", "nullvalue",
- "onecolumn", "preupdate", "profile",
- "progress", "rekey", "restore",
- "rollback_hook", "serialize", "status",
- "timeout", "total_changes", "trace",
- "trace_v2", "transaction", "unlock_notify",
- "update_hook", "version", "wal_hook",
- 0
+ "exists", "format", "function",
+ "incrblob", "interrupt", "last_insert_rowid",
+ "nullvalue", "onecolumn", "preupdate",
+ "profile", "progress", "rekey",
+ "restore", "rollback_hook", "serialize",
+ "status", "timeout", "total_changes",
+ "trace", "trace_v2", "transaction",
+ "unlock_notify", "update_hook", "version",
+ "wal_hook", 0
};
enum DB_enum {
DB_AUTHORIZER, DB_BACKUP, DB_BIND_FALLBACK,
@@ -2033,14 +2465,15 @@ static int SQLITE_TCLAPI DbObjCmd(
DB_COMMIT_HOOK, DB_COMPLETE, DB_CONFIG,
DB_COPY, DB_DESERIALIZE, DB_ENABLE_LOAD_EXTENSION,
DB_ERRORCODE, DB_ERROROFFSET, DB_EVAL,
- DB_EXISTS, DB_FUNCTION, DB_INCRBLOB,
- DB_INTERRUPT, DB_LAST_INSERT_ROWID, DB_NULLVALUE,
- DB_ONECOLUMN, DB_PREUPDATE, DB_PROFILE,
- DB_PROGRESS, DB_REKEY, DB_RESTORE,
- DB_ROLLBACK_HOOK, DB_SERIALIZE, DB_STATUS,
- DB_TIMEOUT, DB_TOTAL_CHANGES, DB_TRACE,
- DB_TRACE_V2, DB_TRANSACTION, DB_UNLOCK_NOTIFY,
- DB_UPDATE_HOOK, DB_VERSION, DB_WAL_HOOK,
+ DB_EXISTS, DB_FORMAT, DB_FUNCTION,
+ DB_INCRBLOB, DB_INTERRUPT, DB_LAST_INSERT_ROWID,
+ DB_NULLVALUE, DB_ONECOLUMN, DB_PREUPDATE,
+ DB_PROFILE, DB_PROGRESS, DB_REKEY,
+ DB_RESTORE, DB_ROLLBACK_HOOK, DB_SERIALIZE,
+ DB_STATUS, DB_TIMEOUT, DB_TOTAL_CHANGES,
+ DB_TRACE, DB_TRACE_V2, DB_TRANSACTION,
+ DB_UNLOCK_NOTIFY, DB_UPDATE_HOOK, DB_VERSION,
+ DB_WAL_HOOK
};
/* don't leave trailing commas on DB_enum, it confuses the AIX xlc compiler */
@@ -2858,13 +3291,15 @@ deserialize_error:
}
/*
- ** $db eval ?options? $sql ?array? ?{ ...code... }?
+ ** $db eval ?options? $sql ?varName? ?{ ...code... }?
**
- ** The SQL statement in $sql is evaluated. For each row, the values are
- ** placed in elements of the array named "array" and ...code... is executed.
- ** If "array" and "code" are omitted, then no callback is every invoked.
- ** If "array" is an empty string, then the values are placed in variables
- ** that have the same name as the fields extracted by the query.
+ ** The SQL statement in $sql is evaluated. For each row, the values
+ ** are placed in elements of the array or dict named $varName and
+ ** ...code... is executed. If $varName and $code are omitted, then
+ ** no callback is ever invoked. If $varName is an empty string,
+ ** then the values are placed in variables that have the same name
+ ** as the fields extracted by the query, and those variables are
+ ** accessible during the eval of $code.
*/
case DB_EVAL: {
int evalFlags = 0;
@@ -2872,8 +3307,9 @@ deserialize_error:
while( objc>3 && (zOpt = Tcl_GetString(objv[2]))!=0 && zOpt[0]=='-' ){
if( strcmp(zOpt, "-withoutnulls")==0 ){
evalFlags |= SQLITE_EVAL_WITHOUTNULLS;
- }
- else{
+ }else if( strcmp(zOpt, "-asdict")==0 ){
+ evalFlags |= SQLITE_EVAL_ASDICT;
+ }else{
Tcl_AppendResult(interp, "unknown option: \"", zOpt, "\"", (void*)0);
return TCL_ERROR;
}
@@ -2881,8 +3317,8 @@ deserialize_error:
objv++;
}
if( objc<3 || objc>5 ){
- Tcl_WrongNumArgs(interp, 2, objv,
- "?OPTIONS? SQL ?ARRAY-NAME? ?SCRIPT?");
+ Tcl_WrongNumArgs(interp, 2, objv,
+ "?OPTIONS? SQL ?VAR-NAME? ?SCRIPT?");
return TCL_ERROR;
}
@@ -2908,17 +3344,17 @@ deserialize_error:
}else{
ClientData cd2[2];
DbEvalContext *p;
- Tcl_Obj *pArray = 0;
+ Tcl_Obj *pVarName = 0;
Tcl_Obj *pScript;
if( objc>=5 && *(char *)Tcl_GetString(objv[3]) ){
- pArray = objv[3];
+ pVarName = objv[3];
}
pScript = objv[objc-1];
Tcl_IncrRefCount(pScript);
p = (DbEvalContext *)Tcl_Alloc(sizeof(DbEvalContext));
- dbEvalInit(p, pDb, objv[2], pArray, evalFlags);
+ dbEvalInit(p, pDb, objv[2], pVarName, evalFlags);
cd2[0] = (void *)p;
cd2[1] = (void *)pScript;
@@ -2928,6 +3364,18 @@ deserialize_error:
}
/*
+ ** $db format [OPTIONS] SQL
+ **
+ ** Run the SQL statement(s) given as the final argument. Use the
+ ** Query Result Formatter extension of SQLite to format the output as
+ ** text and return that text.
+ */
+ case DB_FORMAT: {
+ rc = dbQrf(pDb, objc, objv);
+ break;
+ }
+
+ /*
** $db function NAME [OPTIONS] SCRIPT
**
** Create a new SQL function called NAME. Whenever that function is
diff --git a/tea/teaish.tcl b/tea/teaish.tcl
index 9333495aa3da..47e0ea7013a1 100644
--- a/tea/teaish.tcl
+++ b/tea/teaish.tcl
@@ -64,12 +64,18 @@ apply {{dir} {
-name.pkg sqlite3
-version $version
-name.dist $distname
- -vsatisfies 8.6-
-libDir sqlite$version
-pragmas $pragmas
+ -src generic/tclsqlite3.c
}
+ # We should also have:
+ # -vsatisfies 8.6-
+ # But at least one platform is failing this vsatisfies check
+ # for no apparent reason:
+ # https://sqlite.org/forum/forumpost/fde857fb8101a4be
}} [teaish-get -dir]
+
#
# Must return either an empty string or a list in the form accepted by
# autosetup's [options] function.
@@ -119,8 +125,6 @@ proc teaish-options {} {
proc teaish-configure {} {
use teaish/feature
- teaish-src-add -dist -dir generic/tclsqlite3.c
-
if {[proj-opt-was-provided override-sqlite-version]} {
teaish-pkginfo-set -version [opt-val override-sqlite-version]
proj-warn "overriding sqlite version number:" [teaish-pkginfo-get -version]