aboutsummaryrefslogtreecommitdiff
path: root/ext/session/test_session.c
diff options
context:
space:
mode:
Diffstat (limited to 'ext/session/test_session.c')
-rw-r--r--ext/session/test_session.c623
1 files changed, 623 insertions, 0 deletions
diff --git a/ext/session/test_session.c b/ext/session/test_session.c
new file mode 100644
index 000000000..49e45c5fb
--- /dev/null
+++ b/ext/session/test_session.c
@@ -0,0 +1,623 @@
+
+#if defined(SQLITE_TEST) && defined(SQLITE_ENABLE_SESSION) \
+ && defined(SQLITE_ENABLE_PREUPDATE_HOOK)
+
+#include "sqlite3session.h"
+#include <assert.h>
+#include <string.h>
+#include <tcl.h>
+
+static int test_session_error(Tcl_Interp *interp, int rc){
+ extern const char *sqlite3ErrName(int);
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
+ return TCL_ERROR;
+}
+
+/*
+** Tclcmd: $session attach TABLE
+** $session changeset
+** $session delete
+** $session enable BOOL
+** $session indirect INTEGER
+*/
+static int test_session_cmd(
+ void *clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ sqlite3_session *pSession = (sqlite3_session *)clientData;
+ struct SessionSubcmd {
+ const char *zSub;
+ int nArg;
+ const char *zMsg;
+ int iSub;
+ } aSub[] = {
+ { "attach", 1, "TABLE", }, /* 0 */
+ { "changeset", 0, "", }, /* 1 */
+ { "delete", 0, "", }, /* 2 */
+ { "enable", 1, "BOOL", }, /* 3 */
+ { "indirect", 1, "BOOL", }, /* 4 */
+ { "isempty", 0, "", }, /* 5 */
+ { 0 }
+ };
+ int iSub;
+ int rc;
+
+ if( objc<2 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
+ return TCL_ERROR;
+ }
+ rc = Tcl_GetIndexFromObjStruct(interp,
+ objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub
+ );
+ if( rc!=TCL_OK ) return rc;
+ if( objc!=2+aSub[iSub].nArg ){
+ Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg);
+ return TCL_ERROR;
+ }
+
+ switch( iSub ){
+ case 0: { /* attach */
+ char *zArg = Tcl_GetString(objv[2]);
+ if( zArg[0]=='*' && zArg[1]=='\0' ) zArg = 0;
+ rc = sqlite3session_attach(pSession, zArg);
+ if( rc!=SQLITE_OK ){
+ return test_session_error(interp, rc);
+ }
+ }
+ break;
+
+ case 1: { /* changeset */
+ int nChange;
+ void *pChange;
+ rc = sqlite3session_changeset(pSession, &nChange, &pChange);
+ if( rc==SQLITE_OK ){
+ Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pChange, nChange));
+ sqlite3_free(pChange);
+ }else{
+ return test_session_error(interp, rc);
+ }
+ break;
+ }
+
+ case 2: /* delete */
+ Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
+ break;
+
+ case 3: { /* enable */
+ int val;
+ if( Tcl_GetIntFromObj(interp, objv[2], &val) ) return TCL_ERROR;
+ val = sqlite3session_enable(pSession, val);
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
+ break;
+ }
+
+ case 4: { /* indirect */
+ int val;
+ if( Tcl_GetIntFromObj(interp, objv[2], &val) ) return TCL_ERROR;
+ val = sqlite3session_indirect(pSession, val);
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
+ break;
+ }
+
+ case 5: { /* isempty */
+ int val;
+ val = sqlite3session_isempty(pSession);
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
+ break;
+ }
+ }
+
+ return TCL_OK;
+}
+
+static void test_session_del(void *clientData){
+ sqlite3_session *pSession = (sqlite3_session *)clientData;
+ sqlite3session_delete(pSession);
+}
+
+/*
+** Tclcmd: sqlite3session CMD DB-HANDLE DB-NAME
+*/
+static int test_sqlite3session(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ sqlite3 *db;
+ Tcl_CmdInfo info;
+ int rc; /* sqlite3session_create() return code */
+ sqlite3_session *pSession; /* New session object */
+
+ if( objc!=4 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "CMD DB-HANDLE DB-NAME");
+ return TCL_ERROR;
+ }
+
+ if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[2]), &info) ){
+ Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0);
+ return TCL_ERROR;
+ }
+ db = *(sqlite3 **)info.objClientData;
+
+ rc = sqlite3session_create(db, Tcl_GetString(objv[3]), &pSession);
+ if( rc!=SQLITE_OK ){
+ return test_session_error(interp, rc);
+ }
+
+ Tcl_CreateObjCommand(
+ interp, Tcl_GetString(objv[1]), test_session_cmd, (ClientData)pSession,
+ test_session_del
+ );
+ Tcl_SetObjResult(interp, objv[1]);
+ return TCL_OK;
+}
+
+static void test_append_value(Tcl_Obj *pList, sqlite3_value *pVal){
+ if( pVal==0 ){
+ Tcl_ListObjAppendElement(0, pList, Tcl_NewObj());
+ Tcl_ListObjAppendElement(0, pList, Tcl_NewObj());
+ }else{
+ Tcl_Obj *pObj;
+ switch( sqlite3_value_type(pVal) ){
+ case SQLITE_NULL:
+ Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("n", 1));
+ pObj = Tcl_NewObj();
+ break;
+ case SQLITE_INTEGER:
+ Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("i", 1));
+ pObj = Tcl_NewWideIntObj(sqlite3_value_int64(pVal));
+ break;
+ case SQLITE_FLOAT:
+ Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("f", 1));
+ pObj = Tcl_NewDoubleObj(sqlite3_value_double(pVal));
+ break;
+ case SQLITE_TEXT:
+ Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("t", 1));
+ pObj = Tcl_NewStringObj((char *)sqlite3_value_text(pVal), -1);
+ break;
+ case SQLITE_BLOB:
+ Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("b", 1));
+ pObj = Tcl_NewByteArrayObj(
+ sqlite3_value_blob(pVal),
+ sqlite3_value_bytes(pVal)
+ );
+ break;
+ }
+ Tcl_ListObjAppendElement(0, pList, pObj);
+ }
+}
+
+typedef struct TestConflictHandler TestConflictHandler;
+struct TestConflictHandler {
+ Tcl_Interp *interp;
+ Tcl_Obj *pConflictScript;
+ Tcl_Obj *pFilterScript;
+};
+
+static int test_obj_eq_string(Tcl_Obj *p, const char *z){
+ int n;
+ int nObj;
+ char *zObj;
+
+ n = strlen(z);
+ zObj = Tcl_GetStringFromObj(p, &nObj);
+
+ return (nObj==n && (n==0 || 0==memcmp(zObj, z, n)));
+}
+
+static int test_filter_handler(
+ void *pCtx, /* Pointer to TestConflictHandler structure */
+ const char *zTab /* Table name */
+){
+ TestConflictHandler *p = (TestConflictHandler *)pCtx;
+ int res = 1;
+ Tcl_Obj *pEval;
+ Tcl_Interp *interp = p->interp;
+
+ pEval = Tcl_DuplicateObj(p->pFilterScript);
+ Tcl_IncrRefCount(pEval);
+
+ if( TCL_OK!=Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zTab, -1))
+ || TCL_OK!=Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL)
+ || TCL_OK!=Tcl_GetIntFromObj(interp, Tcl_GetObjResult(interp), &res)
+ ){
+ Tcl_BackgroundError(interp);
+ }
+
+ Tcl_DecrRefCount(pEval);
+ return res;
+}
+
+static int test_conflict_handler(
+ void *pCtx, /* Pointer to TestConflictHandler structure */
+ int eConf, /* DATA, MISSING, CONFLICT, CONSTRAINT */
+ sqlite3_changeset_iter *pIter /* Handle describing change and conflict */
+){
+ TestConflictHandler *p = (TestConflictHandler *)pCtx;
+ Tcl_Obj *pEval;
+ Tcl_Interp *interp = p->interp;
+ int ret = 0; /* Return value */
+
+ int op; /* SQLITE_UPDATE, DELETE or INSERT */
+ const char *zTab; /* Name of table conflict is on */
+ int nCol; /* Number of columns in table zTab */
+
+ pEval = Tcl_DuplicateObj(p->pConflictScript);
+ Tcl_IncrRefCount(pEval);
+
+ sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0);
+
+ if( eConf==SQLITE_CHANGESET_FOREIGN_KEY ){
+ int nFk;
+ sqlite3changeset_fk_conflicts(pIter, &nFk);
+ Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj("FOREIGN_KEY", -1));
+ Tcl_ListObjAppendElement(0, pEval, Tcl_NewIntObj(nFk));
+ }else{
+
+ /* Append the operation type. */
+ Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(
+ op==SQLITE_INSERT ? "INSERT" :
+ op==SQLITE_UPDATE ? "UPDATE" :
+ "DELETE", -1
+ ));
+
+ /* Append the table name. */
+ Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zTab, -1));
+
+ /* Append the conflict type. */
+ switch( eConf ){
+ case SQLITE_CHANGESET_DATA:
+ Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("DATA",-1));
+ break;
+ case SQLITE_CHANGESET_NOTFOUND:
+ Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("NOTFOUND",-1));
+ break;
+ case SQLITE_CHANGESET_CONFLICT:
+ Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("CONFLICT",-1));
+ break;
+ case SQLITE_CHANGESET_CONSTRAINT:
+ Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("CONSTRAINT",-1));
+ break;
+ }
+
+ /* If this is not an INSERT, append the old row */
+ if( op!=SQLITE_INSERT ){
+ int i;
+ Tcl_Obj *pOld = Tcl_NewObj();
+ for(i=0; i<nCol; i++){
+ sqlite3_value *pVal;
+ sqlite3changeset_old(pIter, i, &pVal);
+ test_append_value(pOld, pVal);
+ }
+ Tcl_ListObjAppendElement(0, pEval, pOld);
+ }
+
+ /* If this is not a DELETE, append the new row */
+ if( op!=SQLITE_DELETE ){
+ int i;
+ Tcl_Obj *pNew = Tcl_NewObj();
+ for(i=0; i<nCol; i++){
+ sqlite3_value *pVal;
+ sqlite3changeset_new(pIter, i, &pVal);
+ test_append_value(pNew, pVal);
+ }
+ Tcl_ListObjAppendElement(0, pEval, pNew);
+ }
+
+ /* If this is a CHANGESET_DATA or CHANGESET_CONFLICT conflict, append
+ ** the conflicting row. */
+ if( eConf==SQLITE_CHANGESET_DATA || eConf==SQLITE_CHANGESET_CONFLICT ){
+ int i;
+ Tcl_Obj *pConflict = Tcl_NewObj();
+ for(i=0; i<nCol; i++){
+ int rc;
+ sqlite3_value *pVal;
+ rc = sqlite3changeset_conflict(pIter, i, &pVal);
+ assert( rc==SQLITE_OK );
+ test_append_value(pConflict, pVal);
+ }
+ Tcl_ListObjAppendElement(0, pEval, pConflict);
+ }
+
+ /***********************************************************************
+ ** This block is purely for testing some error conditions.
+ */
+ if( eConf==SQLITE_CHANGESET_CONSTRAINT
+ || eConf==SQLITE_CHANGESET_NOTFOUND
+ ){
+ sqlite3_value *pVal;
+ int rc = sqlite3changeset_conflict(pIter, 0, &pVal);
+ assert( rc==SQLITE_MISUSE );
+ }else{
+ sqlite3_value *pVal;
+ int rc = sqlite3changeset_conflict(pIter, -1, &pVal);
+ assert( rc==SQLITE_RANGE );
+ rc = sqlite3changeset_conflict(pIter, nCol, &pVal);
+ assert( rc==SQLITE_RANGE );
+ }
+ if( op==SQLITE_DELETE ){
+ sqlite3_value *pVal;
+ int rc = sqlite3changeset_new(pIter, 0, &pVal);
+ assert( rc==SQLITE_MISUSE );
+ }else{
+ sqlite3_value *pVal;
+ int rc = sqlite3changeset_new(pIter, -1, &pVal);
+ assert( rc==SQLITE_RANGE );
+ rc = sqlite3changeset_new(pIter, nCol, &pVal);
+ assert( rc==SQLITE_RANGE );
+ }
+ if( op==SQLITE_INSERT ){
+ sqlite3_value *pVal;
+ int rc = sqlite3changeset_old(pIter, 0, &pVal);
+ assert( rc==SQLITE_MISUSE );
+ }else{
+ sqlite3_value *pVal;
+ int rc = sqlite3changeset_old(pIter, -1, &pVal);
+ assert( rc==SQLITE_RANGE );
+ rc = sqlite3changeset_old(pIter, nCol, &pVal);
+ assert( rc==SQLITE_RANGE );
+ }
+ /* End of testing block
+ ***********************************************************************/
+ }
+
+ if( TCL_OK!=Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL) ){
+ Tcl_BackgroundError(interp);
+ }else{
+ Tcl_Obj *pRes = Tcl_GetObjResult(interp);
+ if( test_obj_eq_string(pRes, "OMIT") || test_obj_eq_string(pRes, "") ){
+ ret = SQLITE_CHANGESET_OMIT;
+ }else if( test_obj_eq_string(pRes, "REPLACE") ){
+ ret = SQLITE_CHANGESET_REPLACE;
+ }else if( test_obj_eq_string(pRes, "ABORT") ){
+ ret = SQLITE_CHANGESET_ABORT;
+ }else{
+ Tcl_GetIntFromObj(0, pRes, &ret);
+ }
+ }
+
+ Tcl_DecrRefCount(pEval);
+ return ret;
+}
+
+/*
+** sqlite3changeset_apply DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
+*/
+static int test_sqlite3changeset_apply(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ sqlite3 *db; /* Database handle */
+ Tcl_CmdInfo info; /* Database Tcl command (objv[1]) info */
+ int rc; /* Return code from changeset_invert() */
+ void *pChangeset; /* Buffer containing changeset */
+ int nChangeset; /* Size of buffer aChangeset in bytes */
+ TestConflictHandler ctx;
+
+ if( objc!=4 && objc!=5 ){
+ Tcl_WrongNumArgs(interp, 1, objv,
+ "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?"
+ );
+ return TCL_ERROR;
+ }
+ if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &info) ){
+ Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0);
+ return TCL_ERROR;
+ }
+ db = *(sqlite3 **)info.objClientData;
+ pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset);
+ ctx.pConflictScript = objv[3];
+ ctx.pFilterScript = objc==5 ? objv[4] : 0;
+ ctx.interp = interp;
+
+ rc = sqlite3changeset_apply(db, nChangeset, pChangeset,
+ (objc==5) ? test_filter_handler : 0, test_conflict_handler, (void *)&ctx
+ );
+ if( rc!=SQLITE_OK ){
+ return test_session_error(interp, rc);
+ }
+ Tcl_ResetResult(interp);
+ return TCL_OK;
+}
+
+/*
+** sqlite3changeset_invert CHANGESET
+*/
+static int test_sqlite3changeset_invert(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ int rc; /* Return code from changeset_invert() */
+ void *aChangeset; /* Input changeset */
+ int nChangeSet; /* Size of buffer aChangeset in bytes */
+ void *aOut; /* Output changeset */
+ int nOut; /* Size of buffer aOut in bytes */
+
+ if( objc!=2 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "CHANGESET");
+ return TCL_ERROR;
+ }
+ aChangeset = (void *)Tcl_GetByteArrayFromObj(objv[1], &nChangeSet);
+
+ rc = sqlite3changeset_invert(nChangeSet, aChangeset, &nOut, &aOut);
+ if( rc!=SQLITE_OK ){
+ return test_session_error(interp, rc);
+ }
+ Tcl_SetObjResult(interp, Tcl_NewByteArrayObj((unsigned char *)aOut, nOut));
+ sqlite3_free(aOut);
+ return TCL_OK;
+}
+
+/*
+** sqlite3changeset_concat LEFT RIGHT
+*/
+static int test_sqlite3changeset_concat(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ int rc; /* Return code from changeset_invert() */
+ void *aLeft; /* Input changeset */
+ int nLeft; /* Size of buffer aChangeset in bytes */
+ void *aRight; /* Input changeset */
+ int nRight; /* Size of buffer aChangeset in bytes */
+ void *aOut; /* Output changeset */
+ int nOut; /* Size of buffer aOut in bytes */
+
+ if( objc!=3 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "LEFT RIGHT");
+ return TCL_ERROR;
+ }
+ aLeft = (void *)Tcl_GetByteArrayFromObj(objv[1], &nLeft);
+ aRight = (void *)Tcl_GetByteArrayFromObj(objv[2], &nRight);
+
+ rc = sqlite3changeset_concat(nLeft, aLeft, nRight, aRight, &nOut, &aOut);
+ if( rc!=SQLITE_OK ){
+ return test_session_error(interp, rc);
+ }
+ Tcl_SetObjResult(interp, Tcl_NewByteArrayObj((unsigned char *)aOut, nOut));
+ sqlite3_free(aOut);
+ return TCL_OK;
+}
+
+/*
+** sqlite3session_foreach VARNAME CHANGESET SCRIPT
+*/
+static int test_sqlite3session_foreach(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ void *pChangeSet;
+ int nChangeSet;
+ sqlite3_changeset_iter *pIter;
+ int rc;
+ Tcl_Obj *pVarname;
+ Tcl_Obj *pCS;
+ Tcl_Obj *pScript;
+ int isCheckNext = 0;
+
+ if( objc>1 ){
+ char *zOpt = Tcl_GetString(objv[1]);
+ isCheckNext = (strcmp(zOpt, "-next")==0);
+ }
+ if( objc!=4+isCheckNext ){
+ Tcl_WrongNumArgs(interp, 1, objv, "?-next? VARNAME CHANGESET SCRIPT");
+ return TCL_ERROR;
+ }
+
+ pVarname = objv[1+isCheckNext];
+ pCS = objv[2+isCheckNext];
+ pScript = objv[3+isCheckNext];
+
+ pChangeSet = (void *)Tcl_GetByteArrayFromObj(pCS, &nChangeSet);
+ rc = sqlite3changeset_start(&pIter, nChangeSet, pChangeSet);
+ if( rc!=SQLITE_OK ){
+ return test_session_error(interp, rc);
+ }
+
+ while( SQLITE_ROW==sqlite3changeset_next(pIter) ){
+ int nCol; /* Number of columns in table */
+ int nCol2; /* Number of columns in table */
+ int op; /* SQLITE_INSERT, UPDATE or DELETE */
+ const char *zTab; /* Name of table change applies to */
+ Tcl_Obj *pVar; /* Tcl value to set $VARNAME to */
+ Tcl_Obj *pOld; /* Vector of old.* values */
+ Tcl_Obj *pNew; /* Vector of new.* values */
+ int bIndirect;
+
+ char *zPK;
+ unsigned char *abPK;
+ int i;
+
+ sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
+ pVar = Tcl_NewObj();
+ Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(
+ op==SQLITE_INSERT ? "INSERT" :
+ op==SQLITE_UPDATE ? "UPDATE" :
+ "DELETE", -1
+ ));
+
+ Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zTab, -1));
+ Tcl_ListObjAppendElement(0, pVar, Tcl_NewBooleanObj(bIndirect));
+
+ zPK = ckalloc(nCol+1);
+ memset(zPK, 0, nCol+1);
+ sqlite3changeset_pk(pIter, &abPK, &nCol2);
+ assert( nCol==nCol2 );
+ for(i=0; i<nCol; i++){
+ zPK[i] = (abPK[i] ? 'X' : '.');
+ }
+ Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zPK, -1));
+ ckfree(zPK);
+
+ pOld = Tcl_NewObj();
+ if( op!=SQLITE_INSERT ){
+ int i;
+ for(i=0; i<nCol; i++){
+ sqlite3_value *pVal;
+ sqlite3changeset_old(pIter, i, &pVal);
+ test_append_value(pOld, pVal);
+ }
+ }
+ pNew = Tcl_NewObj();
+ if( op!=SQLITE_DELETE ){
+ int i;
+ for(i=0; i<nCol; i++){
+ sqlite3_value *pVal;
+ sqlite3changeset_new(pIter, i, &pVal);
+ test_append_value(pNew, pVal);
+ }
+ }
+ Tcl_ListObjAppendElement(0, pVar, pOld);
+ Tcl_ListObjAppendElement(0, pVar, pNew);
+
+ Tcl_ObjSetVar2(interp, pVarname, 0, pVar, 0);
+ rc = Tcl_EvalObjEx(interp, pScript, 0);
+ if( rc!=TCL_OK && rc!=TCL_CONTINUE ){
+ sqlite3changeset_finalize(pIter);
+ return rc==TCL_BREAK ? TCL_OK : rc;
+ }
+ }
+
+ if( isCheckNext ){
+ int rc2 = sqlite3changeset_next(pIter);
+ rc = sqlite3changeset_finalize(pIter);
+ assert( (rc2==SQLITE_DONE && rc==SQLITE_OK) || rc2==rc );
+ }else{
+ rc = sqlite3changeset_finalize(pIter);
+ }
+ if( rc!=SQLITE_OK ){
+ return test_session_error(interp, rc);
+ }
+
+ return TCL_OK;
+}
+
+int TestSession_Init(Tcl_Interp *interp){
+ Tcl_CreateObjCommand(interp, "sqlite3session", test_sqlite3session, 0, 0);
+ Tcl_CreateObjCommand(
+ interp, "sqlite3session_foreach", test_sqlite3session_foreach, 0, 0
+ );
+ Tcl_CreateObjCommand(
+ interp, "sqlite3changeset_invert", test_sqlite3changeset_invert, 0, 0
+ );
+ Tcl_CreateObjCommand(
+ interp, "sqlite3changeset_concat", test_sqlite3changeset_concat, 0, 0
+ );
+ Tcl_CreateObjCommand(
+ interp, "sqlite3changeset_apply", test_sqlite3changeset_apply, 0, 0
+ );
+ return TCL_OK;
+}
+
+#endif /* SQLITE_TEST && SQLITE_SESSION && SQLITE_PREUPDATE_HOOK */