SQLite

Changes On Branch tcl-cw
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Changes In Branch tcl-cw Excluding Merge-Ins

This is equivalent to a diff from 38d4c94d8c to 46b0bf5603

2025-05-05
23:44
Account for Tcl 8.x's lack of Tcl_BounceRefCount(). (Leaf check-in: 46b0bf5603 user: stephan tags: tcl-cw)
22:56
Part 2 of 2(?) of adding the -asdict flag to the db eval command of the Tcl interface. This needs a critical review from seasoned Tcl C API users before merging can be considered (noting that it's not planned for inclusion until 3.51). (check-in: 5368647a1f user: stephan tags: tcl-cw)
16:49
Allow Tcl-defined UDFs to 'break' to result in an SQL NULL, as per suggestion in forum post 585ebac2c48f1411. (check-in: 034211985d user: stephan tags: tcl-cw)
15:12
Merge trunk into cygwin-fixes branch. 'make test' failure count = 1 (delete_db-1.3.0) both before and after the merge, (Leaf check-in: 7471088197 user: stephan tags: cygwin-fixes)
2025-05-03
15:17
Enhance sqlite3_rsync so that if the first attempt to invoke a copy of itself on the remote system using ssh fails, try again after augmenting the PATH. This enables sqlite3_rsync to work without the --exe option when the remote system is a Mac. (Leaf check-in: 38d4c94d8c user: drh tags: trunk)
10:55
Fix a harmless redundant variable declaration in sqlite3_rsync. (check-in: f8f15eff6a user: drh tags: trunk)

Changes to src/tclsqlite.c.
45
46
47
48
49
50
51




52
53
54
55
56
57
58
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62







+
+
+
+







# endif
#endif
/* Compatability between Tcl8.6 and Tcl9.0 */
#if TCL_MAJOR_VERSION==9
# 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 ****/

#include <errno.h>

/*
** Some additional include files are needed if this file is not
1080
1081
1082
1083
1084
1085
1086


1087

1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105

1106
1107
1108
1109
1110
1111
1112
1084
1085
1086
1087
1088
1089
1090
1091
1092

1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110

1111
1112
1113
1114
1115
1116
1117
1118







+
+
-
+

















-
+







      ** happening, make sure pCmd has a valid string representation */
      Tcl_GetString(pCmd);
    }
    rc = Tcl_EvalObjEx(p->interp, pCmd, TCL_EVAL_DIRECT);
    Tcl_DecrRefCount(pCmd);
  }

  if( TCL_BREAK==rc ){
    sqlite3_result_null(context);
  if( rc && rc!=TCL_RETURN ){
  }else if( rc && rc!=TCL_RETURN ){
    sqlite3_result_error(context, Tcl_GetStringResult(p->interp), -1);
  }else{
    Tcl_Obj *pVar = Tcl_GetObjResult(p->interp);
    Tcl_Size n;
    u8 *data;
    const char *zType = (pVar->typePtr ? pVar->typePtr->name : "");
    char c = zType[0];
    int eType = p->eType;

    if( eType==SQLITE_NULL ){
      if( c=='b' && strcmp(zType,"bytearray")==0 && pVar->bytes==0 ){
        /* Only return a BLOB type if the Tcl variable is a bytearray and
        ** has no string representation. */
        eType = SQLITE_BLOB;
      }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 ){
        eType = SQLITE_FLOAT;
      }else{
        eType = SQLITE_TEXT;
      }
1612
1613
1614
1615
1616
1617
1618
1619

1620
1621
1622
1623

1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644

1645
1646
1647
1648
1649
1650
1651

1652
1653
1654
1655
1656
1657

1658
1659
1660
1661
1662
1663
1664
1665
1666
1667



1668
1669
1670
1671
1672
1673
1674
1618
1619
1620
1621
1622
1623
1624

1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650

1651
1652
1653
1654
1655
1656
1657

1658
1659
1660
1661
1662
1663

1664
1665
1666
1667
1668
1669
1670
1671



1672
1673
1674
1675
1676
1677
1678
1679
1680
1681







-
+




+




















-
+






-
+





-
+







-
-
-
+
+
+







struct DbEvalContext {
  SqliteDb *pDb;                  /* Database handle */
  Tcl_Obj *pSql;                  /* Object holding string zSql */
  const char *zSql;               /* Remaining SQL to execute */
  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 *pTgtName;              /* 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
** the DbEvalContext structure passed as the first argument.
*/
static void dbReleaseColumnNames(DbEvalContext *p){
  if( p->apColName ){
    int i;
    for(i=0; i<p->nCol; i++){
      Tcl_DecrRefCount(p->apColName[i]);
    }
    Tcl_Free((char *)p->apColName);
    p->apColName = 0;
  }
  p->nCol = 0;
}

/*
** Initialize a DbEvalContext structure.
**
** If pArray is not NULL, then it contains the name of a Tcl array
** If pTgtName 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 ${pTgtName}(*) {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 *pTgtName,              /* Name of Tcl array to set (*) element of */
  int evalFlags                   /* Flags controlling evaluation */
){
  memset(p, 0, sizeof(DbEvalContext));
  p->pDb = pDb;
  p->zSql = Tcl_GetString(pSql);
  p->pSql = pSql;
  Tcl_IncrRefCount(pSql);
  if( pArray ){
    p->pArray = pArray;
    Tcl_IncrRefCount(pArray);
  if( pTgtName ){
    p->pTgtName = pTgtName;
    Tcl_IncrRefCount(pTgtName);
  }
  p->evalFlags = evalFlags;
  addDatabaseRef(p->pDb);
}

/*
** Obtain information about the row that the DbEvalContext passed as the
1683
1684
1685
1686
1687
1688
1689
1690

1691
1692
1693
1694
1695
1696
1697
1698
1699
1700


1701
1702

1703
1704
1705
1706


1707
1708
1709






1710
1711








1712

1713
1714
1715
1716
1717
1718
1719
1690
1691
1692
1693
1694
1695
1696

1697
1698
1699
1700
1701
1702
1703
1704
1705


1706
1707
1708

1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724


1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741







-
+








-
-
+
+

-
+




+
+



+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+

+







  if( 0==p->apColName ){
    sqlite3_stmt *pStmt = p->pPreStmt->pStmt;
    int i;                        /* Iterator variable */
    int nCol;                     /* Number of columns returned by pStmt */
    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->pTgtName) ){
      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);
        Tcl_IncrRefCount(apColName[i]);
      }
      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->pTgtName ){
      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]);
      }
      if( 0==(SQLITE_EVAL_ASDICT & p->evalFlags) ){
        Tcl_ObjSetVar2(interp, p->pTgtName, pStar, pColList, 0);
      }else{
        Tcl_Obj * pDict = Tcl_ObjGetVar2(interp, p->pTgtName, NULL, 0);
        if( !pDict ){
          pDict = Tcl_NewDictObj();
      Tcl_IncrRefCount(pStar);
      Tcl_ObjSetVar2(interp, p->pArray, pStar, pColList, 0);
        }else if( Tcl_IsShared(pDict) ){
          pDict = Tcl_DuplicateObj(pDict);
        }
        if( Tcl_DictObjPut(interp, pDict, pStar, pColList)==TCL_OK ){
          Tcl_ObjSetVar2(interp, p->pTgtName, NULL, pDict, 0);
        }
        Tcl_BounceRefCount(pDict);
      }
      Tcl_DecrRefCount(pStar);
      Tcl_DecrRefCount(pColList);
    }
  }

  if( papColName ){
    *papColName = p->apColName;
  }
  if( pnCol ){
1747
1748
1749
1750
1751
1752
1753
1754

1755
1756
1757
1758
1759
1760
1761
1769
1770
1771
1772
1773
1774
1775

1776
1777
1778
1779
1780
1781
1782
1783







-
+







      SqlPreparedStmt *pPreStmt = p->pPreStmt;
      sqlite3_stmt *pStmt = pPreStmt->pStmt;

      rcs = sqlite3_step(pStmt);
      if( rcs==SQLITE_ROW ){
        return TCL_OK;
      }
      if( p->pArray ){
      if( p->pTgtName ){
        dbEvalRowInfo(p, 0, 0);
      }
      rcs = sqlite3_reset(pStmt);

      pDb->nStep = sqlite3_stmt_status(pStmt,SQLITE_STMTSTATUS_FULLSCAN_STEP,1);
      pDb->nSort = sqlite3_stmt_status(pStmt,SQLITE_STMTSTATUS_SORT,1);
      pDb->nIndex = sqlite3_stmt_status(pStmt,SQLITE_STMTSTATUS_AUTOINDEX,1);
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807



1808
1809
1810
1811
1812
1813
1814
1820
1821
1822
1823
1824
1825
1826



1827
1828
1829
1830
1831
1832
1833
1834
1835
1836







-
-
-
+
+
+







*/
static void dbEvalFinalize(DbEvalContext *p){
  if( p->pPreStmt ){
    sqlite3_reset(p->pPreStmt->pStmt);
    dbReleaseStmt(p->pDb, p->pPreStmt, 0);
    p->pPreStmt = 0;
  }
  if( p->pArray ){
    Tcl_DecrRefCount(p->pArray);
    p->pArray = 0;
  if( p->pTgtName ){
    Tcl_DecrRefCount(p->pTgtName);
    p->pTgtName = 0;
  }
  Tcl_DecrRefCount(p->pSql);
  dbReleaseColumnNames(p);
  delDatabaseRef(p->pDb);
}

/*
1875
1876
1877
1878
1879
1880
1881
1882

1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897


1898
1899
1900
1901
1902
1903
1904
1905

1906
1907
1908

1909



1910
1911
1912
1913
































1914
1915
1916
1917
1918
1919
1920
1897
1898
1899
1900
1901
1902
1903

1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917


1918
1919
1920
1921
1922
1923
1924
1925
1926

1927
1928
1929

1930
1931
1932
1933
1934




1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973







-
+













-
-
+
+







-
+


-
+

+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







# define Tcl_NREvalObj(a,b,c) 0
# define Tcl_NRCreateCommand(a,b,c,d,e,f) (void)0
#endif

/*
** 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*) */
  Tcl_Interp *interp,                  /* Tcl interpreter */
  int result                           /* Result so far */
){
  int rc = result;                     /* Return code */

  /* The first element of the data[] array is a pointer to a DbEvalContext
  ** structure allocated using Tcl_Alloc(). The second element of data[]
  ** 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 pTgtName = p->pTgtName;

  while( (rc==TCL_OK || rc==TCL_CONTINUE) && TCL_OK==(rc = dbEvalStep(p)) ){
    int i;
    int nCol;
    Tcl_Obj **apColName;
    dbEvalRowInfo(p, &nCol, &apColName);
    for(i=0; i<nCol; i++){
      if( pArray==0 ){
      if( pTgtName==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
      ){
        /* Remove NULL-containing column from the target container... */
        if( 0==(SQLITE_EVAL_ASDICT & p->evalFlags) ){
          /* Target is an array */
        Tcl_UnsetVar2(interp, Tcl_GetString(pArray), 
                      Tcl_GetString(apColName[i]), 0);
      }else{
        Tcl_ObjSetVar2(interp, pArray, apColName[i], dbEvalColumnValue(p,i), 0);
          Tcl_UnsetVar2(interp, Tcl_GetString(pTgtName),
                        Tcl_GetString(apColName[i]), 0);
        }else{
          /* Target is a dict */
          Tcl_Obj *pDict = Tcl_ObjGetVar2(interp, pTgtName, NULL, 0);
          if( pDict ){
            if( Tcl_IsShared(pDict) ){
              pDict = Tcl_DuplicateObj(pDict);
            }
            if( Tcl_DictObjRemove(interp, pDict, apColName[i])==TCL_OK ){
              Tcl_ObjSetVar2(interp, pTgtName, 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, pTgtName, apColName[i],
                       dbEvalColumnValue(p,i), 0);
      }else{
        /* Target is a dict: set target(colName) = colValue */
        Tcl_Obj *pDict = Tcl_ObjGetVar2(interp, pTgtName, 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, pTgtName, NULL, pDict, 0);
        }
        Tcl_BounceRefCount(pDict);
      }
    }

    /* The required interpreter variables are now populated with the data
    ** from the current row. If using NRE, schedule callbacks to evaluate
    ** script pScript, then to invoke this function again to fetch the next
    ** row (or clean up if there is no next row or the script throws an
2015
2016
2017
2018
2019
2020
2021
2022

2023
2024
2025
2026
2027
2028
2029
2068
2069
2070
2071
2072
2073
2074

2075
2076
2077
2078
2079
2080
2081
2082







-
+







    "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                        
    0
  };
  enum DB_enum {
    DB_AUTHORIZER,            DB_BACKUP,               DB_BIND_FALLBACK,
    DB_BUSY,                  DB_CACHE,                DB_CHANGES,
    DB_CLOSE,                 DB_COLLATE,              DB_COLLATION_NEEDED,
    DB_COMMIT_HOOK,           DB_COMPLETE,             DB_CONFIG,
    DB_COPY,                  DB_DESERIALIZE,          DB_ENABLE_LOAD_EXTENSION,
2849
2850
2851
2852
2853
2854
2855
2856

2857
2858
2859
2860
2861
2862







2863
2864
2865
2866
2867
2868
2869
2870
2871



2872
2873
2874
2875
2876
2877
2878
2879
2880


2881
2882
2883
2884
2885
2886
2887
2902
2903
2904
2905
2906
2907
2908

2909
2910





2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924


2925
2926
2927
2928
2929
2930
2931
2932
2933
2934


2935
2936
2937
2938
2939
2940
2941
2942
2943







-
+

-
-
-
-
-
+
+
+
+
+
+
+







-
-
+
+
+







-
-
+
+







    if( rc==TCL_BREAK ){
      rc = TCL_OK;
    }
    break;
  }

  /*
  **    $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;
    const char *zOpt;
    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;
      }
      objc--;
      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;
    }

    if( objc==3 ){
      DbEvalContext sEval;
      Tcl_Obj *pRet = Tcl_NewObj();
      Tcl_IncrRefCount(pRet);
2899
2900
2901
2902
2903
2904
2905
2906

2907
2908
2909
2910

2911
2912
2913
2914
2915
2916

2917
2918
2919
2920
2921
2922
2923
2955
2956
2957
2958
2959
2960
2961

2962
2963
2964
2965

2966
2967
2968
2969
2970
2971

2972
2973
2974
2975
2976
2977
2978
2979







-
+



-
+





-
+







        Tcl_SetObjResult(interp, pRet);
        rc = TCL_OK;
      }
      Tcl_DecrRefCount(pRet);
    }else{
      ClientData cd2[2];
      DbEvalContext *p;
      Tcl_Obj *pArray = 0;
      Tcl_Obj *pTgtName = 0;
      Tcl_Obj *pScript;

      if( objc>=5 && *(char *)Tcl_GetString(objv[3]) ){
        pArray = objv[3];
        pTgtName = 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], pTgtName, evalFlags);

      cd2[0] = (void *)p;
      cd2[1] = (void *)pScript;
      rc = DbEvalNextCmd(cd2, interp, TCL_OK);
    }
    break;
  }
Changes to test/tclsqlite.test.
1
2
3
4
5
6
7
8
9
10
11
12

13
14
15
16
17
18
19
1
2
3
4
5
6
7
8
9
10
11

12
13
14
15
16
17
18
19











-
+







# 2001 September 15
#
# 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.
#
#***********************************************************************
# This file implements regression tests for TCL interface to the
# SQLite library. 
# SQLite library.
#
# Actually, all tests are based on the TCL interface, so the main
# interface is pretty well tested.  This file contains some addition
# tests for fringe issues that the main test suite does not cover.
#
# $Id: tclsqlite.test,v 1.73 2009/03/16 13:19:36 danielk1977 Exp $

117
118
119
120
121
122
123
124

125
126
127
128
129
130
131
117
118
119
120
121
122
123

124
125
126
127
128
129
130
131







-
+







    set v [catch {db complete} msg]
    lappend v $msg
  } {1 {wrong # args: should be "db complete SQL"}}
}
do_test tcl-1.14 {
  set v [catch {db eval} msg]
  lappend v $msg
} {1 {wrong # args: should be "db eval ?OPTIONS? SQL ?ARRAY-NAME? ?SCRIPT?"}}
} {1 {wrong # args: should be "db eval ?OPTIONS? SQL ?VAR-NAME? ?SCRIPT?"}}
do_test tcl-1.15 {
  set v [catch {db function} msg]
  lappend v $msg
} {1 {wrong # args: should be "db function NAME ?SWITCHES? SCRIPT"}}
do_test tcl-1.16 {
  set v [catch {db last_insert_rowid xyz} msg]
  lappend v $msg
354
355
356
357
358
359
360












361
362
363
364
365
366
367
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379







+
+
+
+
+
+
+
+
+
+
+
+







  db function ret_dbl {return [expr {rand()*0.5}]}
  execsql {SELECT typeof(ret_dbl())}
} {real}
do_test tcl-9.3 {
  db function ret_int {return [expr {int(rand()*200)}]}
  execsql {SELECT typeof(ret_int())}
} {integer}
proc breakAsNullUdf args {
  if {"1" eq [lindex $args 0]} {return -code break}
}
do_test tcl-9.4 {
  db function banu breakAsNullUdf
  execsql {SELECT typeof(banu()), typeof(banu(1))}
} {text null}
do_test tcl-9.5 {
  db nullvalue banunull
  db eval {SELECT banu(), banu(1)}
} {{} banunull}


# Recursive calls to the same user-defined function
#
ifcapable tclvar {
  do_test tcl-9.10 {
    proc userfunc_r1 {n} {
      if {$n<=0} {return 0}
461
462
463
464
465
466
467
468

469
470
471
472
473
474
475
476
477
478

479
480
481
482
483
484
485
473
474
475
476
477
478
479

480
481
482
483
484
485
486
487
488
489

490
491
492
493
494
495
496
497







-
+









-
+







    }
  }]
} {2}
do_test tcl-10.13 {
  db eval {SELECT * FROM t4}
} {1 2 5 6 7}

# Now test that [db transaction] commands may be nested with 
# Now test that [db transaction] commands may be nested with
# the expected results.
#
do_test tcl-10.14 {
  db transaction {
    db eval {
      DELETE FROM t4;
      INSERT INTO t4 VALUES('one');
    }

    catch { 
    catch {
      db transaction {
        db eval { INSERT INTO t4 VALUES('two') }
        db transaction {
          db eval { INSERT INTO t4 VALUES('three') }
          error "throw an error!"
        }
      }
670
671
672
673
674
675
676
677

678
679
680
681



682
683
684
685
686
687
688
682
683
684
685
686
687
688

689
690



691
692
693
694
695
696
697
698
699
700







-
+

-
-
-
+
+
+







  db exists {SELECT a FROM t1 WHERE a>2}
} {1}
do_test tcl-15.5 {
  db exists {SELECT a FROM t1 WHERE a>3}
} {0}


# 2017-06-26: The --withoutnulls flag to "db eval".
# 2017-06-26: The -withoutnulls flag to "db eval".
#
# In the "db eval --withoutnulls SQL ARRAY" form, NULL results cause the
# corresponding array entry to be unset.  The default behavior (without
# the -withoutnulls flags) is for the corresponding array value to get
# In the "db eval -withoutnulls SQL TARGET" form, NULL results cause the
# corresponding target entry to be unset.  The default behavior (without
# the -withoutnulls flags) is for the corresponding target value to get
# the [db nullvalue] string.
#
catch {db close}
forcedelete test.db
sqlite3 db test.db
do_execsql_test tcl-16.100 {
  CREATE TABLE t1(a,b);
716
717
718
719
720
721
722
723

724
725
726
727



728
729

730
731
732
733



734
735
736
737
738
739
740




741
742
743
744
745
746
747
748




749
750
751
752
753
754
755
756




757
758
759
760
761
762
763
764




765
766
767
768
769
770
771
772




773
774
775
776
777
778
779
780




781
782
783
784
785
786
787
728
729
730
731
732
733
734

735
736



737
738
739
740

741
742



743
744
745
746
747
748




749
750
751
752
753
754
755
756




757
758
759
760
761
762
763
764




765
766
767
768
769
770
771
772




773
774
775
776
777
778
779
780




781
782
783
784
785
786
787
788




789
790
791
792
793
794
795
796
797
798
799







-
+

-
-
-
+
+
+

-
+

-
-
-
+
+
+



-
-
-
-
+
+
+
+




-
-
-
-
+
+
+
+




-
-
-
-
+
+
+
+




-
-
-
-
+
+
+
+




-
-
-
-
+
+
+
+




-
-
-
-
+
+
+
+







#-------------------------------------------------------------------------
# Test the -type option to [db function].
#
reset_db
proc add {a b} { return [expr $a + $b] }
proc ret {a} { return $a }

db function add_i -returntype integer add 
db function add_i -returntype integer add
db function add_r -ret        real    add
db function add_t -return     text    add 
db function add_b -returntype blob    add 
db function add_a -returntype any     add 
db function add_t -return     text    add
db function add_b -returntype blob    add
db function add_a -returntype any     add

db function ret_i -returntype int     ret 
db function ret_i -returntype int     ret
db function ret_r -returntype real    ret
db function ret_t -returntype text    ret 
db function ret_b -returntype blob    ret 
db function ret_a -r          any     ret 
db function ret_t -returntype text    ret
db function ret_b -returntype blob    ret
db function ret_a -r          any     ret

do_execsql_test 17.0 {
  SELECT quote( add_i(2, 3) );
  SELECT quote( add_r(2, 3) ); 
  SELECT quote( add_t(2, 3) ); 
  SELECT quote( add_b(2, 3) ); 
  SELECT quote( add_a(2, 3) ); 
  SELECT quote( add_r(2, 3) );
  SELECT quote( add_t(2, 3) );
  SELECT quote( add_b(2, 3) );
  SELECT quote( add_a(2, 3) );
} {5 5.0 '5' X'35' 5}

do_execsql_test 17.1 {
  SELECT quote( add_i(2.2, 3.3) );
  SELECT quote( add_r(2.2, 3.3) ); 
  SELECT quote( add_t(2.2, 3.3) ); 
  SELECT quote( add_b(2.2, 3.3) ); 
  SELECT quote( add_a(2.2, 3.3) ); 
  SELECT quote( add_r(2.2, 3.3) );
  SELECT quote( add_t(2.2, 3.3) );
  SELECT quote( add_b(2.2, 3.3) );
  SELECT quote( add_a(2.2, 3.3) );
} {5.5 5.5 '5.5' X'352E35' 5.5}

do_execsql_test 17.2 {
  SELECT quote( ret_i(2.5) );
  SELECT quote( ret_r(2.5) ); 
  SELECT quote( ret_t(2.5) ); 
  SELECT quote( ret_b(2.5) ); 
  SELECT quote( ret_a(2.5) ); 
  SELECT quote( ret_r(2.5) );
  SELECT quote( ret_t(2.5) );
  SELECT quote( ret_b(2.5) );
  SELECT quote( ret_a(2.5) );
} {2.5 2.5 '2.5' X'322E35' 2.5}

do_execsql_test 17.3 {
  SELECT quote( ret_i('2.5') );
  SELECT quote( ret_r('2.5') ); 
  SELECT quote( ret_t('2.5') ); 
  SELECT quote( ret_b('2.5') ); 
  SELECT quote( ret_a('2.5') ); 
  SELECT quote( ret_r('2.5') );
  SELECT quote( ret_t('2.5') );
  SELECT quote( ret_b('2.5') );
  SELECT quote( ret_a('2.5') );
} {2.5 2.5 '2.5' X'322E35' '2.5'}

do_execsql_test 17.4 {
  SELECT quote( ret_i('abc') );
  SELECT quote( ret_r('abc') ); 
  SELECT quote( ret_t('abc') ); 
  SELECT quote( ret_b('abc') ); 
  SELECT quote( ret_a('abc') ); 
  SELECT quote( ret_r('abc') );
  SELECT quote( ret_t('abc') );
  SELECT quote( ret_b('abc') );
  SELECT quote( ret_a('abc') );
} {'abc' 'abc' 'abc' X'616263' 'abc'}

do_execsql_test 17.5 {
  SELECT quote( ret_i(X'616263') );
  SELECT quote( ret_r(X'616263') ); 
  SELECT quote( ret_t(X'616263') ); 
  SELECT quote( ret_b(X'616263') ); 
  SELECT quote( ret_a(X'616263') ); 
  SELECT quote( ret_r(X'616263') );
  SELECT quote( ret_t(X'616263') );
  SELECT quote( ret_b(X'616263') );
  SELECT quote( ret_a(X'616263') );
} {'abc' 'abc' 'abc' X'616263' X'616263'}

do_test 17.6.1 {
  list [catch { db function xyz -return object ret } msg] $msg
} {1 {bad type "object": must be integer, real, text, blob, or any}}

do_test 17.6.2 {
844
845
846
847
848
849
850














































851
852

853
854
855
856
857
858

859
860
861
862
863
864
865


866
867
868
869
870
871
872
873
874
875
876
877

878
879
880
881
882
883
884
885
886
887

888
889
890
891

892
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909

910
911
912
913
914
915

916
917
918
919
920
921
922

923
924
925
926
927
928
929
930
931
932
933
934
935

936
937
938
939
940
941
942
943
944
945

946
947
948
949
950
951
952







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
+





-
+






-
+
+











-
+









-
+




+

  db bind_fallback bind_fallback_does_not_exist
} {}
do_catchsql_test 19.911 {
  SELECT $abc, typeof($abc), $def, typeof($def), $ghi, typeof($ghi);
} {1 {invalid command name "bind_fallback_does_not_exist"}}
db bind_fallback {}

# 2025-05-05: the -asdict eval flag
#
do_test 20.0 {
  execsql {CREATE TABLE tad(a,b)}
  execsql {INSERT INTO tad(a,b) VALUES('aa','bb'),('AA','BB')}
  db eval -asdict {
    SELECT a, b FROM tad WHERE 0
  } D {}
  set D
} {* {a b}}
do_test 20.1 {
  unset D
  set i 0
  set res {}
  set colNames {}
  db eval -asdict {
    SELECT a, b FROM tad ORDER BY a
  } D {
    dict set D i [incr i]
    lappend res $i [dict get $D a] [dict get $D b]
    if {1 == $i} {
      set colNames [dict get $D *]
    }
  }
  lappend res $colNames
  unset D
  set res
} {1 AA BB 2 aa bb {a b}}
do_test 20.2 {
  set res {}
  db eval -asdict -withoutnulls {
    SELECT n, a, b FROM (
      SELECT 1 as n, 'aa' as a, NULL as b
      UNION ALL
      SELECT 2 as n, NULL as a, 'bb' as b
    )
    ORDER BY n
  } D {
    dict unset D *
    lappend res [dict values $D]
  }
  unset D
  execsql {DROP TABLE tad}
  set res
} {{1 aa} {2 bb}}

#-------------------------------------------------------------------------
do_test 20.0 {
do_test 21.0 {
  db transaction {
    db close
  }
} {}

do_test 20.1 {
do_test 21.1 {
  sqlite3 db test.db
  set rc [catch {
    db eval {SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3} { db close }
  } msg]
  list $rc $msg
} {1 {invalid command name "db"}}
  



proc closedb {} {
  db close
  return 10
}
proc func1 {} { return 1 }

sqlite3 db test.db
db func closedb closedb
db func func1 func1

do_test 20.2 {
do_test 21.2 {
  set rc [catch {
    db eval {
      SELECT closedb(),func1() UNION ALL SELECT 20,30 UNION ALL SELECT 30,40
    }
  } msg]
  list $rc $msg
} {0 {10 1 20 30 30 40}}

sqlite3 db :memory:
do_test 21.1 {
do_test 22.1 {
  catch {db eval {SELECT 1 2 3;}} msg
  db erroroffset
} {9}


finish_test