drh | a43c8c8 | 2017-10-11 13:48:11 | [diff] [blame] | 1 | /* |
| 2 | ** 2017-10-11 |
| 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 contains an implementation of the "sqlite_dbpage" virtual table. |
| 14 | ** |
| 15 | ** The sqlite_dbpage virtual table is used to read or write whole raw |
| 16 | ** pages of the database file. The pager interface is used so that |
| 17 | ** uncommitted changes and changes recorded in the WAL file are correctly |
| 18 | ** retrieved. |
drh | 34d0b1a | 2017-10-11 15:02:53 | [diff] [blame] | 19 | ** |
| 20 | ** Usage example: |
| 21 | ** |
| 22 | ** SELECT data FROM sqlite_dbpage('aux1') WHERE pgno=123; |
| 23 | ** |
| 24 | ** This is an eponymous virtual table so it does not need to be created before |
| 25 | ** use. The optional argument to the sqlite_dbpage() table name is the |
| 26 | ** schema for the database file that is to be read. The default schema is |
| 27 | ** "main". |
| 28 | ** |
| 29 | ** The data field of sqlite_dbpage table can be updated. The new |
| 30 | ** value must be a BLOB which is the correct page size, otherwise the |
drh | 38b26d8 | 2024-09-10 12:09:03 | [diff] [blame] | 31 | ** update fails. INSERT operations also work, and operate as if they |
| 32 | ** where REPLACE. The size of the database can be extended by INSERT-ing |
| 33 | ** new pages on the end. |
| 34 | ** |
| 35 | ** Rows may not be deleted. However, doing an INSERT to page number N |
| 36 | ** with NULL page data causes the N-th page and all subsequent pages to be |
| 37 | ** deleted and the database to be truncated. |
drh | a43c8c8 | 2017-10-11 13:48:11 | [diff] [blame] | 38 | */ |
| 39 | |
| 40 | #include "sqliteInt.h" /* Requires access to internal data structures */ |
| 41 | #if (defined(SQLITE_ENABLE_DBPAGE_VTAB) || defined(SQLITE_TEST)) \ |
| 42 | && !defined(SQLITE_OMIT_VIRTUALTABLE) |
| 43 | |
| 44 | typedef struct DbpageTable DbpageTable; |
| 45 | typedef struct DbpageCursor DbpageCursor; |
| 46 | |
| 47 | struct DbpageCursor { |
| 48 | sqlite3_vtab_cursor base; /* Base class. Must be first */ |
| 49 | int pgno; /* Current page number */ |
drh | 34d0b1a | 2017-10-11 15:02:53 | [diff] [blame] | 50 | int mxPgno; /* Last page to visit on this scan */ |
drh | 3cd8aaa | 2017-10-25 19:18:33 | [diff] [blame] | 51 | Pager *pPager; /* Pager being read/written */ |
| 52 | DbPage *pPage1; /* Page 1 of the database */ |
| 53 | int iDb; /* Index of database to analyze */ |
| 54 | int szPage; /* Size of each page in bytes */ |
drh | a43c8c8 | 2017-10-11 13:48:11 | [diff] [blame] | 55 | }; |
| 56 | |
| 57 | struct DbpageTable { |
| 58 | sqlite3_vtab base; /* Base class. Must be first */ |
| 59 | sqlite3 *db; /* The database */ |
drh | 46a62af | 2024-10-02 18:54:40 | [diff] [blame] | 60 | int iDbTrunc; /* Database to truncate */ |
| 61 | Pgno pgnoTrunc; /* Size to truncate to */ |
drh | a43c8c8 | 2017-10-11 13:48:11 | [diff] [blame] | 62 | }; |
| 63 | |
drh | 3cd8aaa | 2017-10-25 19:18:33 | [diff] [blame] | 64 | /* Columns */ |
| 65 | #define DBPAGE_COLUMN_PGNO 0 |
| 66 | #define DBPAGE_COLUMN_DATA 1 |
| 67 | #define DBPAGE_COLUMN_SCHEMA 2 |
| 68 | |
| 69 | |
drh | a43c8c8 | 2017-10-11 13:48:11 | [diff] [blame] | 70 | /* |
| 71 | ** Connect to or create a dbpagevfs virtual table. |
| 72 | */ |
| 73 | static int dbpageConnect( |
| 74 | sqlite3 *db, |
| 75 | void *pAux, |
| 76 | int argc, const char *const*argv, |
| 77 | sqlite3_vtab **ppVtab, |
| 78 | char **pzErr |
| 79 | ){ |
| 80 | DbpageTable *pTab = 0; |
| 81 | int rc = SQLITE_OK; |
drh | 3547e49 | 2022-12-23 14:49:24 | [diff] [blame] | 82 | (void)pAux; |
| 83 | (void)argc; |
| 84 | (void)argv; |
| 85 | (void)pzErr; |
drh | a43c8c8 | 2017-10-11 13:48:11 | [diff] [blame] | 86 | |
drh | 2b1c2aa | 2020-01-07 19:45:40 | [diff] [blame] | 87 | sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY); |
drh | 0669d6e | 2023-04-03 15:01:37 | [diff] [blame] | 88 | sqlite3_vtab_config(db, SQLITE_VTAB_USES_ALL_SCHEMAS); |
drh | a43c8c8 | 2017-10-11 13:48:11 | [diff] [blame] | 89 | rc = sqlite3_declare_vtab(db, |
drh | 34d0b1a | 2017-10-11 15:02:53 | [diff] [blame] | 90 | "CREATE TABLE x(pgno INTEGER PRIMARY KEY, data BLOB, schema HIDDEN)"); |
drh | a43c8c8 | 2017-10-11 13:48:11 | [diff] [blame] | 91 | if( rc==SQLITE_OK ){ |
| 92 | pTab = (DbpageTable *)sqlite3_malloc64(sizeof(DbpageTable)); |
| 93 | if( pTab==0 ) rc = SQLITE_NOMEM_BKPT; |
| 94 | } |
| 95 | |
| 96 | assert( rc==SQLITE_OK || pTab==0 ); |
| 97 | if( rc==SQLITE_OK ){ |
drh | a43c8c8 | 2017-10-11 13:48:11 | [diff] [blame] | 98 | memset(pTab, 0, sizeof(DbpageTable)); |
| 99 | pTab->db = db; |
drh | a43c8c8 | 2017-10-11 13:48:11 | [diff] [blame] | 100 | } |
| 101 | |
| 102 | *ppVtab = (sqlite3_vtab*)pTab; |
| 103 | return rc; |
| 104 | } |
| 105 | |
| 106 | /* |
| 107 | ** Disconnect from or destroy a dbpagevfs virtual table. |
| 108 | */ |
| 109 | static int dbpageDisconnect(sqlite3_vtab *pVtab){ |
| 110 | sqlite3_free(pVtab); |
| 111 | return SQLITE_OK; |
| 112 | } |
| 113 | |
| 114 | /* |
| 115 | ** idxNum: |
| 116 | ** |
drh | 3cd8aaa | 2017-10-25 19:18:33 | [diff] [blame] | 117 | ** 0 schema=main, full table scan |
| 118 | ** 1 schema=main, pgno=?1 |
| 119 | ** 2 schema=?1, full table scan |
| 120 | ** 3 schema=?1, pgno=?2 |
drh | a43c8c8 | 2017-10-11 13:48:11 | [diff] [blame] | 121 | */ |
| 122 | static int dbpageBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ |
drh | 34d0b1a | 2017-10-11 15:02:53 | [diff] [blame] | 123 | int i; |
drh | 3cd8aaa | 2017-10-25 19:18:33 | [diff] [blame] | 124 | int iPlan = 0; |
drh | 3547e49 | 2022-12-23 14:49:24 | [diff] [blame] | 125 | (void)tab; |
drh | 3cd8aaa | 2017-10-25 19:18:33 | [diff] [blame] | 126 | |
| 127 | /* If there is a schema= constraint, it must be honored. Report a |
| 128 | ** ridiculously large estimated cost if the schema= constraint is |
| 129 | ** unavailable |
| 130 | */ |
| 131 | for(i=0; i<pIdxInfo->nConstraint; i++){ |
| 132 | struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[i]; |
| 133 | if( p->iColumn!=DBPAGE_COLUMN_SCHEMA ) continue; |
| 134 | if( p->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; |
| 135 | if( !p->usable ){ |
drh | 20b3fc4 | 2018-11-16 20:18:07 | [diff] [blame] | 136 | /* No solution. */ |
| 137 | return SQLITE_CONSTRAINT; |
drh | 3cd8aaa | 2017-10-25 19:18:33 | [diff] [blame] | 138 | } |
| 139 | iPlan = 2; |
| 140 | pIdxInfo->aConstraintUsage[i].argvIndex = 1; |
| 141 | pIdxInfo->aConstraintUsage[i].omit = 1; |
| 142 | break; |
| 143 | } |
| 144 | |
| 145 | /* If we reach this point, it means that either there is no schema= |
| 146 | ** constraint (in which case we use the "main" schema) or else the |
| 147 | ** schema constraint was accepted. Lower the estimated cost accordingly |
| 148 | */ |
| 149 | pIdxInfo->estimatedCost = 1.0e6; |
| 150 | |
| 151 | /* Check for constraints against pgno */ |
drh | 34d0b1a | 2017-10-11 15:02:53 | [diff] [blame] | 152 | for(i=0; i<pIdxInfo->nConstraint; i++){ |
| 153 | struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[i]; |
| 154 | if( p->usable && p->iColumn<=0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ ){ |
| 155 | pIdxInfo->estimatedRows = 1; |
| 156 | pIdxInfo->idxFlags = SQLITE_INDEX_SCAN_UNIQUE; |
| 157 | pIdxInfo->estimatedCost = 1.0; |
drh | 3cd8aaa | 2017-10-25 19:18:33 | [diff] [blame] | 158 | pIdxInfo->aConstraintUsage[i].argvIndex = iPlan ? 2 : 1; |
drh | 34d0b1a | 2017-10-11 15:02:53 | [diff] [blame] | 159 | pIdxInfo->aConstraintUsage[i].omit = 1; |
drh | 3cd8aaa | 2017-10-25 19:18:33 | [diff] [blame] | 160 | iPlan |= 1; |
drh | 34d0b1a | 2017-10-11 15:02:53 | [diff] [blame] | 161 | break; |
| 162 | } |
| 163 | } |
drh | 3cd8aaa | 2017-10-25 19:18:33 | [diff] [blame] | 164 | pIdxInfo->idxNum = iPlan; |
| 165 | |
drh | 34d0b1a | 2017-10-11 15:02:53 | [diff] [blame] | 166 | if( pIdxInfo->nOrderBy>=1 |
| 167 | && pIdxInfo->aOrderBy[0].iColumn<=0 |
| 168 | && pIdxInfo->aOrderBy[0].desc==0 |
| 169 | ){ |
| 170 | pIdxInfo->orderByConsumed = 1; |
| 171 | } |
drh | a43c8c8 | 2017-10-11 13:48:11 | [diff] [blame] | 172 | return SQLITE_OK; |
| 173 | } |
| 174 | |
| 175 | /* |
| 176 | ** Open a new dbpagevfs cursor. |
| 177 | */ |
| 178 | static int dbpageOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ |
| 179 | DbpageCursor *pCsr; |
| 180 | |
| 181 | pCsr = (DbpageCursor *)sqlite3_malloc64(sizeof(DbpageCursor)); |
| 182 | if( pCsr==0 ){ |
| 183 | return SQLITE_NOMEM_BKPT; |
| 184 | }else{ |
| 185 | memset(pCsr, 0, sizeof(DbpageCursor)); |
| 186 | pCsr->base.pVtab = pVTab; |
| 187 | pCsr->pgno = -1; |
| 188 | } |
| 189 | |
| 190 | *ppCursor = (sqlite3_vtab_cursor *)pCsr; |
| 191 | return SQLITE_OK; |
| 192 | } |
| 193 | |
| 194 | /* |
| 195 | ** Close a dbpagevfs cursor. |
| 196 | */ |
| 197 | static int dbpageClose(sqlite3_vtab_cursor *pCursor){ |
| 198 | DbpageCursor *pCsr = (DbpageCursor *)pCursor; |
drh | 3cd8aaa | 2017-10-25 19:18:33 | [diff] [blame] | 199 | if( pCsr->pPage1 ) sqlite3PagerUnrefPageOne(pCsr->pPage1); |
drh | a43c8c8 | 2017-10-11 13:48:11 | [diff] [blame] | 200 | sqlite3_free(pCsr); |
| 201 | return SQLITE_OK; |
| 202 | } |
| 203 | |
| 204 | /* |
| 205 | ** Move a dbpagevfs cursor to the next entry in the file. |
| 206 | */ |
| 207 | static int dbpageNext(sqlite3_vtab_cursor *pCursor){ |
| 208 | int rc = SQLITE_OK; |
| 209 | DbpageCursor *pCsr = (DbpageCursor *)pCursor; |
| 210 | pCsr->pgno++; |
| 211 | return rc; |
| 212 | } |
| 213 | |
| 214 | static int dbpageEof(sqlite3_vtab_cursor *pCursor){ |
| 215 | DbpageCursor *pCsr = (DbpageCursor *)pCursor; |
drh | 34d0b1a | 2017-10-11 15:02:53 | [diff] [blame] | 216 | return pCsr->pgno > pCsr->mxPgno; |
drh | a43c8c8 | 2017-10-11 13:48:11 | [diff] [blame] | 217 | } |
| 218 | |
drh | 3cd8aaa | 2017-10-25 19:18:33 | [diff] [blame] | 219 | /* |
| 220 | ** idxNum: |
| 221 | ** |
| 222 | ** 0 schema=main, full table scan |
| 223 | ** 1 schema=main, pgno=?1 |
| 224 | ** 2 schema=?1, full table scan |
| 225 | ** 3 schema=?1, pgno=?2 |
| 226 | ** |
| 227 | ** idxStr is not used |
| 228 | */ |
drh | a43c8c8 | 2017-10-11 13:48:11 | [diff] [blame] | 229 | static int dbpageFilter( |
stephan | b504aab | 2025-05-31 09:44:00 | [diff] [blame] | 230 | sqlite3_vtab_cursor *pCursor, |
drh | a43c8c8 | 2017-10-11 13:48:11 | [diff] [blame] | 231 | int idxNum, const char *idxStr, |
| 232 | int argc, sqlite3_value **argv |
| 233 | ){ |
| 234 | DbpageCursor *pCsr = (DbpageCursor *)pCursor; |
| 235 | DbpageTable *pTab = (DbpageTable *)pCursor->pVtab; |
drh | 0503f2a | 2017-10-27 18:24:11 | [diff] [blame] | 236 | int rc; |
drh | 3cd8aaa | 2017-10-25 19:18:33 | [diff] [blame] | 237 | sqlite3 *db = pTab->db; |
| 238 | Btree *pBt; |
drh | a43c8c8 | 2017-10-11 13:48:11 | [diff] [blame] | 239 | |
stephan | b504aab | 2025-05-31 09:44:00 | [diff] [blame] | 240 | UNUSED_PARAMETER(idxStr); |
| 241 | UNUSED_PARAMETER(argc); |
| 242 | |
drh | 3cd8aaa | 2017-10-25 19:18:33 | [diff] [blame] | 243 | /* Default setting is no rows of result */ |
stephan | b504aab | 2025-05-31 09:44:00 | [diff] [blame] | 244 | pCsr->pgno = 1; |
drh | 3cd8aaa | 2017-10-25 19:18:33 | [diff] [blame] | 245 | pCsr->mxPgno = 0; |
| 246 | |
| 247 | if( idxNum & 2 ){ |
| 248 | const char *zSchema; |
| 249 | assert( argc>=1 ); |
| 250 | zSchema = (const char*)sqlite3_value_text(argv[0]); |
| 251 | pCsr->iDb = sqlite3FindDbName(db, zSchema); |
| 252 | if( pCsr->iDb<0 ) return SQLITE_OK; |
| 253 | }else{ |
| 254 | pCsr->iDb = 0; |
| 255 | } |
| 256 | pBt = db->aDb[pCsr->iDb].pBt; |
drh | 2f9525b | 2023-02-05 00:24:42 | [diff] [blame] | 257 | if( NEVER(pBt==0) ) return SQLITE_OK; |
drh | 3cd8aaa | 2017-10-25 19:18:33 | [diff] [blame] | 258 | pCsr->pPager = sqlite3BtreePager(pBt); |
| 259 | pCsr->szPage = sqlite3BtreeGetPageSize(pBt); |
| 260 | pCsr->mxPgno = sqlite3BtreeLastPage(pBt); |
| 261 | if( idxNum & 1 ){ |
| 262 | assert( argc>(idxNum>>1) ); |
| 263 | pCsr->pgno = sqlite3_value_int(argv[idxNum>>1]); |
| 264 | if( pCsr->pgno<1 || pCsr->pgno>pCsr->mxPgno ){ |
drh | 34d0b1a | 2017-10-11 15:02:53 | [diff] [blame] | 265 | pCsr->pgno = 1; |
| 266 | pCsr->mxPgno = 0; |
| 267 | }else{ |
| 268 | pCsr->mxPgno = pCsr->pgno; |
| 269 | } |
| 270 | }else{ |
drh | 3cd8aaa | 2017-10-25 19:18:33 | [diff] [blame] | 271 | assert( pCsr->pgno==1 ); |
drh | 34d0b1a | 2017-10-11 15:02:53 | [diff] [blame] | 272 | } |
drh | 0503f2a | 2017-10-27 18:24:11 | [diff] [blame] | 273 | if( pCsr->pPage1 ) sqlite3PagerUnrefPageOne(pCsr->pPage1); |
drh | 3cd8aaa | 2017-10-25 19:18:33 | [diff] [blame] | 274 | rc = sqlite3PagerGet(pCsr->pPager, 1, &pCsr->pPage1, 0); |
drh | a43c8c8 | 2017-10-11 13:48:11 | [diff] [blame] | 275 | return rc; |
| 276 | } |
| 277 | |
| 278 | static int dbpageColumn( |
stephan | b504aab | 2025-05-31 09:44:00 | [diff] [blame] | 279 | sqlite3_vtab_cursor *pCursor, |
| 280 | sqlite3_context *ctx, |
drh | a43c8c8 | 2017-10-11 13:48:11 | [diff] [blame] | 281 | int i |
| 282 | ){ |
| 283 | DbpageCursor *pCsr = (DbpageCursor *)pCursor; |
drh | a43c8c8 | 2017-10-11 13:48:11 | [diff] [blame] | 284 | int rc = SQLITE_OK; |
| 285 | switch( i ){ |
| 286 | case 0: { /* pgno */ |
| 287 | sqlite3_result_int(ctx, pCsr->pgno); |
| 288 | break; |
| 289 | } |
| 290 | case 1: { /* data */ |
| 291 | DbPage *pDbPage = 0; |
dan | 7985e05 | 2022-09-13 18:08:24 | [diff] [blame] | 292 | if( pCsr->pgno==((PENDING_BYTE/pCsr->szPage)+1) ){ |
| 293 | /* The pending byte page. Assume it is zeroed out. Attempting to |
| 294 | ** request this page from the page is an SQLITE_CORRUPT error. */ |
| 295 | sqlite3_result_zeroblob(ctx, pCsr->szPage); |
| 296 | }else{ |
| 297 | rc = sqlite3PagerGet(pCsr->pPager, pCsr->pgno, (DbPage**)&pDbPage, 0); |
| 298 | if( rc==SQLITE_OK ){ |
| 299 | sqlite3_result_blob(ctx, sqlite3PagerGetData(pDbPage), pCsr->szPage, |
| 300 | SQLITE_TRANSIENT); |
| 301 | } |
| 302 | sqlite3PagerUnref(pDbPage); |
drh | a43c8c8 | 2017-10-11 13:48:11 | [diff] [blame] | 303 | } |
drh | a43c8c8 | 2017-10-11 13:48:11 | [diff] [blame] | 304 | break; |
| 305 | } |
| 306 | default: { /* schema */ |
| 307 | sqlite3 *db = sqlite3_context_db_handle(ctx); |
drh | 3cd8aaa | 2017-10-25 19:18:33 | [diff] [blame] | 308 | sqlite3_result_text(ctx, db->aDb[pCsr->iDb].zDbSName, -1, SQLITE_STATIC); |
drh | a43c8c8 | 2017-10-11 13:48:11 | [diff] [blame] | 309 | break; |
| 310 | } |
| 311 | } |
dan | 5ca0b38 | 2022-09-12 20:02:33 | [diff] [blame] | 312 | return rc; |
drh | a43c8c8 | 2017-10-11 13:48:11 | [diff] [blame] | 313 | } |
| 314 | |
| 315 | static int dbpageRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){ |
| 316 | DbpageCursor *pCsr = (DbpageCursor *)pCursor; |
| 317 | *pRowid = pCsr->pgno; |
| 318 | return SQLITE_OK; |
| 319 | } |
| 320 | |
drh | a683b05 | 2025-01-02 15:03:13 | [diff] [blame] | 321 | /* |
| 322 | ** Open write transactions. Since we do not know in advance which database |
| 323 | ** files will be written by the sqlite_dbpage virtual table, start a write |
| 324 | ** transaction on them all. |
| 325 | ** |
| 326 | ** Return SQLITE_OK if successful, or an SQLite error code otherwise. |
| 327 | */ |
| 328 | static int dbpageBeginTrans(DbpageTable *pTab){ |
| 329 | sqlite3 *db = pTab->db; |
| 330 | int rc = SQLITE_OK; |
| 331 | int i; |
| 332 | for(i=0; rc==SQLITE_OK && i<db->nDb; i++){ |
| 333 | Btree *pBt = db->aDb[i].pBt; |
| 334 | if( pBt ) rc = sqlite3BtreeBeginTrans(pBt, 1, 0); |
| 335 | } |
| 336 | return rc; |
| 337 | } |
| 338 | |
drh | 34d0b1a | 2017-10-11 15:02:53 | [diff] [blame] | 339 | static int dbpageUpdate( |
| 340 | sqlite3_vtab *pVtab, |
| 341 | int argc, |
| 342 | sqlite3_value **argv, |
| 343 | sqlite_int64 *pRowid |
| 344 | ){ |
| 345 | DbpageTable *pTab = (DbpageTable *)pVtab; |
mistachkin | aca84e6 | 2017-11-10 12:41:21 | [diff] [blame] | 346 | Pgno pgno; |
drh | 34d0b1a | 2017-10-11 15:02:53 | [diff] [blame] | 347 | DbPage *pDbPage = 0; |
| 348 | int rc = SQLITE_OK; |
| 349 | char *zErr = 0; |
drh | 3cd8aaa | 2017-10-25 19:18:33 | [diff] [blame] | 350 | int iDb; |
| 351 | Btree *pBt; |
| 352 | Pager *pPager; |
| 353 | int szPage; |
drh | 38b26d8 | 2024-09-10 12:09:03 | [diff] [blame] | 354 | int isInsert; |
drh | 34d0b1a | 2017-10-11 15:02:53 | [diff] [blame] | 355 | |
drh | 3547e49 | 2022-12-23 14:49:24 | [diff] [blame] | 356 | (void)pRowid; |
drh | a296cda | 2018-11-03 16:09:59 | [diff] [blame] | 357 | if( pTab->db->flags & SQLITE_Defensive ){ |
| 358 | zErr = "read-only"; |
| 359 | goto update_fail; |
| 360 | } |
drh | 34d0b1a | 2017-10-11 15:02:53 | [diff] [blame] | 361 | if( argc==1 ){ |
| 362 | zErr = "cannot delete"; |
| 363 | goto update_fail; |
| 364 | } |
drh | 882aba4 | 2024-09-09 18:45:58 | [diff] [blame] | 365 | if( sqlite3_value_type(argv[0])==SQLITE_NULL ){ |
| 366 | pgno = (Pgno)sqlite3_value_int(argv[2]); |
drh | 38b26d8 | 2024-09-10 12:09:03 | [diff] [blame] | 367 | isInsert = 1; |
drh | 882aba4 | 2024-09-09 18:45:58 | [diff] [blame] | 368 | }else{ |
| 369 | pgno = sqlite3_value_int(argv[0]); |
| 370 | if( (Pgno)sqlite3_value_int(argv[1])!=pgno ){ |
| 371 | zErr = "cannot insert"; |
| 372 | goto update_fail; |
| 373 | } |
drh | 38b26d8 | 2024-09-10 12:09:03 | [diff] [blame] | 374 | isInsert = 0; |
drh | 34d0b1a | 2017-10-11 15:02:53 | [diff] [blame] | 375 | } |
drh | 882aba4 | 2024-09-09 18:45:58 | [diff] [blame] | 376 | if( sqlite3_value_type(argv[4])==SQLITE_NULL ){ |
| 377 | iDb = 0; |
| 378 | }else{ |
| 379 | const char *zSchema = (const char*)sqlite3_value_text(argv[4]); |
drh | 762946b | 2024-09-13 21:47:57 | [diff] [blame] | 380 | iDb = sqlite3FindDbName(pTab->db, zSchema); |
drh | 882aba4 | 2024-09-09 18:45:58 | [diff] [blame] | 381 | if( iDb<0 ){ |
| 382 | zErr = "no such schema"; |
| 383 | goto update_fail; |
| 384 | } |
drh | 3cd8aaa | 2017-10-25 19:18:33 | [diff] [blame] | 385 | } |
| 386 | pBt = pTab->db->aDb[iDb].pBt; |
drh | 882aba4 | 2024-09-09 18:45:58 | [diff] [blame] | 387 | if( pgno<1 || NEVER(pBt==0) ){ |
drh | 3cd8aaa | 2017-10-25 19:18:33 | [diff] [blame] | 388 | zErr = "bad page number"; |
| 389 | goto update_fail; |
| 390 | } |
| 391 | szPage = sqlite3BtreeGetPageSize(pBt); |
drh | 34d0b1a | 2017-10-11 15:02:53 | [diff] [blame] | 392 | if( sqlite3_value_type(argv[3])!=SQLITE_BLOB |
drh | 3cd8aaa | 2017-10-25 19:18:33 | [diff] [blame] | 393 | || sqlite3_value_bytes(argv[3])!=szPage |
drh | 34d0b1a | 2017-10-11 15:02:53 | [diff] [blame] | 394 | ){ |
drh | 3b3f230 | 2024-10-02 16:55:27 | [diff] [blame] | 395 | if( sqlite3_value_type(argv[3])==SQLITE_NULL && isInsert && pgno>1 ){ |
| 396 | /* "INSERT INTO dbpage($PGNO,NULL)" causes page number $PGNO and |
| 397 | ** all subsequent pages to be deleted. */ |
drh | 46a62af | 2024-10-02 18:54:40 | [diff] [blame] | 398 | pTab->iDbTrunc = iDb; |
drh | 4c13878 | 2025-03-20 11:47:39 | [diff] [blame] | 399 | pTab->pgnoTrunc = pgno-1; |
| 400 | pgno = 1; |
drh | 38b26d8 | 2024-09-10 12:09:03 | [diff] [blame] | 401 | }else{ |
| 402 | zErr = "bad page value"; |
| 403 | goto update_fail; |
| 404 | } |
drh | 34d0b1a | 2017-10-11 15:02:53 | [diff] [blame] | 405 | } |
drh | a683b05 | 2025-01-02 15:03:13 | [diff] [blame] | 406 | |
| 407 | if( dbpageBeginTrans(pTab)!=SQLITE_OK ){ |
| 408 | zErr = "failed to open transaction"; |
| 409 | goto update_fail; |
| 410 | } |
| 411 | |
drh | 3cd8aaa | 2017-10-25 19:18:33 | [diff] [blame] | 412 | pPager = sqlite3BtreePager(pBt); |
| 413 | rc = sqlite3PagerGet(pPager, pgno, (DbPage**)&pDbPage, 0); |
drh | 34d0b1a | 2017-10-11 15:02:53 | [diff] [blame] | 414 | if( rc==SQLITE_OK ){ |
drh | c7dd9b6 | 2022-10-31 18:01:05 | [diff] [blame] | 415 | const void *pData = sqlite3_value_blob(argv[3]); |
drh | 38b26d8 | 2024-09-10 12:09:03 | [diff] [blame] | 416 | if( (rc = sqlite3PagerWrite(pDbPage))==SQLITE_OK && pData ){ |
| 417 | unsigned char *aPage = sqlite3PagerGetData(pDbPage); |
| 418 | memcpy(aPage, pData, szPage); |
drh | 46a62af | 2024-10-02 18:54:40 | [diff] [blame] | 419 | pTab->pgnoTrunc = 0; |
drh | 34d0b1a | 2017-10-11 15:02:53 | [diff] [blame] | 420 | } |
drh | c5b9da3 | 2024-11-30 14:13:35 | [diff] [blame] | 421 | }else{ |
| 422 | pTab->pgnoTrunc = 0; |
drh | 34d0b1a | 2017-10-11 15:02:53 | [diff] [blame] | 423 | } |
| 424 | sqlite3PagerUnref(pDbPage); |
| 425 | return rc; |
| 426 | |
| 427 | update_fail: |
drh | a033790 | 2025-01-31 14:52:05 | [diff] [blame] | 428 | pTab->pgnoTrunc = 0; |
drh | 34d0b1a | 2017-10-11 15:02:53 | [diff] [blame] | 429 | sqlite3_free(pVtab->zErrMsg); |
| 430 | pVtab->zErrMsg = sqlite3_mprintf("%s", zErr); |
| 431 | return SQLITE_ERROR; |
| 432 | } |
| 433 | |
drh | 3cd8aaa | 2017-10-25 19:18:33 | [diff] [blame] | 434 | static int dbpageBegin(sqlite3_vtab *pVtab){ |
| 435 | DbpageTable *pTab = (DbpageTable *)pVtab; |
drh | 46a62af | 2024-10-02 18:54:40 | [diff] [blame] | 436 | pTab->pgnoTrunc = 0; |
drh | 38b26d8 | 2024-09-10 12:09:03 | [diff] [blame] | 437 | return SQLITE_OK; |
| 438 | } |
| 439 | |
| 440 | /* Invoke sqlite3PagerTruncate() as necessary, just prior to COMMIT |
| 441 | */ |
| 442 | static int dbpageSync(sqlite3_vtab *pVtab){ |
drh | 38b26d8 | 2024-09-10 12:09:03 | [diff] [blame] | 443 | DbpageTable *pTab = (DbpageTable *)pVtab; |
drh | 46a62af | 2024-10-02 18:54:40 | [diff] [blame] | 444 | if( pTab->pgnoTrunc>0 ){ |
| 445 | Btree *pBt = pTab->db->aDb[pTab->iDbTrunc].pBt; |
| 446 | Pager *pPager = sqlite3BtreePager(pBt); |
drh | be2a40d | 2024-12-19 19:02:09 | [diff] [blame] | 447 | sqlite3BtreeEnter(pBt); |
drh | 2fb488d | 2024-12-17 14:32:37 | [diff] [blame] | 448 | if( pTab->pgnoTrunc<sqlite3BtreeLastPage(pBt) ){ |
| 449 | sqlite3PagerTruncateImage(pPager, pTab->pgnoTrunc); |
| 450 | } |
drh | be2a40d | 2024-12-19 19:02:09 | [diff] [blame] | 451 | sqlite3BtreeLeave(pBt); |
drh | 38b26d8 | 2024-09-10 12:09:03 | [diff] [blame] | 452 | } |
drh | 46a62af | 2024-10-02 18:54:40 | [diff] [blame] | 453 | pTab->pgnoTrunc = 0; |
drh | 2f9525b | 2023-02-05 00:24:42 | [diff] [blame] | 454 | return SQLITE_OK; |
drh | 3cd8aaa | 2017-10-25 19:18:33 | [diff] [blame] | 455 | } |
| 456 | |
drh | 46a62af | 2024-10-02 18:54:40 | [diff] [blame] | 457 | /* Cancel any pending truncate. |
| 458 | */ |
drh | c51dccb | 2024-10-03 09:53:44 | [diff] [blame] | 459 | static int dbpageRollbackTo(sqlite3_vtab *pVtab, int notUsed1){ |
drh | 46a62af | 2024-10-02 18:54:40 | [diff] [blame] | 460 | DbpageTable *pTab = (DbpageTable *)pVtab; |
| 461 | pTab->pgnoTrunc = 0; |
| 462 | (void)notUsed1; |
drh | 1e2834d | 2024-10-03 10:06:51 | [diff] [blame] | 463 | return SQLITE_OK; |
drh | 46a62af | 2024-10-02 18:54:40 | [diff] [blame] | 464 | } |
drh | 3cd8aaa | 2017-10-25 19:18:33 | [diff] [blame] | 465 | |
drh | a43c8c8 | 2017-10-11 13:48:11 | [diff] [blame] | 466 | /* |
| 467 | ** Invoke this routine to register the "dbpage" virtual table module |
| 468 | */ |
| 469 | int sqlite3DbpageRegister(sqlite3 *db){ |
| 470 | static sqlite3_module dbpage_module = { |
dan | 3835cf6 | 2025-01-02 15:27:15 | [diff] [blame] | 471 | 2, /* iVersion */ |
drh | a43c8c8 | 2017-10-11 13:48:11 | [diff] [blame] | 472 | dbpageConnect, /* xCreate */ |
| 473 | dbpageConnect, /* xConnect */ |
| 474 | dbpageBestIndex, /* xBestIndex */ |
| 475 | dbpageDisconnect, /* xDisconnect */ |
| 476 | dbpageDisconnect, /* xDestroy */ |
| 477 | dbpageOpen, /* xOpen - open a cursor */ |
| 478 | dbpageClose, /* xClose - close a cursor */ |
| 479 | dbpageFilter, /* xFilter - configure scan constraints */ |
| 480 | dbpageNext, /* xNext - advance a cursor */ |
| 481 | dbpageEof, /* xEof - check for end of scan */ |
| 482 | dbpageColumn, /* xColumn - read data */ |
| 483 | dbpageRowid, /* xRowid - read data */ |
drh | 34d0b1a | 2017-10-11 15:02:53 | [diff] [blame] | 484 | dbpageUpdate, /* xUpdate */ |
drh | 3cd8aaa | 2017-10-25 19:18:33 | [diff] [blame] | 485 | dbpageBegin, /* xBegin */ |
drh | 38b26d8 | 2024-09-10 12:09:03 | [diff] [blame] | 486 | dbpageSync, /* xSync */ |
drh | a43c8c8 | 2017-10-11 13:48:11 | [diff] [blame] | 487 | 0, /* xCommit */ |
| 488 | 0, /* xRollback */ |
| 489 | 0, /* xFindMethod */ |
| 490 | 0, /* xRename */ |
| 491 | 0, /* xSavepoint */ |
| 492 | 0, /* xRelease */ |
dan | 3835cf6 | 2025-01-02 15:27:15 | [diff] [blame] | 493 | dbpageRollbackTo, /* xRollbackTo */ |
drh | 1935887 | 2023-10-06 12:51:05 | [diff] [blame] | 494 | 0, /* xShadowName */ |
| 495 | 0 /* xIntegrity */ |
drh | a43c8c8 | 2017-10-11 13:48:11 | [diff] [blame] | 496 | }; |
| 497 | return sqlite3_create_module(db, "sqlite_dbpage", &dbpage_module, 0); |
| 498 | } |
| 499 | #elif defined(SQLITE_ENABLE_DBPAGE_VTAB) |
| 500 | int sqlite3DbpageRegister(sqlite3 *db){ return SQLITE_OK; } |
| 501 | #endif /* SQLITE_ENABLE_DBSTAT_VTAB */ |