wasCSharpSQLite – Blame information for rev
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | using System; |
2 | using System.Diagnostics; |
||
3 | using System.Text; |
||
4 | |||
5 | using sqlite_int64 = System.Int64; |
||
6 | |||
7 | namespace Community.CsharpSqlite |
||
8 | { |
||
9 | #if TCLSH |
||
10 | using tcl.lang; |
||
11 | using sqlite3_stmt = Sqlite3.Vdbe; |
||
12 | using sqlite3_value = Sqlite3.Mem; |
||
13 | using Tcl_CmdInfo = tcl.lang.WrappedCommand; |
||
14 | using Tcl_DString = tcl.lang.TclString; |
||
15 | using Tcl_Interp = tcl.lang.Interp; |
||
16 | using Tcl_Obj = tcl.lang.TclObject; |
||
17 | using ClientData = System.Object; |
||
18 | |||
19 | |||
20 | public partial class Sqlite3 |
||
21 | { |
||
22 | /* |
||
23 | ** 2006 June 10 |
||
24 | ** |
||
25 | ** The author disclaims copyright to this source code. In place of |
||
26 | ** a legal notice, here is a blessing: |
||
27 | ** |
||
28 | ** May you do good and not evil. |
||
29 | ** May you find forgiveness for yourself and forgive others. |
||
30 | ** May you share freely, never taking more than you give. |
||
31 | ** |
||
32 | ************************************************************************* |
||
33 | ** Code for testing the virtual table interfaces. This code |
||
34 | ** is not included in the SQLite library. It is used for automated |
||
35 | ** testing of the SQLite library. |
||
36 | ************************************************************************* |
||
37 | ** Included in SQLite3 port to C#-SQLite; 2008 Noah B Hart |
||
38 | ** C#-SQLite is an independent reimplementation of the SQLite software library |
||
39 | ** |
||
40 | ** SQLITE_SOURCE_ID: 2011-06-23 19:49:22 4374b7e83ea0a3fbc3691f9c0c936272862f32f2 |
||
41 | ** |
||
42 | ************************************************************************* |
||
43 | */ |
||
44 | //#include "sqliteInt.h" |
||
45 | //#include "tcl.h" |
||
46 | //#include <stdlib.h> |
||
47 | //#include <string.h> |
||
48 | |||
49 | #if !SQLITE_OMIT_VIRTUALTABLE |
||
50 | |||
51 | //typedef struct echo_vtab echo_vtab; |
||
52 | //typedef struct echo_cursor echo_cursor; |
||
53 | |||
54 | /* |
||
55 | ** The test module defined in this file uses four global Tcl variables to |
||
56 | ** commicate with test-scripts: |
||
57 | ** |
||
58 | ** $::echo_module |
||
59 | ** $::echo_module_sync_fail |
||
60 | ** $::echo_module_begin_fail |
||
61 | ** $::echo_module_cost |
||
62 | ** |
||
63 | ** The variable ::echo_module is a list. Each time one of the following |
||
64 | ** methods is called, one or more elements are appended to the list. |
||
65 | ** This is used for automated testing of virtual table modules. |
||
66 | ** |
||
67 | ** The ::echo_module_sync_fail variable is set by test scripts and read |
||
68 | ** by code in this file. If it is set to the name of a real table in the |
||
69 | ** the database, then all xSync operations on echo virtual tables that |
||
70 | ** use the named table as a backing store will fail. |
||
71 | */ |
||
72 | |||
73 | /* |
||
74 | ** Errors can be provoked within the following echo virtual table methods: |
||
75 | ** |
||
76 | ** xBestIndex xOpen xFilter xNext |
||
77 | ** xColumn xRowid xUpdate xSync |
||
78 | ** xBegin xRename |
||
79 | ** |
||
80 | ** This is done by setting the global tcl variable: |
||
81 | ** |
||
82 | ** echo_module_fail($method,$tbl) |
||
83 | ** |
||
84 | ** where $method is set to the name of the virtual table method to fail |
||
85 | ** (i.e. "xBestIndex") and $tbl is the name of the table being echoed (not |
||
86 | ** the name of the virtual table, the name of the underlying real table). |
||
87 | */ |
||
88 | |||
89 | /* |
||
90 | ** An echo virtual-table object. |
||
91 | ** |
||
92 | ** echo.vtab.aIndex is an array of booleans. The nth entry is true if |
||
93 | ** the nth column of the real table is the left-most column of an index |
||
94 | ** (implicit or otherwise). In other words, if SQLite can optimize |
||
95 | ** a query like "SELECT * FROM real_table WHERE col = ?". |
||
96 | ** |
||
97 | ** Member variable aCol[] contains copies of the column names of the real |
||
98 | ** table. |
||
99 | */ |
||
100 | class echo_vtab : sqlite3_vtab |
||
101 | { |
||
102 | //public sqlite3_vtab base; |
||
103 | public Tcl_Interp interp; /* Tcl interpreter containing debug variables */ |
||
104 | public sqlite3 db; /* Database connection */ |
||
105 | |||
106 | public int isPattern; |
||
107 | public int inTransaction; /* True if within a transaction */ |
||
108 | public string zThis; /* Name of the echo table */ |
||
109 | public string zTableName; /* Name of the real table */ |
||
110 | public string zLogName; /* Name of the log table */ |
||
111 | public int nCol; /* Number of columns in the real table */ |
||
112 | public int[] aIndex; /* Array of size nCol. True if column has an index */ |
||
113 | public string[] aCol; /* Array of size nCol. Column names */ |
||
114 | }; |
||
115 | |||
116 | /* An echo cursor object */ |
||
117 | class echo_cursor : sqlite3_vtab_cursor |
||
118 | { |
||
119 | //public sqlite3_vtab_cursor base; |
||
120 | public sqlite3_stmt pStmt; |
||
121 | }; |
||
122 | |||
123 | static int simulateVtabError( echo_vtab p, string zMethod ) |
||
124 | { |
||
125 | string zErr; |
||
126 | StringBuilder zVarname = new StringBuilder( 128 ); |
||
127 | //zVarname[127] = '\0'; |
||
128 | sqlite3_snprintf( 127, zVarname, "echo_module_fail(%s,%s)", zMethod, p.zTableName ); |
||
129 | zErr = TCL.Tcl_GetVar( p.interp, zVarname.ToString(), (TCL.VarFlag)TCL.TCL_GLOBAL_ONLY ).ToString(); |
||
130 | if ( zErr != "" ) |
||
131 | { |
||
132 | p.zErrMsg = sqlite3_mprintf( "echo-vtab-error: %s", zErr ); |
||
133 | } |
||
134 | return ( zErr != "" ? 1 : 0 ); |
||
135 | } |
||
136 | |||
137 | /* |
||
138 | ** Convert an SQL-style quoted string into a normal string by removing |
||
139 | ** the quote characters. The conversion is done in-place. If the |
||
140 | ** input does not begin with a quote character, then this routine |
||
141 | ** is a no-op. |
||
142 | ** |
||
143 | ** Examples: |
||
144 | ** |
||
145 | ** "abc" becomes abc |
||
146 | ** 'xyz' becomes xyz |
||
147 | ** [pqr] becomes pqr |
||
148 | ** `mno` becomes mno |
||
149 | */ |
||
150 | static void dequoteString( ref string z ) |
||
151 | { |
||
152 | //int quote; |
||
153 | //int i, j; |
||
154 | if ( String.IsNullOrEmpty( z ) ) |
||
155 | return; |
||
156 | sqlite3Dequote( ref z ); |
||
157 | //quote = z[0]; |
||
158 | //switch( quote ){ |
||
159 | // case '\'': break; |
||
160 | // case '"': break; |
||
161 | // case '`': break; /* For MySQL compatibility */ |
||
162 | // case '[': quote = ']'; break; /* For MS SqlServer compatibility */ |
||
163 | // default: return; |
||
164 | //} |
||
165 | //for(i=1, j=0; z[i]; i++){ |
||
166 | // if( z[i]==quote ){ |
||
167 | // if( z[i+1]==quote ){ |
||
168 | // z[j++] = quote; |
||
169 | // i++; |
||
170 | // }else{ |
||
171 | // z[j++] = 0; |
||
172 | // break; |
||
173 | // } |
||
174 | // }else{ |
||
175 | // z[j++] = z[i]; |
||
176 | // } |
||
177 | //} |
||
178 | } |
||
179 | |||
180 | /* |
||
181 | ** Retrieve the column names for the table named zTab via database |
||
182 | ** connection db. SQLITE_OK is returned on success, or an sqlite error |
||
183 | ** code otherwise. |
||
184 | ** |
||
185 | ** If successful, the number of columns is written to pnCol. paCol is |
||
186 | ** set to point at sqlite3_malloc()'d space containing the array of |
||
187 | ** nCol column names. The caller is responsible for calling sqlite3_free |
||
188 | ** on paCol. |
||
189 | */ |
||
190 | static int getColumnNames( |
||
191 | sqlite3 db, |
||
192 | string zTab, |
||
193 | out string[] paCol, |
||
194 | out int pnCol |
||
195 | ) |
||
196 | { |
||
197 | string[] aCol = null; |
||
198 | string zSql; |
||
199 | sqlite3_stmt pStmt = null; |
||
200 | int rc = SQLITE_OK; |
||
201 | int nCol = 0; |
||
202 | |||
203 | /* Prepare the statement "SELECT * FROM <tbl>". The column names |
||
204 | ** of the result set of the compiled SELECT will be the same as |
||
205 | ** the column names of table <tbl>. |
||
206 | */ |
||
207 | zSql = sqlite3_mprintf( "SELECT * FROM %Q", zTab ); |
||
208 | //if( null==zSql ){ |
||
209 | // rc = SQLITE_NOMEM; |
||
210 | // goto _out; |
||
211 | //} |
||
212 | rc = sqlite3_prepare( db, zSql, -1, ref pStmt, 0 ); |
||
213 | //sqlite3_free(zSql); |
||
214 | |||
215 | if ( rc == SQLITE_OK ) |
||
216 | { |
||
217 | int ii; |
||
218 | int nBytes; |
||
219 | string zSpace; |
||
220 | nCol = sqlite3_column_count( pStmt ); |
||
221 | |||
222 | /* Figure out how much space to allocate for the array of column names |
||
223 | ** (including space for the strings themselves). Then allocate it. |
||
224 | */ |
||
225 | nBytes = sizeof( char ) * nCol; |
||
226 | for ( ii = 0; ii < nCol; ii++ ) |
||
227 | { |
||
228 | string zName = sqlite3_column_name( pStmt, ii ); |
||
229 | //if( null==zName ){ |
||
230 | // rc = SQLITE_NOMEM; |
||
231 | // goto _out; |
||
232 | //} |
||
233 | nBytes += zName.Length + 1;//strlen( zName ) + 1; |
||
234 | } |
||
235 | aCol = new string[nCol];//(char )sqlite3MallocZero(nBytes); |
||
236 | //if( null==aCol ){ |
||
237 | // rc = SQLITE_NOMEM; |
||
238 | // goto out_; |
||
239 | //} |
||
240 | |||
241 | /* Copy the column names into the allocated space and set up the |
||
242 | ** pointers in the aCol[] array. |
||
243 | */ |
||
244 | //zSpace = (char)( &aCol[nCol] ); |
||
245 | for ( ii = 0; ii < nCol; ii++ ) |
||
246 | { |
||
247 | //aCol[ii] = zSpace; |
||
248 | //zSpace += sprintf( zSpace, "%s", sqlite3_column_name( pStmt, ii ) ); |
||
249 | //zSpace++; |
||
250 | aCol[ii] = sqlite3_column_name( pStmt, ii ); |
||
251 | } |
||
252 | //Debug.Assert( (zSpace-nBytes)==(char )aCol ); |
||
253 | } |
||
254 | |||
255 | paCol = aCol; |
||
256 | pnCol = nCol; |
||
257 | |||
258 | //_out: |
||
259 | sqlite3_finalize( pStmt ); |
||
260 | return rc; |
||
261 | } |
||
262 | |||
263 | /* |
||
264 | ** Parameter zTab is the name of a table in database db with nCol |
||
265 | ** columns. This function allocates an array of integers nCol in |
||
266 | ** size and populates it according to any implicit or explicit |
||
267 | ** indices on table zTab. |
||
268 | ** |
||
269 | ** If successful, SQLITE_OK is returned and paIndex set to point |
||
270 | ** at the allocated array. Otherwise, an error code is returned. |
||
271 | ** |
||
272 | ** See comments associated with the member variable aIndex above |
||
273 | ** "struct echo_vtab" for details of the contents of the array. |
||
274 | */ |
||
275 | static int getIndexArray( |
||
276 | sqlite3 db, /* Database connection */ |
||
277 | string zTab, /* Name of table in database db */ |
||
278 | int nCol, |
||
279 | ref int[] paIndex |
||
280 | ) |
||
281 | { |
||
282 | sqlite3_stmt pStmt = null; |
||
283 | int[] aIndex = null; |
||
284 | int rc = 0; |
||
285 | string zSql; |
||
286 | |||
287 | /* Allocate space for the index array */ |
||
288 | aIndex = new int[nCol];//(int )sqlite3MallocZero(sizeof(int) * nCol); |
||
289 | // if( null==aIndex ){ |
||
290 | // rc = SQLITE_NOMEM; |
||
291 | // goto get_index_array_out; |
||
292 | // } |
||
293 | |||
294 | /* Compile an sqlite pragma to loop through all indices on table zTab */ |
||
295 | zSql = sqlite3_mprintf( "PRAGMA index_list(%s)", zTab ); |
||
296 | // if( null==zSql ){ |
||
297 | // rc = SQLITE_NOMEM; |
||
298 | // goto get_index_array_out; |
||
299 | // } |
||
300 | rc = sqlite3_prepare( db, zSql, -1, ref pStmt, 0 ); |
||
301 | // //sqlite3_free(zSql); |
||
302 | |||
303 | /* For each index, figure out the left-most column and set the |
||
304 | ** corresponding entry in aIndex[] to 1. |
||
305 | */ |
||
306 | while ( pStmt != null && sqlite3_step( pStmt ) == SQLITE_ROW ) |
||
307 | { |
||
308 | string zIdx = (string)sqlite3_column_text( pStmt, 1 ); |
||
309 | sqlite3_stmt pStmt2 = null; |
||
310 | zSql = sqlite3_mprintf( "PRAGMA index_info(%s)", zIdx ); |
||
311 | //if ( null == zSql ) |
||
312 | //{ |
||
313 | // rc = SQLITE_NOMEM; |
||
314 | // goto get_index_array_out; |
||
315 | //} |
||
316 | rc = sqlite3_prepare( db, zSql, -1, ref pStmt2, 0 ); |
||
317 | //sqlite3_free(zSql); |
||
318 | if ( pStmt2 != null && sqlite3_step( pStmt2 ) == SQLITE_ROW ) |
||
319 | { |
||
320 | int cid = sqlite3_column_int( pStmt2, 1 ); |
||
321 | Debug.Assert( cid >= 0 && cid < nCol ); |
||
322 | aIndex[cid] = 1; |
||
323 | } |
||
324 | if ( pStmt2 != null ) |
||
325 | { |
||
326 | rc = sqlite3_finalize( pStmt2 ); |
||
327 | } |
||
328 | if ( rc != SQLITE_OK ) |
||
329 | { |
||
330 | goto get_index_array_out; |
||
331 | } |
||
332 | } |
||
333 | |||
334 | |||
335 | get_index_array_out: |
||
336 | if ( pStmt != null ) |
||
337 | { |
||
338 | int rc2 = sqlite3_finalize( pStmt ); |
||
339 | if ( rc == SQLITE_OK ) |
||
340 | { |
||
341 | rc = rc2; |
||
342 | } |
||
343 | } |
||
344 | if ( rc != SQLITE_OK ) |
||
345 | { |
||
346 | //sqlite3_free(aIndex); |
||
347 | aIndex = null; |
||
348 | } |
||
349 | paIndex = aIndex; |
||
350 | return rc; |
||
351 | } |
||
352 | |||
353 | /* |
||
354 | ** Global Tcl variable $echo_module is a list. This routine appends |
||
355 | ** the string element zArg to that list in interpreter interp. |
||
356 | */ |
||
357 | static void appendToEchoModule( Tcl_Interp interp, string zArg ) |
||
358 | { |
||
359 | int flags = ( TCL.TCL_APPEND_VALUE | TCL.TCL_LIST_ELEMENT | TCL.TCL_GLOBAL_ONLY ); |
||
360 | TCL.Tcl_SetVar( interp, "echo_module", ( zArg != null ? zArg : "" ), flags ); |
||
361 | } |
||
362 | |||
363 | /* |
||
364 | ** This function is called from within the echo-modules xCreate and |
||
365 | ** xConnect methods. The argc and argv arguments are copies of those |
||
366 | ** passed to the calling method. This function is responsible for |
||
367 | ** calling sqlite3_declare_vtab() to declare the schema of the virtual |
||
368 | ** table being created or connected. |
||
369 | ** |
||
370 | ** If the constructor was passed just one argument, i.e.: |
||
371 | ** |
||
372 | ** CREATE TABLE t1 AS echo(t2); |
||
373 | ** |
||
374 | ** Then t2 is assumed to be the name of a *real* database table. The |
||
375 | ** schema of the virtual table is declared by passing a copy of the |
||
376 | ** CREATE TABLE statement for the real table to sqlite3_declare_vtab(). |
||
377 | ** Hence, the virtual table should have exactly the same column names and |
||
378 | ** types as the real table. |
||
379 | */ |
||
380 | static int echoDeclareVtab( |
||
381 | echo_vtab pVtab, |
||
382 | sqlite3 db |
||
383 | ) |
||
384 | { |
||
385 | int rc = SQLITE_OK; |
||
386 | |||
387 | if ( pVtab.zTableName != null ) |
||
388 | { |
||
389 | sqlite3_stmt pStmt = null; |
||
390 | rc = sqlite3_prepare( db, |
||
391 | "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?", |
||
392 | -1, ref pStmt, 0 ); |
||
393 | if ( rc == SQLITE_OK ) |
||
394 | { |
||
395 | sqlite3_bind_text( pStmt, 1, pVtab.zTableName, -1, null ); |
||
396 | if ( sqlite3_step( pStmt ) == SQLITE_ROW ) |
||
397 | { |
||
398 | int rc2; |
||
399 | string zCreateTable = (string)sqlite3_column_text( pStmt, 0 ); |
||
400 | rc = sqlite3_declare_vtab( db, zCreateTable ); |
||
401 | rc2 = sqlite3_finalize( pStmt ); |
||
402 | if ( rc == SQLITE_OK ) |
||
403 | { |
||
404 | rc = rc2; |
||
405 | } |
||
406 | } |
||
407 | else |
||
408 | { |
||
409 | rc = sqlite3_finalize( pStmt ); |
||
410 | if ( rc == SQLITE_OK ) |
||
411 | { |
||
412 | rc = SQLITE_ERROR; |
||
413 | } |
||
414 | } |
||
415 | if ( rc == SQLITE_OK ) |
||
416 | { |
||
417 | rc = getColumnNames( db, pVtab.zTableName, out pVtab.aCol, out pVtab.nCol ); |
||
418 | } |
||
419 | if ( rc == SQLITE_OK ) |
||
420 | { |
||
421 | rc = getIndexArray( db, pVtab.zTableName, pVtab.nCol, ref pVtab.aIndex ); |
||
422 | } |
||
423 | } |
||
424 | } |
||
425 | |||
426 | return rc; |
||
427 | } |
||
428 | |||
429 | /* |
||
430 | ** This function frees all runtime structures associated with the virtual |
||
431 | ** table pVtab. |
||
432 | */ |
||
433 | static int echoDestructor( ref object pVtab ) |
||
434 | { |
||
435 | //echo_vtab p = (echo_vtab)pVtab; |
||
436 | //sqlite3_free(p.aIndex); |
||
437 | //sqlite3_free(p.aCol); |
||
438 | //sqlite3_free(p.zThis); |
||
439 | //sqlite3_free(p.zTableName); |
||
440 | //sqlite3_free(p.zLogName); |
||
441 | //sqlite3_free(p); |
||
442 | pVtab = null; |
||
443 | return 0; |
||
444 | } |
||
445 | |||
446 | //typedef struct EchoModule EchoModule; |
||
447 | class EchoModule : sqlite3_vtab |
||
448 | { |
||
449 | public Tcl_Interp interp; |
||
450 | }; |
||
451 | |||
452 | /* |
||
453 | ** This function is called to do the work of the xConnect() method - |
||
454 | ** to allocate the required in-memory structures for a newly connected |
||
455 | ** virtual table. |
||
456 | */ |
||
457 | static int echoConstructor( |
||
458 | sqlite3 db, |
||
459 | object pAux, |
||
460 | int argc, string[] argv, |
||
461 | out sqlite3_vtab ppVtab, |
||
462 | out string pzErr |
||
463 | ) |
||
464 | { |
||
465 | int rc; |
||
466 | int i; |
||
467 | echo_vtab pVtab; |
||
468 | pzErr = ""; |
||
469 | |||
470 | /* Allocate the sqlite3_vtab/echo_vtab structure itself */ |
||
471 | pVtab = new echo_vtab();//sqlite3MallocZero( sizeof(*pVtab) ); |
||
472 | //if( null==pVtab ){ |
||
473 | // return SQLITE_NOMEM; |
||
474 | //} |
||
475 | pVtab.interp = ( (EchoModule)pAux ).interp; |
||
476 | pVtab.db = db; |
||
477 | |||
478 | /* Allocate echo_vtab.zThis */ |
||
479 | pVtab.zThis = sqlite3_mprintf( "%s", argv[2] ); |
||
480 | //if( null==pVtab.zThis ){ |
||
481 | // object obj = ppVtab; |
||
482 | // echoDestructor( ref obj ); |
||
483 | // obj = null; |
||
484 | // return SQLITE_NOMEM; |
||
485 | //} |
||
486 | |||
487 | /* Allocate echo_vtab.zTableName */ |
||
488 | if ( argc > 3 ) |
||
489 | { |
||
490 | pVtab.zTableName = sqlite3_mprintf( "%s", argv[3] ); |
||
491 | dequoteString( ref pVtab.zTableName ); |
||
492 | if ( !String.IsNullOrEmpty( pVtab.zTableName ) && pVtab.zTableName[0] == '*' ) |
||
493 | { |
||
494 | string z = sqlite3_mprintf( "%s%s", argv[2], ( pVtab.zTableName.Substring(1) ) ); |
||
495 | //sqlite3_free(pVtab.zTableName); |
||
496 | pVtab.zTableName = z; |
||
497 | pVtab.isPattern = 1; |
||
498 | } |
||
499 | //if( null==pVtab.zTableName ){ |
||
500 | // object obj = ppVtab; |
||
501 | // echoDestructor( ref obj ); |
||
502 | // obj = null; |
||
503 | // return SQLITE_NOMEM; |
||
504 | //} |
||
505 | } |
||
506 | |||
507 | /* Log the arguments to this function to Tcl var ::echo_module */ |
||
508 | for ( i = 0; i < argc; i++ ) |
||
509 | { |
||
510 | appendToEchoModule( pVtab.interp, argv[i] ); |
||
511 | } |
||
512 | |||
513 | /* Invoke sqlite3_declare_vtab and set up other members of the echo_vtab |
||
514 | ** structure. If an error occurs, delete the sqlite3_vtab structure and |
||
515 | ** return an error code. |
||
516 | */ |
||
517 | rc = echoDeclareVtab( pVtab, db ); |
||
518 | if ( rc != SQLITE_OK ) |
||
519 | { |
||
520 | //object obj = ppVtab; |
||
521 | //echoDestructor( ref obj ); |
||
522 | //obj = null; |
||
523 | ppVtab = null; |
||
524 | return rc; |
||
525 | } |
||
526 | |||
527 | /* Success. Set ppVtab and return */ |
||
528 | ppVtab = pVtab;//.base; |
||
529 | return SQLITE_OK; |
||
530 | } |
||
531 | |||
532 | /* |
||
533 | ** Echo virtual table module xCreate method. |
||
534 | */ |
||
535 | static int echoCreate( |
||
536 | sqlite3 db, |
||
537 | object pAux, |
||
538 | int argc, |
||
539 | string[] argv, |
||
540 | out sqlite3_vtab ppVtab, |
||
541 | out string pzErr |
||
542 | ) |
||
543 | { |
||
544 | int rc = SQLITE_OK; |
||
545 | appendToEchoModule( ( (EchoModule)pAux ).interp, "xCreate" ); |
||
546 | rc = echoConstructor( db, pAux, argc, argv, out ppVtab, out pzErr ); |
||
547 | |||
548 | /* If there were two arguments passed to the module at the SQL level |
||
549 | ** (i.e. "CREATE VIRTUAL TABLE tbl USING echo(arg1, arg2)"), then |
||
550 | ** the second argument is used as a table name. Attempt to create |
||
551 | ** such a table with a single column, "logmsg". This table will |
||
552 | ** be used to log calls to the xUpdate method. It will be deleted |
||
553 | ** when the virtual table is DROPed. |
||
554 | ** |
||
555 | ** Note: The main point of this is to test that we can drop tables |
||
556 | ** from within an xDestroy method call. |
||
557 | */ |
||
558 | if ( rc == SQLITE_OK && argc == 5 ) |
||
559 | { |
||
560 | string zSql; |
||
561 | echo_vtab pVtab = (echo_vtab)ppVtab; |
||
562 | pVtab.zLogName = sqlite3_mprintf( "%s", argv[4] ); |
||
563 | zSql = sqlite3_mprintf( "CREATE TABLE %Q(logmsg)", pVtab.zLogName ); |
||
564 | rc = sqlite3_exec( db, zSql, 0, 0, 0 ); |
||
565 | //sqlite3_free(zSql); |
||
566 | if ( rc != SQLITE_OK ) |
||
567 | { |
||
568 | pzErr = sqlite3_mprintf( "%s", sqlite3_errmsg( db ) ); |
||
569 | } |
||
570 | } |
||
571 | |||
572 | if ( ppVtab != null && rc != SQLITE_OK ) |
||
573 | { |
||
574 | //object obj = ppVtab; |
||
575 | //echoDestructor( ref obj ); |
||
576 | //obj = null; |
||
577 | ppVtab = null; |
||
578 | } |
||
579 | |||
580 | if ( rc == SQLITE_OK ) |
||
581 | { |
||
582 | ( (echo_vtab)ppVtab ).inTransaction = 1; |
||
583 | } |
||
584 | |||
585 | return rc; |
||
586 | } |
||
587 | |||
588 | /* |
||
589 | ** Echo virtual table module xConnect method. |
||
590 | */ |
||
591 | static int echoConnect( |
||
592 | sqlite3 db, |
||
593 | object pAux, |
||
594 | int argc, |
||
595 | string[] argv, |
||
596 | out sqlite3_vtab ppVtab, |
||
597 | out string pzErr |
||
598 | ) |
||
599 | { |
||
600 | appendToEchoModule( ( (EchoModule)pAux ).interp, "xConnect" ); |
||
601 | return echoConstructor( db, pAux, argc, argv, out ppVtab, out pzErr ); |
||
602 | } |
||
603 | |||
604 | /* |
||
605 | ** Echo virtual table module xDisconnect method. |
||
606 | */ |
||
607 | static int echoDisconnect( ref object pVtab ) |
||
608 | { |
||
609 | appendToEchoModule( ( (echo_vtab)pVtab ).interp, "xDisconnect" ); |
||
610 | return echoDestructor( ref pVtab ); |
||
611 | } |
||
612 | |||
613 | /* |
||
614 | ** Echo virtual table module xDestroy method. |
||
615 | */ |
||
616 | static int echoDestroy( ref object pVtab ) |
||
617 | { |
||
618 | int rc = SQLITE_OK; |
||
619 | echo_vtab p = (echo_vtab)pVtab; |
||
620 | appendToEchoModule( ( (echo_vtab)pVtab ).interp, "xDestroy" ); |
||
621 | |||
622 | /* Drop the "log" table, if one exists (see echoCreate() for details) */ |
||
623 | if ( p != null && !String.IsNullOrEmpty( p.zLogName ) ) |
||
624 | { |
||
625 | string zSql; |
||
626 | zSql = sqlite3_mprintf( "DROP TABLE %Q", p.zLogName ); |
||
627 | rc = sqlite3_exec( p.db, zSql, 0, 0, 0 ); |
||
628 | //sqlite3_free(zSql); |
||
629 | } |
||
630 | |||
631 | if ( rc == SQLITE_OK ) |
||
632 | { |
||
633 | rc = echoDestructor( ref pVtab ); |
||
634 | } |
||
635 | return rc; |
||
636 | } |
||
637 | |||
638 | /* |
||
639 | ** Echo virtual table module xOpen method. |
||
640 | */ |
||
641 | static int echoOpen( sqlite3_vtab pVTab, out sqlite3_vtab_cursor ppCursor ) |
||
642 | { |
||
643 | echo_cursor pCur; |
||
644 | if ( simulateVtabError( (echo_vtab)pVTab, "xOpen" ) != 0 ) |
||
645 | { |
||
646 | ppCursor = null; |
||
647 | return SQLITE_ERROR; |
||
648 | } |
||
649 | pCur = new echo_cursor();//sqlite3MallocZero( sizeof( echo_cursor ) ); |
||
650 | ppCursor = (sqlite3_vtab_cursor)pCur; |
||
651 | return ( pCur != null ? SQLITE_OK : SQLITE_NOMEM ); |
||
652 | } |
||
653 | |||
654 | /* |
||
655 | ** Echo virtual table module xClose method. |
||
656 | */ |
||
657 | static int echoClose( ref sqlite3_vtab_cursor cur ) |
||
658 | { |
||
659 | int rc = 0; |
||
660 | echo_cursor pCur = (echo_cursor)cur; |
||
661 | sqlite3_stmt pStmt = pCur.pStmt; |
||
662 | pCur.pStmt = null; |
||
663 | //sqlite3_free(pCur); |
||
664 | pCur = null; |
||
665 | rc = sqlite3_finalize( pStmt ); |
||
666 | return rc; |
||
667 | } |
||
668 | |||
669 | /* |
||
670 | ** Return non-zero if the cursor does not currently point to a valid record |
||
671 | ** (i.e if the scan has finished), or zero otherwise. |
||
672 | */ |
||
673 | static int echoEof( sqlite3_vtab_cursor cur ) |
||
674 | { |
||
675 | return ( ( (echo_cursor)cur ).pStmt != null ? 0 : 1 ); |
||
676 | } |
||
677 | |||
678 | /* |
||
679 | ** Echo virtual table module xNext method. |
||
680 | */ |
||
681 | static int echoNext( sqlite3_vtab_cursor cur ) |
||
682 | { |
||
683 | int rc = SQLITE_OK; |
||
684 | echo_cursor pCur = (echo_cursor)cur; |
||
685 | |||
686 | if ( simulateVtabError( (echo_vtab)( cur.pVtab ), "xNext" ) != 0 ) |
||
687 | { |
||
688 | return SQLITE_ERROR; |
||
689 | } |
||
690 | |||
691 | if ( pCur.pStmt != null ) |
||
692 | { |
||
693 | rc = sqlite3_step( pCur.pStmt ); |
||
694 | if ( rc == SQLITE_ROW ) |
||
695 | { |
||
696 | rc = SQLITE_OK; |
||
697 | } |
||
698 | else |
||
699 | { |
||
700 | rc = sqlite3_finalize( pCur.pStmt ); |
||
701 | pCur.pStmt = null; |
||
702 | } |
||
703 | } |
||
704 | |||
705 | return rc; |
||
706 | } |
||
707 | |||
708 | /* |
||
709 | ** Echo virtual table module xColumn method. |
||
710 | */ |
||
711 | static int echoColumn( sqlite3_vtab_cursor cur, sqlite3_context ctx, int i ) |
||
712 | { |
||
713 | int iCol = i + 1; |
||
714 | sqlite3_stmt pStmt = ( (echo_cursor)cur ).pStmt; |
||
715 | |||
716 | if ( simulateVtabError( (echo_vtab)( cur.pVtab ), "xColumn" ) != 0 ) |
||
717 | { |
||
718 | return SQLITE_ERROR; |
||
719 | } |
||
720 | |||
721 | if ( null == pStmt ) |
||
722 | { |
||
723 | sqlite3_result_null( ctx ); |
||
724 | } |
||
725 | else |
||
726 | { |
||
727 | Debug.Assert( sqlite3_data_count( pStmt ) > iCol ); |
||
728 | sqlite3_result_value( ctx, sqlite3_column_value( pStmt, iCol ) ); |
||
729 | } |
||
730 | return SQLITE_OK; |
||
731 | } |
||
732 | |||
733 | /* |
||
734 | ** Echo virtual table module xRowid method. |
||
735 | */ |
||
736 | static int echoRowid( sqlite3_vtab_cursor cur, out sqlite_int64 pRowid ) |
||
737 | { |
||
738 | sqlite3_stmt pStmt = ( (echo_cursor)cur ).pStmt; |
||
739 | |||
740 | if ( simulateVtabError( (echo_vtab)( cur.pVtab ), "xRowid" ) != 0 ) |
||
741 | { |
||
742 | pRowid = 0; |
||
743 | return SQLITE_ERROR; |
||
744 | } |
||
745 | |||
746 | pRowid = sqlite3_column_int64( pStmt, 0 ); |
||
747 | return SQLITE_OK; |
||
748 | } |
||
749 | |||
750 | /* |
||
751 | ** Compute a simple hash of the null terminated string zString. |
||
752 | ** |
||
753 | ** This module uses only sqlite3_index_info.idxStr, not |
||
754 | ** sqlite3_index_info.idxNum. So to test idxNum, when idxStr is set |
||
755 | ** in echoBestIndex(), idxNum is set to the corresponding hash value. |
||
756 | ** In echoFilter(), code Debug.Assert()s that the supplied idxNum value is |
||
757 | ** indeed the hash of the supplied idxStr. |
||
758 | */ |
||
759 | static int hashString( string zString ) |
||
760 | { |
||
761 | int val = 0; |
||
762 | int ii; |
||
763 | for ( ii = 0; ii < zString.Length; ii++ ) |
||
764 | { |
||
765 | val = ( val << 3 ) + (int)zString[ii]; |
||
766 | } |
||
767 | return val; |
||
768 | } |
||
769 | |||
770 | /* |
||
771 | ** Echo virtual table module xFilter method. |
||
772 | */ |
||
773 | static int echoFilter( |
||
774 | sqlite3_vtab_cursor pVtabCursor, |
||
775 | int idxNum, string idxStr, |
||
776 | int argc, sqlite3_value[] argv |
||
777 | ) |
||
778 | { |
||
779 | int rc; |
||
780 | int i; |
||
781 | |||
782 | echo_cursor pCur = (echo_cursor)pVtabCursor; |
||
783 | echo_vtab pVtab = (echo_vtab)pVtabCursor.pVtab; |
||
784 | sqlite3 db = pVtab.db; |
||
785 | |||
786 | if ( simulateVtabError( pVtab, "xFilter" ) != 0 ) |
||
787 | { |
||
788 | return SQLITE_ERROR; |
||
789 | } |
||
790 | |||
791 | /* Check that idxNum matches idxStr */ |
||
792 | Debug.Assert( idxNum == hashString( idxStr ) ); |
||
793 | |||
794 | /* Log arguments to the ::echo_module Tcl variable */ |
||
795 | appendToEchoModule( pVtab.interp, "xFilter" ); |
||
796 | appendToEchoModule( pVtab.interp, idxStr ); |
||
797 | for ( i = 0; i < argc; i++ ) |
||
798 | { |
||
799 | appendToEchoModule( pVtab.interp, sqlite3_value_text( argv[i] ) ); |
||
800 | } |
||
801 | |||
802 | sqlite3_finalize( pCur.pStmt ); |
||
803 | pCur.pStmt = null; |
||
804 | |||
805 | /* Prepare the SQL statement created by echoBestIndex and bind the |
||
806 | ** runtime parameters passed to this function to it. |
||
807 | */ |
||
808 | rc = sqlite3_prepare( db, idxStr, -1, ref pCur.pStmt, 0 ); |
||
809 | Debug.Assert( pCur.pStmt != null || rc != SQLITE_OK ); |
||
810 | for ( i = 0; rc == SQLITE_OK && i < argc; i++ ) |
||
811 | { |
||
812 | rc = sqlite3_bind_value( pCur.pStmt, i + 1, argv[i] ); |
||
813 | } |
||
814 | |||
815 | /* If everything was successful, advance to the first row of the scan */ |
||
816 | if ( rc == SQLITE_OK ) |
||
817 | { |
||
818 | rc = echoNext( pVtabCursor ); |
||
819 | } |
||
820 | |||
821 | return rc; |
||
822 | } |
||
823 | |||
824 | |||
825 | /* |
||
826 | ** A helper function used by echoUpdate() and echoBestIndex() for |
||
827 | ** manipulating strings in concert with the sqlite3_mprintf() function. |
||
828 | ** |
||
829 | ** Parameter pzStr points to a pointer to a string allocated with |
||
830 | ** sqlite3_mprintf. The second parameter, zAppend, points to another |
||
831 | ** string. The two strings are concatenated together and pzStr |
||
832 | ** set to point at the result. The initial buffer pointed to by pzStr |
||
833 | ** is deallocated via sqlite3_free(). |
||
834 | ** |
||
835 | ** If the third argument, doFree, is true, then sqlite3_free() is |
||
836 | ** also called to free the buffer pointed to by zAppend. |
||
837 | */ |
||
838 | static void string_concat( ref string pzStr, string zAppend, int doFree, ref int pRc ) |
||
839 | { |
||
840 | string zIn = pzStr; |
||
841 | if ( null == zAppend && doFree != 0 && pRc == SQLITE_OK ) |
||
842 | { |
||
843 | pRc = SQLITE_NOMEM; |
||
844 | } |
||
845 | if ( pRc != SQLITE_OK ) |
||
846 | { |
||
847 | //sqlite3_free(zIn); |
||
848 | zIn = null; |
||
849 | } |
||
850 | else |
||
851 | { |
||
852 | if ( zIn != null ) |
||
853 | { |
||
854 | string zTemp = zIn; |
||
855 | zIn = sqlite3_mprintf( "%s%s", zIn, zAppend ); |
||
856 | //sqlite3_free(zTemp); |
||
857 | } |
||
858 | else |
||
859 | { |
||
860 | zIn = sqlite3_mprintf( "%s", zAppend ); |
||
861 | } |
||
862 | //if ( null == zIn ) |
||
863 | //{ |
||
864 | // pRc = SQLITE_NOMEM; |
||
865 | //} |
||
866 | } |
||
867 | pzStr = zIn; |
||
868 | //if( doFree ){ |
||
869 | // sqlite3_free(zAppend); |
||
870 | //} |
||
871 | } |
||
872 | |||
873 | /* |
||
874 | ** The echo module implements the subset of query constraints and sort |
||
875 | ** orders that may take advantage of SQLite indices on the underlying |
||
876 | ** real table. For example, if the real table is declared as: |
||
877 | ** |
||
878 | ** CREATE TABLE real(a, b, c); |
||
879 | ** CREATE INDEX real_index ON real(b); |
||
880 | ** |
||
881 | ** then the echo module handles WHERE or ORDER BY clauses that refer |
||
882 | ** to the column "b", but not "a" or "c". If a multi-column index is |
||
883 | ** present, only its left most column is considered. |
||
884 | ** |
||
885 | ** This xBestIndex method encodes the proposed search strategy as |
||
886 | ** an SQL query on the real table underlying the virtual echo module |
||
887 | ** table and stores the query in sqlite3_index_info.idxStr. The SQL |
||
888 | ** statement is of the form: |
||
889 | ** |
||
890 | ** SELECT rowid, * FROM <real-table> ?<where-clause>? ?<order-by-clause>? |
||
891 | ** |
||
892 | ** where the <where-clause> and <order-by-clause> are determined |
||
893 | ** by the contents of the structure pointed to by the pIdxInfo argument. |
||
894 | */ |
||
895 | static int echoBestIndex( sqlite3_vtab vtab, ref sqlite3_index_info pIdxInfo ) |
||
896 | { |
||
897 | int ii; |
||
898 | string zQuery = ""; |
||
899 | string zNew; |
||
900 | int nArg = 0; |
||
901 | string zSep = "WHERE"; |
||
902 | echo_vtab pVtab = (echo_vtab)vtab; |
||
903 | sqlite3_stmt pStmt = null; |
||
904 | Tcl_Interp interp = pVtab.interp; |
||
905 | |||
906 | int nRow = 0; |
||
907 | int useIdx = 0; |
||
908 | int rc = SQLITE_OK; |
||
909 | int useCost = 0; |
||
910 | double cost = 0; |
||
911 | int isIgnoreUsable = 0; |
||
912 | if ( TCL.Tcl_GetVar( interp, "echo_module_ignore_usable", (TCL.VarFlag)TCL.TCL_GLOBAL_ONLY ).ToString() != "" ) |
||
913 | { |
||
914 | isIgnoreUsable = 1; |
||
915 | } |
||
916 | |||
917 | if ( simulateVtabError( pVtab, "xBestIndex" ) != 0 ) |
||
918 | { |
||
919 | return SQLITE_ERROR; |
||
920 | } |
||
921 | |||
922 | /* Determine the number of rows in the table and store this value in local |
||
923 | ** variable nRow. The 'estimated-cost' of the scan will be the number of |
||
924 | ** rows in the table for a linear scan, or the log (base 2) of the |
||
925 | ** number of rows if the proposed scan uses an index. |
||
926 | */ |
||
927 | if ( TCL.Tcl_GetVar( interp, "echo_module_cost", (TCL.VarFlag)TCL.TCL_GLOBAL_ONLY ).ToString() != "" ) |
||
928 | { |
||
929 | Double.TryParse( TCL.Tcl_GetVar( interp, "echo_module_cost", (TCL.VarFlag)TCL.TCL_GLOBAL_ONLY ).ToString(), out cost ); |
||
930 | useCost = 1; |
||
931 | } |
||
932 | else |
||
933 | { |
||
934 | zQuery = sqlite3_mprintf( "SELECT count() FROM %Q", pVtab.zTableName ); |
||
935 | //if ( null == zQuery ) |
||
936 | //{ |
||
937 | // return SQLITE_NOMEM; |
||
938 | //} |
||
939 | rc = sqlite3_prepare( pVtab.db, zQuery, -1, ref pStmt, 0 ); |
||
940 | //sqlite3_free(zQuery); |
||
941 | if ( rc != SQLITE_OK ) |
||
942 | { |
||
943 | return rc; |
||
944 | } |
||
945 | sqlite3_step( pStmt ); |
||
946 | nRow = sqlite3_column_int( pStmt, 0 ); |
||
947 | rc = sqlite3_finalize( pStmt ); |
||
948 | if ( rc != SQLITE_OK ) |
||
949 | { |
||
950 | return rc; |
||
951 | } |
||
952 | } |
||
953 | |||
954 | zQuery = sqlite3_mprintf( "SELECT rowid, * FROM %Q", pVtab.zTableName ); |
||
955 | //if ( null == zQuery ) |
||
956 | //{ |
||
957 | // return SQLITE_NOMEM; |
||
958 | //} |
||
959 | for ( ii = 0; ii < pIdxInfo.nConstraint; ii++ ) |
||
960 | { |
||
961 | sqlite3_index_constraint pConstraint; |
||
962 | sqlite3_index_constraint_usage pUsage; |
||
963 | int iCol; |
||
964 | |||
965 | pConstraint = pIdxInfo.aConstraint[ii]; |
||
966 | pUsage = pIdxInfo.aConstraintUsage[ii]; |
||
967 | |||
968 | if ( 0 == isIgnoreUsable && !pConstraint.usable ) |
||
969 | continue; |
||
970 | |||
971 | iCol = pConstraint.iColumn; |
||
972 | if ( iCol < 0 || pVtab.aIndex[iCol] != 0 ) |
||
973 | { |
||
974 | string zCol; |
||
975 | string zOp = ""; |
||
976 | useIdx = 1; |
||
977 | if ( iCol >= 0 ) |
||
978 | { |
||
979 | zCol = pVtab.aCol[iCol]; |
||
980 | } |
||
981 | else |
||
982 | { |
||
983 | zCol = "rowid"; |
||
984 | } |
||
985 | switch ( pConstraint.op ) |
||
986 | { |
||
987 | case SQLITE_INDEX_CONSTRAINT_EQ: |
||
988 | zOp = "="; |
||
989 | break; |
||
990 | case SQLITE_INDEX_CONSTRAINT_LT: |
||
991 | zOp = "<"; |
||
992 | break; |
||
993 | case SQLITE_INDEX_CONSTRAINT_GT: |
||
994 | zOp = ">"; |
||
995 | break; |
||
996 | case SQLITE_INDEX_CONSTRAINT_LE: |
||
997 | zOp = "<="; |
||
998 | break; |
||
999 | case SQLITE_INDEX_CONSTRAINT_GE: |
||
1000 | zOp = ">="; |
||
1001 | break; |
||
1002 | case SQLITE_INDEX_CONSTRAINT_MATCH: |
||
1003 | zOp = "LIKE"; |
||
1004 | break; |
||
1005 | } |
||
1006 | if ( zOp[0] == 'L' ) |
||
1007 | { |
||
1008 | zNew = sqlite3_mprintf( " %s %s LIKE (SELECT '%%'||?||'%%')", |
||
1009 | zSep, zCol ); |
||
1010 | } |
||
1011 | else |
||
1012 | { |
||
1013 | zNew = sqlite3_mprintf( " %s %s %s ?", zSep, zCol, zOp ); |
||
1014 | } |
||
1015 | string_concat( ref zQuery, zNew, 1, ref rc ); |
||
1016 | |||
1017 | zSep = "AND"; |
||
1018 | pUsage.argvIndex = ++nArg; |
||
1019 | pUsage.omit = true; |
||
1020 | } |
||
1021 | } |
||
1022 | |||
1023 | /* If there is only one term in the ORDER BY clause, and it is |
||
1024 | ** on a column that this virtual table has an index for, then consume |
||
1025 | ** the ORDER BY clause. |
||
1026 | */ |
||
1027 | if ( pIdxInfo.nOrderBy == 1 && ( pIdxInfo.aOrderBy[0].iColumn < 0 || pVtab.aIndex[pIdxInfo.aOrderBy[0].iColumn] != 0 ) ) |
||
1028 | { |
||
1029 | int iCol = pIdxInfo.aOrderBy[0].iColumn; |
||
1030 | string zCol; |
||
1031 | string zDir = pIdxInfo.aOrderBy[0].desc ? "DESC" : "ASC"; |
||
1032 | if ( iCol >= 0 ) |
||
1033 | { |
||
1034 | zCol = pVtab.aCol[iCol]; |
||
1035 | } |
||
1036 | else |
||
1037 | { |
||
1038 | zCol = "rowid"; |
||
1039 | } |
||
1040 | zNew = sqlite3_mprintf( " ORDER BY %s %s", zCol, zDir ); |
||
1041 | string_concat( ref zQuery, zNew, 1, ref rc ); |
||
1042 | pIdxInfo.orderByConsumed = true; |
||
1043 | } |
||
1044 | |||
1045 | appendToEchoModule( pVtab.interp, "xBestIndex" ); |
||
1046 | ; |
||
1047 | appendToEchoModule( pVtab.interp, zQuery ); |
||
1048 | |||
1049 | if ( null == zQuery ) |
||
1050 | { |
||
1051 | return rc; |
||
1052 | } |
||
1053 | pIdxInfo.idxNum = hashString( zQuery ); |
||
1054 | pIdxInfo.idxStr = zQuery; |
||
1055 | pIdxInfo.needToFreeIdxStr = 1; |
||
1056 | if ( useCost != 0 ) |
||
1057 | { |
||
1058 | pIdxInfo.estimatedCost = cost; |
||
1059 | } |
||
1060 | else if ( useIdx != 0 ) |
||
1061 | { |
||
1062 | /* Approximation of log2(nRow). */ |
||
1063 | for ( ii = 0; ii < ( sizeof( int ) * 8 ); ii++ ) |
||
1064 | { |
||
1065 | if ( ( nRow & ( 1 << ii ) ) != 0 ) |
||
1066 | { |
||
1067 | pIdxInfo.estimatedCost = (double)ii; |
||
1068 | } |
||
1069 | } |
||
1070 | } |
||
1071 | else |
||
1072 | { |
||
1073 | pIdxInfo.estimatedCost = (double)nRow; |
||
1074 | } |
||
1075 | return rc; |
||
1076 | } |
||
1077 | |||
1078 | /* |
||
1079 | ** The xUpdate method for echo module virtual tables. |
||
1080 | ** |
||
1081 | ** apData[0] apData[1] apData[2..] |
||
1082 | ** |
||
1083 | ** INTEGER DELETE |
||
1084 | ** |
||
1085 | ** INTEGER NULL (nCol args) UPDATE (do not set rowid) |
||
1086 | ** INTEGER INTEGER (nCol args) UPDATE (with SET rowid = <arg1>) |
||
1087 | ** |
||
1088 | ** NULL NULL (nCol args) INSERT INTO (automatic rowid value) |
||
1089 | ** NULL INTEGER (nCol args) INSERT (incl. rowid value) |
||
1090 | ** |
||
1091 | */ |
||
1092 | static int echoUpdate( |
||
1093 | sqlite3_vtab vtab, |
||
1094 | int nData, |
||
1095 | sqlite3_value[] apData, |
||
1096 | out sqlite_int64 pRowid |
||
1097 | ) |
||
1098 | { |
||
1099 | echo_vtab pVtab = (echo_vtab)vtab; |
||
1100 | sqlite3 db = pVtab.db; |
||
1101 | int rc = SQLITE_OK; |
||
1102 | |||
1103 | pRowid = 0; |
||
1104 | sqlite3_stmt pStmt = null; |
||
1105 | string z = ""; /* SQL statement to execute */ |
||
1106 | int bindArgZero = 0; /* True to bind apData[0] to sql var no. nData */ |
||
1107 | int bindArgOne = 0; /* True to bind apData[1] to sql var no. 1 */ |
||
1108 | int i; /* Counter variable used by for loops */ |
||
1109 | |||
1110 | Debug.Assert( nData == pVtab.nCol + 2 || nData == 1 ); |
||
1111 | |||
1112 | /* Ticket #3083 - make sure we always start a transaction prior to |
||
1113 | ** making any changes to a virtual table */ |
||
1114 | Debug.Assert( pVtab.inTransaction != 0 ); |
||
1115 | |||
1116 | if ( simulateVtabError( pVtab, "xUpdate" ) != 0 ) |
||
1117 | { |
||
1118 | return SQLITE_ERROR; |
||
1119 | } |
||
1120 | |||
1121 | /* If apData[0] is an integer and nData>1 then do an UPDATE */ |
||
1122 | if ( nData > 1 && sqlite3_value_type( apData[0] ) == SQLITE_INTEGER ) |
||
1123 | { |
||
1124 | string zSep = " SET"; |
||
1125 | z = sqlite3_mprintf( "UPDATE %Q", pVtab.zTableName ); |
||
1126 | //if ( null == z ) |
||
1127 | //{ |
||
1128 | // rc = SQLITE_NOMEM; |
||
1129 | //} |
||
1130 | |||
1131 | bindArgOne = ( apData[1] != null && sqlite3_value_type( apData[1] ) == SQLITE_INTEGER )?1:0; |
||
1132 | bindArgZero = 1; |
||
1133 | |||
1134 | if ( bindArgOne != 0 ) |
||
1135 | { |
||
1136 | string_concat( ref z, " SET rowid=?1 ", 0, ref rc ); |
||
1137 | zSep = ","; |
||
1138 | } |
||
1139 | for ( i = 2; i < nData; i++ ) |
||
1140 | { |
||
1141 | if ( apData[i] == null ) |
||
1142 | continue; |
||
1143 | string_concat( ref z, sqlite3_mprintf( |
||
1144 | "%s %Q=?%d", zSep, pVtab.aCol[i - 2], i ), 1, ref rc ); |
||
1145 | zSep = ","; |
||
1146 | } |
||
1147 | string_concat( ref z, sqlite3_mprintf( " WHERE rowid=?%d", nData ), 1, ref rc ); |
||
1148 | } |
||
1149 | |||
1150 | /* If apData[0] is an integer and nData==1 then do a DELETE */ |
||
1151 | else if ( nData == 1 && sqlite3_value_type( apData[0] ) == SQLITE_INTEGER ) |
||
1152 | { |
||
1153 | z = sqlite3_mprintf( "DELETE FROM %Q WHERE rowid = ?1", pVtab.zTableName ); |
||
1154 | //if ( null == z ) |
||
1155 | //{ |
||
1156 | // rc = SQLITE_NOMEM; |
||
1157 | //} |
||
1158 | bindArgZero = 1; |
||
1159 | } |
||
1160 | |||
1161 | /* If the first argument is NULL and there are more than two args, INSERT */ |
||
1162 | else if ( nData > 2 && sqlite3_value_type( apData[0] ) == SQLITE_NULL ) |
||
1163 | { |
||
1164 | int ii; |
||
1165 | string zInsert = ""; |
||
1166 | string zValues = ""; |
||
1167 | |||
1168 | zInsert = sqlite3_mprintf( "INSERT INTO %Q (", pVtab.zTableName ); |
||
1169 | //if ( null == zInsert ) |
||
1170 | //{ |
||
1171 | // rc = SQLITE_NOMEM; |
||
1172 | //} |
||
1173 | if ( sqlite3_value_type( apData[1] ) == SQLITE_INTEGER ) |
||
1174 | { |
||
1175 | bindArgOne = 1; |
||
1176 | zValues = sqlite3_mprintf( "?" ); |
||
1177 | string_concat( ref zInsert, "rowid", 0, ref rc ); |
||
1178 | } |
||
1179 | |||
1180 | Debug.Assert( ( pVtab.nCol + 2 ) == nData ); |
||
1181 | for ( ii = 2; ii < nData; ii++ ) |
||
1182 | { |
||
1183 | string_concat( ref zInsert, |
||
1184 | sqlite3_mprintf( "%s%Q", !String.IsNullOrEmpty(zValues) ? ", " : "", pVtab.aCol[ii - 2] ), 1, ref rc ); |
||
1185 | string_concat( ref zValues, |
||
1186 | sqlite3_mprintf( "%s?%d", !String.IsNullOrEmpty( zValues ) ? ", " : "", ii ), 1, ref rc ); |
||
1187 | } |
||
1188 | |||
1189 | string_concat( ref z, zInsert, 1, ref rc ); |
||
1190 | string_concat( ref z, ") VALUES(", 0, ref rc ); |
||
1191 | string_concat( ref z, zValues, 1, ref rc ); |
||
1192 | string_concat( ref z, ")", 0, ref rc ); |
||
1193 | } |
||
1194 | |||
1195 | /* Anything else is an error */ |
||
1196 | else |
||
1197 | { |
||
1198 | Debug.Assert( false ); |
||
1199 | return SQLITE_ERROR; |
||
1200 | } |
||
1201 | |||
1202 | if ( rc == SQLITE_OK ) |
||
1203 | { |
||
1204 | rc = sqlite3_prepare( db, z, -1, ref pStmt, 0 ); |
||
1205 | } |
||
1206 | Debug.Assert( rc != SQLITE_OK || pStmt != null ); |
||
1207 | //sqlite3_free(z); |
||
1208 | if ( rc == SQLITE_OK ) |
||
1209 | { |
||
1210 | if ( bindArgZero != 0 ) |
||
1211 | { |
||
1212 | sqlite3_bind_value( pStmt, nData, apData[0] ); |
||
1213 | } |
||
1214 | if ( bindArgOne != 0 ) |
||
1215 | { |
||
1216 | sqlite3_bind_value( pStmt, 1, apData[1] ); |
||
1217 | } |
||
1218 | for ( i = 2; i < nData && rc == SQLITE_OK; i++ ) |
||
1219 | { |
||
1220 | if ( apData[i] != null ) |
||
1221 | rc = sqlite3_bind_value( pStmt, i, apData[i] ); |
||
1222 | } |
||
1223 | if ( rc == SQLITE_OK ) |
||
1224 | { |
||
1225 | sqlite3_step( pStmt ); |
||
1226 | rc = sqlite3_finalize( pStmt ); |
||
1227 | } |
||
1228 | else |
||
1229 | { |
||
1230 | sqlite3_finalize( pStmt ); |
||
1231 | } |
||
1232 | } |
||
1233 | |||
1234 | if (/* pRowid != 0 && */ rc == SQLITE_OK ) |
||
1235 | { |
||
1236 | pRowid = sqlite3_last_insert_rowid( db ); |
||
1237 | } |
||
1238 | if ( rc != SQLITE_OK ) |
||
1239 | { |
||
1240 | vtab.zErrMsg = sqlite3_mprintf( "echo-vtab-error: %s", sqlite3_errmsg( db ) ); |
||
1241 | } |
||
1242 | |||
1243 | return rc; |
||
1244 | } |
||
1245 | |||
1246 | /* |
||
1247 | ** xBegin, xSync, xCommit and xRollback callbacks for echo module |
||
1248 | ** virtual tables. Do nothing other than add the name of the callback |
||
1249 | ** to the $::echo_module Tcl variable. |
||
1250 | */ |
||
1251 | static int echoTransactionCall( sqlite3_vtab vtab, string zCall ) |
||
1252 | { |
||
1253 | string z; |
||
1254 | echo_vtab pVtab = (echo_vtab)vtab; |
||
1255 | z = sqlite3_mprintf( "echo(%s)", pVtab.zTableName ); |
||
1256 | //if( z==null ) return SQLITE_NOMEM; |
||
1257 | appendToEchoModule( pVtab.interp, zCall ); |
||
1258 | appendToEchoModule( pVtab.interp, z ); |
||
1259 | //sqlite3_free(z); |
||
1260 | return SQLITE_OK; |
||
1261 | } |
||
1262 | static int echoBegin( sqlite3_vtab vtab ) |
||
1263 | { |
||
1264 | int rc; |
||
1265 | echo_vtab pVtab = (echo_vtab)vtab; |
||
1266 | Tcl_Interp interp = pVtab.interp; |
||
1267 | string zVal; |
||
1268 | |||
1269 | /* Ticket #3083 - do not start a transaction if we are already in |
||
1270 | ** a transaction */ |
||
1271 | Debug.Assert( 0 == pVtab.inTransaction ); |
||
1272 | |||
1273 | if ( simulateVtabError( pVtab, "xBegin" ) != 0 ) |
||
1274 | { |
||
1275 | return SQLITE_ERROR; |
||
1276 | } |
||
1277 | |||
1278 | rc = echoTransactionCall( vtab, "xBegin" ); |
||
1279 | |||
1280 | if ( rc == SQLITE_OK ) |
||
1281 | { |
||
1282 | /* Check if the $::echo_module_begin_fail variable is defined. If it is, |
||
1283 | ** and it is set to the name of the real table underlying this virtual |
||
1284 | ** echo module table, then cause this xSync operation to fail. |
||
1285 | */ |
||
1286 | zVal = TCL.Tcl_GetVar( interp, "echo_module_begin_fail", TCL.VarFlag.GLOBAL_ONLY ).ToString(); |
||
1287 | if ( zVal != null && 0 == zVal.CompareTo( pVtab.zTableName ) ) |
||
1288 | { |
||
1289 | rc = SQLITE_ERROR; |
||
1290 | } |
||
1291 | } |
||
1292 | if ( rc == SQLITE_OK ) |
||
1293 | { |
||
1294 | pVtab.inTransaction = 1; |
||
1295 | } |
||
1296 | return rc; |
||
1297 | } |
||
1298 | static int echoSync( sqlite3_vtab vtab ) |
||
1299 | { |
||
1300 | int rc; |
||
1301 | echo_vtab pVtab = (echo_vtab)vtab; |
||
1302 | Tcl_Interp interp = pVtab.interp; |
||
1303 | string zVal; |
||
1304 | |||
1305 | /* Ticket #3083 - Only call xSync if we have previously started a |
||
1306 | ** transaction */ |
||
1307 | Debug.Assert( pVtab.inTransaction != 0 ); |
||
1308 | |||
1309 | if ( simulateVtabError( pVtab, "xSync" ) != 0 ) |
||
1310 | { |
||
1311 | return SQLITE_ERROR; |
||
1312 | } |
||
1313 | |||
1314 | rc = echoTransactionCall( vtab, "xSync" ); |
||
1315 | |||
1316 | if ( rc == SQLITE_OK ) |
||
1317 | { |
||
1318 | /* Check if the $::echo_module_sync_fail variable is defined. If it is, |
||
1319 | ** and it is set to the name of the real table underlying this virtual |
||
1320 | ** echo module table, then cause this xSync operation to fail. |
||
1321 | */ |
||
1322 | zVal = TCL.Tcl_GetVar( interp, "echo_module_sync_fail", TCL.VarFlag.GLOBAL_ONLY ).ToString(); |
||
1323 | if ( zVal != "" && 0 == zVal.CompareTo( pVtab.zTableName ) ) |
||
1324 | { |
||
1325 | rc = -1; |
||
1326 | } |
||
1327 | } |
||
1328 | return rc; |
||
1329 | } |
||
1330 | static int echoCommit( sqlite3_vtab vtab ) |
||
1331 | { |
||
1332 | echo_vtab pVtab = (echo_vtab)vtab; |
||
1333 | int rc; |
||
1334 | |||
1335 | /* Ticket #3083 - Only call xCommit if we have previously started |
||
1336 | ** a transaction */ |
||
1337 | Debug.Assert( pVtab.inTransaction != 0 ); |
||
1338 | |||
1339 | if ( simulateVtabError( pVtab, "xCommit" ) != 0 ) |
||
1340 | { |
||
1341 | return SQLITE_ERROR; |
||
1342 | } |
||
1343 | |||
1344 | sqlite3BeginBenignMalloc(); |
||
1345 | rc = echoTransactionCall( vtab, "xCommit" ); |
||
1346 | sqlite3EndBenignMalloc(); |
||
1347 | pVtab.inTransaction = 0; |
||
1348 | return rc; |
||
1349 | } |
||
1350 | static int echoRollback( sqlite3_vtab vtab ) |
||
1351 | { |
||
1352 | int rc; |
||
1353 | echo_vtab pVtab = (echo_vtab)vtab; |
||
1354 | |||
1355 | /* Ticket #3083 - Only call xRollback if we have previously started |
||
1356 | ** a transaction */ |
||
1357 | Debug.Assert( pVtab.inTransaction != 0 ); |
||
1358 | |||
1359 | rc = echoTransactionCall( vtab, "xRollback" ); |
||
1360 | pVtab.inTransaction = 0; |
||
1361 | return rc; |
||
1362 | } |
||
1363 | |||
1364 | /* |
||
1365 | ** Implementation of "GLOB" function on the echo module. Pass |
||
1366 | ** all arguments to the ::echo_glob_overload procedure of TCL |
||
1367 | ** and return the result of that procedure as a string. |
||
1368 | */ |
||
1369 | static void overloadedGlobFunction( |
||
1370 | sqlite3_context pContext, |
||
1371 | int nArg, |
||
1372 | sqlite3_value[] apArg |
||
1373 | ) |
||
1374 | { |
||
1375 | Tcl_Interp interp = (Interp)sqlite3_user_data( pContext ); |
||
1376 | TclObject str = null; |
||
1377 | int i; |
||
1378 | int rc; |
||
1379 | TCL.Tcl_DStringInit( out str ); |
||
1380 | TCL.Tcl_DStringAppendElement( str, "::echo_glob_overload" ); |
||
1381 | for ( i = 0; i < nArg; i++ ) |
||
1382 | { |
||
1383 | TCL.Tcl_DStringAppendElement( str, " " + sqlite3_value_text( apArg[i] ) ); |
||
1384 | } |
||
1385 | |||
1386 | rc = TCL.Tcl_EvalObjEx( interp, str, 0 );// rc = TCL.Tcl_Eval( interp, TCL.Tcl_DStringValue( ref str ) ); |
||
1387 | TCL.Tcl_DStringFree( ref str ); |
||
1388 | if ( rc != 0 ) |
||
1389 | { |
||
1390 | sqlite3_result_error( pContext, TCL.Tcl_GetStringResult( interp ), -1 ); |
||
1391 | } |
||
1392 | else |
||
1393 | { |
||
1394 | sqlite3_result_text( pContext, TCL.Tcl_GetStringResult( interp ), |
||
1395 | -1, SQLITE_TRANSIENT ); |
||
1396 | } |
||
1397 | TCL.Tcl_ResetResult( interp ); |
||
1398 | } |
||
1399 | |||
1400 | /* |
||
1401 | ** This is the xFindFunction implementation for the echo module. |
||
1402 | ** SQLite calls this routine when the first argument of a function |
||
1403 | ** is a column of an echo virtual table. This routine can optionally |
||
1404 | ** override the implementation of that function. It will choose to |
||
1405 | ** do so if the function is named "glob", and a TCL command named |
||
1406 | ** ::echo_glob_overload exists. |
||
1407 | */ |
||
1408 | static int echoFindFunction( |
||
1409 | sqlite3_vtab vtab, |
||
1410 | int nArg, |
||
1411 | string zFuncName, |
||
1412 | ref dxFunc pxFunc, //void (**pxFunc)(sqlite3_context*,int,sqlite3_value), |
||
1413 | ref object ppArg |
||
1414 | ) |
||
1415 | { |
||
1416 | echo_vtab pVtab = (echo_vtab)vtab; |
||
1417 | Tcl_Interp interp = pVtab.interp; |
||
1418 | Tcl_CmdInfo info; |
||
1419 | if ( !zFuncName.StartsWith( "glob", StringComparison.InvariantCultureIgnoreCase ) ) |
||
1420 | { |
||
1421 | return 0; |
||
1422 | } |
||
1423 | TCL.Tcl_GetCommandInfo( interp, "::echo_glob_overload", out info ); |
||
1424 | if ( info ==null) |
||
1425 | { |
||
1426 | return 0; |
||
1427 | } |
||
1428 | pxFunc = overloadedGlobFunction; |
||
1429 | ppArg = interp; |
||
1430 | return 1; |
||
1431 | } |
||
1432 | |||
1433 | static int echoRename( sqlite3_vtab vtab, string zNewName ) |
||
1434 | { |
||
1435 | int rc = SQLITE_OK; |
||
1436 | echo_vtab p = (echo_vtab)vtab; |
||
1437 | |||
1438 | if ( simulateVtabError( p, "xRename" ) != 0 ) |
||
1439 | { |
||
1440 | return SQLITE_ERROR; |
||
1441 | } |
||
1442 | |||
1443 | if ( p.isPattern != 0 ) |
||
1444 | { |
||
1445 | int nThis = p.zThis.Length; |
||
1446 | string zSql = sqlite3_mprintf( "ALTER TABLE %s RENAME TO %s%s", |
||
1447 | p.zTableName, zNewName, p.zTableName.Substring(nThis) |
||
1448 | ); |
||
1449 | rc = sqlite3_exec( p.db, zSql, 0, 0, 0 ); |
||
1450 | //sqlite3_free( zSql ); |
||
1451 | } |
||
1452 | |||
1453 | return rc; |
||
1454 | } |
||
1455 | |||
1456 | /* |
||
1457 | ** A virtual table module that merely "echos" the contents of another |
||
1458 | ** table (like an SQL VIEW). |
||
1459 | */ |
||
1460 | static sqlite3_module echoModule = new sqlite3_module( |
||
1461 | 0, /* iVersion */ |
||
1462 | echoCreate, |
||
1463 | echoConnect, |
||
1464 | echoBestIndex, |
||
1465 | echoDisconnect, |
||
1466 | echoDestroy, |
||
1467 | echoOpen, /* xOpen - open a cursor */ |
||
1468 | echoClose, /* xClose - close a cursor */ |
||
1469 | echoFilter, /* xFilter - configure scan constraints */ |
||
1470 | echoNext, /* xNext - advance a cursor */ |
||
1471 | echoEof, /* xEof */ |
||
1472 | echoColumn, /* xColumn - read data */ |
||
1473 | echoRowid, /* xRowid - read data */ |
||
1474 | echoUpdate, /* xUpdate - write data */ |
||
1475 | echoBegin, /* xBegin - begin transaction */ |
||
1476 | echoSync, /* xSync - sync transaction */ |
||
1477 | echoCommit, /* xCommit - commit transaction */ |
||
1478 | echoRollback, /* xRollback - rollback transaction */ |
||
1479 | echoFindFunction, /* xFindFunction - function overloading */ |
||
1480 | echoRename /* xRename - rename the table */ |
||
1481 | ); |
||
1482 | |||
1483 | /* |
||
1484 | ** Decode a pointer to an sqlite3 object. |
||
1485 | */ |
||
1486 | //extern int getDbPointer(Tcl_Interp interp, string zA, sqlite3 ppDb); |
||
1487 | |||
1488 | static int moduleDestroy( ref object p ) |
||
1489 | { |
||
1490 | p = null;// sqlite3_free( p ); |
||
1491 | return SQLITE_OK; |
||
1492 | } |
||
1493 | |||
1494 | /* |
||
1495 | ** Register the echo virtual table module. |
||
1496 | */ |
||
1497 | static int register_echo_module( |
||
1498 | ClientData clientData, /* Pointer to sqlite3_enable_XXX function */ |
||
1499 | Tcl_Interp interp, /* The TCL interpreter that invoked this command */ |
||
1500 | int objc, /* Number of arguments */ |
||
1501 | Tcl_Obj[] objv /* Command arguments */ |
||
1502 | ) |
||
1503 | { |
||
1504 | sqlite3 db = null; |
||
1505 | ; |
||
1506 | ; |
||
1507 | EchoModule pMod; |
||
1508 | if ( objc != 2 ) |
||
1509 | { |
||
1510 | TCL.Tcl_WrongNumArgs( interp, 1, objv, "DB" ); |
||
1511 | return TCL.TCL_ERROR; |
||
1512 | } |
||
1513 | if ( getDbPointer( interp, TCL.Tcl_GetString( objv[1] ), out db ) != 0 ) |
||
1514 | return TCL.TCL_ERROR; |
||
1515 | pMod = new EchoModule();//sqlite3_malloc(sizeof(EchoModule)); |
||
1516 | pMod.interp = interp; |
||
1517 | sqlite3_create_module_v2( db, "echo", echoModule, pMod, moduleDestroy ); |
||
1518 | return TCL.TCL_OK; |
||
1519 | } |
||
1520 | |||
1521 | /* |
||
1522 | ** Tcl interface to sqlite3_declare_vtab, invoked as follows from Tcl: |
||
1523 | ** |
||
1524 | ** sqlite3_declare_vtab DB SQL |
||
1525 | */ |
||
1526 | static int declare_vtab( |
||
1527 | ClientData clientData, /* Pointer to sqlite3_enable_XXX function */ |
||
1528 | Tcl_Interp interp, /* The TCL interpreter that invoked this command */ |
||
1529 | int objc, /* Number of arguments */ |
||
1530 | Tcl_Obj[] objv /* Command arguments */ |
||
1531 | ) |
||
1532 | { |
||
1533 | sqlite3 db = null; |
||
1534 | int rc; |
||
1535 | if ( objc != 3 ) |
||
1536 | { |
||
1537 | TCL.Tcl_WrongNumArgs( interp, 1, objv, "DB SQL" ); |
||
1538 | return TCL.TCL_ERROR; |
||
1539 | } |
||
1540 | if ( getDbPointer( interp, TCL.Tcl_GetString( objv[1] ), out db ) != 0 ) |
||
1541 | return TCL.TCL_ERROR; |
||
1542 | rc = sqlite3_declare_vtab( db, TCL.Tcl_GetString( objv[2] ) ); |
||
1543 | if ( rc != SQLITE_OK ) |
||
1544 | { |
||
1545 | TCL.Tcl_SetResult( interp, sqlite3_errmsg( db ), TCL.TCL_VOLATILE ); |
||
1546 | return TCL.TCL_ERROR; |
||
1547 | } |
||
1548 | return TCL.TCL_OK; |
||
1549 | } |
||
1550 | |||
1551 | #endif //* ifndef SQLITE_OMIT_VIRTUALTABLE */ |
||
1552 | |||
1553 | /* |
||
1554 | ** Register commands with the TCL interpreter. |
||
1555 | */ |
||
1556 | static public int Sqlitetest8_Init( Tcl_Interp interp ) |
||
1557 | { |
||
1558 | #if !SQLITE_OMIT_VIRTUALTABLE |
||
1559 | //static struct { |
||
1560 | // string zName; |
||
1561 | // Tcl_ObjCmdProc *xProc; |
||
1562 | // void *clientData; |
||
1563 | //} |
||
1564 | _aObjCmd[] aObjCmd = new _aObjCmd[]{ |
||
1565 | new _aObjCmd( "register_echo_module", register_echo_module, 0 ), |
||
1566 | new _aObjCmd( "sqlite3_declare_vtab", declare_vtab, 0 ), |
||
1567 | }; |
||
1568 | int i; |
||
1569 | for ( i = 0; i < aObjCmd.Length; i++ )//sizeof(aObjCmd)/sizeof(aObjCmd[0]); i++) |
||
1570 | { |
||
1571 | TCL.Tcl_CreateObjCommand( interp, aObjCmd[i].zName, |
||
1572 | aObjCmd[i].xProc, aObjCmd[i].clientData, null ); |
||
1573 | } |
||
1574 | #endif |
||
1575 | return TCL.TCL_OK; |
||
1576 | } |
||
1577 | } |
||
1578 | #endif |
||
1579 | } |