1
2 #if defined(SQLITE_TEST) && defined(SQLITE_ENABLE_SESSION) \
3 && defined(SQLITE_ENABLE_PREUPDATE_HOOK)
4
5 #include "sqlite3session.h"
6 #include <assert.h>
7 #include <string.h>
8 #if defined(INCLUDE_SQLITE_TCL_H)
9 # include "sqlite_tcl.h"
10 #else
11 # include "tcl.h"
12 # ifndef SQLITE_TCLAPI
13 # define SQLITE_TCLAPI
14 # endif
15 #endif
16
17 #ifndef SQLITE_AMALGAMATION
18 typedef unsigned char u8;
19 #endif
20
21 typedef struct TestSession TestSession;
22 struct TestSession {
23 sqlite3_session *pSession;
24 Tcl_Interp *interp;
25 Tcl_Obj *pFilterScript;
26 };
27
28 typedef struct TestStreamInput TestStreamInput;
29 struct TestStreamInput {
30 int nStream; /* Maximum chunk size */
31 unsigned char *aData; /* Pointer to buffer containing data */
32 int nData; /* Size of buffer aData in bytes */
33 int iData; /* Bytes of data already read by sessions */
34 };
35
36 /*
37 ** Extract an sqlite3* db handle from the object passed as the second
38 ** argument. If successful, set *pDb to point to the db handle and return
39 ** TCL_OK. Otherwise, return TCL_ERROR.
40 */
dbHandleFromObj(Tcl_Interp * interp,Tcl_Obj * pObj,sqlite3 ** pDb)41 static int dbHandleFromObj(Tcl_Interp *interp, Tcl_Obj *pObj, sqlite3 **pDb){
42 Tcl_CmdInfo info;
43 if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(pObj), &info) ){
44 Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(pObj), 0);
45 return TCL_ERROR;
46 }
47
48 *pDb = *(sqlite3 **)info.objClientData;
49 return TCL_OK;
50 }
51
52 /*************************************************************************
53 ** The following code is copied byte-for-byte from the sessions module
54 ** documentation. It is used by some of the sessions modules tests to
55 ** ensure that the example in the documentation does actually work.
56 */
57 /*
58 ** Argument zSql points to a buffer containing an SQL script to execute
59 ** against the database handle passed as the first argument. As well as
60 ** executing the SQL script, this function collects a changeset recording
61 ** all changes made to the "main" database file. Assuming no error occurs,
62 ** output variables (*ppChangeset) and (*pnChangeset) are set to point
63 ** to a buffer containing the changeset and the size of the changeset in
64 ** bytes before returning SQLITE_OK. In this case it is the responsibility
65 ** of the caller to eventually free the changeset blob by passing it to
66 ** the sqlite3_free function.
67 **
68 ** Or, if an error does occur, return an SQLite error code. The final
69 ** value of (*pChangeset) and (*pnChangeset) are undefined in this case.
70 */
sql_exec_changeset(sqlite3 * db,const char * zSql,int * pnChangeset,void ** ppChangeset)71 int sql_exec_changeset(
72 sqlite3 *db, /* Database handle */
73 const char *zSql, /* SQL script to execute */
74 int *pnChangeset, /* OUT: Size of changeset blob in bytes */
75 void **ppChangeset /* OUT: Pointer to changeset blob */
76 ){
77 sqlite3_session *pSession = 0;
78 int rc;
79
80 /* Create a new session object */
81 rc = sqlite3session_create(db, "main", &pSession);
82
83 /* Configure the session object to record changes to all tables */
84 if( rc==SQLITE_OK ) rc = sqlite3session_attach(pSession, NULL);
85
86 /* Execute the SQL script */
87 if( rc==SQLITE_OK ) rc = sqlite3_exec(db, zSql, 0, 0, 0);
88
89 /* Collect the changeset */
90 if( rc==SQLITE_OK ){
91 rc = sqlite3session_changeset(pSession, pnChangeset, ppChangeset);
92 }
93
94 /* Delete the session object */
95 sqlite3session_delete(pSession);
96
97 return rc;
98 }
99 /************************************************************************/
100
101 /*
102 ** Tclcmd: sql_exec_changeset DB SQL
103 */
test_sql_exec_changeset(void * clientData,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])104 static int SQLITE_TCLAPI test_sql_exec_changeset(
105 void * clientData,
106 Tcl_Interp *interp,
107 int objc,
108 Tcl_Obj *CONST objv[]
109 ){
110 const char *zSql;
111 sqlite3 *db;
112 void *pChangeset;
113 int nChangeset;
114 int rc;
115
116 if( objc!=3 ){
117 Tcl_WrongNumArgs(interp, 1, objv, "DB SQL");
118 return TCL_ERROR;
119 }
120 if( dbHandleFromObj(interp, objv[1], &db) ) return TCL_ERROR;
121 zSql = (const char*)Tcl_GetString(objv[2]);
122
123 rc = sql_exec_changeset(db, zSql, &nChangeset, &pChangeset);
124 if( rc!=SQLITE_OK ){
125 Tcl_ResetResult(interp);
126 Tcl_AppendResult(interp, "error in sql_exec_changeset()", 0);
127 return TCL_ERROR;
128 }
129
130 Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pChangeset, nChangeset));
131 sqlite3_free(pChangeset);
132 return TCL_OK;
133 }
134
135
136
137 #define SESSION_STREAM_TCL_VAR "sqlite3session_streams"
138
139 /*
140 ** Attempt to find the global variable zVar within interpreter interp
141 ** and extract an integer value from it. Return this value.
142 **
143 ** If the named variable cannot be found, or if it cannot be interpreted
144 ** as a integer, return 0.
145 */
test_tcl_integer(Tcl_Interp * interp,const char * zVar)146 static int test_tcl_integer(Tcl_Interp *interp, const char *zVar){
147 Tcl_Obj *pObj;
148 int iVal = 0;
149 Tcl_Obj *pName = Tcl_NewStringObj(zVar, -1);
150 Tcl_IncrRefCount(pName);
151 pObj = Tcl_ObjGetVar2(interp, pName, 0, TCL_GLOBAL_ONLY);
152 Tcl_DecrRefCount(pName);
153 if( pObj ) Tcl_GetIntFromObj(0, pObj, &iVal);
154 return iVal;
155 }
156
test_session_error(Tcl_Interp * interp,int rc,char * zErr)157 static int test_session_error(Tcl_Interp *interp, int rc, char *zErr){
158 extern const char *sqlite3ErrName(int);
159 Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
160 if( zErr ){
161 Tcl_AppendResult(interp, " - ", zErr, 0);
162 sqlite3_free(zErr);
163 }
164 return TCL_ERROR;
165 }
166
test_table_filter(void * pCtx,const char * zTbl)167 static int test_table_filter(void *pCtx, const char *zTbl){
168 TestSession *p = (TestSession*)pCtx;
169 Tcl_Obj *pEval;
170 int rc;
171 int bRes = 0;
172
173 pEval = Tcl_DuplicateObj(p->pFilterScript);
174 Tcl_IncrRefCount(pEval);
175 rc = Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewStringObj(zTbl, -1));
176 if( rc==TCL_OK ){
177 rc = Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL);
178 }
179 if( rc==TCL_OK ){
180 rc = Tcl_GetBooleanFromObj(p->interp, Tcl_GetObjResult(p->interp), &bRes);
181 }
182 if( rc!=TCL_OK ){
183 /* printf("error: %s\n", Tcl_GetStringResult(p->interp)); */
184 Tcl_BackgroundError(p->interp);
185 }
186 Tcl_DecrRefCount(pEval);
187
188 return bRes;
189 }
190
191 struct TestSessionsBlob {
192 void *p;
193 int n;
194 };
195 typedef struct TestSessionsBlob TestSessionsBlob;
196
testStreamOutput(void * pCtx,const void * pData,int nData)197 static int testStreamOutput(
198 void *pCtx,
199 const void *pData,
200 int nData
201 ){
202 TestSessionsBlob *pBlob = (TestSessionsBlob*)pCtx;
203 char *pNew;
204
205 assert( nData>0 );
206 pNew = (char*)sqlite3_realloc(pBlob->p, pBlob->n + nData);
207 if( pNew==0 ){
208 return SQLITE_NOMEM;
209 }
210 pBlob->p = (void*)pNew;
211 memcpy(&pNew[pBlob->n], pData, nData);
212 pBlob->n += nData;
213 return SQLITE_OK;
214 }
215
216 /*
217 ** Tclcmd: $session attach TABLE
218 ** $session changeset
219 ** $session delete
220 ** $session enable BOOL
221 ** $session indirect INTEGER
222 ** $session patchset
223 ** $session table_filter SCRIPT
224 */
test_session_cmd(void * clientData,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])225 static int SQLITE_TCLAPI test_session_cmd(
226 void *clientData,
227 Tcl_Interp *interp,
228 int objc,
229 Tcl_Obj *CONST objv[]
230 ){
231 TestSession *p = (TestSession*)clientData;
232 sqlite3_session *pSession = p->pSession;
233 static struct SessionSubcmd {
234 const char *zSub;
235 int nArg;
236 const char *zMsg;
237 int iSub;
238 } aSub[] = {
239 { "attach", 1, "TABLE", }, /* 0 */
240 { "changeset", 0, "", }, /* 1 */
241 { "delete", 0, "", }, /* 2 */
242 { "enable", 1, "BOOL", }, /* 3 */
243 { "indirect", 1, "BOOL", }, /* 4 */
244 { "isempty", 0, "", }, /* 5 */
245 { "table_filter", 1, "SCRIPT", }, /* 6 */
246 { "patchset", 0, "", }, /* 7 */
247 { "diff", 2, "FROMDB TBL", }, /* 8 */
248 { "memory_used", 0, "", }, /* 9 */
249 { "changeset_size", 0, "", }, /* 10 */
250 { "object_config_size", 1, "INTEGER", }, /* 11 */
251 { 0 }
252 };
253 int iSub;
254 int rc;
255
256 if( objc<2 ){
257 Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
258 return TCL_ERROR;
259 }
260 rc = Tcl_GetIndexFromObjStruct(interp,
261 objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub
262 );
263 if( rc!=TCL_OK ) return rc;
264 if( objc!=2+aSub[iSub].nArg ){
265 Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg);
266 return TCL_ERROR;
267 }
268
269 switch( iSub ){
270 case 0: { /* attach */
271 char *zArg = Tcl_GetString(objv[2]);
272 if( zArg[0]=='*' && zArg[1]=='\0' ) zArg = 0;
273 rc = sqlite3session_attach(pSession, zArg);
274 if( rc!=SQLITE_OK ){
275 return test_session_error(interp, rc, 0);
276 }
277 break;
278 }
279
280 case 7: /* patchset */
281 case 1: { /* changeset */
282 TestSessionsBlob o = {0, 0};
283 if( test_tcl_integer(interp, SESSION_STREAM_TCL_VAR) ){
284 void *pCtx = (void*)&o;
285 if( iSub==7 ){
286 rc = sqlite3session_patchset_strm(pSession, testStreamOutput, pCtx);
287 }else{
288 rc = sqlite3session_changeset_strm(pSession, testStreamOutput, pCtx);
289 }
290 }else{
291 if( iSub==7 ){
292 rc = sqlite3session_patchset(pSession, &o.n, &o.p);
293 }else{
294 rc = sqlite3session_changeset(pSession, &o.n, &o.p);
295 }
296 }
297 if( rc==SQLITE_OK ){
298 Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(o.p, o.n));
299 }
300 sqlite3_free(o.p);
301 if( rc!=SQLITE_OK ){
302 return test_session_error(interp, rc, 0);
303 }
304 break;
305 }
306
307 case 2: /* delete */
308 Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
309 break;
310
311 case 3: { /* enable */
312 int val;
313 if( Tcl_GetIntFromObj(interp, objv[2], &val) ) return TCL_ERROR;
314 val = sqlite3session_enable(pSession, val);
315 Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
316 break;
317 }
318
319 case 4: { /* indirect */
320 int val;
321 if( Tcl_GetIntFromObj(interp, objv[2], &val) ) return TCL_ERROR;
322 val = sqlite3session_indirect(pSession, val);
323 Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
324 break;
325 }
326
327 case 5: { /* isempty */
328 int val;
329 val = sqlite3session_isempty(pSession);
330 Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val));
331 break;
332 }
333
334 case 6: { /* table_filter */
335 if( p->pFilterScript ) Tcl_DecrRefCount(p->pFilterScript);
336 p->interp = interp;
337 p->pFilterScript = Tcl_DuplicateObj(objv[2]);
338 Tcl_IncrRefCount(p->pFilterScript);
339 sqlite3session_table_filter(pSession, test_table_filter, clientData);
340 break;
341 }
342
343 case 8: { /* diff */
344 char *zErr = 0;
345 rc = sqlite3session_diff(pSession,
346 Tcl_GetString(objv[2]),
347 Tcl_GetString(objv[3]),
348 &zErr
349 );
350 assert( rc!=SQLITE_OK || zErr==0 );
351 if( rc ){
352 return test_session_error(interp, rc, zErr);
353 }
354 break;
355 }
356
357 case 9: { /* memory_used */
358 sqlite3_int64 nMalloc = sqlite3session_memory_used(pSession);
359 Tcl_SetObjResult(interp, Tcl_NewWideIntObj(nMalloc));
360 break;
361 }
362
363 case 10: {
364 sqlite3_int64 nSize = sqlite3session_changeset_size(pSession);
365 Tcl_SetObjResult(interp, Tcl_NewWideIntObj(nSize));
366 break;
367 }
368 case 11: {
369 int rc;
370 int iArg;
371 if( Tcl_GetIntFromObj(interp, objv[2], &iArg) ){
372 return TCL_ERROR;
373 }
374 rc = sqlite3session_object_config(
375 pSession, SQLITE_SESSION_OBJCONFIG_SIZE, &iArg
376 );
377 if( rc!=SQLITE_OK ){
378 extern const char *sqlite3ErrName(int);
379 Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
380 }else{
381 Tcl_SetObjResult(interp, Tcl_NewIntObj(iArg));
382 }
383 break;
384 }
385 }
386
387 return TCL_OK;
388 }
389
test_session_del(void * clientData)390 static void SQLITE_TCLAPI test_session_del(void *clientData){
391 TestSession *p = (TestSession*)clientData;
392 if( p->pFilterScript ) Tcl_DecrRefCount(p->pFilterScript);
393 sqlite3session_delete(p->pSession);
394 ckfree((char*)p);
395 }
396
397 /*
398 ** Tclcmd: sqlite3session CMD DB-HANDLE DB-NAME
399 */
test_sqlite3session(void * clientData,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])400 static int SQLITE_TCLAPI test_sqlite3session(
401 void * clientData,
402 Tcl_Interp *interp,
403 int objc,
404 Tcl_Obj *CONST objv[]
405 ){
406 sqlite3 *db;
407 Tcl_CmdInfo info;
408 int rc; /* sqlite3session_create() return code */
409 TestSession *p; /* New wrapper object */
410 int iArg = -1;
411
412 if( objc!=4 ){
413 Tcl_WrongNumArgs(interp, 1, objv, "CMD DB-HANDLE DB-NAME");
414 return TCL_ERROR;
415 }
416
417 if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[2]), &info) ){
418 Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0);
419 return TCL_ERROR;
420 }
421 db = *(sqlite3 **)info.objClientData;
422
423 p = (TestSession*)ckalloc(sizeof(TestSession));
424 memset(p, 0, sizeof(TestSession));
425 rc = sqlite3session_create(db, Tcl_GetString(objv[3]), &p->pSession);
426 if( rc!=SQLITE_OK ){
427 ckfree((char*)p);
428 return test_session_error(interp, rc, 0);
429 }
430
431 /* Query the SQLITE_SESSION_OBJCONFIG_SIZE option to ensure that it
432 ** is clear by default. Then set it. */
433 sqlite3session_object_config(p->pSession,SQLITE_SESSION_OBJCONFIG_SIZE,&iArg);
434 assert( iArg==0 );
435 iArg = 1;
436 sqlite3session_object_config(p->pSession,SQLITE_SESSION_OBJCONFIG_SIZE,&iArg);
437
438 Tcl_CreateObjCommand(
439 interp, Tcl_GetString(objv[1]), test_session_cmd, (ClientData)p,
440 test_session_del
441 );
442 Tcl_SetObjResult(interp, objv[1]);
443 return TCL_OK;
444 }
445
test_append_value(Tcl_Obj * pList,sqlite3_value * pVal)446 static void test_append_value(Tcl_Obj *pList, sqlite3_value *pVal){
447 if( pVal==0 ){
448 Tcl_ListObjAppendElement(0, pList, Tcl_NewObj());
449 Tcl_ListObjAppendElement(0, pList, Tcl_NewObj());
450 }else{
451 Tcl_Obj *pObj;
452 switch( sqlite3_value_type(pVal) ){
453 case SQLITE_NULL:
454 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("n", 1));
455 pObj = Tcl_NewObj();
456 break;
457 case SQLITE_INTEGER:
458 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("i", 1));
459 pObj = Tcl_NewWideIntObj(sqlite3_value_int64(pVal));
460 break;
461 case SQLITE_FLOAT:
462 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("f", 1));
463 pObj = Tcl_NewDoubleObj(sqlite3_value_double(pVal));
464 break;
465 case SQLITE_TEXT: {
466 const char *z = (char*)sqlite3_value_blob(pVal);
467 int n = sqlite3_value_bytes(pVal);
468 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("t", 1));
469 pObj = Tcl_NewStringObj(z, n);
470 break;
471 }
472 default:
473 assert( sqlite3_value_type(pVal)==SQLITE_BLOB );
474 Tcl_ListObjAppendElement(0, pList, Tcl_NewStringObj("b", 1));
475 pObj = Tcl_NewByteArrayObj(
476 sqlite3_value_blob(pVal),
477 sqlite3_value_bytes(pVal)
478 );
479 break;
480 }
481 Tcl_ListObjAppendElement(0, pList, pObj);
482 }
483 }
484
485 typedef struct TestConflictHandler TestConflictHandler;
486 struct TestConflictHandler {
487 Tcl_Interp *interp;
488 Tcl_Obj *pConflictScript;
489 Tcl_Obj *pFilterScript;
490 };
491
test_obj_eq_string(Tcl_Obj * p,const char * z)492 static int test_obj_eq_string(Tcl_Obj *p, const char *z){
493 int n;
494 int nObj;
495 char *zObj;
496
497 n = (int)strlen(z);
498 zObj = Tcl_GetStringFromObj(p, &nObj);
499
500 return (nObj==n && (n==0 || 0==memcmp(zObj, z, n)));
501 }
502
test_filter_handler(void * pCtx,const char * zTab)503 static int test_filter_handler(
504 void *pCtx, /* Pointer to TestConflictHandler structure */
505 const char *zTab /* Table name */
506 ){
507 TestConflictHandler *p = (TestConflictHandler *)pCtx;
508 int res = 1;
509 Tcl_Obj *pEval;
510 Tcl_Interp *interp = p->interp;
511
512 pEval = Tcl_DuplicateObj(p->pFilterScript);
513 Tcl_IncrRefCount(pEval);
514
515 if( TCL_OK!=Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zTab, -1))
516 || TCL_OK!=Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL)
517 || TCL_OK!=Tcl_GetIntFromObj(interp, Tcl_GetObjResult(interp), &res)
518 ){
519 Tcl_BackgroundError(interp);
520 }
521
522 Tcl_DecrRefCount(pEval);
523 return res;
524 }
525
test_conflict_handler(void * pCtx,int eConf,sqlite3_changeset_iter * pIter)526 static int test_conflict_handler(
527 void *pCtx, /* Pointer to TestConflictHandler structure */
528 int eConf, /* DATA, MISSING, CONFLICT, CONSTRAINT */
529 sqlite3_changeset_iter *pIter /* Handle describing change and conflict */
530 ){
531 TestConflictHandler *p = (TestConflictHandler *)pCtx;
532 Tcl_Obj *pEval;
533 Tcl_Interp *interp = p->interp;
534 int ret = 0; /* Return value */
535
536 int op; /* SQLITE_UPDATE, DELETE or INSERT */
537 const char *zTab; /* Name of table conflict is on */
538 int nCol; /* Number of columns in table zTab */
539
540 pEval = Tcl_DuplicateObj(p->pConflictScript);
541 Tcl_IncrRefCount(pEval);
542
543 sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0);
544
545 if( eConf==SQLITE_CHANGESET_FOREIGN_KEY ){
546 int nFk;
547 sqlite3changeset_fk_conflicts(pIter, &nFk);
548 Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj("FOREIGN_KEY", -1));
549 Tcl_ListObjAppendElement(0, pEval, Tcl_NewIntObj(nFk));
550 }else{
551
552 /* Append the operation type. */
553 Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(
554 op==SQLITE_INSERT ? "INSERT" :
555 op==SQLITE_UPDATE ? "UPDATE" :
556 "DELETE", -1
557 ));
558
559 /* Append the table name. */
560 Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj(zTab, -1));
561
562 /* Append the conflict type. */
563 switch( eConf ){
564 case SQLITE_CHANGESET_DATA:
565 Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("DATA",-1));
566 break;
567 case SQLITE_CHANGESET_NOTFOUND:
568 Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("NOTFOUND",-1));
569 break;
570 case SQLITE_CHANGESET_CONFLICT:
571 Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("CONFLICT",-1));
572 break;
573 case SQLITE_CHANGESET_CONSTRAINT:
574 Tcl_ListObjAppendElement(interp, pEval,Tcl_NewStringObj("CONSTRAINT",-1));
575 break;
576 }
577
578 /* If this is not an INSERT, append the old row */
579 if( op!=SQLITE_INSERT ){
580 int i;
581 Tcl_Obj *pOld = Tcl_NewObj();
582 for(i=0; i<nCol; i++){
583 sqlite3_value *pVal;
584 sqlite3changeset_old(pIter, i, &pVal);
585 test_append_value(pOld, pVal);
586 }
587 Tcl_ListObjAppendElement(0, pEval, pOld);
588 }
589
590 /* If this is not a DELETE, append the new row */
591 if( op!=SQLITE_DELETE ){
592 int i;
593 Tcl_Obj *pNew = Tcl_NewObj();
594 for(i=0; i<nCol; i++){
595 sqlite3_value *pVal;
596 sqlite3changeset_new(pIter, i, &pVal);
597 test_append_value(pNew, pVal);
598 }
599 Tcl_ListObjAppendElement(0, pEval, pNew);
600 }
601
602 /* If this is a CHANGESET_DATA or CHANGESET_CONFLICT conflict, append
603 ** the conflicting row. */
604 if( eConf==SQLITE_CHANGESET_DATA || eConf==SQLITE_CHANGESET_CONFLICT ){
605 int i;
606 Tcl_Obj *pConflict = Tcl_NewObj();
607 for(i=0; i<nCol; i++){
608 int rc;
609 sqlite3_value *pVal;
610 rc = sqlite3changeset_conflict(pIter, i, &pVal);
611 assert( rc==SQLITE_OK );
612 test_append_value(pConflict, pVal);
613 }
614 Tcl_ListObjAppendElement(0, pEval, pConflict);
615 }
616
617 /***********************************************************************
618 ** This block is purely for testing some error conditions.
619 */
620 if( eConf==SQLITE_CHANGESET_CONSTRAINT
621 || eConf==SQLITE_CHANGESET_NOTFOUND
622 ){
623 sqlite3_value *pVal;
624 int rc = sqlite3changeset_conflict(pIter, 0, &pVal);
625 assert( rc==SQLITE_MISUSE );
626 }else{
627 sqlite3_value *pVal;
628 int rc = sqlite3changeset_conflict(pIter, -1, &pVal);
629 assert( rc==SQLITE_RANGE );
630 rc = sqlite3changeset_conflict(pIter, nCol, &pVal);
631 assert( rc==SQLITE_RANGE );
632 }
633 if( op==SQLITE_DELETE ){
634 sqlite3_value *pVal;
635 int rc = sqlite3changeset_new(pIter, 0, &pVal);
636 assert( rc==SQLITE_MISUSE );
637 }else{
638 sqlite3_value *pVal;
639 int rc = sqlite3changeset_new(pIter, -1, &pVal);
640 assert( rc==SQLITE_RANGE );
641 rc = sqlite3changeset_new(pIter, nCol, &pVal);
642 assert( rc==SQLITE_RANGE );
643 }
644 if( op==SQLITE_INSERT ){
645 sqlite3_value *pVal;
646 int rc = sqlite3changeset_old(pIter, 0, &pVal);
647 assert( rc==SQLITE_MISUSE );
648 }else{
649 sqlite3_value *pVal;
650 int rc = sqlite3changeset_old(pIter, -1, &pVal);
651 assert( rc==SQLITE_RANGE );
652 rc = sqlite3changeset_old(pIter, nCol, &pVal);
653 assert( rc==SQLITE_RANGE );
654 }
655 if( eConf!=SQLITE_CHANGESET_FOREIGN_KEY ){
656 /* eConf!=FOREIGN_KEY is always true at this point. The condition is
657 ** just there to make it clearer what is being tested. */
658 int nDummy;
659 int rc = sqlite3changeset_fk_conflicts(pIter, &nDummy);
660 assert( rc==SQLITE_MISUSE );
661 }
662 /* End of testing block
663 ***********************************************************************/
664 }
665
666 if( TCL_OK!=Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL) ){
667 Tcl_BackgroundError(interp);
668 }else{
669 Tcl_Obj *pRes = Tcl_GetObjResult(interp);
670 if( test_obj_eq_string(pRes, "OMIT") || test_obj_eq_string(pRes, "") ){
671 ret = SQLITE_CHANGESET_OMIT;
672 }else if( test_obj_eq_string(pRes, "REPLACE") ){
673 ret = SQLITE_CHANGESET_REPLACE;
674 }else if( test_obj_eq_string(pRes, "ABORT") ){
675 ret = SQLITE_CHANGESET_ABORT;
676 }else{
677 Tcl_GetIntFromObj(0, pRes, &ret);
678 }
679 }
680
681 Tcl_DecrRefCount(pEval);
682 return ret;
683 }
684
685 /*
686 ** The conflict handler used by sqlite3changeset_apply_replace_all().
687 ** This conflict handler calls sqlite3_value_text16() on all available
688 ** sqlite3_value objects and then returns CHANGESET_REPLACE, or
689 ** CHANGESET_OMIT if REPLACE is not applicable. This is used to test the
690 ** effect of a malloc failure within an sqlite3_value_xxx() function
691 ** invoked by a conflict-handler callback.
692 */
replace_handler(void * pCtx,int eConf,sqlite3_changeset_iter * pIter)693 static int replace_handler(
694 void *pCtx, /* Pointer to TestConflictHandler structure */
695 int eConf, /* DATA, MISSING, CONFLICT, CONSTRAINT */
696 sqlite3_changeset_iter *pIter /* Handle describing change and conflict */
697 ){
698 int op; /* SQLITE_UPDATE, DELETE or INSERT */
699 const char *zTab; /* Name of table conflict is on */
700 int nCol; /* Number of columns in table zTab */
701 int i;
702 int x = 0;
703
704 sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0);
705
706 if( op!=SQLITE_INSERT ){
707 for(i=0; i<nCol; i++){
708 sqlite3_value *pVal;
709 sqlite3changeset_old(pIter, i, &pVal);
710 sqlite3_value_text16(pVal);
711 x++;
712 }
713 }
714
715 if( op!=SQLITE_DELETE ){
716 for(i=0; i<nCol; i++){
717 sqlite3_value *pVal;
718 sqlite3changeset_new(pIter, i, &pVal);
719 sqlite3_value_text16(pVal);
720 x++;
721 }
722 }
723
724 if( eConf==SQLITE_CHANGESET_DATA ){
725 return SQLITE_CHANGESET_REPLACE;
726 }
727 return SQLITE_CHANGESET_OMIT;
728 }
729
testStreamInput(void * pCtx,void * pData,int * pnData)730 static int testStreamInput(
731 void *pCtx, /* Context pointer */
732 void *pData, /* Buffer to populate */
733 int *pnData /* IN/OUT: Bytes requested/supplied */
734 ){
735 TestStreamInput *p = (TestStreamInput*)pCtx;
736 int nReq = *pnData; /* Bytes of data requested */
737 int nRem = p->nData - p->iData; /* Bytes of data available */
738 int nRet = p->nStream; /* Bytes actually returned */
739
740 /* Allocate and free some space. There is no point to this, other than
741 ** that it allows the regular OOM fault-injection tests to cause an error
742 ** in this function. */
743 void *pAlloc = sqlite3_malloc(10);
744 if( pAlloc==0 ) return SQLITE_NOMEM;
745 sqlite3_free(pAlloc);
746
747 if( nRet>nReq ) nRet = nReq;
748 if( nRet>nRem ) nRet = nRem;
749
750 assert( nRet>=0 );
751 if( nRet>0 ){
752 memcpy(pData, &p->aData[p->iData], nRet);
753 p->iData += nRet;
754 }
755
756 *pnData = nRet;
757 return SQLITE_OK;
758 }
759
760
testSqlite3changesetApply(int bV2,void * clientData,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])761 static int SQLITE_TCLAPI testSqlite3changesetApply(
762 int bV2,
763 void * clientData,
764 Tcl_Interp *interp,
765 int objc,
766 Tcl_Obj *CONST objv[]
767 ){
768 sqlite3 *db; /* Database handle */
769 Tcl_CmdInfo info; /* Database Tcl command (objv[1]) info */
770 int rc; /* Return code from changeset_invert() */
771 void *pChangeset; /* Buffer containing changeset */
772 int nChangeset; /* Size of buffer aChangeset in bytes */
773 TestConflictHandler ctx;
774 TestStreamInput sStr;
775 void *pRebase = 0;
776 int nRebase = 0;
777 int flags = 0; /* Flags for apply_v2() */
778
779 memset(&sStr, 0, sizeof(sStr));
780 sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
781
782 /* Check for the -nosavepoint flag */
783 if( bV2 ){
784 if( objc>1 ){
785 const char *z1 = Tcl_GetString(objv[1]);
786 int n = strlen(z1);
787 if( n>1 && n<=12 && 0==sqlite3_strnicmp("-nosavepoint", z1, n) ){
788 flags |= SQLITE_CHANGESETAPPLY_NOSAVEPOINT;
789 objc--;
790 objv++;
791 }
792 }
793 if( objc>1 ){
794 const char *z1 = Tcl_GetString(objv[1]);
795 int n = strlen(z1);
796 if( n>1 && n<=7 && 0==sqlite3_strnicmp("-invert", z1, n) ){
797 flags |= SQLITE_CHANGESETAPPLY_INVERT;
798 objc--;
799 objv++;
800 }
801 }
802 }
803
804 if( objc!=4 && objc!=5 ){
805 const char *zMsg;
806 if( bV2 ){
807 zMsg = "?-nosavepoint? ?-inverse? "
808 "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?";
809 }else{
810 zMsg = "DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?";
811 }
812 Tcl_WrongNumArgs(interp, 1, objv, zMsg);
813 return TCL_ERROR;
814 }
815 if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &info) ){
816 Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[1]), 0);
817 return TCL_ERROR;
818 }
819 db = *(sqlite3 **)info.objClientData;
820 pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset);
821 ctx.pConflictScript = objv[3];
822 ctx.pFilterScript = objc==5 ? objv[4] : 0;
823 ctx.interp = interp;
824
825 if( sStr.nStream==0 ){
826 if( bV2==0 ){
827 rc = sqlite3changeset_apply(db, nChangeset, pChangeset,
828 (objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx
829 );
830 }else{
831 rc = sqlite3changeset_apply_v2(db, nChangeset, pChangeset,
832 (objc==5)?test_filter_handler:0, test_conflict_handler, (void *)&ctx,
833 &pRebase, &nRebase, flags
834 );
835 }
836 }else{
837 sStr.aData = (unsigned char*)pChangeset;
838 sStr.nData = nChangeset;
839 if( bV2==0 ){
840 rc = sqlite3changeset_apply_strm(db, testStreamInput, (void*)&sStr,
841 (objc==5) ? test_filter_handler : 0,
842 test_conflict_handler, (void *)&ctx
843 );
844 }else{
845 rc = sqlite3changeset_apply_v2_strm(db, testStreamInput, (void*)&sStr,
846 (objc==5) ? test_filter_handler : 0,
847 test_conflict_handler, (void *)&ctx,
848 &pRebase, &nRebase, flags
849 );
850 }
851 }
852
853 if( rc!=SQLITE_OK ){
854 return test_session_error(interp, rc, 0);
855 }else{
856 Tcl_ResetResult(interp);
857 if( bV2 && pRebase ){
858 Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(pRebase, nRebase));
859 }
860 }
861 sqlite3_free(pRebase);
862 return TCL_OK;
863 }
864
865 /*
866 ** sqlite3changeset_apply DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
867 */
test_sqlite3changeset_apply(void * clientData,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])868 static int SQLITE_TCLAPI test_sqlite3changeset_apply(
869 void * clientData,
870 Tcl_Interp *interp,
871 int objc,
872 Tcl_Obj *CONST objv[]
873 ){
874 return testSqlite3changesetApply(0, clientData, interp, objc, objv);
875 }
876 /*
877 ** sqlite3changeset_apply_v2 DB CHANGESET CONFLICT-SCRIPT ?FILTER-SCRIPT?
878 */
test_sqlite3changeset_apply_v2(void * clientData,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])879 static int SQLITE_TCLAPI test_sqlite3changeset_apply_v2(
880 void * clientData,
881 Tcl_Interp *interp,
882 int objc,
883 Tcl_Obj *CONST objv[]
884 ){
885 return testSqlite3changesetApply(1, clientData, interp, objc, objv);
886 }
887
888 /*
889 ** sqlite3changeset_apply_replace_all DB CHANGESET
890 */
test_sqlite3changeset_apply_replace_all(void * clientData,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])891 static int SQLITE_TCLAPI test_sqlite3changeset_apply_replace_all(
892 void * clientData,
893 Tcl_Interp *interp,
894 int objc,
895 Tcl_Obj *CONST objv[]
896 ){
897 sqlite3 *db; /* Database handle */
898 Tcl_CmdInfo info; /* Database Tcl command (objv[1]) info */
899 int rc; /* Return code from changeset_invert() */
900 void *pChangeset; /* Buffer containing changeset */
901 int nChangeset; /* Size of buffer aChangeset in bytes */
902
903 if( objc!=3 ){
904 Tcl_WrongNumArgs(interp, 1, objv, "DB CHANGESET");
905 return TCL_ERROR;
906 }
907 if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &info) ){
908 Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0);
909 return TCL_ERROR;
910 }
911 db = *(sqlite3 **)info.objClientData;
912 pChangeset = (void *)Tcl_GetByteArrayFromObj(objv[2], &nChangeset);
913
914 rc = sqlite3changeset_apply(db, nChangeset, pChangeset, 0, replace_handler,0);
915 if( rc!=SQLITE_OK ){
916 return test_session_error(interp, rc, 0);
917 }
918 Tcl_ResetResult(interp);
919 return TCL_OK;
920 }
921
922
923 /*
924 ** sqlite3changeset_invert CHANGESET
925 */
test_sqlite3changeset_invert(void * clientData,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])926 static int SQLITE_TCLAPI test_sqlite3changeset_invert(
927 void * clientData,
928 Tcl_Interp *interp,
929 int objc,
930 Tcl_Obj *CONST objv[]
931 ){
932 int rc; /* Return code from changeset_invert() */
933 TestStreamInput sIn; /* Input stream */
934 TestSessionsBlob sOut; /* Output blob */
935
936 if( objc!=2 ){
937 Tcl_WrongNumArgs(interp, 1, objv, "CHANGESET");
938 return TCL_ERROR;
939 }
940
941 memset(&sIn, 0, sizeof(sIn));
942 memset(&sOut, 0, sizeof(sOut));
943 sIn.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
944 sIn.aData = Tcl_GetByteArrayFromObj(objv[1], &sIn.nData);
945
946 if( sIn.nStream ){
947 rc = sqlite3changeset_invert_strm(
948 testStreamInput, (void*)&sIn, testStreamOutput, (void*)&sOut
949 );
950 }else{
951 rc = sqlite3changeset_invert(sIn.nData, sIn.aData, &sOut.n, &sOut.p);
952 }
953 if( rc!=SQLITE_OK ){
954 rc = test_session_error(interp, rc, 0);
955 }else{
956 Tcl_SetObjResult(interp,Tcl_NewByteArrayObj((unsigned char*)sOut.p,sOut.n));
957 }
958 sqlite3_free(sOut.p);
959 return rc;
960 }
961
962 /*
963 ** sqlite3changeset_concat LEFT RIGHT
964 */
test_sqlite3changeset_concat(void * clientData,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])965 static int SQLITE_TCLAPI test_sqlite3changeset_concat(
966 void * clientData,
967 Tcl_Interp *interp,
968 int objc,
969 Tcl_Obj *CONST objv[]
970 ){
971 int rc; /* Return code from changeset_invert() */
972
973 TestStreamInput sLeft; /* Input stream */
974 TestStreamInput sRight; /* Input stream */
975 TestSessionsBlob sOut = {0,0}; /* Output blob */
976
977 if( objc!=3 ){
978 Tcl_WrongNumArgs(interp, 1, objv, "LEFT RIGHT");
979 return TCL_ERROR;
980 }
981
982 memset(&sLeft, 0, sizeof(sLeft));
983 memset(&sRight, 0, sizeof(sRight));
984 sLeft.aData = Tcl_GetByteArrayFromObj(objv[1], &sLeft.nData);
985 sRight.aData = Tcl_GetByteArrayFromObj(objv[2], &sRight.nData);
986 sLeft.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
987 sRight.nStream = sLeft.nStream;
988
989 if( sLeft.nStream>0 ){
990 rc = sqlite3changeset_concat_strm(
991 testStreamInput, (void*)&sLeft,
992 testStreamInput, (void*)&sRight,
993 testStreamOutput, (void*)&sOut
994 );
995 }else{
996 rc = sqlite3changeset_concat(
997 sLeft.nData, sLeft.aData, sRight.nData, sRight.aData, &sOut.n, &sOut.p
998 );
999 }
1000
1001 if( rc!=SQLITE_OK ){
1002 rc = test_session_error(interp, rc, 0);
1003 }else{
1004 Tcl_SetObjResult(interp,Tcl_NewByteArrayObj((unsigned char*)sOut.p,sOut.n));
1005 }
1006 sqlite3_free(sOut.p);
1007 return rc;
1008 }
1009
1010 /*
1011 ** sqlite3session_foreach VARNAME CHANGESET SCRIPT
1012 */
test_sqlite3session_foreach(void * clientData,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])1013 static int SQLITE_TCLAPI test_sqlite3session_foreach(
1014 void * clientData,
1015 Tcl_Interp *interp,
1016 int objc,
1017 Tcl_Obj *CONST objv[]
1018 ){
1019 void *pChangeset;
1020 int nChangeset;
1021 sqlite3_changeset_iter *pIter;
1022 int rc;
1023 Tcl_Obj *pVarname;
1024 Tcl_Obj *pCS;
1025 Tcl_Obj *pScript;
1026 int isCheckNext = 0;
1027 int isInvert = 0;
1028
1029 TestStreamInput sStr;
1030 memset(&sStr, 0, sizeof(sStr));
1031
1032 while( objc>1 ){
1033 char *zOpt = Tcl_GetString(objv[1]);
1034 int nOpt = strlen(zOpt);
1035 if( zOpt[0]!='-' ) break;
1036 if( nOpt<=7 && 0==sqlite3_strnicmp(zOpt, "-invert", nOpt) ){
1037 isInvert = 1;
1038 }else
1039 if( nOpt<=5 && 0==sqlite3_strnicmp(zOpt, "-next", nOpt) ){
1040 isCheckNext = 1;
1041 }else{
1042 break;
1043 }
1044 objv++;
1045 objc--;
1046 }
1047 if( objc!=4 ){
1048 Tcl_WrongNumArgs(
1049 interp, 1, objv, "?-next? ?-invert? VARNAME CHANGESET SCRIPT");
1050 return TCL_ERROR;
1051 }
1052
1053 pVarname = objv[1];
1054 pCS = objv[2];
1055 pScript = objv[3];
1056
1057 pChangeset = (void *)Tcl_GetByteArrayFromObj(pCS, &nChangeset);
1058 sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
1059 if( isInvert ){
1060 int f = SQLITE_CHANGESETSTART_INVERT;
1061 if( sStr.nStream==0 ){
1062 rc = sqlite3changeset_start_v2(&pIter, nChangeset, pChangeset, f);
1063 }else{
1064 void *pCtx = (void*)&sStr;
1065 sStr.aData = (unsigned char*)pChangeset;
1066 sStr.nData = nChangeset;
1067 rc = sqlite3changeset_start_v2_strm(&pIter, testStreamInput, pCtx, f);
1068 }
1069 }else{
1070 if( sStr.nStream==0 ){
1071 rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset);
1072 }else{
1073 sStr.aData = (unsigned char*)pChangeset;
1074 sStr.nData = nChangeset;
1075 rc = sqlite3changeset_start_strm(&pIter, testStreamInput, (void*)&sStr);
1076 }
1077 }
1078 if( rc!=SQLITE_OK ){
1079 return test_session_error(interp, rc, 0);
1080 }
1081
1082 while( SQLITE_ROW==sqlite3changeset_next(pIter) ){
1083 int nCol; /* Number of columns in table */
1084 int nCol2; /* Number of columns in table */
1085 int op; /* SQLITE_INSERT, UPDATE or DELETE */
1086 const char *zTab; /* Name of table change applies to */
1087 Tcl_Obj *pVar; /* Tcl value to set $VARNAME to */
1088 Tcl_Obj *pOld; /* Vector of old.* values */
1089 Tcl_Obj *pNew; /* Vector of new.* values */
1090 int bIndirect;
1091
1092 char *zPK;
1093 unsigned char *abPK;
1094 int i;
1095
1096 /* Test that _fk_conflicts() returns SQLITE_MISUSE if called on this
1097 ** iterator. */
1098 int nDummy;
1099 if( SQLITE_MISUSE!=sqlite3changeset_fk_conflicts(pIter, &nDummy) ){
1100 sqlite3changeset_finalize(pIter);
1101 return TCL_ERROR;
1102 }
1103
1104 sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect);
1105 pVar = Tcl_NewObj();
1106 Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(
1107 op==SQLITE_INSERT ? "INSERT" :
1108 op==SQLITE_UPDATE ? "UPDATE" :
1109 "DELETE", -1
1110 ));
1111
1112 Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zTab, -1));
1113 Tcl_ListObjAppendElement(0, pVar, Tcl_NewBooleanObj(bIndirect));
1114
1115 zPK = ckalloc(nCol+1);
1116 memset(zPK, 0, nCol+1);
1117 sqlite3changeset_pk(pIter, &abPK, &nCol2);
1118 assert( nCol==nCol2 );
1119 for(i=0; i<nCol; i++){
1120 zPK[i] = (abPK[i] ? 'X' : '.');
1121 }
1122 Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zPK, -1));
1123 ckfree(zPK);
1124
1125 pOld = Tcl_NewObj();
1126 if( op!=SQLITE_INSERT ){
1127 for(i=0; i<nCol; i++){
1128 sqlite3_value *pVal;
1129 sqlite3changeset_old(pIter, i, &pVal);
1130 test_append_value(pOld, pVal);
1131 }
1132 }
1133 pNew = Tcl_NewObj();
1134 if( op!=SQLITE_DELETE ){
1135 for(i=0; i<nCol; i++){
1136 sqlite3_value *pVal;
1137 sqlite3changeset_new(pIter, i, &pVal);
1138 test_append_value(pNew, pVal);
1139 }
1140 }
1141 Tcl_ListObjAppendElement(0, pVar, pOld);
1142 Tcl_ListObjAppendElement(0, pVar, pNew);
1143
1144 Tcl_ObjSetVar2(interp, pVarname, 0, pVar, 0);
1145 rc = Tcl_EvalObjEx(interp, pScript, 0);
1146 if( rc!=TCL_OK && rc!=TCL_CONTINUE ){
1147 sqlite3changeset_finalize(pIter);
1148 return rc==TCL_BREAK ? TCL_OK : rc;
1149 }
1150 }
1151
1152 if( isCheckNext ){
1153 int rc2 = sqlite3changeset_next(pIter);
1154 rc = sqlite3changeset_finalize(pIter);
1155 assert( (rc2==SQLITE_DONE && rc==SQLITE_OK) || rc2==rc );
1156 }else{
1157 rc = sqlite3changeset_finalize(pIter);
1158 }
1159 if( rc!=SQLITE_OK ){
1160 return test_session_error(interp, rc, 0);
1161 }
1162
1163 return TCL_OK;
1164 }
1165
1166 /*
1167 ** tclcmd: CMD configure REBASE-BLOB
1168 ** tclcmd: CMD rebase CHANGESET
1169 ** tclcmd: CMD delete
1170 */
test_rebaser_cmd(void * clientData,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])1171 static int SQLITE_TCLAPI test_rebaser_cmd(
1172 void * clientData,
1173 Tcl_Interp *interp,
1174 int objc,
1175 Tcl_Obj *CONST objv[]
1176 ){
1177 static struct RebaseSubcmd {
1178 const char *zSub;
1179 int nArg;
1180 const char *zMsg;
1181 int iSub;
1182 } aSub[] = {
1183 { "configure", 1, "REBASE-BLOB" }, /* 0 */
1184 { "delete", 0, "" }, /* 1 */
1185 { "rebase", 1, "CHANGESET" }, /* 2 */
1186 { 0 }
1187 };
1188
1189 sqlite3_rebaser *p = (sqlite3_rebaser*)clientData;
1190 int iSub;
1191 int rc;
1192
1193 if( objc<2 ){
1194 Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
1195 return TCL_ERROR;
1196 }
1197 rc = Tcl_GetIndexFromObjStruct(interp,
1198 objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub
1199 );
1200 if( rc!=TCL_OK ) return rc;
1201 if( objc!=2+aSub[iSub].nArg ){
1202 Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg);
1203 return TCL_ERROR;
1204 }
1205
1206 assert( iSub==0 || iSub==1 || iSub==2 );
1207 assert( rc==SQLITE_OK );
1208 switch( iSub ){
1209 case 0: { /* configure */
1210 int nRebase = 0;
1211 unsigned char *pRebase = Tcl_GetByteArrayFromObj(objv[2], &nRebase);
1212 rc = sqlite3rebaser_configure(p, nRebase, pRebase);
1213 break;
1214 }
1215
1216 case 1: /* delete */
1217 Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
1218 break;
1219
1220 default: { /* rebase */
1221 TestStreamInput sStr; /* Input stream */
1222 TestSessionsBlob sOut; /* Output blob */
1223
1224 memset(&sStr, 0, sizeof(sStr));
1225 memset(&sOut, 0, sizeof(sOut));
1226 sStr.aData = Tcl_GetByteArrayFromObj(objv[2], &sStr.nData);
1227 sStr.nStream = test_tcl_integer(interp, SESSION_STREAM_TCL_VAR);
1228
1229 if( sStr.nStream ){
1230 rc = sqlite3rebaser_rebase_strm(p,
1231 testStreamInput, (void*)&sStr,
1232 testStreamOutput, (void*)&sOut
1233 );
1234 }else{
1235 rc = sqlite3rebaser_rebase(p, sStr.nData, sStr.aData, &sOut.n, &sOut.p);
1236 }
1237
1238 if( rc==SQLITE_OK ){
1239 Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(sOut.p, sOut.n));
1240 }
1241 sqlite3_free(sOut.p);
1242 break;
1243 }
1244 }
1245
1246 if( rc!=SQLITE_OK ){
1247 return test_session_error(interp, rc, 0);
1248 }
1249 return TCL_OK;
1250 }
1251
test_rebaser_del(void * clientData)1252 static void SQLITE_TCLAPI test_rebaser_del(void *clientData){
1253 sqlite3_rebaser *p = (sqlite3_rebaser*)clientData;
1254 sqlite3rebaser_delete(p);
1255 }
1256
1257 /*
1258 ** tclcmd: sqlite3rebaser_create NAME
1259 */
test_sqlite3rebaser_create(void * clientData,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])1260 static int SQLITE_TCLAPI test_sqlite3rebaser_create(
1261 void * clientData,
1262 Tcl_Interp *interp,
1263 int objc,
1264 Tcl_Obj *CONST objv[]
1265 ){
1266 int rc;
1267 sqlite3_rebaser *pNew = 0;
1268 if( objc!=2 ){
1269 Tcl_WrongNumArgs(interp, 1, objv, "NAME");
1270 return SQLITE_ERROR;
1271 }
1272
1273 rc = sqlite3rebaser_create(&pNew);
1274 if( rc!=SQLITE_OK ){
1275 return test_session_error(interp, rc, 0);
1276 }
1277
1278 Tcl_CreateObjCommand(interp, Tcl_GetString(objv[1]), test_rebaser_cmd,
1279 (ClientData)pNew, test_rebaser_del
1280 );
1281 Tcl_SetObjResult(interp, objv[1]);
1282 return TCL_OK;
1283 }
1284
1285 /*
1286 ** tclcmd: sqlite3rebaser_configure OP VALUE
1287 */
test_sqlite3session_config(void * clientData,Tcl_Interp * interp,int objc,Tcl_Obj * CONST objv[])1288 static int SQLITE_TCLAPI test_sqlite3session_config(
1289 void * clientData,
1290 Tcl_Interp *interp,
1291 int objc,
1292 Tcl_Obj *CONST objv[]
1293 ){
1294 static struct ConfigOpt {
1295 const char *zSub;
1296 int op;
1297 } aSub[] = {
1298 { "strm_size", SQLITE_SESSION_CONFIG_STRMSIZE },
1299 { "invalid", 0 },
1300 { 0 }
1301 };
1302 int rc;
1303 int iSub;
1304 int iVal;
1305
1306 if( objc!=3 ){
1307 Tcl_WrongNumArgs(interp, 1, objv, "OP VALUE");
1308 return SQLITE_ERROR;
1309 }
1310 rc = Tcl_GetIndexFromObjStruct(interp,
1311 objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub
1312 );
1313 if( rc!=TCL_OK ) return rc;
1314 if( Tcl_GetIntFromObj(interp, objv[2], &iVal) ) return TCL_ERROR;
1315
1316 rc = sqlite3session_config(aSub[iSub].op, (void*)&iVal);
1317 if( rc!=SQLITE_OK ){
1318 return test_session_error(interp, rc, 0);
1319 }
1320 Tcl_SetObjResult(interp, Tcl_NewIntObj(iVal));
1321 return TCL_OK;
1322 }
1323
TestSession_Init(Tcl_Interp * interp)1324 int TestSession_Init(Tcl_Interp *interp){
1325 struct Cmd {
1326 const char *zCmd;
1327 Tcl_ObjCmdProc *xProc;
1328 } aCmd[] = {
1329 { "sqlite3session", test_sqlite3session },
1330 { "sqlite3session_foreach", test_sqlite3session_foreach },
1331 { "sqlite3changeset_invert", test_sqlite3changeset_invert },
1332 { "sqlite3changeset_concat", test_sqlite3changeset_concat },
1333 { "sqlite3changeset_apply", test_sqlite3changeset_apply },
1334 { "sqlite3changeset_apply_v2", test_sqlite3changeset_apply_v2 },
1335 { "sqlite3changeset_apply_replace_all",
1336 test_sqlite3changeset_apply_replace_all },
1337 { "sql_exec_changeset", test_sql_exec_changeset },
1338 { "sqlite3rebaser_create", test_sqlite3rebaser_create },
1339 { "sqlite3session_config", test_sqlite3session_config },
1340 };
1341 int i;
1342
1343 for(i=0; i<sizeof(aCmd)/sizeof(struct Cmd); i++){
1344 struct Cmd *p = &aCmd[i];
1345 Tcl_CreateObjCommand(interp, p->zCmd, p->xProc, 0, 0);
1346 }
1347
1348 return TCL_OK;
1349 }
1350
1351 #endif /* SQLITE_TEST && SQLITE_SESSION && SQLITE_PREUPDATE_HOOK */
1352