1*610e17bdSdan /*
2*610e17bdSdan ** 2017 January 31
3*610e17bdSdan **
4*610e17bdSdan ** The author disclaims copyright to this source code.  In place of
5*610e17bdSdan ** a legal notice, here is a blessing:
6*610e17bdSdan **
7*610e17bdSdan **    May you do good and not evil.
8*610e17bdSdan **    May you find forgiveness for yourself and forgive others.
9*610e17bdSdan **    May you share freely, never taking more than you give.
10*610e17bdSdan **
11*610e17bdSdan *************************************************************************
12*610e17bdSdan ** This file contains the source code for a standalone program used to
13*610e17bdSdan ** test the performance of the sessions module. Compile and run:
14*610e17bdSdan **
15*610e17bdSdan **   ./session_speed_test -help
16*610e17bdSdan **
17*610e17bdSdan ** for details.
18*610e17bdSdan */
19*610e17bdSdan 
20*610e17bdSdan #include "sqlite3.h"
21*610e17bdSdan #include <stdio.h>
22*610e17bdSdan #include <stdlib.h>
23*610e17bdSdan #include <string.h>
24*610e17bdSdan #include <stddef.h>
25*610e17bdSdan #include <unistd.h>
26*610e17bdSdan 
27*610e17bdSdan /*************************************************************************
28*610e17bdSdan ** Start of generic command line parser.
29*610e17bdSdan */
30*610e17bdSdan #define CMDLINE_BARE       0
31*610e17bdSdan #define CMDLINE_INTEGER    1
32*610e17bdSdan #define CMDLINE_STRING     2
33*610e17bdSdan #define CMDLINE_BOOLEAN    3
34*610e17bdSdan 
35*610e17bdSdan typedef struct CmdLineOption CmdLineOption;
36*610e17bdSdan struct CmdLineOption {
37*610e17bdSdan   const char *zText;              /* Name of command line option */
38*610e17bdSdan   const char *zHelp;              /* Help text for option */
39*610e17bdSdan   int eType;                      /* One of the CMDLINE_* values */
40*610e17bdSdan   int iOff;                       /* Offset of output variable */
41*610e17bdSdan };
42*610e17bdSdan 
43*610e17bdSdan #define CMDLINE_INT32(x,y,z) {x, y, CMDLINE_INTEGER, z}
44*610e17bdSdan #define CMDLINE_BOOL(x,y,z)  {x, y, CMDLINE_BOOLEAN, z}
45*610e17bdSdan #define CMDLINE_TEXT(x,y,z)  {x, y, CMDLINE_STRING, z}
46*610e17bdSdan #define CMDLINE_NONE(x,y,z)  {x, y, CMDLINE_BARE, z}
47*610e17bdSdan 
option_requires_argument_error(CmdLineOption * pOpt)48*610e17bdSdan static void option_requires_argument_error(CmdLineOption *pOpt){
49*610e17bdSdan   fprintf(stderr, "Option requires a%s argument: %s\n",
50*610e17bdSdan       pOpt->eType==CMDLINE_INTEGER ? "n integer" :
51*610e17bdSdan       pOpt->eType==CMDLINE_STRING ? " string" : " boolean",
52*610e17bdSdan       pOpt->zText
53*610e17bdSdan   );
54*610e17bdSdan   exit(1);
55*610e17bdSdan }
56*610e17bdSdan 
ambiguous_option_error(const char * zArg)57*610e17bdSdan static void ambiguous_option_error(const char *zArg){
58*610e17bdSdan   fprintf(stderr, "Option is ambiguous: %s\n", zArg);
59*610e17bdSdan   exit(1);
60*610e17bdSdan }
61*610e17bdSdan 
unknown_option_error(const char * zArg,CmdLineOption * aOpt,const char * zHelp)62*610e17bdSdan static void unknown_option_error(
63*610e17bdSdan   const char *zArg,
64*610e17bdSdan   CmdLineOption *aOpt,
65*610e17bdSdan   const char *zHelp
66*610e17bdSdan ){
67*610e17bdSdan   int i;
68*610e17bdSdan   fprintf(stderr, "Unknown option: %s\n", zArg);
69*610e17bdSdan   fprintf(stderr, "\nOptions are:\n");
70*610e17bdSdan   fprintf(stderr, "  % -30sEcho command line options\n", "-cmdline:verbose");
71*610e17bdSdan   for(i=0; aOpt[i].zText; i++){
72*610e17bdSdan     int eType = aOpt[i].eType;
73*610e17bdSdan     char *zOpt = sqlite3_mprintf("%s %s", aOpt[i].zText,
74*610e17bdSdan         eType==CMDLINE_BARE ? "" :
75*610e17bdSdan         eType==CMDLINE_INTEGER ? "N" :
76*610e17bdSdan         eType==CMDLINE_BOOLEAN ? "BOOLEAN" : "TEXT"
77*610e17bdSdan     );
78*610e17bdSdan     fprintf(stderr, "  % -30s%s\n", zOpt, aOpt[i].zHelp);
79*610e17bdSdan     sqlite3_free(zOpt);
80*610e17bdSdan   }
81*610e17bdSdan   if( zHelp ){
82*610e17bdSdan     fprintf(stderr, "\n%s\n", zHelp);
83*610e17bdSdan   }
84*610e17bdSdan   exit(1);
85*610e17bdSdan }
86*610e17bdSdan 
get_integer_option(CmdLineOption * pOpt,const char * zArg)87*610e17bdSdan static int get_integer_option(CmdLineOption *pOpt, const char *zArg){
88*610e17bdSdan   int i = 0;
89*610e17bdSdan   int iRet = 0;
90*610e17bdSdan   int bSign = 1;
91*610e17bdSdan   if( zArg[0]=='-' ){
92*610e17bdSdan     bSign = -1;
93*610e17bdSdan     i = 1;
94*610e17bdSdan   }
95*610e17bdSdan   while( zArg[i] ){
96*610e17bdSdan     if( zArg[i]<'0' || zArg[i]>'9' ) option_requires_argument_error(pOpt);
97*610e17bdSdan     iRet = iRet*10 + (zArg[i] - '0');
98*610e17bdSdan     i++;
99*610e17bdSdan   }
100*610e17bdSdan   return (iRet*bSign);
101*610e17bdSdan }
102*610e17bdSdan 
get_boolean_option(CmdLineOption * pOpt,const char * zArg)103*610e17bdSdan static int get_boolean_option(CmdLineOption *pOpt, const char *zArg){
104*610e17bdSdan   if( 0==sqlite3_stricmp(zArg, "true") ) return 1;
105*610e17bdSdan   if( 0==sqlite3_stricmp(zArg, "1") ) return 1;
106*610e17bdSdan   if( 0==sqlite3_stricmp(zArg, "0") ) return 0;
107*610e17bdSdan   if( 0==sqlite3_stricmp(zArg, "false") ) return 0;
108*610e17bdSdan   option_requires_argument_error(pOpt);
109*610e17bdSdan   return 0;
110*610e17bdSdan }
111*610e17bdSdan 
parse_command_line(int argc,char ** argv,int iStart,CmdLineOption * aOpt,void * pStruct,const char * zHelp)112*610e17bdSdan static void parse_command_line(
113*610e17bdSdan   int argc,
114*610e17bdSdan   char **argv,
115*610e17bdSdan   int iStart,
116*610e17bdSdan   CmdLineOption *aOpt,
117*610e17bdSdan   void *pStruct,
118*610e17bdSdan   const char *zHelp
119*610e17bdSdan ){
120*610e17bdSdan   char *pOut = (char*)pStruct;
121*610e17bdSdan   int bVerbose = 0;
122*610e17bdSdan   int iArg;
123*610e17bdSdan 
124*610e17bdSdan   for(iArg=iStart; iArg<argc; iArg++){
125*610e17bdSdan     const char *zArg = argv[iArg];
126*610e17bdSdan     int nArg = strlen(zArg);
127*610e17bdSdan     int nMatch = 0;
128*610e17bdSdan     int iOpt;
129*610e17bdSdan 
130*610e17bdSdan     for(iOpt=0; aOpt[iOpt].zText; iOpt++){
131*610e17bdSdan       CmdLineOption *pOpt = &aOpt[iOpt];
132*610e17bdSdan       if( 0==sqlite3_strnicmp(pOpt->zText, zArg, nArg) ){
133*610e17bdSdan         if( nMatch ){
134*610e17bdSdan           ambiguous_option_error(zArg);
135*610e17bdSdan         }
136*610e17bdSdan         nMatch++;
137*610e17bdSdan         if( pOpt->eType==CMDLINE_BARE ){
138*610e17bdSdan           *(int*)(&pOut[pOpt->iOff]) = 1;
139*610e17bdSdan         }else{
140*610e17bdSdan           iArg++;
141*610e17bdSdan           if( iArg==argc ){
142*610e17bdSdan             option_requires_argument_error(pOpt);
143*610e17bdSdan           }
144*610e17bdSdan           switch( pOpt->eType ){
145*610e17bdSdan             case CMDLINE_INTEGER:
146*610e17bdSdan               *(int*)(&pOut[pOpt->iOff]) = get_integer_option(pOpt, argv[iArg]);
147*610e17bdSdan               break;
148*610e17bdSdan             case CMDLINE_STRING:
149*610e17bdSdan               *(const char**)(&pOut[pOpt->iOff]) = argv[iArg];
150*610e17bdSdan               break;
151*610e17bdSdan             case CMDLINE_BOOLEAN:
152*610e17bdSdan               *(int*)(&pOut[pOpt->iOff]) = get_boolean_option(pOpt, argv[iArg]);
153*610e17bdSdan               break;
154*610e17bdSdan           }
155*610e17bdSdan         }
156*610e17bdSdan       }
157*610e17bdSdan     }
158*610e17bdSdan 
159*610e17bdSdan     if( nMatch==0 && 0==sqlite3_strnicmp("-cmdline:verbose", zArg, nArg) ){
160*610e17bdSdan       bVerbose = 1;
161*610e17bdSdan       nMatch = 1;
162*610e17bdSdan     }
163*610e17bdSdan 
164*610e17bdSdan     if( nMatch==0 ){
165*610e17bdSdan       unknown_option_error(zArg, aOpt, zHelp);
166*610e17bdSdan     }
167*610e17bdSdan   }
168*610e17bdSdan 
169*610e17bdSdan   if( bVerbose ){
170*610e17bdSdan     int iOpt;
171*610e17bdSdan     fprintf(stdout, "Options are: ");
172*610e17bdSdan     for(iOpt=0; aOpt[iOpt].zText; iOpt++){
173*610e17bdSdan       CmdLineOption *pOpt = &aOpt[iOpt];
174*610e17bdSdan       if( pOpt->eType!=CMDLINE_BARE || *(int*)(&pOut[pOpt->iOff]) ){
175*610e17bdSdan         fprintf(stdout, "%s ", pOpt->zText);
176*610e17bdSdan       }
177*610e17bdSdan       switch( pOpt->eType ){
178*610e17bdSdan         case CMDLINE_INTEGER:
179*610e17bdSdan           fprintf(stdout, "%d ", *(int*)(&pOut[pOpt->iOff]));
180*610e17bdSdan           break;
181*610e17bdSdan         case CMDLINE_BOOLEAN:
182*610e17bdSdan           fprintf(stdout, "%d ", *(int*)(&pOut[pOpt->iOff]));
183*610e17bdSdan           break;
184*610e17bdSdan         case CMDLINE_STRING:
185*610e17bdSdan           fprintf(stdout, "%s ", *(const char**)(&pOut[pOpt->iOff]));
186*610e17bdSdan           break;
187*610e17bdSdan       }
188*610e17bdSdan     }
189*610e17bdSdan     fprintf(stdout, "\n");
190*610e17bdSdan   }
191*610e17bdSdan }
192*610e17bdSdan /*
193*610e17bdSdan ** End of generic command line parser.
194*610e17bdSdan *************************************************************************/
195*610e17bdSdan 
abort_due_to_error(int rc)196*610e17bdSdan static void abort_due_to_error(int rc){
197*610e17bdSdan   fprintf(stderr, "Error: %d\n");
198*610e17bdSdan   exit(-1);
199*610e17bdSdan }
200*610e17bdSdan 
execsql(sqlite3 * db,const char * zSql)201*610e17bdSdan static void execsql(sqlite3 *db, const char *zSql){
202*610e17bdSdan   int rc = sqlite3_exec(db, zSql, 0, 0, 0);
203*610e17bdSdan   if( rc!=SQLITE_OK ) abort_due_to_error(rc);
204*610e17bdSdan }
205*610e17bdSdan 
xConflict(void * pCtx,int eConflict,sqlite3_changeset_iter * p)206*610e17bdSdan static int xConflict(void *pCtx, int eConflict, sqlite3_changeset_iter *p){
207*610e17bdSdan   return SQLITE_CHANGESET_ABORT;
208*610e17bdSdan }
209*610e17bdSdan 
run_test(sqlite3 * db,sqlite3 * db2,int nRow,const char * zSql)210*610e17bdSdan static void run_test(
211*610e17bdSdan   sqlite3 *db,
212*610e17bdSdan   sqlite3 *db2,
213*610e17bdSdan   int nRow,
214*610e17bdSdan   const char *zSql
215*610e17bdSdan ){
216*610e17bdSdan   sqlite3_session *pSession = 0;
217*610e17bdSdan   sqlite3_stmt *pStmt = 0;
218*610e17bdSdan   int rc;
219*610e17bdSdan   int i;
220*610e17bdSdan   int nChangeset;
221*610e17bdSdan   void *pChangeset;
222*610e17bdSdan 
223*610e17bdSdan   /* Attach a session object to database db */
224*610e17bdSdan   rc = sqlite3session_create(db, "main", &pSession);
225*610e17bdSdan   if( rc!=SQLITE_OK ) abort_due_to_error(rc);
226*610e17bdSdan 
227*610e17bdSdan   /* Configure the session to capture changes on all tables */
228*610e17bdSdan   rc = sqlite3session_attach(pSession, 0);
229*610e17bdSdan   if( rc!=SQLITE_OK ) abort_due_to_error(rc);
230*610e17bdSdan 
231*610e17bdSdan   /* Prepare the SQL statement */
232*610e17bdSdan   rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0);
233*610e17bdSdan   if( rc!=SQLITE_OK ) abort_due_to_error(rc);
234*610e17bdSdan 
235*610e17bdSdan   /* Open a transaction */
236*610e17bdSdan   execsql(db, "BEGIN");
237*610e17bdSdan 
238*610e17bdSdan   /* Execute the SQL statement nRow times */
239*610e17bdSdan   for(i=0; i<nRow; i++){
240*610e17bdSdan     sqlite3_bind_int(pStmt, 1, i);
241*610e17bdSdan     sqlite3_step(pStmt);
242*610e17bdSdan     rc = sqlite3_reset(pStmt);
243*610e17bdSdan     if( rc!=SQLITE_OK ) abort_due_to_error(rc);
244*610e17bdSdan   }
245*610e17bdSdan   sqlite3_finalize(pStmt);
246*610e17bdSdan 
247*610e17bdSdan   /* Extract a changeset from the sessions object */
248*610e17bdSdan   rc = sqlite3session_changeset(pSession, &nChangeset, &pChangeset);
249*610e17bdSdan   if( rc!=SQLITE_OK ) abort_due_to_error(rc);
250*610e17bdSdan   execsql(db, "COMMIT");
251*610e17bdSdan 
252*610e17bdSdan   /* Apply the changeset to the second db */
253*610e17bdSdan   rc = sqlite3changeset_apply(db2, nChangeset, pChangeset, 0, xConflict, 0);
254*610e17bdSdan   if( rc!=SQLITE_OK ) abort_due_to_error(rc);
255*610e17bdSdan 
256*610e17bdSdan   /* Cleanup */
257*610e17bdSdan   sqlite3_free(pChangeset);
258*610e17bdSdan   sqlite3session_delete(pSession);
259*610e17bdSdan }
260*610e17bdSdan 
main(int argc,char ** argv)261*610e17bdSdan int main(int argc, char **argv){
262*610e17bdSdan   struct Options {
263*610e17bdSdan     int nRow;
264*610e17bdSdan     int bWithoutRowid;
265*610e17bdSdan     int bInteger;
266*610e17bdSdan     int bAll;
267*610e17bdSdan     const char *zDb;
268*610e17bdSdan   };
269*610e17bdSdan   struct Options o = { 2500, 0, 0, 0, "session_speed_test.db" };
270*610e17bdSdan 
271*610e17bdSdan   CmdLineOption aOpt[] = {
272*610e17bdSdan     CMDLINE_INT32( "-rows", "number of rows in test",
273*610e17bdSdan       offsetof(struct Options, nRow) ),
274*610e17bdSdan     CMDLINE_BOOL("-without-rowid", "use WITHOUT ROWID tables",
275*610e17bdSdan       offsetof(struct Options, bWithoutRowid) ),
276*610e17bdSdan     CMDLINE_BOOL("-integer", "use integer data (instead of text/blobs)",
277*610e17bdSdan       offsetof(struct Options, bInteger) ),
278*610e17bdSdan     CMDLINE_NONE("-all", "Run all 4 combos of -without-rowid and -integer",
279*610e17bdSdan       offsetof(struct Options, bAll) ),
280*610e17bdSdan     CMDLINE_TEXT("-database", "prefix for database files to use",
281*610e17bdSdan       offsetof(struct Options, zDb) ),
282*610e17bdSdan     {0, 0, 0, 0}
283*610e17bdSdan   };
284*610e17bdSdan 
285*610e17bdSdan   const char *azCreate[] = {
286*610e17bdSdan     "CREATE TABLE t1(a PRIMARY KEY, b, c, d)",
287*610e17bdSdan     "CREATE TABLE t1(a PRIMARY KEY, b, c, d) WITHOUT ROWID",
288*610e17bdSdan   };
289*610e17bdSdan 
290*610e17bdSdan   const char *azInsert[] = {
291*610e17bdSdan     "INSERT INTO t1 VALUES("
292*610e17bdSdan     "printf('%.8d',?), randomblob(50), randomblob(50), randomblob(50))",
293*610e17bdSdan     "INSERT INTO t1 VALUES(?, random(), random(), random())"
294*610e17bdSdan   };
295*610e17bdSdan 
296*610e17bdSdan   const char *azUpdate[] = {
297*610e17bdSdan     "UPDATE t1 SET d = randomblob(50) WHERE a = printf('%.8d',?)",
298*610e17bdSdan     "UPDATE t1 SET d = random() WHERE a = ?"
299*610e17bdSdan   };
300*610e17bdSdan 
301*610e17bdSdan   const char *azDelete[] = {
302*610e17bdSdan     "DELETE FROM t1 WHERE a = printf('%.8d',?)",
303*610e17bdSdan     "DELETE FROM t1 WHERE a = ?"
304*610e17bdSdan   };
305*610e17bdSdan 
306*610e17bdSdan   int rc;
307*610e17bdSdan   sqlite3 *db;
308*610e17bdSdan   sqlite3 *db2;
309*610e17bdSdan   char *zDb2;
310*610e17bdSdan   int bWithoutRowid;
311*610e17bdSdan   int bInteger;
312*610e17bdSdan 
313*610e17bdSdan   parse_command_line(argc, argv, 1, aOpt, (void*)&o,
314*610e17bdSdan     "This program creates two new, empty, databases each containing a single\n"
315*610e17bdSdan     "table. It then does the following:\n\n"
316*610e17bdSdan     "  1. Inserts -rows rows into the first database\n"
317*610e17bdSdan     "  2. Updates each row in the first db\n"
318*610e17bdSdan     "  3. Delete each row from the first db\n\n"
319*610e17bdSdan     "The modifications made by each step are captured in a changeset and\n"
320*610e17bdSdan     "applied to the second database.\n"
321*610e17bdSdan   );
322*610e17bdSdan   zDb2 = sqlite3_mprintf("%s2", o.zDb);
323*610e17bdSdan 
324*610e17bdSdan   for(bWithoutRowid=0; bWithoutRowid<2; bWithoutRowid++){
325*610e17bdSdan     for(bInteger=0; bInteger<2; bInteger++){
326*610e17bdSdan       if( o.bAll || (o.bWithoutRowid==bWithoutRowid && o.bInteger==bInteger) ){
327*610e17bdSdan         fprintf(stdout, "Testing %s data with %s table\n",
328*610e17bdSdan             bInteger ? "integer" : "blob/text",
329*610e17bdSdan             bWithoutRowid ? "WITHOUT ROWID" : "rowid"
330*610e17bdSdan         );
331*610e17bdSdan 
332*610e17bdSdan         /* Open new database handles on two empty databases */
333*610e17bdSdan         unlink(o.zDb);
334*610e17bdSdan         rc = sqlite3_open(o.zDb, &db);
335*610e17bdSdan         if( rc!=SQLITE_OK ) abort_due_to_error(rc);
336*610e17bdSdan         unlink(zDb2);
337*610e17bdSdan         rc = sqlite3_open(zDb2, &db2);
338*610e17bdSdan         if( rc!=SQLITE_OK ) abort_due_to_error(rc);
339*610e17bdSdan 
340*610e17bdSdan         /* Create the schema in both databases. */
341*610e17bdSdan         execsql(db, azCreate[o.bWithoutRowid]);
342*610e17bdSdan         execsql(db2, azCreate[o.bWithoutRowid]);
343*610e17bdSdan 
344*610e17bdSdan         /* Run the three tests */
345*610e17bdSdan         run_test(db, db2, o.nRow, azInsert[o.bInteger]);
346*610e17bdSdan         run_test(db, db2, o.nRow, azUpdate[o.bInteger]);
347*610e17bdSdan         run_test(db, db2, o.nRow, azDelete[o.bInteger]);
348*610e17bdSdan 
349*610e17bdSdan         /* Close the db handles */
350*610e17bdSdan         sqlite3_close(db);
351*610e17bdSdan         sqlite3_close(db2);
352*610e17bdSdan       }
353*610e17bdSdan     }
354*610e17bdSdan   }
355*610e17bdSdan 
356*610e17bdSdan 
357*610e17bdSdan   return 0;
358*610e17bdSdan }
359