danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 1 | /* |
| 2 | ** 2006 June 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 | ** Code for testing the virtual table interfaces. This code |
| 13 | ** is not included in the SQLite library. It is used for automated |
| 14 | ** testing of the SQLite library. |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 15 | */ |
| 16 | |
drh | 428397c | 2006-06-17 13:21:32 +0000 | [diff] [blame] | 17 | /* The code in this file defines a sqlite3 virtual-table module that |
| 18 | ** provides a read-only view of the current database schema. There is one |
| 19 | ** row in the schema table for each column in the database schema. |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 20 | */ |
| 21 | #define SCHEMA \ |
| 22 | "CREATE TABLE x(" \ |
| 23 | "database," /* Name of database (i.e. main, temp etc.) */ \ |
| 24 | "tablename," /* Name of table */ \ |
| 25 | "cid," /* Column number (from left-to-right, 0 upward) */ \ |
| 26 | "name," /* Column name */ \ |
| 27 | "type," /* Specified type (i.e. VARCHAR(32)) */ \ |
| 28 | "not_null," /* Boolean. True if NOT NULL was specified */ \ |
| 29 | "dflt_value," /* Default value for this column */ \ |
| 30 | "pk" /* True if this column is part of the primary key */ \ |
| 31 | ")" |
| 32 | |
| 33 | /* If SQLITE_TEST is defined this code is preprocessed for use as part |
| 34 | ** of the sqlite test binary "testfixture". Otherwise it is preprocessed |
| 35 | ** to be compiled into an sqlite dynamic extension. |
| 36 | */ |
| 37 | #ifdef SQLITE_TEST |
| 38 | #include "sqliteInt.h" |
| 39 | #include "tcl.h" |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 40 | #else |
| 41 | #include "sqlite3ext.h" |
| 42 | SQLITE_EXTENSION_INIT1 |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 43 | #endif |
| 44 | |
| 45 | #include <stdlib.h> |
| 46 | #include <string.h> |
| 47 | #include <assert.h> |
| 48 | |
| 49 | typedef struct schema_vtab schema_vtab; |
| 50 | typedef struct schema_cursor schema_cursor; |
| 51 | |
| 52 | /* A schema table object */ |
| 53 | struct schema_vtab { |
| 54 | sqlite3_vtab base; |
| 55 | sqlite3 *db; |
| 56 | }; |
| 57 | |
| 58 | /* A schema table cursor object */ |
| 59 | struct schema_cursor { |
| 60 | sqlite3_vtab_cursor base; |
| 61 | sqlite3_stmt *pDbList; |
| 62 | sqlite3_stmt *pTableList; |
| 63 | sqlite3_stmt *pColumnList; |
| 64 | int rowid; |
| 65 | }; |
| 66 | |
| 67 | /* |
drh | 26e4a8b | 2008-05-01 17:16:52 +0000 | [diff] [blame] | 68 | ** None of this works unless we have virtual tables. |
| 69 | */ |
| 70 | #ifndef SQLITE_OMIT_VIRTUALTABLE |
| 71 | |
| 72 | /* |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 73 | ** Table destructor for the schema module. |
| 74 | */ |
| 75 | static int schemaDestroy(sqlite3_vtab *pVtab){ |
drh | 1743575 | 2007-08-16 04:30:38 +0000 | [diff] [blame] | 76 | sqlite3_free(pVtab); |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 77 | return 0; |
| 78 | } |
| 79 | |
| 80 | /* |
| 81 | ** Table constructor for the schema module. |
| 82 | */ |
| 83 | static int schemaCreate( |
| 84 | sqlite3 *db, |
| 85 | void *pAux, |
drh | e410296 | 2006-09-11 00:34:22 +0000 | [diff] [blame] | 86 | int argc, const char *const*argv, |
drh | 4ca8aac | 2006-09-10 17:31:58 +0000 | [diff] [blame] | 87 | sqlite3_vtab **ppVtab, |
| 88 | char **pzErr |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 89 | ){ |
| 90 | int rc = SQLITE_NOMEM; |
drh | 1743575 | 2007-08-16 04:30:38 +0000 | [diff] [blame] | 91 | schema_vtab *pVtab = sqlite3_malloc(sizeof(schema_vtab)); |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 92 | if( pVtab ){ |
| 93 | memset(pVtab, 0, sizeof(schema_vtab)); |
| 94 | pVtab->db = db; |
danielk1977 | 4b2688a | 2006-06-20 11:01:07 +0000 | [diff] [blame] | 95 | #ifndef SQLITE_OMIT_VIRTUALTABLE |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 96 | rc = sqlite3_declare_vtab(db, SCHEMA); |
danielk1977 | 4b2688a | 2006-06-20 11:01:07 +0000 | [diff] [blame] | 97 | #endif |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 98 | } |
| 99 | *ppVtab = (sqlite3_vtab *)pVtab; |
| 100 | return rc; |
| 101 | } |
| 102 | |
| 103 | /* |
| 104 | ** Open a new cursor on the schema table. |
| 105 | */ |
| 106 | static int schemaOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ |
| 107 | int rc = SQLITE_NOMEM; |
| 108 | schema_cursor *pCur; |
drh | 1743575 | 2007-08-16 04:30:38 +0000 | [diff] [blame] | 109 | pCur = sqlite3_malloc(sizeof(schema_cursor)); |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 110 | if( pCur ){ |
| 111 | memset(pCur, 0, sizeof(schema_cursor)); |
| 112 | *ppCursor = (sqlite3_vtab_cursor *)pCur; |
| 113 | rc = SQLITE_OK; |
| 114 | } |
| 115 | return rc; |
| 116 | } |
| 117 | |
| 118 | /* |
| 119 | ** Close a schema table cursor. |
| 120 | */ |
| 121 | static int schemaClose(sqlite3_vtab_cursor *cur){ |
| 122 | schema_cursor *pCur = (schema_cursor *)cur; |
| 123 | sqlite3_finalize(pCur->pDbList); |
| 124 | sqlite3_finalize(pCur->pTableList); |
| 125 | sqlite3_finalize(pCur->pColumnList); |
drh | 1743575 | 2007-08-16 04:30:38 +0000 | [diff] [blame] | 126 | sqlite3_free(pCur); |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 127 | return SQLITE_OK; |
| 128 | } |
| 129 | |
| 130 | /* |
| 131 | ** Retrieve a column of data. |
| 132 | */ |
| 133 | static int schemaColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){ |
| 134 | schema_cursor *pCur = (schema_cursor *)cur; |
| 135 | switch( i ){ |
| 136 | case 0: |
| 137 | sqlite3_result_value(ctx, sqlite3_column_value(pCur->pDbList, 1)); |
| 138 | break; |
| 139 | case 1: |
| 140 | sqlite3_result_value(ctx, sqlite3_column_value(pCur->pTableList, 0)); |
| 141 | break; |
| 142 | default: |
| 143 | sqlite3_result_value(ctx, sqlite3_column_value(pCur->pColumnList, i-2)); |
| 144 | break; |
| 145 | } |
| 146 | return SQLITE_OK; |
| 147 | } |
| 148 | |
| 149 | /* |
| 150 | ** Retrieve the current rowid. |
| 151 | */ |
| 152 | static int schemaRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ |
| 153 | schema_cursor *pCur = (schema_cursor *)cur; |
| 154 | *pRowid = pCur->rowid; |
| 155 | return SQLITE_OK; |
| 156 | } |
| 157 | |
| 158 | static int finalize(sqlite3_stmt **ppStmt){ |
| 159 | int rc = sqlite3_finalize(*ppStmt); |
| 160 | *ppStmt = 0; |
| 161 | return rc; |
| 162 | } |
| 163 | |
danielk1977 | a298e90 | 2006-06-22 09:53:48 +0000 | [diff] [blame] | 164 | static int schemaEof(sqlite3_vtab_cursor *cur){ |
| 165 | schema_cursor *pCur = (schema_cursor *)cur; |
| 166 | return (pCur->pDbList ? 0 : 1); |
| 167 | } |
| 168 | |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 169 | /* |
| 170 | ** Advance the cursor to the next row. |
| 171 | */ |
| 172 | static int schemaNext(sqlite3_vtab_cursor *cur){ |
danielk1977 | 5017dc3 | 2006-06-24 09:34:22 +0000 | [diff] [blame] | 173 | int rc = SQLITE_OK; |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 174 | schema_cursor *pCur = (schema_cursor *)cur; |
| 175 | schema_vtab *pVtab = (schema_vtab *)(cur->pVtab); |
| 176 | char *zSql = 0; |
| 177 | |
| 178 | while( !pCur->pColumnList || SQLITE_ROW!=sqlite3_step(pCur->pColumnList) ){ |
danielk1977 | a298e90 | 2006-06-22 09:53:48 +0000 | [diff] [blame] | 179 | if( SQLITE_OK!=(rc = finalize(&pCur->pColumnList)) ) goto next_exit; |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 180 | |
| 181 | while( !pCur->pTableList || SQLITE_ROW!=sqlite3_step(pCur->pTableList) ){ |
danielk1977 | a298e90 | 2006-06-22 09:53:48 +0000 | [diff] [blame] | 182 | if( SQLITE_OK!=(rc = finalize(&pCur->pTableList)) ) goto next_exit; |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 183 | |
| 184 | assert(pCur->pDbList); |
| 185 | while( SQLITE_ROW!=sqlite3_step(pCur->pDbList) ){ |
danielk1977 | a298e90 | 2006-06-22 09:53:48 +0000 | [diff] [blame] | 186 | rc = finalize(&pCur->pDbList); |
| 187 | goto next_exit; |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 188 | } |
| 189 | |
| 190 | /* Set zSql to the SQL to pull the list of tables from the |
| 191 | ** sqlite_master (or sqlite_temp_master) table of the database |
| 192 | ** identfied by the row pointed to by the SQL statement pCur->pDbList |
| 193 | ** (iterating through a "PRAGMA database_list;" statement). |
| 194 | */ |
| 195 | if( sqlite3_column_int(pCur->pDbList, 0)==1 ){ |
| 196 | zSql = sqlite3_mprintf( |
| 197 | "SELECT name FROM sqlite_temp_master WHERE type='table'" |
| 198 | ); |
| 199 | }else{ |
| 200 | sqlite3_stmt *pDbList = pCur->pDbList; |
| 201 | zSql = sqlite3_mprintf( |
| 202 | "SELECT name FROM %Q.sqlite_master WHERE type='table'", |
| 203 | sqlite3_column_text(pDbList, 1) |
| 204 | ); |
| 205 | } |
| 206 | if( !zSql ){ |
| 207 | rc = SQLITE_NOMEM; |
danielk1977 | a298e90 | 2006-06-22 09:53:48 +0000 | [diff] [blame] | 208 | goto next_exit; |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 209 | } |
| 210 | |
| 211 | rc = sqlite3_prepare(pVtab->db, zSql, -1, &pCur->pTableList, 0); |
| 212 | sqlite3_free(zSql); |
danielk1977 | a298e90 | 2006-06-22 09:53:48 +0000 | [diff] [blame] | 213 | if( rc!=SQLITE_OK ) goto next_exit; |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 214 | } |
| 215 | |
| 216 | /* Set zSql to the SQL to the table_info pragma for the table currently |
| 217 | ** identified by the rows pointed to by statements pCur->pDbList and |
| 218 | ** pCur->pTableList. |
| 219 | */ |
| 220 | zSql = sqlite3_mprintf("PRAGMA %Q.table_info(%Q)", |
| 221 | sqlite3_column_text(pCur->pDbList, 1), |
| 222 | sqlite3_column_text(pCur->pTableList, 0) |
| 223 | ); |
| 224 | |
| 225 | if( !zSql ){ |
| 226 | rc = SQLITE_NOMEM; |
danielk1977 | a298e90 | 2006-06-22 09:53:48 +0000 | [diff] [blame] | 227 | goto next_exit; |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 228 | } |
| 229 | rc = sqlite3_prepare(pVtab->db, zSql, -1, &pCur->pColumnList, 0); |
| 230 | sqlite3_free(zSql); |
danielk1977 | a298e90 | 2006-06-22 09:53:48 +0000 | [diff] [blame] | 231 | if( rc!=SQLITE_OK ) goto next_exit; |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 232 | } |
| 233 | pCur->rowid++; |
| 234 | |
danielk1977 | a298e90 | 2006-06-22 09:53:48 +0000 | [diff] [blame] | 235 | next_exit: |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 236 | /* TODO: Handle rc */ |
danielk1977 | a298e90 | 2006-06-22 09:53:48 +0000 | [diff] [blame] | 237 | return rc; |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 238 | } |
| 239 | |
| 240 | /* |
| 241 | ** Reset a schema table cursor. |
| 242 | */ |
| 243 | static int schemaFilter( |
| 244 | sqlite3_vtab_cursor *pVtabCursor, |
| 245 | int idxNum, const char *idxStr, |
| 246 | int argc, sqlite3_value **argv |
| 247 | ){ |
| 248 | int rc; |
| 249 | schema_vtab *pVtab = (schema_vtab *)(pVtabCursor->pVtab); |
| 250 | schema_cursor *pCur = (schema_cursor *)pVtabCursor; |
| 251 | pCur->rowid = 0; |
| 252 | finalize(&pCur->pTableList); |
| 253 | finalize(&pCur->pColumnList); |
| 254 | finalize(&pCur->pDbList); |
| 255 | rc = sqlite3_prepare(pVtab->db,"PRAGMA database_list", -1, &pCur->pDbList, 0); |
| 256 | return (rc==SQLITE_OK ? schemaNext(pVtabCursor) : rc); |
| 257 | } |
| 258 | |
| 259 | /* |
| 260 | ** Analyse the WHERE condition. |
| 261 | */ |
| 262 | static int schemaBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ |
| 263 | return SQLITE_OK; |
| 264 | } |
| 265 | |
| 266 | /* |
| 267 | ** A virtual table module that merely echos method calls into TCL |
| 268 | ** variables. |
| 269 | */ |
| 270 | static sqlite3_module schemaModule = { |
| 271 | 0, /* iVersion */ |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 272 | schemaCreate, |
| 273 | schemaCreate, |
| 274 | schemaBestIndex, |
| 275 | schemaDestroy, |
| 276 | schemaDestroy, |
| 277 | schemaOpen, /* xOpen - open a cursor */ |
| 278 | schemaClose, /* xClose - close a cursor */ |
| 279 | schemaFilter, /* xFilter - configure scan constraints */ |
| 280 | schemaNext, /* xNext - advance a cursor */ |
danielk1977 | a298e90 | 2006-06-22 09:53:48 +0000 | [diff] [blame] | 281 | schemaEof, /* xEof */ |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 282 | schemaColumn, /* xColumn - read data */ |
| 283 | schemaRowid, /* xRowid - read data */ |
drh | b7f6f68 | 2006-07-08 17:06:43 +0000 | [diff] [blame] | 284 | 0, /* xUpdate */ |
| 285 | 0, /* xBegin */ |
| 286 | 0, /* xSync */ |
| 287 | 0, /* xCommit */ |
| 288 | 0, /* xRollback */ |
| 289 | 0, /* xFindMethod */ |
danielk1977 | c033b64 | 2007-06-27 16:26:07 +0000 | [diff] [blame] | 290 | 0, /* xRename */ |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 291 | }; |
| 292 | |
drh | 26e4a8b | 2008-05-01 17:16:52 +0000 | [diff] [blame] | 293 | #endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) */ |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 294 | |
| 295 | #ifdef SQLITE_TEST |
| 296 | |
| 297 | /* |
| 298 | ** Decode a pointer to an sqlite3 object. |
| 299 | */ |
drh | 24b58dd | 2008-07-07 14:50:14 +0000 | [diff] [blame] | 300 | extern int getDbPointer(Tcl_Interp *interp, const char *zA, sqlite3 **ppDb); |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 301 | |
| 302 | /* |
| 303 | ** Register the schema virtual table module. |
| 304 | */ |
| 305 | static int register_schema_module( |
| 306 | ClientData clientData, /* Not used */ |
| 307 | Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ |
| 308 | int objc, /* Number of arguments */ |
| 309 | Tcl_Obj *CONST objv[] /* Command arguments */ |
| 310 | ){ |
| 311 | sqlite3 *db; |
| 312 | if( objc!=2 ){ |
| 313 | Tcl_WrongNumArgs(interp, 1, objv, "DB"); |
| 314 | return TCL_ERROR; |
| 315 | } |
danielk1977 | 1f6eec5 | 2006-06-16 06:17:47 +0000 | [diff] [blame] | 316 | if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR; |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 317 | #ifndef SQLITE_OMIT_VIRTUALTABLE |
| 318 | sqlite3_create_module(db, "schema", &schemaModule, 0); |
| 319 | #endif |
| 320 | return TCL_OK; |
| 321 | } |
| 322 | |
| 323 | /* |
| 324 | ** Register commands with the TCL interpreter. |
| 325 | */ |
| 326 | int Sqlitetestschema_Init(Tcl_Interp *interp){ |
| 327 | static struct { |
| 328 | char *zName; |
| 329 | Tcl_ObjCmdProc *xProc; |
| 330 | void *clientData; |
| 331 | } aObjCmd[] = { |
| 332 | { "register_schema_module", register_schema_module, 0 }, |
| 333 | }; |
| 334 | int i; |
| 335 | for(i=0; i<sizeof(aObjCmd)/sizeof(aObjCmd[0]); i++){ |
| 336 | Tcl_CreateObjCommand(interp, aObjCmd[i].zName, |
| 337 | aObjCmd[i].xProc, aObjCmd[i].clientData, 0); |
| 338 | } |
| 339 | return TCL_OK; |
| 340 | } |
| 341 | |
| 342 | #else |
| 343 | |
| 344 | /* |
| 345 | ** Extension load function. |
| 346 | */ |
drh | 428397c | 2006-06-17 13:21:32 +0000 | [diff] [blame] | 347 | int sqlite3_extension_init( |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 348 | sqlite3 *db, |
| 349 | char **pzErrMsg, |
| 350 | const sqlite3_api_routines *pApi |
| 351 | ){ |
| 352 | SQLITE_EXTENSION_INIT2(pApi); |
danielk1977 | 4b2688a | 2006-06-20 11:01:07 +0000 | [diff] [blame] | 353 | #ifndef SQLITE_OMIT_VIRTUALTABLE |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 354 | sqlite3_create_module(db, "schema", &schemaModule, 0); |
danielk1977 | 4b2688a | 2006-06-20 11:01:07 +0000 | [diff] [blame] | 355 | #endif |
danielk1977 | 954ce99 | 2006-06-15 15:59:19 +0000 | [diff] [blame] | 356 | return 0; |
| 357 | } |
| 358 | |
| 359 | #endif |