/ Check-in [4fc35d5e09]
Login

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

Overview
Comment:Add method argument to aggregate function invocations
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | andygoth-tcl-function
Files: files | file ages | folders
SHA3-256: 4fc35d5e09e2a4865cff14f2c6fff175c23891fca833840ff532be7ab1bd50cb
User & Date: andygoth 2019-01-31 05:25:24.085
Context
2019-02-02
20:17
Add support for window functions check-in: 4be97e0cfe user: andygoth tags: andygoth-tcl-function
2019-01-31
05:25
Add method argument to aggregate function invocations check-in: 4fc35d5e09 user: andygoth tags: andygoth-tcl-function
2019-01-30
17:30
Initial implementation of user-defined Tcl aggregate functions. Testing and documentation to come. check-in: e0689f05d1 user: andygoth tags: andygoth-tcl-function
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/tclsqlite.c.
83
84
85
86
87
88
89
















90
91
92
93
94
95
96
#undef TCL_STORAGE_CLASS
#define TCL_STORAGE_CLASS DLLEXPORT
#endif /* BUILD_sqlite */

#define NUM_PREPARED_STMTS 10
#define MAX_PREPARED_STMTS 100

















/* Forward declaration */
typedef struct SqliteDb SqliteDb;

/*
** New SQL functions can be created as TCL scripts.  Each such function
** is described by an instance of the following structure.
*/







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#undef TCL_STORAGE_CLASS
#define TCL_STORAGE_CLASS DLLEXPORT
#endif /* BUILD_sqlite */

#define NUM_PREPARED_STMTS 10
#define MAX_PREPARED_STMTS 100

/*
** Fixed string table index enumeration.  These enumeration values are used as
** indexes into the fixedStrings array.
*/
enum {
  FS_STEP,
  FS_FINAL,
  FS_NUM_FIXED_STRINGS
};

/*
** Fixed string table, indexed by the above enumeration.  This table is
** initialized by Sqlite3_Init() and cleaned up by Sqlite3_Unload().
*/
static Tcl_Obj *fixedStrings[FS_NUM_FIXED_STRINGS];

/* Forward declaration */
typedef struct SqliteDb SqliteDb;

/*
** New SQL functions can be created as TCL scripts.  Each such function
** is described by an instance of the following structure.
*/
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
/*
** Utility function shared by the following functions:
** - tclSqlFuncScalar()
** - tclSqlFuncStep()
** - tclSqlFuncFinal()
** This function calls a user-defined TCL script to implement an SQL function.
** Before being invoked, additional arguments may be appended to the script.
** Optionally, the first argument may be a TCL object.  All remaining arguments
** are TCL objects constructed from SQL values.  On success, the return value
** is a pointer to the TCL object result.  On failure, the return value is NULL,
** and an error message is stored in the SQL context.
*/
static Tcl_Obj *tclSqlFuncCall(
  sqlite3_context *context,
  Tcl_Obj *pState,
  int argc,
  sqlite3_value **argv
){
  SqlFunc *p = sqlite3_user_data(context);
  Tcl_Obj *pCmd;
  int i;
  int rc;

  if( argc==0 && !pState ){
    /* If there are no arguments to the function, call Tcl_EvalObjEx on the
    ** script object directly.  This allows the TCL compiler to generate
    ** bytecode for the command on the first invocation and thus make
    ** subsequent invocations much faster. */
    pCmd = p->pScript;
    Tcl_IncrRefCount(pCmd);
    rc = Tcl_EvalObjEx(p->interp, pCmd, 0);







|
|
|
|



|








|







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
/*
** Utility function shared by the following functions:
** - tclSqlFuncScalar()
** - tclSqlFuncStep()
** - tclSqlFuncFinal()
** This function calls a user-defined TCL script to implement an SQL function.
** Before being invoked, additional arguments may be appended to the script.
** Any number of initial arguments may supplied as TCL objects, and subsequent
** arguments are TCL objects constructed from SQL values.  On success, the
** return value is a pointer to the TCL object result.  On failure, the return
** value is NULL, and an error message is stored in the SQL context.
*/
static Tcl_Obj *tclSqlFuncCall(
  sqlite3_context *context,
  Tcl_Obj **apPrefix,
  int argc,
  sqlite3_value **argv
){
  SqlFunc *p = sqlite3_user_data(context);
  Tcl_Obj *pCmd;
  int i;
  int rc;

  if( argc==0 && !apPrefix ){
    /* If there are no arguments to the function, call Tcl_EvalObjEx on the
    ** script object directly.  This allows the TCL compiler to generate
    ** bytecode for the command on the first invocation and thus make
    ** subsequent invocations much faster. */
    pCmd = p->pScript;
    Tcl_IncrRefCount(pCmd);
    rc = Tcl_EvalObjEx(p->interp, pCmd, 0);
944
945
946
947
948
949
950
951

952
953
954
955
956

957
958
959
960
961
962
963
    int nArg;
    if( Tcl_ListObjGetElements(p->interp, p->pScript, &nArg, &aArg) ){
      sqlite3_result_error(context, Tcl_GetStringResult(p->interp), -1);
      return 0;
    }
    pCmd = Tcl_NewListObj(nArg, aArg);
    Tcl_IncrRefCount(pCmd);
    if( pState ){

      rc = Tcl_ListObjAppendElement(p->interp, pCmd, pState);
      if( rc ){
        Tcl_DecrRefCount(pCmd);
        sqlite3_result_error(context, Tcl_GetStringResult(p->interp), -1);
        return 0;

      }
    }
    for(i=0; i<argc; i++){
      sqlite3_value *pIn = argv[i];
      Tcl_Obj *pVal;

      /* Set pVal to contain the i'th column of this row. */







|
>
|
|
|
|
|
>







960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
    int nArg;
    if( Tcl_ListObjGetElements(p->interp, p->pScript, &nArg, &aArg) ){
      sqlite3_result_error(context, Tcl_GetStringResult(p->interp), -1);
      return 0;
    }
    pCmd = Tcl_NewListObj(nArg, aArg);
    Tcl_IncrRefCount(pCmd);
    if( apPrefix ){
      for(i=0; apPrefix[i]; i++){
        rc = Tcl_ListObjAppendElement(p->interp, pCmd, apPrefix[i]);
        if( rc ){
          Tcl_DecrRefCount(pCmd);
          sqlite3_result_error(context, Tcl_GetStringResult(p->interp), -1);
          return 0;
        }
      }
    }
    for(i=0; i<argc; i++){
      sqlite3_value *pIn = argv[i];
      Tcl_Obj *pVal;

      /* Set pVal to contain the i'th column of this row. */
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112

1113
1114
1115
1116
1117
1118
1119

1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134



1135
1136
1137
1138
1139
1140
1141
1142
  sqlite3_value **argv
){
  tclSqlFuncResult(context, tclSqlFuncCall(context, NULL, argc, argv));
}

/*
** This routine is called to evaluate an aggregate SQL function implemented
** using TCL script.  It is invoked for each row in the group.  The TCL result
** of each invocation becomes the first argument to the next invocation.  The
** first argument of the first invocation is empty string.

*/
static void tclSqlFuncStep(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  Tcl_Obj **ppState;

  Tcl_Obj *pResult;

  /* Even though this check was performed when the function was registered, it
  ** is necessary to double-check here in case -argcount was not used, meaning
  ** SQLite will allow the function to have any number of arguments, even zero.
  ** The presence or absence of arguments is how the TCL script can distinguish
  ** between being called as a step function or a final function.  Thus, being
  ** passed zero arguments would create ambiguity. */
  if( argc==0 ){
    sqlite3_result_error(context, "insufficient arguments: all user-defined "
        "TCL aggregate functions must have at least one argument", -1);
    return;
  }

  ppState = tclSqlFuncState(context);



  pResult = tclSqlFuncCall(context, *ppState, argc, argv);
  if( pResult ){
    /* If the step function succeeded, increment the new state reference count
    ** before decrementing the old state reference count.  This sequence will
    ** deallocate the old state if it is a distinct TCL object from the new, but
    ** it will not deallocate the old state if the TCL script modified it in
    ** place and then returned it again.
    **







|
|
|
>







>


<
<
<
<
<
<
<
<
<
<
<
<

>
>
>
|







1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141












1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
  sqlite3_value **argv
){
  tclSqlFuncResult(context, tclSqlFuncCall(context, NULL, argc, argv));
}

/*
** This routine is called to evaluate an aggregate SQL function implemented
** using TCL script.  It is invoked for each row in the group.  The first
** argument is the fixed string "step".  The second argument is the return value
** of the prior invocation, or empty string for the first invocation.  Remaining
** arguments to the TCL script are the arguments to the aggregate SQL function.
*/
static void tclSqlFuncStep(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  Tcl_Obj **ppState;
  Tcl_Obj *apPrefix[3];
  Tcl_Obj *pResult;













  ppState = tclSqlFuncState(context);
  apPrefix[0] = fixedStrings[FS_STEP];
  apPrefix[1] = *ppState;
  apPrefix[2] = NULL;
  pResult = tclSqlFuncCall(context, apPrefix, argc, argv);
  if( pResult ){
    /* If the step function succeeded, increment the new state reference count
    ** before decrementing the old state reference count.  This sequence will
    ** deallocate the old state if it is a distinct TCL object from the new, but
    ** it will not deallocate the old state if the TCL script modified it in
    ** place and then returned it again.
    **
1180
1181
1182
1183
1184
1185
1186
1187

1188
1189
1190
1191



1192
1193



1194
1195
1196
1197
1198
1199
1200
1201
    Tcl_DecrRefCount(*ppState);
    *ppState = &tclSqlFuncError;
  }
}

/*
** This routine is called to evaluate an aggregate SQL function implemented
** using TCL script.  It is invoked at the end of each group.  The TCL result

** of the prior invocation (via tclSqlFuncStep()) is the first argument to this
** invocation, or empty string if there were no rows.
*/
static void tclSqlFuncFinal(sqlite3_context *context){



  Tcl_Obj **ppState = tclSqlFuncState(context);
  if( *ppState!=&tclSqlFuncError ){



    tclSqlFuncResult(context, tclSqlFuncCall(context, *ppState, 0, 0));
    Tcl_DecrRefCount(*ppState);
  }
}

#ifndef SQLITE_OMIT_AUTHORIZATION
/*
** This is the authentication function.  It appends the authentication







|
>
|
|


>
>
>
|

>
>
>
|







1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
    Tcl_DecrRefCount(*ppState);
    *ppState = &tclSqlFuncError;
  }
}

/*
** This routine is called to evaluate an aggregate SQL function implemented
** using TCL script.  It is invoked at the end of each group.  The first
** argument is the fixed string "final".  The TCL result of the prior invocation
** (via tclSqlFuncStep()) is the second argument to this invocation, or empty
** string if there were no rows.
*/
static void tclSqlFuncFinal(sqlite3_context *context){
  Tcl_Obj **ppState;
  Tcl_Obj *apPrefix[3];

  ppState = tclSqlFuncState(context);
  if( *ppState!=&tclSqlFuncError ){
    apPrefix[0] = fixedStrings[FS_FINAL];
    apPrefix[1] = *ppState;
    apPrefix[2] = NULL;
    tclSqlFuncResult(context, tclSqlFuncCall(context, apPrefix, 0, 0));
    Tcl_DecrRefCount(*ppState);
  }
}

#ifndef SQLITE_OMIT_AUTHORIZATION
/*
** This is the authentication function.  It appends the authentication
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
      }else{
        Tcl_AppendResult(interp, "bad option \"", z,
            "\": must be -argcount, -deterministic, or -aggregate", (char *)0
        );
        return TCL_ERROR;
      }
    }
    if( type==AGGREGATE && nArg==0 ){
      Tcl_AppendResult(interp, "all user-defined TCL aggregate functions must "
          "have at least one argument", (char *)0);
      return TCL_ERROR;
    }

    pScript = objv[objc-1];
    zName = Tcl_GetStringFromObj(objv[2], 0);
    pFunc = findSqlFunc(pDb, zName);
    if( pFunc==0 ) return TCL_ERROR;
    if( pFunc->pScript ){
      Tcl_DecrRefCount(pFunc->pScript);







<
<
<
<
<







2870
2871
2872
2873
2874
2875
2876





2877
2878
2879
2880
2881
2882
2883
      }else{
        Tcl_AppendResult(interp, "bad option \"", z,
            "\": must be -argcount, -deterministic, or -aggregate", (char *)0
        );
        return TCL_ERROR;
      }
    }






    pScript = objv[objc-1];
    zName = Tcl_GetStringFromObj(objv[2], 0);
    pFunc = findSqlFunc(pDb, zName);
    if( pFunc==0 ) return TCL_ERROR;
    if( pFunc->pScript ){
      Tcl_DecrRefCount(pFunc->pScript);
3868
3869
3870
3871
3872
3873
3874








3875
3876
3877
3878
3879
3880
3881
3882
3883
3884
3885
3886
3887









3888


3889
3890
3891
3892
3893
3894
3895
** for additional information.
**
** The EXTERN macros are required by TCL in order to work on windows.
*/
EXTERN int Sqlite3_Init(Tcl_Interp *interp){
  int rc = Tcl_InitStubs(interp, "8.4", 0) ? TCL_OK : TCL_ERROR;
  if( rc==TCL_OK ){








    Tcl_CreateObjCommand(interp, "sqlite3", (Tcl_ObjCmdProc*)DbMain, 0, 0);
#ifndef SQLITE_3_SUFFIX_ONLY
    /* The "sqlite" alias is undocumented.  It is here only to support
    ** legacy scripts.  All new scripts should use only the "sqlite3"
    ** command. */
    Tcl_CreateObjCommand(interp, "sqlite", (Tcl_ObjCmdProc*)DbMain, 0, 0);
#endif
    rc = Tcl_PkgProvide(interp, "sqlite3", PACKAGE_VERSION);
  }
  return rc;
}
EXTERN int Tclsqlite3_Init(Tcl_Interp *interp){ return Sqlite3_Init(interp); }
EXTERN int Sqlite3_Unload(Tcl_Interp *interp, int flags){ return TCL_OK; }









EXTERN int Tclsqlite3_Unload(Tcl_Interp *interp, int flags){ return TCL_OK; }



/* Because it accesses the file-system and uses persistent state, SQLite
** is not considered appropriate for safe interpreters.  Hence, we cause
** the _SafeInit() interfaces return TCL_ERROR.
*/
EXTERN int Sqlite3_SafeInit(Tcl_Interp *interp){ return TCL_ERROR; }
EXTERN int Sqlite3_SafeUnload(Tcl_Interp *interp, int flags){return TCL_ERROR;}







>
>
>
>
>
>
>
>












|
>
>
>
>
>
>
>
>
>
|
>
>







3881
3882
3883
3884
3885
3886
3887
3888
3889
3890
3891
3892
3893
3894
3895
3896
3897
3898
3899
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916
3917
3918
3919
3920
3921
3922
3923
3924
3925
3926
3927
** for additional information.
**
** The EXTERN macros are required by TCL in order to work on windows.
*/
EXTERN int Sqlite3_Init(Tcl_Interp *interp){
  int rc = Tcl_InitStubs(interp, "8.4", 0) ? TCL_OK : TCL_ERROR;
  if( rc==TCL_OK ){
    if( !*fixedStrings ){
#define FIXED_STRING(i, s) \
  (fixedStrings[(i)] = Tcl_NewStringObj((s), sizeof((s)) - 1), \
  Tcl_IncrRefCount(fixedStrings[(i)]))
      FIXED_STRING(FS_STEP , "step" );
      FIXED_STRING(FS_FINAL, "final");
#undef FIXED_STRING
    }
    Tcl_CreateObjCommand(interp, "sqlite3", (Tcl_ObjCmdProc*)DbMain, 0, 0);
#ifndef SQLITE_3_SUFFIX_ONLY
    /* The "sqlite" alias is undocumented.  It is here only to support
    ** legacy scripts.  All new scripts should use only the "sqlite3"
    ** command. */
    Tcl_CreateObjCommand(interp, "sqlite", (Tcl_ObjCmdProc*)DbMain, 0, 0);
#endif
    rc = Tcl_PkgProvide(interp, "sqlite3", PACKAGE_VERSION);
  }
  return rc;
}
EXTERN int Tclsqlite3_Init(Tcl_Interp *interp){ return Sqlite3_Init(interp); }
EXTERN int Sqlite3_Unload(Tcl_Interp *interp, int flags){
  int i;
  if( flags==TCL_UNLOAD_DETACH_FROM_PROCESS ){
    for(i=0; i<FS_NUM_FIXED_STRINGS; i++){
      Tcl_DecrRefCount(fixedStrings[i]);
      fixedStrings[i] = 0;
    }
  }
  return TCL_OK;
}
EXTERN int Tclsqlite3_Unload(Tcl_Interp *interp, int flags){
  return Sqlite3_Unload(interp, flags);
}

/* Because it accesses the file-system and uses persistent state, SQLite
** is not considered appropriate for safe interpreters.  Hence, we cause
** the _SafeInit() interfaces return TCL_ERROR.
*/
EXTERN int Sqlite3_SafeInit(Tcl_Interp *interp){ return TCL_ERROR; }
EXTERN int Sqlite3_SafeUnload(Tcl_Interp *interp, int flags){return TCL_ERROR;}