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