xref: /sqlite-3.40.0/ext/misc/vtablog.c (revision 84c501ba)
1 /*
2 ** 2017-08-10
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 **
13 ** This file implements a virtual table that prints diagnostic information
14 ** on stdout when its key interfaces are called.  This is intended for
15 ** interactive analysis and debugging of virtual table interfaces.
16 **
17 ** Usage example:
18 **
19 **     .load ./vtablog
20 **     CREATE VIRTUAL TABLE temp.log USING vtablog(
21 **        schema='CREATE TABLE x(a,b,c)',
22 **        rows=25
23 **     );
24 **     SELECT * FROM log;
25 */
26 #include "sqlite3ext.h"
27 SQLITE_EXTENSION_INIT1
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <assert.h>
31 #include <string.h>
32 #include <ctype.h>
33 
34 
35 /* vtablog_vtab is a subclass of sqlite3_vtab which will
36 ** serve as the underlying representation of a vtablog virtual table
37 */
38 typedef struct vtablog_vtab vtablog_vtab;
39 struct vtablog_vtab {
40   sqlite3_vtab base;  /* Base class - must be first */
41   int nRow;           /* Number of rows in the table */
42   int iInst;          /* Instance number for this vtablog table */
43   int nCursor;        /* Number of cursors created */
44 };
45 
46 /* vtablog_cursor is a subclass of sqlite3_vtab_cursor which will
47 ** serve as the underlying representation of a cursor that scans
48 ** over rows of the result
49 */
50 typedef struct vtablog_cursor vtablog_cursor;
51 struct vtablog_cursor {
52   sqlite3_vtab_cursor base;  /* Base class - must be first */
53   int iCursor;               /* Cursor number */
54   sqlite3_int64 iRowid;      /* The rowid */
55 };
56 
57 /* Skip leading whitespace.  Return a pointer to the first non-whitespace
58 ** character, or to the zero terminator if the string has only whitespace */
vtablog_skip_whitespace(const char * z)59 static const char *vtablog_skip_whitespace(const char *z){
60   while( isspace((unsigned char)z[0]) ) z++;
61   return z;
62 }
63 
64 /* Remove trailing whitespace from the end of string z[] */
vtablog_trim_whitespace(char * z)65 static void vtablog_trim_whitespace(char *z){
66   size_t n = strlen(z);
67   while( n>0 && isspace((unsigned char)z[n]) ) n--;
68   z[n] = 0;
69 }
70 
71 /* Dequote the string */
vtablog_dequote(char * z)72 static void vtablog_dequote(char *z){
73   int j;
74   char cQuote = z[0];
75   size_t i, n;
76 
77   if( cQuote!='\'' && cQuote!='"' ) return;
78   n = strlen(z);
79   if( n<2 || z[n-1]!=z[0] ) return;
80   for(i=1, j=0; i<n-1; i++){
81     if( z[i]==cQuote && z[i+1]==cQuote ) i++;
82     z[j++] = z[i];
83   }
84   z[j] = 0;
85 }
86 
87 /* Check to see if the string is of the form:  "TAG = VALUE" with optional
88 ** whitespace before and around tokens.  If it is, return a pointer to the
89 ** first character of VALUE.  If it is not, return NULL.
90 */
vtablog_parameter(const char * zTag,int nTag,const char * z)91 static const char *vtablog_parameter(const char *zTag, int nTag, const char *z){
92   z = vtablog_skip_whitespace(z);
93   if( strncmp(zTag, z, nTag)!=0 ) return 0;
94   z = vtablog_skip_whitespace(z+nTag);
95   if( z[0]!='=' ) return 0;
96   return vtablog_skip_whitespace(z+1);
97 }
98 
99 /* Decode a parameter that requires a dequoted string.
100 **
101 ** Return non-zero on an error.
102 */
vtablog_string_parameter(char ** pzErr,const char * zParam,const char * zArg,char ** pzVal)103 static int vtablog_string_parameter(
104   char **pzErr,            /* Leave the error message here, if there is one */
105   const char *zParam,      /* Parameter we are checking for */
106   const char *zArg,        /* Raw text of the virtual table argment */
107   char **pzVal             /* Write the dequoted string value here */
108 ){
109   const char *zValue;
110   zValue = vtablog_parameter(zParam,(int)strlen(zParam),zArg);
111   if( zValue==0 ) return 0;
112   if( *pzVal ){
113     *pzErr = sqlite3_mprintf("more than one '%s' parameter", zParam);
114     return 1;
115   }
116   *pzVal = sqlite3_mprintf("%s", zValue);
117   if( *pzVal==0 ){
118     *pzErr = sqlite3_mprintf("out of memory");
119     return 1;
120   }
121   vtablog_trim_whitespace(*pzVal);
122   vtablog_dequote(*pzVal);
123   return 0;
124 }
125 
126 #if 0 /* not used - yet */
127 /* Return 0 if the argument is false and 1 if it is true.  Return -1 if
128 ** we cannot really tell.
129 */
130 static int vtablog_boolean(const char *z){
131   if( sqlite3_stricmp("yes",z)==0
132    || sqlite3_stricmp("on",z)==0
133    || sqlite3_stricmp("true",z)==0
134    || (z[0]=='1' && z[1]==0)
135   ){
136     return 1;
137   }
138   if( sqlite3_stricmp("no",z)==0
139    || sqlite3_stricmp("off",z)==0
140    || sqlite3_stricmp("false",z)==0
141    || (z[0]=='0' && z[1]==0)
142   ){
143     return 0;
144   }
145   return -1;
146 }
147 #endif
148 
149 /*
150 ** The vtablogConnect() method is invoked to create a new
151 ** vtablog_vtab that describes the vtablog virtual table.
152 **
153 ** Think of this routine as the constructor for vtablog_vtab objects.
154 **
155 ** All this routine needs to do is:
156 **
157 **    (1) Allocate the vtablog_vtab object and initialize all fields.
158 **
159 **    (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the
160 **        result set of queries against vtablog will look like.
161 */
vtablogConnectCreate(sqlite3 * db,void * pAux,int argc,const char * const * argv,sqlite3_vtab ** ppVtab,char ** pzErr,int isCreate)162 static int vtablogConnectCreate(
163   sqlite3 *db,
164   void *pAux,
165   int argc, const char *const*argv,
166   sqlite3_vtab **ppVtab,
167   char **pzErr,
168   int isCreate
169 ){
170   static int nInst = 0;
171   vtablog_vtab *pNew;
172   int i;
173   int rc;
174   int iInst = ++nInst;
175   char *zSchema = 0;
176   char *zNRow = 0;
177 
178   printf("vtablog%s(tab=%d):\n", isCreate ? "Create" : "Connect", iInst);
179   printf("  argc=%d\n", argc);
180   for(i=0; i<argc; i++){
181     printf("  argv[%d] = ", i);
182     if( argv[i] ){
183       printf("[%s]\n", argv[i]);
184     }else{
185       printf("NULL\n");
186     }
187   }
188 
189   for(i=3; i<argc; i++){
190     const char *z = argv[i];
191     if( vtablog_string_parameter(pzErr, "schema", z, &zSchema) ){
192       return SQLITE_ERROR;
193     }
194     if( vtablog_string_parameter(pzErr, "rows", z, &zNRow) ){
195       return SQLITE_ERROR;
196     }
197   }
198 
199   if( zSchema==0 ){
200     *pzErr = sqlite3_mprintf("no schema defined");
201     return SQLITE_ERROR;
202   }
203   rc = sqlite3_declare_vtab(db, zSchema);
204   if( rc==SQLITE_OK ){
205     pNew = sqlite3_malloc( sizeof(*pNew) );
206     *ppVtab = (sqlite3_vtab*)pNew;
207     if( pNew==0 ) return SQLITE_NOMEM;
208     memset(pNew, 0, sizeof(*pNew));
209     pNew->nRow = 10;
210     if( zNRow ) pNew->nRow = atoi(zNRow);
211     pNew->iInst = iInst;
212   }
213   return rc;
214 }
vtablogCreate(sqlite3 * db,void * pAux,int argc,const char * const * argv,sqlite3_vtab ** ppVtab,char ** pzErr)215 static int vtablogCreate(
216   sqlite3 *db,
217   void *pAux,
218   int argc, const char *const*argv,
219   sqlite3_vtab **ppVtab,
220   char **pzErr
221 ){
222   return vtablogConnectCreate(db,pAux,argc,argv,ppVtab,pzErr,1);
223 }
vtablogConnect(sqlite3 * db,void * pAux,int argc,const char * const * argv,sqlite3_vtab ** ppVtab,char ** pzErr)224 static int vtablogConnect(
225   sqlite3 *db,
226   void *pAux,
227   int argc, const char *const*argv,
228   sqlite3_vtab **ppVtab,
229   char **pzErr
230 ){
231   return vtablogConnectCreate(db,pAux,argc,argv,ppVtab,pzErr,0);
232 }
233 
234 
235 /*
236 ** This method is the destructor for vtablog_cursor objects.
237 */
vtablogDisconnect(sqlite3_vtab * pVtab)238 static int vtablogDisconnect(sqlite3_vtab *pVtab){
239   vtablog_vtab *pTab = (vtablog_vtab*)pVtab;
240   printf("vtablogDisconnect(%d)\n", pTab->iInst);
241   sqlite3_free(pVtab);
242   return SQLITE_OK;
243 }
244 
245 /*
246 ** This method is the destructor for vtablog_cursor objects.
247 */
vtablogDestroy(sqlite3_vtab * pVtab)248 static int vtablogDestroy(sqlite3_vtab *pVtab){
249   vtablog_vtab *pTab = (vtablog_vtab*)pVtab;
250   printf("vtablogDestroy(%d)\n", pTab->iInst);
251   sqlite3_free(pVtab);
252   return SQLITE_OK;
253 }
254 
255 /*
256 ** Constructor for a new vtablog_cursor object.
257 */
vtablogOpen(sqlite3_vtab * p,sqlite3_vtab_cursor ** ppCursor)258 static int vtablogOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
259   vtablog_vtab *pTab = (vtablog_vtab*)p;
260   vtablog_cursor *pCur;
261   printf("vtablogOpen(tab=%d, cursor=%d)\n", pTab->iInst, ++pTab->nCursor);
262   pCur = sqlite3_malloc( sizeof(*pCur) );
263   if( pCur==0 ) return SQLITE_NOMEM;
264   memset(pCur, 0, sizeof(*pCur));
265   pCur->iCursor = pTab->nCursor;
266   *ppCursor = &pCur->base;
267   return SQLITE_OK;
268 }
269 
270 /*
271 ** Destructor for a vtablog_cursor.
272 */
vtablogClose(sqlite3_vtab_cursor * cur)273 static int vtablogClose(sqlite3_vtab_cursor *cur){
274   vtablog_cursor *pCur = (vtablog_cursor*)cur;
275   vtablog_vtab *pTab = (vtablog_vtab*)cur->pVtab;
276   printf("vtablogClose(tab=%d, cursor=%d)\n", pTab->iInst, pCur->iCursor);
277   sqlite3_free(cur);
278   return SQLITE_OK;
279 }
280 
281 
282 /*
283 ** Advance a vtablog_cursor to its next row of output.
284 */
vtablogNext(sqlite3_vtab_cursor * cur)285 static int vtablogNext(sqlite3_vtab_cursor *cur){
286   vtablog_cursor *pCur = (vtablog_cursor*)cur;
287   vtablog_vtab *pTab = (vtablog_vtab*)cur->pVtab;
288   printf("vtablogNext(tab=%d, cursor=%d)  rowid %d -> %d\n",
289          pTab->iInst, pCur->iCursor, (int)pCur->iRowid, (int)pCur->iRowid+1);
290   pCur->iRowid++;
291   return SQLITE_OK;
292 }
293 
294 /*
295 ** Return values of columns for the row at which the vtablog_cursor
296 ** is currently pointing.
297 */
vtablogColumn(sqlite3_vtab_cursor * cur,sqlite3_context * ctx,int i)298 static int vtablogColumn(
299   sqlite3_vtab_cursor *cur,   /* The cursor */
300   sqlite3_context *ctx,       /* First argument to sqlite3_result_...() */
301   int i                       /* Which column to return */
302 ){
303   vtablog_cursor *pCur = (vtablog_cursor*)cur;
304   vtablog_vtab *pTab = (vtablog_vtab*)cur->pVtab;
305   char zVal[50];
306 
307   if( i<26 ){
308     sqlite3_snprintf(sizeof(zVal),zVal,"%c%d",
309                      "abcdefghijklmnopqrstuvwyz"[i], pCur->iRowid);
310   }else{
311     sqlite3_snprintf(sizeof(zVal),zVal,"{%d}%d", i, pCur->iRowid);
312   }
313   printf("vtablogColumn(tab=%d, cursor=%d, i=%d): [%s]\n",
314          pTab->iInst, pCur->iCursor, i, zVal);
315   sqlite3_result_text(ctx, zVal, -1, SQLITE_TRANSIENT);
316   return SQLITE_OK;
317 }
318 
319 /*
320 ** Return the rowid for the current row.  In this implementation, the
321 ** rowid is the same as the output value.
322 */
vtablogRowid(sqlite3_vtab_cursor * cur,sqlite_int64 * pRowid)323 static int vtablogRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
324   vtablog_cursor *pCur = (vtablog_cursor*)cur;
325   vtablog_vtab *pTab = (vtablog_vtab*)cur->pVtab;
326   printf("vtablogRowid(tab=%d, cursor=%d): %d\n",
327          pTab->iInst, pCur->iCursor, (int)pCur->iRowid);
328   *pRowid = pCur->iRowid;
329   return SQLITE_OK;
330 }
331 
332 /*
333 ** Return TRUE if the cursor has been moved off of the last
334 ** row of output.
335 */
vtablogEof(sqlite3_vtab_cursor * cur)336 static int vtablogEof(sqlite3_vtab_cursor *cur){
337   vtablog_cursor *pCur = (vtablog_cursor*)cur;
338   vtablog_vtab *pTab = (vtablog_vtab*)cur->pVtab;
339   int rc = pCur->iRowid >= pTab->nRow;
340   printf("vtablogEof(tab=%d, cursor=%d): %d\n",
341          pTab->iInst, pCur->iCursor, rc);
342   return rc;
343 }
344 
345 /*
346 ** Output an sqlite3_value object's value as an SQL literal.
347 */
vtablogQuote(sqlite3_value * p)348 static void vtablogQuote(sqlite3_value *p){
349   char z[50];
350   switch( sqlite3_value_type(p) ){
351     case SQLITE_NULL: {
352       printf("NULL");
353       break;
354     }
355     case SQLITE_INTEGER: {
356       sqlite3_snprintf(50,z,"%lld", sqlite3_value_int64(p));
357       printf("%s", z);
358       break;
359     }
360     case SQLITE_FLOAT: {
361       sqlite3_snprintf(50,z,"%!.20g", sqlite3_value_double(p));
362       printf("%s", z);
363       break;
364     }
365     case SQLITE_BLOB: {
366       int n = sqlite3_value_bytes(p);
367       const unsigned char *z = (const unsigned char*)sqlite3_value_blob(p);
368       int i;
369       printf("x'");
370       for(i=0; i<n; i++) printf("%02x", z[i]);
371       printf("'");
372       break;
373     }
374     case SQLITE_TEXT: {
375       const char *z = (const char*)sqlite3_value_text(p);
376       int i;
377       char c;
378       for(i=0; (c = z[i])!=0 && c!='\''; i++){}
379       if( c==0 ){
380         printf("'%s'",z);
381       }else{
382         printf("'");
383         while( *z ){
384           for(i=0; (c = z[i])!=0 && c!='\''; i++){}
385           if( c=='\'' ) i++;
386           if( i ){
387             printf("%.*s", i, z);
388             z += i;
389           }
390           if( c=='\'' ){
391             printf("'");
392             continue;
393           }
394           if( c==0 ){
395             break;
396           }
397           z++;
398         }
399         printf("'");
400       }
401       break;
402     }
403   }
404 }
405 
406 
407 /*
408 ** This method is called to "rewind" the vtablog_cursor object back
409 ** to the first row of output.  This method is always called at least
410 ** once prior to any call to vtablogColumn() or vtablogRowid() or
411 ** vtablogEof().
412 */
vtablogFilter(sqlite3_vtab_cursor * cur,int idxNum,const char * idxStr,int argc,sqlite3_value ** argv)413 static int vtablogFilter(
414   sqlite3_vtab_cursor *cur,
415   int idxNum, const char *idxStr,
416   int argc, sqlite3_value **argv
417 ){
418   vtablog_cursor *pCur = (vtablog_cursor *)cur;
419   vtablog_vtab *pTab = (vtablog_vtab*)cur->pVtab;
420   printf("vtablogFilter(tab=%d, cursor=%d):\n", pTab->iInst, pCur->iCursor);
421   pCur->iRowid = 0;
422   return SQLITE_OK;
423 }
424 
425 /*
426 ** SQLite will invoke this method one or more times while planning a query
427 ** that uses the vtablog virtual table.  This routine needs to create
428 ** a query plan for each invocation and compute an estimated cost for that
429 ** plan.
430 */
vtablogBestIndex(sqlite3_vtab * tab,sqlite3_index_info * pIdxInfo)431 static int vtablogBestIndex(
432   sqlite3_vtab *tab,
433   sqlite3_index_info *pIdxInfo
434 ){
435   vtablog_vtab *pTab = (vtablog_vtab*)tab;
436   printf("vtablogBestIndex(tab=%d):\n", pTab->iInst);
437   pIdxInfo->estimatedCost = (double)500;
438   pIdxInfo->estimatedRows = 500;
439   return SQLITE_OK;
440 }
441 
442 /*
443 ** SQLite invokes this method to INSERT, UPDATE, or DELETE content from
444 ** the table.
445 **
446 ** This implementation does not actually make any changes to the table
447 ** content.  It merely logs the fact that the method was invoked
448 */
vtablogUpdate(sqlite3_vtab * tab,int argc,sqlite3_value ** argv,sqlite_int64 * pRowid)449 static int vtablogUpdate(
450   sqlite3_vtab *tab,
451   int argc,
452   sqlite3_value **argv,
453   sqlite_int64 *pRowid
454 ){
455   vtablog_vtab *pTab = (vtablog_vtab*)tab;
456   int i;
457   printf("vtablogUpdate(tab=%d):\n", pTab->iInst);
458   printf("  argc=%d\n", argc);
459   for(i=0; i<argc; i++){
460     printf("  argv[%d]=", i);
461     vtablogQuote(argv[i]);
462     printf("\n");
463   }
464   return SQLITE_OK;
465 }
466 
467 /*
468 ** This following structure defines all the methods for the
469 ** vtablog virtual table.
470 */
471 static sqlite3_module vtablogModule = {
472   0,                         /* iVersion */
473   vtablogCreate,             /* xCreate */
474   vtablogConnect,            /* xConnect */
475   vtablogBestIndex,          /* xBestIndex */
476   vtablogDisconnect,         /* xDisconnect */
477   vtablogDestroy,            /* xDestroy */
478   vtablogOpen,               /* xOpen - open a cursor */
479   vtablogClose,              /* xClose - close a cursor */
480   vtablogFilter,             /* xFilter - configure scan constraints */
481   vtablogNext,               /* xNext - advance a cursor */
482   vtablogEof,                /* xEof - check for end of scan */
483   vtablogColumn,             /* xColumn - read data */
484   vtablogRowid,              /* xRowid - read data */
485   vtablogUpdate,             /* xUpdate */
486   0,                         /* xBegin */
487   0,                         /* xSync */
488   0,                         /* xCommit */
489   0,                         /* xRollback */
490   0,                         /* xFindMethod */
491   0,                         /* xRename */
492   0,                         /* xSavepoint */
493   0,                         /* xRelease */
494   0,                         /* xRollbackTo */
495   0,                         /* xShadowName */
496 };
497 
498 #ifdef _WIN32
499 __declspec(dllexport)
500 #endif
sqlite3_vtablog_init(sqlite3 * db,char ** pzErrMsg,const sqlite3_api_routines * pApi)501 int sqlite3_vtablog_init(
502   sqlite3 *db,
503   char **pzErrMsg,
504   const sqlite3_api_routines *pApi
505 ){
506   int rc;
507   SQLITE_EXTENSION_INIT2(pApi);
508   rc = sqlite3_create_module(db, "vtablog", &vtablogModule, 0);
509   return rc;
510 }
511