drh | f6cee7d | 2011-08-09 18:14:36 +0000 | [diff] [blame] | 1 | /* |
| 2 | ** This program searches an SQLite database file for the lengths and |
| 3 | ** offsets for all TEXT or BLOB entries for a particular column of a |
| 4 | ** particular table. The rowid, size and offset for the column are |
| 5 | ** written to standard output. There are three arguments, which are the |
| 6 | ** name of the database file, the table, and the column. |
| 7 | */ |
| 8 | #include "sqlite3.h" |
| 9 | #include <stdio.h> |
| 10 | #include <stdlib.h> |
| 11 | #include <stdarg.h> |
| 12 | #include <string.h> |
| 13 | |
| 14 | typedef unsigned char u8; |
| 15 | typedef struct GState GState; |
| 16 | |
| 17 | #define ArraySize(X) (sizeof(X)/sizeof(X[0])) |
| 18 | |
| 19 | /* |
| 20 | ** Global state information for this program. |
| 21 | */ |
| 22 | struct GState { |
| 23 | char *zErr; /* Error message text */ |
| 24 | FILE *f; /* Open database file */ |
| 25 | int szPg; /* Page size for the database file */ |
| 26 | int iRoot; /* Root page of the table */ |
| 27 | int iCol; /* Column number for the column */ |
| 28 | int pgno; /* Current page number */ |
| 29 | u8 *aPage; /* Current page content */ |
| 30 | u8 *aStack[20]; /* Page stack */ |
| 31 | int aPgno[20]; /* Page number stack */ |
| 32 | int nStack; /* Depth of stack */ |
| 33 | int bTrace; /* True for tracing output */ |
| 34 | }; |
| 35 | |
| 36 | /* |
| 37 | ** Write an error. |
| 38 | */ |
| 39 | static void ofstError(GState *p, const char *zFormat, ...){ |
| 40 | va_list ap; |
| 41 | sqlite3_free(p->zErr); |
| 42 | va_start(ap, zFormat); |
| 43 | p->zErr = sqlite3_vmprintf(zFormat, ap); |
| 44 | va_end(ap); |
| 45 | } |
| 46 | |
| 47 | /* |
| 48 | ** Write a trace message |
| 49 | */ |
| 50 | static void ofstTrace(GState *p, const char *zFormat, ...){ |
| 51 | va_list ap; |
| 52 | if( p->bTrace ){ |
| 53 | va_start(ap, zFormat); |
| 54 | vprintf(zFormat, ap); |
| 55 | va_end(ap); |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | /* |
| 60 | ** Find the root page of the table and the column number of the column. |
| 61 | */ |
| 62 | static void ofstRootAndColumn( |
| 63 | GState *p, /* Global state */ |
| 64 | const char *zFile, /* Name of the database file */ |
| 65 | const char *zTable, /* Name of the table */ |
| 66 | const char *zColumn /* Name of the column */ |
| 67 | ){ |
| 68 | sqlite3 *db = 0; |
| 69 | sqlite3_stmt *pStmt = 0; |
| 70 | char *zSql = 0; |
| 71 | int rc; |
| 72 | if( p->zErr ) return; |
| 73 | rc = sqlite3_open(zFile, &db); |
| 74 | if( rc ){ |
| 75 | ofstError(p, "cannot open database file \"%s\"", zFile); |
| 76 | goto rootAndColumn_exit; |
| 77 | } |
| 78 | zSql = sqlite3_mprintf("SELECT rootpage FROM sqlite_master WHERE name=%Q", |
| 79 | zTable); |
| 80 | rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); |
| 81 | if( rc ) ofstError(p, "%s: [%s]", sqlite3_errmsg(db), zSql); |
| 82 | sqlite3_free(zSql); |
| 83 | if( p->zErr ) goto rootAndColumn_exit; |
| 84 | if( sqlite3_step(pStmt)!=SQLITE_ROW ){ |
| 85 | ofstError(p, "cannot find table [%s]\n", zTable); |
| 86 | sqlite3_finalize(pStmt); |
| 87 | goto rootAndColumn_exit; |
| 88 | } |
| 89 | p->iRoot = sqlite3_column_int(pStmt , 0); |
| 90 | sqlite3_finalize(pStmt); |
| 91 | |
| 92 | p->iCol = -1; |
| 93 | zSql = sqlite3_mprintf("PRAGMA table_info(%Q)", zTable); |
| 94 | rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); |
| 95 | if( rc ) ofstError(p, "%s: [%s}", sqlite3_errmsg(db), zSql); |
| 96 | sqlite3_free(zSql); |
| 97 | if( p->zErr ) goto rootAndColumn_exit; |
| 98 | while( sqlite3_step(pStmt)==SQLITE_ROW ){ |
| 99 | const char *zCol = sqlite3_column_text(pStmt, 1); |
| 100 | if( strlen(zCol)==strlen(zColumn) |
| 101 | && sqlite3_strnicmp(zCol, zColumn, strlen(zCol))==0 |
| 102 | ){ |
| 103 | p->iCol = sqlite3_column_int(pStmt, 0); |
| 104 | break; |
| 105 | } |
| 106 | } |
| 107 | sqlite3_finalize(pStmt); |
| 108 | if( p->iCol<0 ){ |
| 109 | ofstError(p, "no such column: %s.%s", zTable, zColumn); |
| 110 | goto rootAndColumn_exit; |
| 111 | } |
| 112 | |
| 113 | zSql = sqlite3_mprintf("PRAGMA page_size"); |
| 114 | rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); |
| 115 | if( rc ) ofstError(p, "%s: [%s]", sqlite3_errmsg(db), zSql); |
| 116 | sqlite3_free(zSql); |
| 117 | if( p->zErr ) goto rootAndColumn_exit; |
| 118 | if( sqlite3_step(pStmt)!=SQLITE_ROW ){ |
| 119 | ofstError(p, "cannot find page size"); |
| 120 | }else{ |
| 121 | p->szPg = sqlite3_column_int(pStmt, 0); |
| 122 | } |
| 123 | sqlite3_finalize(pStmt); |
| 124 | |
| 125 | rootAndColumn_exit: |
| 126 | sqlite3_close(db); |
| 127 | return; |
| 128 | } |
| 129 | |
| 130 | /* |
| 131 | ** Pop a page from the stack |
| 132 | */ |
| 133 | static void ofstPopPage(GState *p){ |
| 134 | if( p->nStack<=0 ) return; |
| 135 | p->nStack--; |
| 136 | sqlite3_free(p->aStack[p->nStack]); |
| 137 | p->pgno = p->aPgno[p->nStack-1]; |
| 138 | p->aPage = p->aStack[p->nStack-1]; |
| 139 | } |
| 140 | |
| 141 | |
| 142 | /* |
| 143 | ** Push a new page onto the stack. |
| 144 | */ |
| 145 | static void ofstPushPage(GState *p, int pgno){ |
| 146 | u8 *pPage; |
| 147 | size_t got; |
| 148 | if( p->zErr ) return; |
| 149 | if( p->nStack >= ArraySize(p->aStack) ){ |
| 150 | ofstError(p, "page stack overflow"); |
| 151 | return; |
| 152 | } |
| 153 | p->aPgno[p->nStack] = pgno; |
| 154 | p->aStack[p->nStack] = pPage = sqlite3_malloc( p->szPg ); |
| 155 | if( pPage==0 ){ |
| 156 | fprintf(stderr, "out of memory\n"); |
| 157 | exit(1); |
| 158 | } |
| 159 | p->nStack++; |
| 160 | p->aPage = pPage; |
| 161 | p->pgno = pgno; |
| 162 | fseek(p->f, (pgno-1)*p->szPg, SEEK_SET); |
| 163 | got = fread(pPage, 1, p->szPg, p->f); |
| 164 | if( got!=p->szPg ){ |
| 165 | ofstError(p, "unable to read page %d", pgno); |
| 166 | ofstPopPage(p); |
| 167 | } |
| 168 | } |
| 169 | |
| 170 | /* Read a two-byte integer at the given offset into the current page */ |
| 171 | static int ofst2byte(GState *p, int ofst){ |
| 172 | int x = p->aPage[ofst]; |
| 173 | return (x<<8) + p->aPage[ofst+1]; |
| 174 | } |
| 175 | |
| 176 | /* Read a four-byte integer at the given offset into the current page */ |
| 177 | static int ofst4byte(GState *p, int ofst){ |
| 178 | int x = p->aPage[ofst]; |
| 179 | x = (x<<8) + p->aPage[ofst+1]; |
| 180 | x = (x<<8) + p->aPage[ofst+2]; |
| 181 | x = (x<<8) + p->aPage[ofst+3]; |
| 182 | return x; |
| 183 | } |
| 184 | |
| 185 | /* Read a variable-length integer. Update the offset */ |
| 186 | static sqlite3_int64 ofstVarint(GState *p, int *pOfst){ |
| 187 | sqlite3_int64 x = 0; |
| 188 | u8 *a = &p->aPage[*pOfst]; |
| 189 | int n = 0; |
| 190 | while( n<8 && (a[0] & 0x80)!=0 ){ |
| 191 | x = (x<<7) + (a[0] & 0x7f); |
| 192 | n++; |
| 193 | a++; |
| 194 | } |
| 195 | if( n==8 ){ |
| 196 | x = (x<<8) + a[0]; |
| 197 | }else{ |
| 198 | x = (x<<7) + a[0]; |
| 199 | } |
| 200 | *pOfst += (n+1); |
| 201 | return x; |
| 202 | } |
| 203 | |
| 204 | /* Return the absolute offset into a file for the given offset |
| 205 | ** into the current page */ |
| 206 | static int ofstInFile(GState *p, int ofst){ |
| 207 | return p->szPg*(p->pgno-1) + ofst; |
| 208 | } |
| 209 | |
| 210 | /* Return the size (in bytes) of the data corresponding to the |
| 211 | ** given serial code */ |
| 212 | static int ofstSerialSize(int scode){ |
| 213 | if( scode<5 ) return scode; |
| 214 | if( scode==5 ) return 6; |
| 215 | if( scode<8 ) return 8; |
| 216 | if( scode<12 ) return 0; |
| 217 | return (scode-12)/2; |
| 218 | } |
| 219 | |
| 220 | /* Forward reference */ |
| 221 | static void ofstWalkPage(GState*, int); |
| 222 | |
| 223 | /* Walk an interior btree page */ |
| 224 | static void ofstWalkInteriorPage(GState *p){ |
| 225 | int nCell; |
| 226 | int i; |
| 227 | int ofst; |
| 228 | int iChild; |
| 229 | |
| 230 | nCell = ofst2byte(p, 3); |
| 231 | for(i=0; i<nCell; i++){ |
| 232 | ofst = ofst2byte(p, 12+i*2); |
| 233 | iChild = ofst4byte(p, ofst); |
| 234 | ofstWalkPage(p, iChild); |
| 235 | if( p->zErr ) return; |
| 236 | } |
| 237 | ofstWalkPage(p, ofst4byte(p, 8)); |
| 238 | } |
| 239 | |
| 240 | /* Walk a leaf btree page */ |
| 241 | static void ofstWalkLeafPage(GState *p){ |
| 242 | int nCell; |
| 243 | int i; |
| 244 | int ofst; |
| 245 | int nPayload; |
| 246 | sqlite3_int64 rowid; |
| 247 | int nHdr; |
| 248 | int j; |
| 249 | int scode; |
| 250 | int sz; |
| 251 | int dataOfst; |
| 252 | char zMsg[200]; |
| 253 | |
| 254 | nCell = ofst2byte(p, 3); |
| 255 | for(i=0; i<nCell; i++){ |
| 256 | ofst = ofst2byte(p, 8+i*2); |
| 257 | nPayload = ofstVarint(p, &ofst); |
| 258 | rowid = ofstVarint(p, &ofst); |
| 259 | if( nPayload > p->szPg-35 ){ |
| 260 | sqlite3_snprintf(sizeof(zMsg), zMsg, |
| 261 | "# overflow rowid %lld", rowid); |
| 262 | printf("%s\n", zMsg); |
| 263 | continue; |
| 264 | } |
| 265 | dataOfst = ofst; |
| 266 | nHdr = ofstVarint(p, &ofst); |
| 267 | dataOfst += nHdr; |
| 268 | for(j=0; j<p->iCol; j++){ |
| 269 | scode = ofstVarint(p, &ofst); |
| 270 | dataOfst += ofstSerialSize(scode); |
| 271 | } |
| 272 | scode = ofstVarint(p, &ofst); |
| 273 | sz = ofstSerialSize(scode); |
| 274 | sqlite3_snprintf(sizeof(zMsg), zMsg, |
| 275 | "rowid %12lld size %5d offset %8d", |
| 276 | rowid, sz, ofstInFile(p, dataOfst)); |
| 277 | printf("%s\n", zMsg); |
| 278 | } |
| 279 | } |
| 280 | |
| 281 | /* |
| 282 | ** Output results from a single page. |
| 283 | */ |
| 284 | static void ofstWalkPage(GState *p, int pgno){ |
| 285 | if( p->zErr ) return; |
| 286 | ofstPushPage(p, pgno); |
| 287 | if( p->zErr ) return; |
| 288 | if( p->aPage[0]==5 ){ |
| 289 | ofstWalkInteriorPage(p); |
| 290 | }else if( p->aPage[0]==13 ){ |
| 291 | ofstWalkLeafPage(p); |
| 292 | }else{ |
| 293 | ofstError(p, "page %d has a faulty type byte: %d", pgno, p->aPage[0]); |
| 294 | } |
| 295 | ofstPopPage(p); |
| 296 | } |
| 297 | |
| 298 | int main(int argc, char **argv){ |
| 299 | GState g; |
| 300 | memset(&g, 0, sizeof(g)); |
| 301 | if( argc>2 && strcmp(argv[1],"--trace")==0 ){ |
| 302 | g.bTrace = 1; |
| 303 | argc--; |
| 304 | argv++; |
| 305 | } |
| 306 | if( argc!=4 ){ |
| 307 | fprintf(stderr, "Usage: %s DATABASE TABLE COLUMN\n", *argv); |
| 308 | exit(1); |
| 309 | } |
| 310 | ofstRootAndColumn(&g, argv[1], argv[2], argv[3]); |
| 311 | if( g.zErr ){ |
| 312 | fprintf(stderr, "%s\n", g.zErr); |
| 313 | exit(1); |
| 314 | } |
| 315 | ofstTrace(&g, "# szPg = %d\n", g.szPg); |
| 316 | ofstTrace(&g, "# iRoot = %d\n", g.iRoot); |
| 317 | ofstTrace(&g, "# iCol = %d\n", g.iCol); |
| 318 | g.f = fopen(argv[1], "rb"); |
| 319 | if( g.f==0 ){ |
| 320 | fprintf(stderr, "cannot open \"%s\"\n", argv[1]); |
| 321 | exit(1); |
| 322 | } |
| 323 | ofstWalkPage(&g, g.iRoot); |
| 324 | if( g.zErr ){ |
| 325 | fprintf(stderr, "%s\n", g.zErr); |
| 326 | exit(1); |
| 327 | } |
| 328 | return 0; |
| 329 | } |