/ Check-in [f82fa707]
Login
Overview
Comment:Pager is working, mostly. (CVS 211)
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1:f82fa7070ae281804c019e6b05cd767dadaf0827
User & Date: drh 2001-04-15 00:37:09
Context
2001-04-15
02:27
Working on the pager (CVS 212) check-in: 1f07abe4 user: drh tags: trunk
00:37
Pager is working, mostly. (CVS 211) check-in: f82fa707 user: drh tags: trunk
2001-04-14
16:38
Getting ready to redo the journal file format. (CVS 210) check-in: 42c2f3fe user: drh tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to Makefile.in.

44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

66
67
68
69
70
71
72
..
74
75
76
77
78
79
80






81
82
83
84
85
86
87
...
178
179
180
181
182
183
184



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# The library that programs using readline() must link against.
#
LIBREADLINE = @TARGET_READLINE_LIBS@

# Object files for the SQLite library.
#
LIBOBJ = build.o dbbe.o dbbegdbm.o dbbemem.o delete.o expr.o insert.o \
         main.o parse.o printf.o random.o select.o table.o tokenize.o \
         update.o util.o vdbe.o where.o tclsqlite.o

# All of the source code files.
#
SRC = \
  $(TOP)/src/build.c \
  $(TOP)/src/dbbe.c \
  $(TOP)/src/dbbe.h \
  $(TOP)/src/dbbegdbm.c \
  $(TOP)/src/dbbemem.c \
  $(TOP)/src/delete.c \
  $(TOP)/src/expr.c \
  $(TOP)/src/insert.c \
  $(TOP)/src/main.c \

  $(TOP)/src/parse.y \
  $(TOP)/src/printf.c \
  $(TOP)/src/random.c \
  $(TOP)/src/select.c \
  $(TOP)/src/shell.c \
  $(TOP)/src/sqlite.h.in \
  $(TOP)/src/sqliteInt.h \
................................................................................
  $(TOP)/src/tclsqlite.c \
  $(TOP)/src/tokenize.c \
  $(TOP)/src/update.c \
  $(TOP)/src/util.c \
  $(TOP)/src/vdbe.c \
  $(TOP)/src/vdbe.h \
  $(TOP)/src/where.c







# This is the default Makefile target.  The objects listed here
# are what get build when you type just "make" with no arguments.
#
all:	sqlite.h libsqlite.a sqlite 

# Generate the file "last_change" which contains the date of change
................................................................................

tclsqlite.o:	$(TOP)/src/tclsqlite.c $(HDR)
	$(TCC) $(GDBM_FLAGS) $(TCL_FLAGS) -c $(TOP)/src/tclsqlite.c

printf.o:	$(TOP)/src/printf.c $(HDR)
	$(TCC) $(GDBM_FLAGS) $(TCL_FLAGS) -c $(TOP)/src/printf.c




gdbmdump:	$(TOP)/tool/gdbmdump.c
	$(TCC) $(GDBM_FLAGS) -o gdbmdump $(TOP)/tool/gdbmdump.c $(LIBGDBM)

tclsqlite:	$(TOP)/src/tclsqlite.c libsqlite.a
	$(TCC) $(TCL_FLAGS) -DTCLSH=1 -o sqlite_tester \
		$(TOP)/src/tclsqlite.c libsqlite.a $(LIBGDBM) $(LIBTCL)

sqlite_tester:	$(TOP)/src/tclsqlite.c libsqlite.a $(TOP)/src/test1.c
	$(TCC) $(TCL_FLAGS) -DTCLSH=1 -DSQLITE_TEST1=1 -o sqlite_tester \
		$(TOP)/src/test1.c $(TOP)/src/tclsqlite.c \
		libsqlite.a $(LIBGDBM) $(LIBTCL)

test:	sqlite_tester sqlite
	./sqlite_tester $(TOP)/test/all.test

sqlite.tar.gz:	
	pwd=`pwd`; cd $(TOP)/..; tar czf $$pwd/sqlite.tar.gz sqlite







|
|













>







 







>
>
>
>
>
>







 







>
>
>







|
|
|







44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
..
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
...
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# The library that programs using readline() must link against.
#
LIBREADLINE = @TARGET_READLINE_LIBS@

# Object files for the SQLite library.
#
LIBOBJ = build.o dbbe.o dbbegdbm.o dbbemem.o delete.o expr.o insert.o \
         main.o pager.o parse.o printf.o random.o select.o table.o \
         tokenize.o update.o util.o vdbe.o where.o tclsqlite.o

# All of the source code files.
#
SRC = \
  $(TOP)/src/build.c \
  $(TOP)/src/dbbe.c \
  $(TOP)/src/dbbe.h \
  $(TOP)/src/dbbegdbm.c \
  $(TOP)/src/dbbemem.c \
  $(TOP)/src/delete.c \
  $(TOP)/src/expr.c \
  $(TOP)/src/insert.c \
  $(TOP)/src/main.c \
  $(TOP)/src/pager.c \
  $(TOP)/src/parse.y \
  $(TOP)/src/printf.c \
  $(TOP)/src/random.c \
  $(TOP)/src/select.c \
  $(TOP)/src/shell.c \
  $(TOP)/src/sqlite.h.in \
  $(TOP)/src/sqliteInt.h \
................................................................................
  $(TOP)/src/tclsqlite.c \
  $(TOP)/src/tokenize.c \
  $(TOP)/src/update.c \
  $(TOP)/src/util.c \
  $(TOP)/src/vdbe.c \
  $(TOP)/src/vdbe.h \
  $(TOP)/src/where.c

# Source code to the test files.
#
TESTSRC = \
  $(TOP)/src/test1.c \
  $(TOP)/src/test2.c

# This is the default Makefile target.  The objects listed here
# are what get build when you type just "make" with no arguments.
#
all:	sqlite.h libsqlite.a sqlite 

# Generate the file "last_change" which contains the date of change
................................................................................

tclsqlite.o:	$(TOP)/src/tclsqlite.c $(HDR)
	$(TCC) $(GDBM_FLAGS) $(TCL_FLAGS) -c $(TOP)/src/tclsqlite.c

printf.o:	$(TOP)/src/printf.c $(HDR)
	$(TCC) $(GDBM_FLAGS) $(TCL_FLAGS) -c $(TOP)/src/printf.c

pager.o:	$(TOP)/src/pager.c $(HDR)
	$(TCC) $(GDBM_FLAGS) $(TCL_FLAGS) -c $(TOP)/src/pager.c

gdbmdump:	$(TOP)/tool/gdbmdump.c
	$(TCC) $(GDBM_FLAGS) -o gdbmdump $(TOP)/tool/gdbmdump.c $(LIBGDBM)

tclsqlite:	$(TOP)/src/tclsqlite.c libsqlite.a
	$(TCC) $(TCL_FLAGS) -DTCLSH=1 -o sqlite_tester \
		$(TOP)/src/tclsqlite.c libsqlite.a $(LIBGDBM) $(LIBTCL)

sqlite_tester:	$(TOP)/src/tclsqlite.c libsqlite.a $(TESTSRC)
	$(TCC) $(TCL_FLAGS) -DTCLSH=1 -DSQLITE_TEST=1 -o sqlite_tester \
		$(TESTSRC) $(TOP)/src/tclsqlite.c \
		libsqlite.a $(LIBGDBM) $(LIBTCL)

test:	sqlite_tester sqlite
	./sqlite_tester $(TOP)/test/all.test

sqlite.tar.gz:	
	pwd=`pwd`; cd $(TOP)/..; tar czf $$pwd/sqlite.tar.gz sqlite

Changes to src/build.c.

29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
...
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
...
232
233
234
235
236
237
238
239






240
241
242
243
244
245
246
...
269
270
271
272
273
274
275





276
277
278
279
280
281
282
...
291
292
293
294
295
296
297



298
299
300
301
302
303
304
...
399
400
401
402
403
404
405

406
407
408
409
410
411
412
**     DROP TABLE
**     CREATE INDEX
**     DROP INDEX
**     creating expressions and ID lists
**     COPY
**     VACUUM
**
** $Id: build.c,v 1.27 2001/04/11 14:28:42 drh Exp $
*/
#include "sqliteInt.h"

/*
** This routine is called after a single SQL statement has been
** parsed and we want to execute the VDBE code to implement 
** that statement.  Prior action routines should have already
................................................................................
}

/*
** Remove the memory data structures associated with the given
** Table.  No changes are made to disk by this routine.
**
** This routine just deletes the data structure.  It does not unlink
** the table data structure from the hash table.  But does it destroy
** memory structures of the indices associated with the table.
**
** Indices associated with the table are unlinked from the "db"
** data structure if db!=NULL.  If db==NULL, indices attached to
** the table are deleted, but it is assumed they have already been
** unlinked.
*/
................................................................................
  sqliteDequote(zName);
  return zName;
}

/*
** Begin constructing a new table representation in memory.  This is
** the first of several action routines that get called in response
** to a CREATE TABLE statement.






*/
void sqliteStartTable(Parse *pParse, Token *pStart, Token *pName){
  Table *pTable;
  char *zName;

  pParse->sFirstToken = *pStart;
  zName = sqliteTableNameFromToken(pName);
................................................................................
  pTable->pIndex = 0;
  if( pParse->pNewTable ) sqliteDeleteTable(pParse->db, pParse->pNewTable);
  pParse->pNewTable = pTable;
}

/*
** Add a new column to the table currently being constructed.





*/
void sqliteAddColumn(Parse *pParse, Token *pName){
  Table *p;
  char **pz;
  if( (p = pParse->pNewTable)==0 ) return;
  if( (p->nCol & 0x7)==0 ){
    p->aCol = sqliteRealloc( p->aCol, (p->nCol+8)*sizeof(p->aCol[0]));
................................................................................
  sqliteDequote(*pz);
}

/*
** The given token is the default value for the last column added to
** the table currently under construction.  If "minusFlag" is true, it
** means the value token was preceded by a minus sign.



*/
void sqliteAddDefaultValue(Parse *pParse, Token *pVal, int minusFlag){
  Table *p;
  int i;
  char **pz;
  if( (p = pParse->pNewTable)==0 ) return;
  i = p->nCol-1;
................................................................................
    pParse->nErr++;
  }
  return pTab;
}

/*
** This routine is called to do the work of a DROP TABLE statement.

*/
void sqliteDropTable(Parse *pParse, Token *pName){
  Table *pTable;
  int h;
  Vdbe *v;
  int base;








|







 







|







 







|
>
>
>
>
>
>







 







>
>
>
>
>







 







>
>
>







 







>







29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
...
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
...
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
...
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
...
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
...
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
**     DROP TABLE
**     CREATE INDEX
**     DROP INDEX
**     creating expressions and ID lists
**     COPY
**     VACUUM
**
** $Id: build.c,v 1.28 2001/04/15 00:37:09 drh Exp $
*/
#include "sqliteInt.h"

/*
** This routine is called after a single SQL statement has been
** parsed and we want to execute the VDBE code to implement 
** that statement.  Prior action routines should have already
................................................................................
}

/*
** Remove the memory data structures associated with the given
** Table.  No changes are made to disk by this routine.
**
** This routine just deletes the data structure.  It does not unlink
** the table data structure from the hash table.  But it does destroy
** memory structures of the indices associated with the table.
**
** Indices associated with the table are unlinked from the "db"
** data structure if db!=NULL.  If db==NULL, indices attached to
** the table are deleted, but it is assumed they have already been
** unlinked.
*/
................................................................................
  sqliteDequote(zName);
  return zName;
}

/*
** Begin constructing a new table representation in memory.  This is
** the first of several action routines that get called in response
** to a CREATE TABLE statement.  In particular, this routine is called
** after seeing tokens "CREATE" and "TABLE" and the table name.  The
** pStart token is the CREATE and pName is the table name.
**
** The new table is constructed in files of the pParse structure.  As
** more of the CREATE TABLE statement is parsed, additional action
** routines are called to build up more of the table.
*/
void sqliteStartTable(Parse *pParse, Token *pStart, Token *pName){
  Table *pTable;
  char *zName;

  pParse->sFirstToken = *pStart;
  zName = sqliteTableNameFromToken(pName);
................................................................................
  pTable->pIndex = 0;
  if( pParse->pNewTable ) sqliteDeleteTable(pParse->db, pParse->pNewTable);
  pParse->pNewTable = pTable;
}

/*
** Add a new column to the table currently being constructed.
**
** The parser calls this routine once for each column declaration
** in a CREATE TABLE statement.  sqliteStartTable() gets called
** first to get things going.  Then this routine is called for each
** column.
*/
void sqliteAddColumn(Parse *pParse, Token *pName){
  Table *p;
  char **pz;
  if( (p = pParse->pNewTable)==0 ) return;
  if( (p->nCol & 0x7)==0 ){
    p->aCol = sqliteRealloc( p->aCol, (p->nCol+8)*sizeof(p->aCol[0]));
................................................................................
  sqliteDequote(*pz);
}

/*
** The given token is the default value for the last column added to
** the table currently under construction.  If "minusFlag" is true, it
** means the value token was preceded by a minus sign.
**
** This routine is called by the parser while in the middle of
** parsing a CREATE TABLE statement.
*/
void sqliteAddDefaultValue(Parse *pParse, Token *pVal, int minusFlag){
  Table *p;
  int i;
  char **pz;
  if( (p = pParse->pNewTable)==0 ) return;
  i = p->nCol-1;
................................................................................
    pParse->nErr++;
  }
  return pTab;
}

/*
** This routine is called to do the work of a DROP TABLE statement.
** pName is the name of the table to be dropped.
*/
void sqliteDropTable(Parse *pParse, Token *pName){
  Table *pTable;
  int h;
  Vdbe *v;
  int base;

Changes to src/pager.c.

23
24
25
26
27
28
29
30
31

32
33
34
35
36

37
38
39
40
41
42
43
..
61
62
63
64
65
66
67

68
69
70
71

72
73
74
75
76
77

78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113

114

115
116

117

118
119


























120
121
122
123
124
125
126
127
128
129
130
131
132

133
134


135

136
137
138
139
140
141

142
143


144

145
146
147





































































148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171

172
173
174
175
176
177
178
179

180
181
182
183
184
185
186
...
189
190
191
192
193
194
195
196

197
198
199
200

201
202
203
204
205
206




207
208
209
210

211

212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283


284
285
286
287
288

289
290
291

292
293

294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337

338


339






340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360




361
362
363
364
365
366


367

368
369
370
371
372
373
374
375
376
377
378
379

380
381
382
383
384
385
386
387
388
389
390
391
392

393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
...
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456






457
458
459









460
461
462
463
464
465
466
467
468

469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488


489
490
491
492
493
494
495
496
497
498

499

500

501
502
503

504
505

506
507
508
509






510






511
512



513








514
515
516










517
518

519

520
521
522



523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538

539
540
541
542
543

544
545
546
547
548
549
550
551
552
553

554

555
556

557
558
559
560
561

562
563
564

565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582



583
584
585
586




587
588
589
590
591
592

593
594
595
596



597
598
599
600
601


602
603
604
605
606
607
608
609
610
611
612
613






614
615
616
617
618
619




620
621

622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643

644

645
646
647
648
649
650
651
652
653


654
655
656
657
658
659

660
661
662

663


664
665
666
667
668




669
670
671
672










673


674
675
676
677
678
679
680

681
682
683
684
685
686
687
688
689
690
691


692
693
694







695
696
697
698
699
700
701







702
703
704



705
706
707
708
709


710
711
712


713

714
715

















*************************************************************************
** This is the implementation of the page cache subsystem.
** 
** The page cache is used to access a database file.  The pager journals
** all writes in order to support rollback.  Locking is used to limit
** access to one or more reader or on writer.
**
** @(#) $Id: pager.c,v 1.2 2001/04/14 16:38:23 drh Exp $
*/

#include "pager.h"
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <assert.h>


/*
** The page cache as a whole is always in one of the following
** states:
**
**   SQLITE_UNLOCK       The page cache is not currently reading or 
**                       writing the database file.  There is no
................................................................................
** that sqlite_page_write() is called, the state transitions to
** PCS_WRITELOCK.  The sqlite_page_rollback() and sqlite_page_commit()
** functions transition the state back to PCS_READLOCK.
*/
#define SQLITE_UNLOCK      0
#define SQLITE_READLOCK    1
#define SQLITE_WRITELOCK   2


/*
** Each in-memory image of a page begins with the following header.
*/

struct PgHdr {
  Pager *pPager;                 /* The pager to which this page belongs */
  Pgno pgno;                     /* The page number for this page */
  PgHdr *pNextHash, *pPrevHash;  /* Hash collision chain for PgHdr.pgno */
  int nRef;                      /* Number of users of this page */
  PgHdr *pNext, *pPrev;          /* Freelist of pages where nRef==0 */

  char inJournal;                /* TRUE if has been written to journal */
  char dirty;                    /* TRUE if we need to write back changes */
  /* SQLITE_PAGE_SIZE bytes of page data follow this header */
};

/*
** Convert a pointer to a PgHdr into a pointer to its data
** and back again.
*/
#define PGHDR_TO_DATA(P)  ((void*)(&(P)[1]))
#define DATA_TO_PGHDR(D)  (&((PgHdr*)(D))[-1])

/*
** The number of page numbers that will fit on one page.
*/
#define SQLITE_INDEX_SIZE   (SQLITE_PAGE_SIZE/sizeof(Pgno))

/*
** How big to make the hash table used for locating in-memory pages
** by page number.
*/
#define N_PG_HASH 353

/*
** A open page cache is an instance of the following structure.
*/
struct Pager {
  char *zFilename;            /* Name of the database file */
  char *zJournal;             /* Name of the journal file */
  int fd, jfd;                /* File descriptors for database and journal */
  int nRef;                   /* Sum of PgHdr.nRef */
  int dbSize;                 /* Number of pages in the file */
  int origDbSize;             /* dbSize before the current change */
  int jSize;                  /* Number of pages in the journal */
  int nIdx;                   /* Number of entries in aIdx[] */
  int nPage;                  /* Total number of in-memory pages */

  int mxPage;                 /* Maximum number of pages to hold in cache */

  char state;                 /* SQLITE_UNLOCK, _READLOCK or _WRITELOCK */
  char ioErr;                 /* True if an I/O error has occurred */

  PgHdr *pFirst, *pLast;      /* List of free pages */

  PgHdr *aHash[N_PG_HASH];    /* Hash table to map page number of PgHdr */
  Pgno aIdx[SQLITE_INDEX_SIZE];  /* Current journal index page */


























};

/*
** Hash a page number
*/
#define sqlite_pager_hash(PN)  ((PN)%N_PG_HASH)

/*
** Attempt to acquire a read lock (if wrlock==0) or a write lock (if wrlock==1)
** on the database file.  Return 0 on success and non-zero if the lock 
** could not be acquired.
*/
static int sqlite_pager_lock(int fd, int wrlock){

  struct flock lock;
  lock.l_type = write_lock ? F_WRLCK : F_RDLCK;


  return fcntl(fd, F_SETLK, &lock)!=0;

}

/*
** Unlock the database file.
*/
static int sqlite_pager_unlock(fd){

  struct flock lock;
  lock.l_type = F_UNLCK;


  return fcntl(fd, F_SETLK, &lock)!=0;

}

/*





































































** Find a page in the hash table given its page number.  Return
** a pointer to the page or NULL if not found.
*/
static PgHdr *sqlite_pager_lookup(Pager *pPager, Pgno pgno){
  PgHdr *p = pPager->aHash[pgno % N_PG_HASH];
  while( p && p->pgno!=pgno ){
    p = p->pNextHash;
  }
  return p;
}

/*
** Unlock the database and clear the in-memory cache.  This routine
** sets the state of the pager back to what it was when it was first
** opened.  Any outstanding pages are invalidated and subsequent attempts
** to access those pages will likely result in a coredump.
*/
static void sqlite_pager_reset(Pager *pPager){
  PgHdr *pPg, *pNext;
  for(pPg=pPager->pFirst; pPg; pPg=pNext){
    pNext = pPg->pNext;
    sqlite_free(pPg);
  }
  pPager->pFirst = 0;

  pPager->pNext = 0;
  memset(pPager->aHash, 0, sizeof(pPager->aHash));
  pPager->nPage = 0;
  if( pPager->state==SQLITE_WRITELOCK ){
    sqlite_pager_rollback(pPager);
  }
  sqlite_pager_unlock(pPager->fd);
  pPager->state = SQLITE_UNLOCK;

  pPager->nRef = 0;
}

/*
** When this routine is called, the pager has the journal file open and
** a write lock on the database.  This routine releases the database
** write lock and acquires a read lock in its place.  The journal file
................................................................................
** We have to release the write lock before acquiring the read lock,
** so there is a race condition where another process can get the lock
** while we are not holding it.  But, no other process should do this
** because we are also holding a lock on the journal, and no process
** should get a write lock on the database without first getting a lock
** on the journal.  So this routine should never fail.  But it can fail
** if another process is not playing by the rules.  If it does fail,
** all in-memory cache pages are invalidated and this routine returns

** SQLITE_PROTOCOL.  SQLITE_OK is returned on success.
*/
static int sqlite_pager_unwritelock(Pager *pPager){
  int rc;

  assert( pPager->state==SQLITE_WRITELOCK );
  sqlite_pager_unlock(pPager->fd);
  rc = sqlite_pager_lock(pPager->fd, 0);
  unlink(pPager->zJournal);
  close(pPager->jfd);
  pPager->jfd = -1;




  if( rc!=SQLITE_OK ){
    pPager->state = SQLITE_UNLOCK;
    sqlite_pager_reset(pPager);
    rc = SQLITE_PROTOCOL;

  }else{

    pPager->state = SQLITE_READLOCK;
  }
  return rc;
}


/*
** Playback the journal and thus restore the database file to
** the state it was in before we started making changes.  
**
** A journal consists of multiple segments.  Every segment begins
** with a single page containing SQLITE_INDEX_SIZE page numbers.  This
** first page is called the index.  Most segments have SQLITE_INDEX_SIZE
** additional pages after the index.  The N-th page after the index
** contains the contents of a page in the database file before that
** page was changed.  The N-th entry in the index tells which page
** of the index file the data is for.
**
** The first segment of a journal is formatted slightly differently.
** The first segment contains an index but only SQLITE_INDEX_SIZE-1
** data pages.  The first page number in the index is actually the
** total number of pages in the original file.  This number is used
** to truncate the original database file back to its original size.
** The second number in the index page is the page number for the
** first data page.  And so forth.
**
** We really need to playback the journal beginning at the end
** and working backwards toward the beginning.  That way changes
** to the database are undone in the reverse order from the way they
** were applied.  This is important if the same page is changed
** more than once.  But many operating systems work more efficiently
** if data is read forward instead of backwards.  So for efficiency
** we want to read the data in the forward direction.
**
** This routine starts with the last segment and works backwards
** toward the first.  Within each segment, however, data is read
** in the forward direction for efficiency.  Care is taken that
** only the first appearance of each page is copied over to the
** database file.  If a page appears in the index more than once,
** only the first occurrance is written.  A hash table is used to
** keep track of  which pages have been written and which have not.
*/
static int sqlite_pager_playback(Pager *pPager){
  int nSeg;                           /* Number of segments */
  int i, j;                           /* Loop counters */
  Pgno mxPg = 0;                      /* Size of the original file in pages */
  struct stat statbuf;                /* Used to size the journal */
  Pgno aIndex[SQLITE_INDEX_SIZE];     /* The index page */
  char aBuf[SQLITE_PAGE_SIZE];        /* Page transfer buffer */
  Pgno aHash[SQLITE_INDEX_SIZE*2-1];  /* Hash table for pages read so far */
  int rc;

  /* Figure out how many segments are in the journal.  Remember that
  ** the first segment is one page shorter than the others and that
  ** the last segment may be incomplete.
  */
  if( fstat(pPager->jfd; &statbuf)!=0 ){
    return SQLITE_OK;
  }
  if( statbuf.st_size <= SQLITE_INDEX_SIZE*SQLITE_PAGE_SIZE ){
    nSeg = 1;
  }else{
    int nPage = statbuf.st_size/SQLITE_PAGE_SIZE;
    nPage -= SQLITE_INDEX_SIZE;
    nSeg = 1 + nPage/(SQLITE_INDEX_SIZE+1);
  }

  /* Process segments beginning with the last and working backwards
  ** to the first.
  */
  for(i=nSeg-1; i>=0; i--){
    /* Seek to the beginning of the segment */


    sqlite_pager_seekpage(pPager->jfd, 
        i>0 ? i*(SQLITE_INDEX_SIZE + 1) - 1 : 0,
        SEEK_SET
    );


    /* Initialize the hash table used to avoid copying duplicate pages */
    memset(aHash, 0, sizeof(aHash));


    /* Read the index page */
    sqlite_pager_readpage(pPager->jfd, aIndex);


    /* Extract the original file size from the first index entry if this
    ** is the first segment */   
    if( i==0 ){
      mxPg = aIndex[0];
      aIndex[0] = 0;
    }

    /* Process pages of this segment in forward order
    */
    for(j=0; j<SQLITE_INDEX_SIZE; j++){
      Pgno pgno = aIndex[i];
      void *pBuf;
      PgHdr *pPg;

      /* 0 means "no such page".  Skip zero entries */
      if( pgno==0 ) continue;

      /* Check to see if pgno is in the hash table.  Skip this
      ** entry if it is.
      */
      h = pgno % (SQLITE_PAGE_SIZE-1);
      while( aHash[h]!=0 && aHash[h]!=pgno ){
        h++;
        if( h>=SQLITE_PAGE_SIZE-1 ) h = 0;
      }
      if( aHash[h]==pgno ){
        lseek(pPager->jfd, SQLITE_PAGE_SIZE, SEEK_CUR);
        continue;
      }
      aHash[h] = pgno;

      /* Playback the page.  Update the in-memory copy of the page
      ** at the same time, if there is one.
      */
      pPg = sqlite_pager_lookup(pPager, pgno);
      if( pPg ){
        pBuf = PGHDR_TO_DATA(pPg);
      }else{
        pBuf = aBuf;
      }
      sqlite_pager_readpage(pPager->jfd, pBuf);
      sqlite_pager_seekpage(pPager->fd, pgno, SEEK_SET);
      rc = sqlite_pager_writepage(pPager->fd, pBuf);

      if( rc!=SQLITE_OK ) return rc;


    }






  }

  /* Truncate the database back to its original size
  */
  if( mxPg>0 ){
    ftrucate(pPager->fd, mxPg * SQLITE_PAGE_SIZE);
  }
  return SQLITE_OK;
}

/*
** Create a new page cache and put a pointer to the page cache in *ppPager.
** The file to be cached need not exist.  The file is not opened until
** the first call to sqlite_pager_get() and is only held open until the
** last page is released using sqlite_pager_unref().
*/
int sqlite_pager_open(Pager **ppPager, const char *zFilename, int mxPage){
  Pager *pPager;
  int nameLen;
  int fd;





  fd = open(zFilename, O_RDWR, 0644);
  if( fd<0 ){
    return SQLITE_CANTOPEN;
  }
  nameLen = strlen(zFilename);
  pPager = sqliteMalloc( sizeof(*pPager) + nameLen*2 + 30 );


  if( pPager==0 ) return SQLITE_NOMEM;

  pPager->zFilename = (char*)&pPager[1];
  pPager->zJournal = &pPager->zFilename[nameLen+1];
  strcpy(pPager->zFilename, zFilename);
  strcpy(pPager->zJournal, zFilename);
  strcpy(&pPager->zJournal[nameLen], "-journal");
  pPager->fd = fd;
  pPager->jfd = -1;
  pPager->nRef = 0;
  pPager->dbSize = -1;
  pPager->nPage = 0;
  pPager->mxPage = mxPage>10 ? mxPage : 10;
  pPager->state = SQLITE_UNLOCK;

  pPager->pFirst = 0;
  pPager->pLast = 0;
  memset(pPager->aHash, 0, sizeof(pPager->aHash));
  *ppPager = pPager;
  return SQLITE_OK;
}

/*
** Return the total number of pages in the file opened by pPager.
*/
int sqlite_pager_pagecount(Pager *pPager){
  int n;
  struct stat statbuf;

  if( pPager->dbSize>=0 ){
    return pPager->dbSize;
  }
  if( fstat(pPager->fd, &statbuf)!=0 ){
    n = 0;
  }else{
    n = statbuf.st_size/SQLITE_PAGE_SIZE;
  }
  if( pPager->state!=SQLITE_NOLOCK ){
    pPager->dbSize = n;
  }
  return n;
}

/*
** Shutdown the page cache.  Free all memory and close all files.
................................................................................
**
** If a transaction was in progress when this routine is called, that
** transaction is rolled back.  All outstanding pages are invalidated
** and their memory is freed.  Any attempt to use a page associated
** with this page cache after this function returns will likely
** result in a coredump.
*/
int sqlite_pager_close(Pager *pPager){
  int i;
  PgHdr *pPg;
  switch( pPager->state ){
    case SQLITE_WRITELOCK: {
      sqlite_pager_rollback(pPager);
      sqlite_pager_unlock(pPager->fd);
      break;
    }
    case SQLITE_READLOCK: {
      sqlite_pager_unlock(pPager->fd);
      break;
    }
    default: {
      /* Do nothing */
      break;
    }
  }
  for(i=0; i<N_PG_HASH; i++){
    PgHdr *pNext;
    for(pPg=pPager->aHash[i]; pPg; pPg=pNext){
      pNext = pPg->pNextHash;
      sqliteFree(pPg);
    }
  }
  if( pPager->fd>=0 ) close(pPager->fd);
  assert( pPager->jfd<0 );
  sqliteFree(pPager);
  return SQLITE_OK;
}

/*
** Return the page number for the given page data
*/
int sqlite_pager_pagenumber(void *pData){
  PgHdr *p = DATA_TO_PGHDR(pData);
  return p->pgno;
}

/*
** Acquire a page






*/
int sqlite_pager_get(Pager *pPager, int pgno, void **ppPage){
  PgHdr *pPg;










  /* If this is the first page accessed, then get a read lock
  ** on the database file.
  */
  if( pPager->nRef==0 ){
    if( sqlite_pager_lock(pPager->fd, 0)!=0 ){
      *ppPage = 0;
      return SQLITE_BUSY;
    }


    /* If a journal file exists, try to play it back.
    */
    if( access(pPager->zJournal,0)==0 ){
       int rc;

       /* Open the journal for exclusive access.  Return SQLITE_BUSY if
       ** we cannot get exclusive access to the journal file
       */
       pPager->jfd = open(pPager->zJournal, O_RDONLY, 0);
       if( pPager->jfd<0 || sqlite_pager_lock(pPager->jfd, 1)!=0 ){
         if( pPager->jfd>=0 ){ close(pPager->jfd); pPager->jfd = -1; }
         sqlite_pager_unlock(pPager->fd);
         *ppPage = 0;
         return SQLITE_BUSY;
       }

       /* Get a write lock on the database */
       sqlite_pager_unlock(pPager->fd);
       if( sqlite_pager_lock(pPager->fd, 1)!=0 ){


         *ppPage = 0;
         return SQLITE_PROTOCOL;
       }

       /* Playback and delete the journal.  Drop the database write
       ** lock and reacquire the read lock.
       */
       sqlite_pager_playback(pPager);
       rc = sqlite_pager_unwritelock(pPager);
       if( rc!=SQLITE_OK ){ return SQLITE_PROTOCOL; }

    }

    pPg = 0;

  }else{
    /* Search for page in cache */
    pPg = sqlite_pager_lookup(pPager, pgno);

  }
  if( pPg==0 ){

    int h;
    if( pPager->nPage<pPager->mxPage || pPager->pFirst==0 ){
      /* Create a new page */
      pPg = sqlite_malloc( sizeof(*pPg) + SQLITE_PAGE_SIZE );






      pPg->pPager = pPager;






    }else{
      /* Recycle an older page */



      pPg = pPager->pFirst;








      if( pPg->dirty ){
        int rc;
        sqlite_pager_seekpage(pPager->fd, pPg->pgno, SEEK_SET);










        rc = sqlite_pager_writepage(pPager->fd, PGHDR_TO_DATA(pPg));
        if( rc!=SQLITE_OK ){

          *ppPage = 0;

          return rc;
        }
      } 



      pPager->pFirst = pPg->pNext;
      if( pPager->pFirst ){
        pPager->pFirst->pPrev = 0;
      }else{
        pPager->pLast = 0;
      }
      if( pPg->pNextHash ){
        pPg->pNextHash->pPrevHash = pPg->pPrevHash;
      }
      if( pPg->pPrevHash ){
        pPg->pPrevHash->pNextHash = pPg->pNextHash;
      }else{
        h = sqlite_pager_hash(pPg->pgno);
        assert( pPager->aHash[h]==pPg );
        pPager->aHash[h] = pPg->pNextHash;
      }

    }
    pPg->pgno = pgno;
    pPg->inJournal = 0;
    pPg->dirty = 0;
    pPg->nRef = 1;

    h = sqlite_pager_hash(pgno);
    pPg->pNextHash = pPager->aHash[h];
    pPager->aHash[h] = pPg;
    if( pPg->pNextHash ){
      assert( pPg->pNextHash->pPrevHash==0 );
      pPg->pNextHash->pPrevHash = pPg;
    }
    sqlite_pager_seekpage(pPager->fd, pgno, SEEK_SET);
    sqlite_pager_readpage(pPager->fd, PGHDR_TO_DATA(pPg));
  }else{

    if( pPg->nRef==0 ){

      if( pPg->pPrev ){
        pPg->pPrev->pNext = pPg->pNext;

      }else{
        pPager->pFirst = pPg->pNext;
      }
      if( pPg->pNext ){
        pPg->pNext->pPrev = pPg->pPrev;

      }else{
        pPager->pLast = pPg->pPrev;
      }

    }
    pPg->nRef++;
  }
  *ppPage = PGHDR_TO_DATA(pPg);
  return SQLITE_OK;
}

/*
** Release a page.
**
** If the number of references to the page drop to zero, then the
** page is added to the LRU list.  When all references to all pages
** are released, a rollback occurs, and the lock on the database is
** removed.
*/
int sqlite_pager_unref(void *pData){
  Pager *pPager;
  PgHdr *pPg;



  pPg = DATA_TO_PGHDR(pData);
  assert( pPg->nRef>0 );
  pPager = pPg->pPager;
  pPg->nRef--;




  if( pPg->nRef==0 ){
    pPg->pNext = 0;
    pPg->pPrev = pPager->pLast;
    pPager->pLast = pPg;
    if( pPg->pPrev ){
      pPg->pPrev->pNext = pPg;

    }else{
      pPager->pFirst = pPg;
    }
  }



  pPager->nRef--;
  assert( pPager->nRef>=0 );
  if( pPager->nRef==0 ){
    sqlite_pager_reset(pPager);
  }


}

/*
** Mark a data page as writeable.  The page is written into the journal 
** if it is not there already.  This routine must be called before making
** changes to a page.
**
** The first time this routine is called, the pager creates a new
** journal and acquires a write lock on the database.  If the write
** lock could not be acquired, this routine returns SQLITE_BUSY.  The
** calling routine must check for that routine and be careful not to
** change any page data until this routine returns SQLITE_OK.






*/
int sqlite_pager_write(void *pData){
  PgHdr *pPg = DATA_TO_PGHDR(pData);
  Pager *pPager = pPg->pPager;
  int rc;





  if( pPg->inJournal ){ return SQLITE_OK; }
  if( pPager->state==SQLITE_UNLOCK ){ return SQLITE_PROTOCOL; }

  if( pPager->state==SQLITE_READLOCK ){
    pPager->jfd = open(pPager->zJournal, O_RDWR|O_CREAT, 0644);
    if( pPager->jfd<0 ){
      return SQLITE_CANTOPEN;
    }
    if( sqlite_pager_lock(pPager->jfd, 1) ){
      close(pPager->jfd);
      pPager->jfd = -1;
      return SQLITE_BUSY;
    }
    sqlite_pager_unlock(pPager->fd);
    if( sqlite_pager_lock(pPager->fd, 1) ){
      close(pPager->jfd);
      pPager->jfd = -1;
      pPager->state = SQLITE_UNLOCK;
      sqlite_pager_reset(pPager);
      return SQLITE_PROTOCOL;
    }
    pPager->state = SQLITE_WRITELOCK;
    pPager->jSize = 1;
    pPager->aIdx[0] = pPager->dbSize;
    pPager->origDbSize = pPager->dbSize;

    pPager->nIdx = 1;

  }
  /* Write this page to the journal */
  assert( pPager->jfd>=0 );
  if( pPg->pgno >= pPager->origDbSize ){
    sqlite_pager_seekpage(pPager->fd, pPg->pgno, SEEK_SET);
    rc = sqlite_pager_writepage(pPager->fd, pData);
    pPg->inJournal = 1;
    return rc;
  }


  pPager->aIdx[pPager->nIdx++] = pPg->pgno;
  sqlite_pager_seekpage(pPager->jfd, pPager->jSize++, SEEK_SET);
  rc = sqlite_pager_write(pPager->jfd, pData);
  pPg->inJournal = 1;
  if( pPager->nIdx==SQLITE_INDEX_SIZE ){
    sqlite_pager_seekpage(pPager->jfd, pPager->idxPgno, SEEK_SET);

    rc = sqlite_pager_writepage(pPager->jfd, &pPager->aIdx);
    pPager->nIdx = 0;
    pPager->jSize++;

  }


  return rc;
}

/*
** Commit all changes to the database and release the write lock.




*/
int sqlite_pager_commit(Pager*){
  int i, rc;
  PgHdr *pPg;










  assert( pPager->state==SQLITE_WRITELOCK );


  assert( pPager->jfd>=0 );
  memset(&pPager->aIdx[&pPager->nIdx], 0, 
          (SQLITE_INDEX_SIZE - pPager->nIdx)*sizeof(Pgno));
  sqlite_pager_seekpage(pPager->jfd, pPager->idxPgno, SEEK_SET);
  rc = sqlite_pager_writepage(pPager->jfd, &pPager->aIdx);
  if( fsync(pPager->jfd) ){
    return SQLITE_IOERR;

  }
  for(i=0; i<N_PG_HASH; i++){
    for(pPg=pPager->aHash[i]; pPg; pPg=pPg->pNextHash){
      if( pPg->dirty==0 ) continue;
      rc = sqlite_pager_seekpage(pPager->fd, pPg->pgno, SEEK_SET);
      if( rc!=SQLITE_OK ) return rc;
      rc = sqlite_pager_writePage(pPager->fd, PGHDR_TO_DATA(pPg));
      if( rc!=SQLITE_OK ) return rc;
    }
  }
  if( fsync(pPager->fd) ){


    return SQLITE_IOERR;
  }
  rc = sqlite_pager_unwritelock(pPager);







  return rc;
}

/*
** Rollback all changes.  The database falls back to read-only mode.
** All in-memory cache pages revert to their original data contents.
** The journal is deleted.







*/
int sqlite_pager_rollback(Pager *pPager){
  int rc;



  if( pPager->state!=SQLITE_WRITELOCK ) return SQLITE_OK;
  memset(&pPager->aIdx[&pPager->nIdx], 0, 
          (SQLITE_INDEX_SIZE - pPager->nIdx)*sizeof(Pgno));
  sqlite_pager_seekpage(pPager->jfd, pPager->idxPgno, SEEK_SET);
  rc = sqlite_pager_writepage(pPager->jfd, &pPager->aIdx);


  rc = sqlite_pager_playback(pPager);
  if( rc!=SQLITE_OK ){
    rc = sqlite_pager_unwritelock(pPager);


  }

  return rc;
};
























|

>





>







 







>




>





|
>












<
<
<
<
<




|








<


<
<

>

>
|
<
>

>

<
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>





|






|
>

|
>
>
|
>





|
>


>
>
|
>



>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>



|













|

|
|
|


>
|



|

|

>







 







|
>
|

|

>
|
|
|



>
>
>
>


<

>

>




<





|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<
<
<
<
<




|

>
>
|
<
<
|
<
>
|
<

>
|
|
>
|
<
<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
|
|
|
<
<
|
<
<
<
>
|
>
>
|
>
>
>
>
>
>

<
<
<
<
<
<
|





|
|

|




>
>
>
>
|





>
>
|
>












>










|


>








|







 







|
<
|


|
|



|







<
<
|
|
|
<










|





|
>
>
>
>
>
>

|

>
>
>
>
>
>
>
>
>





|



>










|

|





|
|
>
>







|
<
|
>
|
>

>


|
>


>



|
>
>
>
>
>
>

>
>
>
>
>
>

|
>
>
>

>
>
>
>
>
>
>
>


<
>
>
>
>
>
>
>
>
>
>
|

>

>


|
>
>
>
|

|









|



>





>
|






|
|

>

>
|
<
>

|

|
<
>

|

>












|


|


>
>
>




>
>
>
>

|
|

|
<
>



|
>
>
>
|
|
|
|
|
>
>












>
>
>
>
>
>

|




>
>
>
>

<
>





|




|
|



|



|
<

>
|
>
|
<
|
|
|
<
<
|
|
>
>
|
|
|
|
|
<
>
|
|
|
>
|
>
>





>
>
>
>

|


>
>
>
>
>
>
>
>
>
>
|
>
>

<
<
<
<

<
>




|
|
|
|


|
>
>
|
|
<
>
>
>
>
>
>
>







>
>
>
>
>
>
>

|

>
>
>
|
<
<
<
<
>
>
|

<
>
>

>


>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
..
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94





95
96
97
98
99
100
101
102
103
104
105
106
107

108
109


110
111
112
113
114

115
116
117
118

119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
...
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318

319
320
321
322
323
324
325
326

327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382





383
384
385
386
387
388
389
390
391


392

393
394

395
396
397
398
399
400





401

























402
403
404
405
406
407


408



409
410
411
412
413
414
415
416
417
418
419
420






421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
...
492
493
494
495
496
497
498
499

500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515


516
517
518

519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593

594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639

640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697

698
699
700
701
702

703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741

742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785

786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806

807
808
809
810
811

812
813
814


815
816
817
818
819
820
821
822
823

824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858




859

860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875

876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903




904
905
906
907

908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
*************************************************************************
** This is the implementation of the page cache subsystem.
** 
** The page cache is used to access a database file.  The pager journals
** all writes in order to support rollback.  Locking is used to limit
** access to one or more reader or on writer.
**
** @(#) $Id: pager.c,v 1.3 2001/04/15 00:37:09 drh Exp $
*/
#include "sqliteInt.h"
#include "pager.h"
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>

/*
** The page cache as a whole is always in one of the following
** states:
**
**   SQLITE_UNLOCK       The page cache is not currently reading or 
**                       writing the database file.  There is no
................................................................................
** that sqlite_page_write() is called, the state transitions to
** PCS_WRITELOCK.  The sqlite_page_rollback() and sqlite_page_commit()
** functions transition the state back to PCS_READLOCK.
*/
#define SQLITE_UNLOCK      0
#define SQLITE_READLOCK    1
#define SQLITE_WRITELOCK   2


/*
** Each in-memory image of a page begins with the following header.
*/
typedef struct PgHdr PgHdr;
struct PgHdr {
  Pager *pPager;                 /* The pager to which this page belongs */
  Pgno pgno;                     /* The page number for this page */
  PgHdr *pNextHash, *pPrevHash;  /* Hash collision chain for PgHdr.pgno */
  int nRef;                      /* Number of users of this page */
  PgHdr *pNextFree, *pPrevFree;  /* Freelist of pages where nRef==0 */
  PgHdr *pNextAll, *pPrevAll;    /* A list of all pages */
  char inJournal;                /* TRUE if has been written to journal */
  char dirty;                    /* TRUE if we need to write back changes */
  /* SQLITE_PAGE_SIZE bytes of page data follow this header */
};

/*
** Convert a pointer to a PgHdr into a pointer to its data
** and back again.
*/
#define PGHDR_TO_DATA(P)  ((void*)(&(P)[1]))
#define DATA_TO_PGHDR(D)  (&((PgHdr*)(D))[-1])






/*
** How big to make the hash table used for locating in-memory pages
** by page number.
*/
#define N_PG_HASH 101

/*
** A open page cache is an instance of the following structure.
*/
struct Pager {
  char *zFilename;            /* Name of the database file */
  char *zJournal;             /* Name of the journal file */
  int fd, jfd;                /* File descriptors for database and journal */

  int dbSize;                 /* Number of pages in the file */
  int origDbSize;             /* dbSize before the current change */


  int nPage;                  /* Total number of in-memory pages */
  int nRef;                   /* Number of in-memory pages with PgHdr.nRef>0 */
  int mxPage;                 /* Maximum number of pages to hold in cache */
  int nHit, nMiss, nOvfl;     /* Cache hits, missing, and LRU overflows */
  unsigned char state;        /* SQLITE_UNLOCK, _READLOCK or _WRITELOCK */

  unsigned char errMask;      /* One of several kinds of errors */
  PgHdr *pFirst, *pLast;      /* List of free pages */
  PgHdr *pAll;                /* List of all pages */
  PgHdr *aHash[N_PG_HASH];    /* Hash table to map page number of PgHdr */

};

/*
** These are bits that can be set in Pager.errMask.
*/
#define PAGER_ERR_FULL     0x01  /* a write() failed */
#define PAGER_ERR_MEM      0x02  /* malloc() failed */
#define PAGER_ERR_LOCK     0x04  /* error in the locking protocol */
#define PAGER_ERR_CORRUPT  0x08  /* database or journal corruption */

/*
** The journal file contains page records in the following
** format.
*/
typedef struct PageRecord PageRecord;
struct PageRecord {
  Pgno pgno;                     /* The page number */
  char aData[SQLITE_PAGE_SIZE];  /* Original data for page pgno */
};

/*
** Journal files begin with the following magic string.  This data
** is completely random.  It is used only as a sanity check.
*/
static const unsigned char aJournalMagic[] = {
  0xd9, 0xd5, 0x05, 0xf9, 0x20, 0xa1, 0x63, 0xd4,
};

/*
** Hash a page number
*/
#define pager_hash(PN)  ((PN)%N_PG_HASH)

/*
** Attempt to acquire a read lock (if wrlock==0) or a write lock (if wrlock==1)
** on the database file.  Return 0 on success and non-zero if the lock 
** could not be acquired.
*/
static int pager_lock(int fd, int wrlock){
  int rc;
  struct flock lock;
  lock.l_type = wrlock ? F_WRLCK : F_RDLCK;
  lock.l_whence = SEEK_SET;
  lock.l_start = lock.l_len = 0L;
  rc = fcntl(fd, F_SETLK, &lock);
  return rc!=0;
}

/*
** Unlock the database file.
*/
static int pager_unlock(fd){
  int rc;
  struct flock lock;
  lock.l_type = F_UNLCK;
  lock.l_whence = SEEK_SET;
  lock.l_start = lock.l_len = 0L;
  rc = fcntl(fd, F_SETLK, &lock);
  return rc!=0;
}

/*
** Move the cursor for file descriptor fd to the point whereto from
** the beginning of the file.
*/
static int pager_seek(int fd, off_t whereto){
  lseek(fd, whereto, SEEK_SET);
  return SQLITE_OK;
}

/*
** Truncate the given file so that it contains exactly mxPg pages
** of data.
*/
static int pager_truncate(int fd, Pgno mxPg){
  int rc;
  rc = ftruncate(fd, mxPg*SQLITE_PAGE_SIZE);
  return rc!=0 ? SQLITE_IOERR : SQLITE_OK;
}

/*
** Read nBytes of data from fd into pBuf.  If the data cannot be
** read or only a partial read occurs, then the unread parts of
** pBuf are filled with zeros and this routine returns SQLITE_IOERR.
** If the read is completely successful, return SQLITE_OK.
*/
static int pager_read(int fd, void *pBuf, int nByte){
  int rc;
  rc = read(fd, pBuf, nByte);
  if( rc<0 ){
    memset(pBuf, 0, nByte);
    return SQLITE_IOERR;
  }
  if( rc<nByte ){
    memset(&((char*)pBuf)[rc], 0, nByte - rc);
    rc = SQLITE_IOERR;
  }else{
    rc = SQLITE_OK;
  }
  return rc;
}

/*
** Write nBytes of data into fd.  If any problem occurs or if the
** write is incomplete, SQLITE_IOERR is returned.  SQLITE_OK is
** returned upon complete success.
*/
static int pager_write(int fd, const void *pBuf, int nByte){
  int rc;
  rc = write(fd, pBuf, nByte);
  if( rc<nByte ){
    return SQLITE_FULL;
  }else{
    return SQLITE_OK;
  }
}

/*
** Convert the bits in the pPager->errMask into an approprate
** return code.
*/
static int pager_errcode(Pager *pPager){
  int rc = SQLITE_OK;
  if( pPager->errMask & PAGER_ERR_LOCK )    rc = SQLITE_PROTOCOL;
  if( pPager->errMask & PAGER_ERR_FULL )    rc = SQLITE_FULL;
  if( pPager->errMask & PAGER_ERR_MEM )     rc = SQLITE_NOMEM;
  if( pPager->errMask & PAGER_ERR_CORRUPT ) rc = SQLITE_CORRUPT;
  return rc;
}

/*
** Find a page in the hash table given its page number.  Return
** a pointer to the page or NULL if not found.
*/
static PgHdr *pager_lookup(Pager *pPager, Pgno pgno){
  PgHdr *p = pPager->aHash[pgno % N_PG_HASH];
  while( p && p->pgno!=pgno ){
    p = p->pNextHash;
  }
  return p;
}

/*
** Unlock the database and clear the in-memory cache.  This routine
** sets the state of the pager back to what it was when it was first
** opened.  Any outstanding pages are invalidated and subsequent attempts
** to access those pages will likely result in a coredump.
*/
static void pager_reset(Pager *pPager){
  PgHdr *pPg, *pNext;
  for(pPg=pPager->pAll; pPg; pPg=pNext){
    pNext = pPg->pNextAll;
    sqliteFree(pPg);
  }
  pPager->pFirst = 0;
  pPager->pLast = 0;
  pPager->pAll = 0;
  memset(pPager->aHash, 0, sizeof(pPager->aHash));
  pPager->nPage = 0;
  if( pPager->state==SQLITE_WRITELOCK ){
    sqlitepager_rollback(pPager);
  }
  pager_unlock(pPager->fd);
  pPager->state = SQLITE_UNLOCK;
  pPager->dbSize = -1;
  pPager->nRef = 0;
}

/*
** When this routine is called, the pager has the journal file open and
** a write lock on the database.  This routine releases the database
** write lock and acquires a read lock in its place.  The journal file
................................................................................
** We have to release the write lock before acquiring the read lock,
** so there is a race condition where another process can get the lock
** while we are not holding it.  But, no other process should do this
** because we are also holding a lock on the journal, and no process
** should get a write lock on the database without first getting a lock
** on the journal.  So this routine should never fail.  But it can fail
** if another process is not playing by the rules.  If it does fail,
** all in-memory cache pages are invalidated, the PAGER_ERR_LOCK bit
** is set in pPager->errMask, and this routine returns SQLITE_PROTOCOL.
** SQLITE_OK is returned on success.
*/
static int pager_unwritelock(Pager *pPager){
  int rc;
  PgHdr *pPg;
  if( pPager->state!=SQLITE_WRITELOCK ) return SQLITE_OK;
  pager_unlock(pPager->fd);
  rc = pager_lock(pPager->fd, 0);
  unlink(pPager->zJournal);
  close(pPager->jfd);
  pPager->jfd = -1;
  for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){
    pPg->inJournal = 0;
    pPg->dirty = 0;
  }
  if( rc!=SQLITE_OK ){
    pPager->state = SQLITE_UNLOCK;

    rc = SQLITE_PROTOCOL;
    pPager->errMask |= PAGER_ERR_LOCK;
  }else{
    rc = SQLITE_OK;
    pPager->state = SQLITE_READLOCK;
  }
  return rc;
}


/*
** Playback the journal and thus restore the database file to
** the state it was in before we started making changes.  
**
** The journal file format is as follows:  There is an initial
** file-type string for sanity checking.  Then there is a single
** Pgno number which is the number of pages in the database before
** changes were made.  The database is truncated to this size.
** Next come zero or more page records which each page record
** consists of a Pgno, SQLITE_PAGE_SIZE bytes of data.  
**
** For playback, the pages have to be read from the journal in
** reverse order and put back into the original database file.
**
** If the file opened as the journal file is not a well-formed
** journal file (as determined by looking at the magic number
** at the beginning) then this routine returns SQLITE_PROTOCOL.
** If any other errors occur during playback, the database will
** likely be corrupted, so the PAGER_ERR_CORRUPT bit is set in
** pPager->errMask and SQLITE_CORRUPT is returned.  If it all
** works, then this routine returns SQLITE_OK.
*/
static int pager_playback(Pager *pPager){
  int nRec;                /* Number of Records */
  int i;                   /* Loop counter */
  Pgno mxPg = 0;           /* Size of the original file in pages */
  struct stat statbuf;     /* Used to size the journal */
  PgHdr *pPg;              /* An existing page in the cache */
  PageRecord pgRec;
  unsigned char aMagic[sizeof(aJournalMagic)];
  int rc;

  /* Read the beginning of the journal and truncate the
  ** database file back to its original size.
  */
  assert( pPager->jfd>=0 );
  pager_seek(pPager->jfd, 0);
  rc = pager_read(pPager->jfd, aMagic, sizeof(aMagic));
  if( rc!=SQLITE_OK || memcmp(aMagic,aJournalMagic,sizeof(aMagic))!=0 ){
    return SQLITE_PROTOCOL;
  }
  rc = pager_read(pPager->jfd, &mxPg, sizeof(mxPg));
  if( rc!=SQLITE_OK ){
    return SQLITE_PROTOCOL;
  }
  pager_truncate(pPager->fd, mxPg);
  pPager->dbSize = mxPg;
  
  /* Begin reading the journal beginning at the end and moving
  ** toward the beginning.
  */
  if( fstat(pPager->jfd, &statbuf)!=0 ){
    return SQLITE_OK;
  }
  nRec = (statbuf.st_size - (sizeof(aMagic)+sizeof(Pgno))) / sizeof(PageRecord);






  /* Process segments beginning with the last and working backwards
  ** to the first.
  */
  for(i=nRec-1; i>=0; i--){
    /* Seek to the beginning of the segment */
    off_t ofst;
    ofst = i*sizeof(PageRecord) + sizeof(aMagic) + sizeof(Pgno);
    rc = pager_seek(pPager->jfd, ofst);


    if( rc!=SQLITE_OK ) break;

    rc = pager_read(pPager->jfd, &pgRec, sizeof(pgRec));
    if( rc!=SQLITE_OK ) break;


    /* Sanity checking on the page */
    if( pgRec.pgno>mxPg || pgRec.pgno==0 ){
      rc = SQLITE_CORRUPT;
      break;
    }































    /* Playback the page.  Update the in-memory copy of the page
    ** at the same time, if there is one.
    */
    pPg = pager_lookup(pPager, pgRec.pgno);
    if( pPg ){
      memcpy(PGHDR_TO_DATA(pPg), pgRec.aData, SQLITE_PAGE_SIZE);


    }



    rc = pager_seek(pPager->fd, (pgRec.pgno-1)*SQLITE_PAGE_SIZE);
    if( rc!=SQLITE_OK ) break;
    rc = pager_write(pPager->fd, pgRec.aData, SQLITE_PAGE_SIZE);
    if( rc!=SQLITE_OK ) break;
  }
  if( rc!=SQLITE_OK ){
    pager_unwritelock(pPager);
    pPager->errMask |= PAGER_ERR_CORRUPT;
    rc = SQLITE_CORRUPT;
  }else{
    rc = pager_unwritelock(pPager);
  }






  return rc;
}

/*
** Create a new page cache and put a pointer to the page cache in *ppPager.
** The file to be cached need not exist.  The file is not opened until
** the first call to sqlitepager_get() and is only held open until the
** last page is released using sqlitepager_unref().
*/
int sqlitepager_open(Pager **ppPager, const char *zFilename, int mxPage){
  Pager *pPager;
  int nameLen;
  int fd;

  *ppPager = 0;
  if( sqlite_malloc_failed ){
    return SQLITE_NOMEM;
  }
  fd = open(zFilename, O_RDWR|O_CREAT, 0644);
  if( fd<0 ){
    return SQLITE_CANTOPEN;
  }
  nameLen = strlen(zFilename);
  pPager = sqliteMalloc( sizeof(*pPager) + nameLen*2 + 30 );
  if( pPager==0 ){
    close(fd);
    return SQLITE_NOMEM;
  }
  pPager->zFilename = (char*)&pPager[1];
  pPager->zJournal = &pPager->zFilename[nameLen+1];
  strcpy(pPager->zFilename, zFilename);
  strcpy(pPager->zJournal, zFilename);
  strcpy(&pPager->zJournal[nameLen], "-journal");
  pPager->fd = fd;
  pPager->jfd = -1;
  pPager->nRef = 0;
  pPager->dbSize = -1;
  pPager->nPage = 0;
  pPager->mxPage = mxPage>10 ? mxPage : 10;
  pPager->state = SQLITE_UNLOCK;
  pPager->errMask = 0;
  pPager->pFirst = 0;
  pPager->pLast = 0;
  memset(pPager->aHash, 0, sizeof(pPager->aHash));
  *ppPager = pPager;
  return SQLITE_OK;
}

/*
** Return the total number of pages in the file opened by pPager.
*/
int sqlitepager_pagecount(Pager *pPager){
  int n;
  struct stat statbuf;
  assert( pPager!=0 );
  if( pPager->dbSize>=0 ){
    return pPager->dbSize;
  }
  if( fstat(pPager->fd, &statbuf)!=0 ){
    n = 0;
  }else{
    n = statbuf.st_size/SQLITE_PAGE_SIZE;
  }
  if( pPager->state!=SQLITE_UNLOCK ){
    pPager->dbSize = n;
  }
  return n;
}

/*
** Shutdown the page cache.  Free all memory and close all files.
................................................................................
**
** If a transaction was in progress when this routine is called, that
** transaction is rolled back.  All outstanding pages are invalidated
** and their memory is freed.  Any attempt to use a page associated
** with this page cache after this function returns will likely
** result in a coredump.
*/
int sqlitepager_close(Pager *pPager){

  PgHdr *pPg, *pNext;
  switch( pPager->state ){
    case SQLITE_WRITELOCK: {
      sqlitepager_rollback(pPager);
      pager_unlock(pPager->fd);
      break;
    }
    case SQLITE_READLOCK: {
      pager_unlock(pPager->fd);
      break;
    }
    default: {
      /* Do nothing */
      break;
    }
  }


  for(pPg=pPager->pAll; pPg; pPg=pNext){
    pNext = pPg->pNextAll;
    sqliteFree(pPg);

  }
  if( pPager->fd>=0 ) close(pPager->fd);
  assert( pPager->jfd<0 );
  sqliteFree(pPager);
  return SQLITE_OK;
}

/*
** Return the page number for the given page data
*/
Pgno sqlitepager_pagenumber(void *pData){
  PgHdr *p = DATA_TO_PGHDR(pData);
  return p->pgno;
}

/*
** Acquire a page.
**
** A read lock is obtained for the first page acquired.  The lock
** is dropped when the last page is released.  
**
** The acquisition might fail for several reasons.  In all cases,
** an appropriate error code is returned and *ppPage is set to NULL.
*/
int sqlitepager_get(Pager *pPager, Pgno pgno, void **ppPage){
  PgHdr *pPg;

  /* Make sure we have not hit any critical errors.
  */ 
  if( pPager==0 || pgno==0 ){
    return SQLITE_ERROR;
  }
  if( pPager->errMask & ~(PAGER_ERR_FULL) ){
    return pager_errcode(pPager);
  }

  /* If this is the first page accessed, then get a read lock
  ** on the database file.
  */
  if( pPager->nRef==0 ){
    if( pager_lock(pPager->fd, 0)!=0 ){
      *ppPage = 0;
      return SQLITE_BUSY;
    }
    pPager->state = SQLITE_READLOCK;

    /* If a journal file exists, try to play it back.
    */
    if( access(pPager->zJournal,0)==0 ){
       int rc;

       /* Open the journal for exclusive access.  Return SQLITE_BUSY if
       ** we cannot get exclusive access to the journal file
       */
       pPager->jfd = open(pPager->zJournal, O_RDONLY, 0);
       if( pPager->jfd<0 || pager_lock(pPager->jfd, 1)!=0 ){
         if( pPager->jfd>=0 ){ close(pPager->jfd); pPager->jfd = -1; }
         pager_unlock(pPager->fd);
         *ppPage = 0;
         return SQLITE_BUSY;
       }

       /* Get a write lock on the database */
       pager_unlock(pPager->fd);
       if( pager_lock(pPager->fd, 1)!=0 ){
         close(pPager->jfd);
         pPager->jfd = -1;
         *ppPage = 0;
         return SQLITE_PROTOCOL;
       }

       /* Playback and delete the journal.  Drop the database write
       ** lock and reacquire the read lock.
       */
       rc = pager_playback(pPager);

       if( rc!=SQLITE_OK ){
         return rc;
       }
    }
    pPg = 0;
    pPager->nMiss++;
  }else{
    /* Search for page in cache */
    pPg = pager_lookup(pPager, pgno);
    pPager->nHit++;
  }
  if( pPg==0 ){
    /* The requested page is not in the page cache. */
    int h;
    if( pPager->nPage<pPager->mxPage || pPager->pFirst==0 ){
      /* Create a new page */
      pPg = sqliteMalloc( sizeof(*pPg) + SQLITE_PAGE_SIZE );
      if( pPg==0 ){
        *ppPage = 0;
        pager_unwritelock(pPager);
        pPager->errMask |= PAGER_ERR_MEM;
        return SQLITE_NOMEM;
      }
      pPg->pPager = pPager;
      pPg->pNextAll = pPager->pAll;
      if( pPager->pAll ){
        pPager->pAll->pPrevAll = pPg;
      }
      pPg->pPrevAll = 0;
      pPager->nPage++;
    }else{
      /* Recycle an older page.  First locate the page to be recycled.
      ** Try to find one that is not dirty and is near the head of
      ** of the free list */
      int cnt = 4;
      pPg = pPager->pFirst;
      while( pPg->dirty && 0<cnt-- ){
        pPg = pPg->pNextFree;
      }
      if( pPg==0 || pPg->dirty ) pPg = pPager->pFirst;
      assert( pPg->nRef==0 );

      /* If the page to be recycled is dirty, sync the journal and write 
      ** the old page into the database. */
      if( pPg->dirty ){
        int rc;

        assert( pPg->inJournal==1 );
        assert( pPager->state==SQLITE_WRITELOCK );
        rc = fsync(pPager->jfd);
        if( rc!=0 ){
          rc = sqlitepager_rollback(pPager);
          *ppPage = 0;
          if( rc==SQLITE_OK ) rc = SQLITE_IOERR;
          return rc;
        }
        pager_seek(pPager->fd, (pPg->pgno-1)*SQLITE_PAGE_SIZE);
        rc = pager_write(pPager->fd, PGHDR_TO_DATA(pPg), SQLITE_PAGE_SIZE);
        if( rc!=SQLITE_OK ){
          rc = sqlitepager_rollback(pPager);
          *ppPage = 0;
          if( rc==SQLITE_OK ) rc = SQLITE_FULL;
          return rc;
        }
      }

      /* Unlink the old page from the free list and the hash table
      */
      pPager->pFirst = pPg->pNextFree;
      if( pPager->pFirst ){
        pPager->pFirst->pPrevFree = 0;
      }else{
        pPager->pLast = 0;
      }
      if( pPg->pNextHash ){
        pPg->pNextHash->pPrevHash = pPg->pPrevHash;
      }
      if( pPg->pPrevHash ){
        pPg->pPrevHash->pNextHash = pPg->pNextHash;
      }else{
        h = pager_hash(pPg->pgno);
        assert( pPager->aHash[h]==pPg );
        pPager->aHash[h] = pPg->pNextHash;
      }
      pPager->nOvfl++;
    }
    pPg->pgno = pgno;
    pPg->inJournal = 0;
    pPg->dirty = 0;
    pPg->nRef = 1;
    pPager->nRef++;
    h = pager_hash(pgno);
    pPg->pNextHash = pPager->aHash[h];
    pPager->aHash[h] = pPg;
    if( pPg->pNextHash ){
      assert( pPg->pNextHash->pPrevHash==0 );
      pPg->pNextHash->pPrevHash = pPg;
    }
    pager_seek(pPager->fd, (pgno-1)*SQLITE_PAGE_SIZE);
    pager_read(pPager->fd, PGHDR_TO_DATA(pPg), SQLITE_PAGE_SIZE);
  }else{
    /* The requested page is in the page cache. */
    if( pPg->nRef==0 ){
      /* The page is currently on the freelist.  Remove it. */
      if( pPg->pPrevFree ){

        pPg->pPrevFree->pNextFree = pPg->pNextFree;
      }else{
        pPager->pFirst = pPg->pNextFree;
      }
      if( pPg->pNextFree ){

        pPg->pNextFree->pPrevFree = pPg->pPrevFree;
      }else{
        pPager->pLast = pPg->pPrevFree;
      }
      pPager->nRef++;
    }
    pPg->nRef++;
  }
  *ppPage = PGHDR_TO_DATA(pPg);
  return SQLITE_OK;
}

/*
** Release a page.
**
** If the number of references to the page drop to zero, then the
** page is added to the LRU list.  When all references to all pages
** are released, a rollback occurs and the lock on the database is
** removed.
*/
int sqlitepager_unref(void *pData){
  Pager *pPager;
  PgHdr *pPg;

  /* Decrement the reference count for this page
  */
  pPg = DATA_TO_PGHDR(pData);
  assert( pPg->nRef>0 );
  pPager = pPg->pPager;
  pPg->nRef--;

  /* When the number of references to a page reach 0, add the
  ** page to the freelist.
  */
  if( pPg->nRef==0 ){
    pPg->pNextFree = 0;
    pPg->pPrevFree = pPager->pLast;
    pPager->pLast = pPg;
    if( pPg->pPrevFree ){

      pPg->pPrevFree->pNextFree = pPg;
    }else{
      pPager->pFirst = pPg;
    }
  
    /* When all pages reach the freelist, drop the read lock from
    ** the database file.
    */
    pPager->nRef--;
    assert( pPager->nRef>=0 );
    if( pPager->nRef==0 ){
      pager_reset(pPager);
    }
  }
  return SQLITE_OK;
}

/*
** Mark a data page as writeable.  The page is written into the journal 
** if it is not there already.  This routine must be called before making
** changes to a page.
**
** The first time this routine is called, the pager creates a new
** journal and acquires a write lock on the database.  If the write
** lock could not be acquired, this routine returns SQLITE_BUSY.  The
** calling routine must check for that routine and be careful not to
** change any page data until this routine returns SQLITE_OK.
**
** If the journal file could not be written because the disk is full,
** then this routine returns SQLITE_FULL and does an immediate rollback.
** All subsequent write attempts also return SQLITE_FULL until there
** is a call to sqlitepager_commit() or sqlitepager_rollback() to
** reset.
*/
int sqlitepager_write(void *pData){
  PgHdr *pPg = DATA_TO_PGHDR(pData);
  Pager *pPager = pPg->pPager;
  int rc;

  if( pPager->errMask ){ 
    return pager_errcode(pPager);
  }
  pPg->dirty = 1;
  if( pPg->inJournal ){ return SQLITE_OK; }

  assert( pPager->state!=SQLITE_UNLOCK );
  if( pPager->state==SQLITE_READLOCK ){
    pPager->jfd = open(pPager->zJournal, O_RDWR|O_CREAT, 0644);
    if( pPager->jfd<0 ){
      return SQLITE_CANTOPEN;
    }
    if( pager_lock(pPager->jfd, 1) ){
      close(pPager->jfd);
      pPager->jfd = -1;
      return SQLITE_BUSY;
    }
    pager_unlock(pPager->fd);
    if( pager_lock(pPager->fd, 1) ){
      close(pPager->jfd);
      pPager->jfd = -1;
      pPager->state = SQLITE_UNLOCK;
      pPager->errMask |= PAGER_ERR_LOCK;
      return SQLITE_PROTOCOL;
    }
    pPager->state = SQLITE_WRITELOCK;
    sqlitepager_pagecount(pPager);

    pPager->origDbSize = pPager->dbSize;
    rc = pager_write(pPager->jfd, aJournalMagic, sizeof(aJournalMagic));
    if( rc==SQLITE_OK ){
      rc = pager_write(pPager->jfd, &pPager->dbSize, sizeof(Pgno));
    }

    if( rc!=SQLITE_OK ){
      rc = pager_unwritelock(pPager);
      if( rc==SQLITE_OK ) rc = SQLITE_FULL;


      return rc;
    }
  }
  assert( pPager->state==SQLITE_WRITELOCK );
  assert( pPager->jfd>=0 );
  if( pPg->pgno <= pPager->origDbSize ){
    rc = pager_write(pPager->jfd, &pPg->pgno, sizeof(Pgno));
    if( rc==SQLITE_OK ){
      rc = pager_write(pPager->jfd, pData, SQLITE_PAGE_SIZE);

    }
    if( rc!=SQLITE_OK ){
      sqlitepager_rollback(pPager);
      pPager->errMask |= PAGER_ERR_FULL;
      return rc;
    }
  }
  pPg->inJournal = 1;
  return rc;
}

/*
** Commit all changes to the database and release the write lock.
**
** If the commit fails for any reason, a rollback attempt is made
** and an error code is returned.  If the commit worked, SQLITE_OK
** is returned.
*/
int sqlitepager_commit(Pager *pPager){
  int i, rc;
  PgHdr *pPg;

  if( pPager->errMask==PAGER_ERR_FULL ){
    rc = sqlitepager_rollback(pPager);
    if( rc==SQLITE_OK ) rc = SQLITE_FULL;
    return rc;
  }
  if( pPager->errMask!=0 ){
    rc = pager_errcode(pPager);
    return rc;
  }
  if( pPager->state!=SQLITE_WRITELOCK ){
    return SQLITE_ERROR;
  }
  assert( pPager->jfd>=0 );




  if( fsync(pPager->jfd) ){

    goto commit_abort;
  }
  for(i=0; i<N_PG_HASH; i++){
    for(pPg=pPager->aHash[i]; pPg; pPg=pPg->pNextHash){
      if( pPg->dirty==0 ) continue;
      rc = pager_seek(pPager->fd, (pPg->pgno-1)*SQLITE_PAGE_SIZE);
      if( rc!=SQLITE_OK ) goto commit_abort;
      rc = pager_write(pPager->fd, PGHDR_TO_DATA(pPg), SQLITE_PAGE_SIZE);
      if( rc!=SQLITE_OK ) goto commit_abort;
    }
  }
  if( fsync(pPager->fd) ) goto commit_abort;
  rc = pager_unwritelock(pPager);
  pPager->dbSize = -1;
  return rc;


  /* Jump here if anything goes wrong during the commit process.
  */
commit_abort:
  rc = sqlitepager_rollback(pPager);
  if( rc==SQLITE_OK ){
    rc = SQLITE_FULL;
  }
  return rc;
}

/*
** Rollback all changes.  The database falls back to read-only mode.
** All in-memory cache pages revert to their original data contents.
** The journal is deleted.
**
** This routine cannot fail unless some other process is not following
** the correct locking protocol (SQLITE_PROTOCOL) or unless some other
** process is writing trash into the journal file (SQLITE_CORRUPT) or
** unless a prior malloc() failed (SQLITE_NOMEM).  Appropriate error
** codes are returned for all these occasions.  Otherwise,
** SQLITE_OK is returned.
*/
int sqlitepager_rollback(Pager *pPager){
  int rc;
  if( pPager->errMask!=0 && pPager->errMask!=PAGER_ERR_FULL ){
    return pager_errcode(pPager);
  }
  if( pPager->state!=SQLITE_WRITELOCK ){




    return SQLITE_OK;
  }
  rc = pager_playback(pPager);
  if( rc!=SQLITE_OK ){

    rc = SQLITE_CORRUPT;
    pPager->errMask |= PAGER_ERR_CORRUPT;
  }
  pPager->dbSize = -1;
  return rc;
};

/*
** This routine is used for testing and analysis only.
*/
int *sqlitepager_stats(Pager *pPager){
  static int a[9];
  a[0] = pPager->nRef;
  a[1] = pPager->nPage;
  a[2] = pPager->mxPage;
  a[3] = pPager->dbSize;
  a[4] = pPager->state;
  a[5] = pPager->errMask;
  a[6] = pPager->nHit;
  a[7] = pPager->nMiss;
  a[8] = pPager->nOvfl;
  return a;
}

Changes to src/pager.h.

21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
..
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56


**   http://www.hwaci.com/drh/
**
*************************************************************************
** This header file defines the interface that the sqlite page cache
** subsystem.  The page cache subsystem reads and writes a file a page
** at a time and provides a journal for rollback.
**
** @(#) $Id: pager.h,v 1.1 2001/04/03 16:53:22 drh Exp $
*/
#include "sqliteInt.h"

/*
** The size of one page
*/
#define SQLITE_PAGE_SIZE 1024

/*
................................................................................
typedef unsigned int Pgno;

/*
** Each open file is managed by a separate instance of the "Pager" structure.
*/
typedef struct Pager Pager;

int sqlite_pager_open(Pager **ppPager, const char *zFilename);
int sqlite_pager_close(Pager *pPager);
int sqlite_pager_get(Pager *pPager, Pgno pgno, void **ppPage);
int sqlite_pager_unref(void*);
Pgno sqlite_pager_pagenumber(void*);
int sqlite_pager_write(void*);
int sqlite_pager_pagecount(Pager*);
int sqlite_pager_commit(Pager*);
int sqlite_pager_rollback(Pager*);









|

<







 







|
|
|
|
|
|
|
|
|
>
>
21
22
23
24
25
26
27
28
29

30
31
32
33
34
35
36
..
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
**   http://www.hwaci.com/drh/
**
*************************************************************************
** This header file defines the interface that the sqlite page cache
** subsystem.  The page cache subsystem reads and writes a file a page
** at a time and provides a journal for rollback.
**
** @(#) $Id: pager.h,v 1.2 2001/04/15 00:37:09 drh Exp $
*/


/*
** The size of one page
*/
#define SQLITE_PAGE_SIZE 1024

/*
................................................................................
typedef unsigned int Pgno;

/*
** Each open file is managed by a separate instance of the "Pager" structure.
*/
typedef struct Pager Pager;

int sqlitepager_open(Pager **ppPager, const char *zFilename, int nPage);
int sqlitepager_close(Pager *pPager);
int sqlitepager_get(Pager *pPager, Pgno pgno, void **ppPage);
int sqlitepager_unref(void*);
Pgno sqlitepager_pagenumber(void*);
int sqlitepager_write(void*);
int sqlitepager_pagecount(Pager*);
int sqlitepager_commit(Pager*);
int sqlitepager_rollback(Pager*);

int *sqlitepager_stats(Pager*);

Changes to src/tclsqlite.c.

19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
...
503
504
505
506
507
508
509
510
511
512

513

514
515
516
517
518
519
520
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*************************************************************************
** A TCL Interface to SQLite
**
** $Id: tclsqlite.c,v 1.17 2001/04/07 15:24:33 drh Exp $
*/
#ifndef NO_TCL     /* Omit this whole file if TCL is unavailable */

#include "sqlite.h"
#include "tcl.h"
#include <stdlib.h>
#include <string.h>
................................................................................

#define TCLSH_MAIN main   /* Needed to fake out mktclapp */
int TCLSH_MAIN(int argc, char **argv){
  Tcl_Interp *interp;
  Tcl_FindExecutable(argv[0]);
  interp = Tcl_CreateInterp();
  Sqlite_Init(interp);
#ifdef SQLITE_TEST1
  {
    extern int Sqlitetest1_Init(Tcl_Interp*);

    Sqlitetest1_Init(interp);

  }
#endif
  if( argc>=2 ){
    int i;
    Tcl_SetVar(interp,"argv0",argv[1],TCL_GLOBAL_ONLY);
    Tcl_SetVar(interp,"argv", "", TCL_GLOBAL_ONLY);
    for(i=2; i<argc; i++){







|







 







|


>

>







19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
...
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*************************************************************************
** A TCL Interface to SQLite
**
** $Id: tclsqlite.c,v 1.18 2001/04/15 00:37:09 drh Exp $
*/
#ifndef NO_TCL     /* Omit this whole file if TCL is unavailable */

#include "sqlite.h"
#include "tcl.h"
#include <stdlib.h>
#include <string.h>
................................................................................

#define TCLSH_MAIN main   /* Needed to fake out mktclapp */
int TCLSH_MAIN(int argc, char **argv){
  Tcl_Interp *interp;
  Tcl_FindExecutable(argv[0]);
  interp = Tcl_CreateInterp();
  Sqlite_Init(interp);
#ifdef SQLITE_TEST
  {
    extern int Sqlitetest1_Init(Tcl_Interp*);
    extern int Sqlitetest2_Init(Tcl_Interp*);
    Sqlitetest1_Init(interp);
    Sqlitetest2_Init(interp);
  }
#endif
  if( argc>=2 ){
    int i;
    Tcl_SetVar(interp,"argv0",argv[1],TCL_GLOBAL_ONLY);
    Tcl_SetVar(interp,"argv", "", TCL_GLOBAL_ONLY);
    for(i=2; i<argc; i++){

Added src/test2.c.

































































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
/*
** Copyright (c) 2001 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public
** License as published by the Free Software Foundation; either
** version 2 of the License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
** General Public License for more details.
** 
** You should have received a copy of the GNU General Public
** License along with this library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA  02111-1307, USA.
**
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*************************************************************************
** Code for testing the pager.c module in SQLite.  This code
** is not included in the SQLite library.  It is used for automated
** testing of the SQLite library.
**
** $Id: test2.c,v 1.1 2001/04/15 00:37:09 drh Exp $
*/
#include "sqliteInt.h"
#include "pager.h"
#include "tcl.h"
#include <stdlib.h>
#include <string.h>

/*
** Interpret an SQLite error number
*/
static char *errorName(int rc){
  char *zName;
  switch( rc ){
    case SQLITE_OK:         zName = "SQLITE_OK";          break;
    case SQLITE_ERROR:      zName = "SQLITE_ERROR";       break;
    case SQLITE_INTERNAL:   zName = "SQLITE_INTERNAL";    break;
    case SQLITE_PERM:       zName = "SQLITE_PERM";        break;
    case SQLITE_ABORT:      zName = "SQLITE_ABORT";       break;
    case SQLITE_BUSY:       zName = "SQLITE_BUSY";        break;
    case SQLITE_NOMEM:      zName = "SQLITE_NOMEM";       break;
    case SQLITE_READONLY:   zName = "SQLITE_READONLY";    break;
    case SQLITE_INTERRUPT:  zName = "SQLITE_INTERRUPT";   break;
    case SQLITE_IOERR:      zName = "SQLITE_IOERR";       break;
    case SQLITE_CORRUPT:    zName = "SQLITE_CORRUPT";     break;
    case SQLITE_NOTFOUND:   zName = "SQLITE_NOTFOUND";    break;
    case SQLITE_FULL:       zName = "SQLITE_FULL";        break;
    case SQLITE_CANTOPEN:   zName = "SQLITE_CANTOPEN";    break;
    case SQLITE_PROTOCOL:   zName = "SQLITE_PROTOCOL";    break;
    default:                zName = "SQLITE_Unknown";     break;
  }
  return zName;
}

/*
** Usage:   pager_open FILENAME N-PAGE
**
** Open a new pager
*/
static int pager_open(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  Pager *pPager;
  int nPage;
  int rc;
  char zBuf[100];
  if( argc!=3 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
       " FILENAME N-PAGE\"", 0);
    return TCL_ERROR;
  }
  if( Tcl_GetInt(interp, argv[2], &nPage) ) return TCL_ERROR;
  rc = sqlitepager_open(&pPager, argv[1], nPage);
  if( rc!=SQLITE_OK ){
    Tcl_AppendResult(interp, errorName(rc), 0);
    return TCL_ERROR;
  }
  sprintf(zBuf,"0x%x",(int)pPager);
  Tcl_AppendResult(interp, zBuf, 0);
  return TCL_OK;
}

/*
** Usage:   pager_close ID
**
** Close the given pager.
*/
static int pager_close(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  Pager *pPager;
  int rc;
  if( argc!=2 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
       " ID\"", 0);
    return TCL_ERROR;
  }
  if( Tcl_GetInt(interp, argv[1], (int*)&pPager) ) return TCL_ERROR;
  rc = sqlitepager_close(pPager);
  if( rc!=SQLITE_OK ){
    Tcl_AppendResult(interp, errorName(rc), 0);
    return TCL_ERROR;
  }
  return TCL_OK;
}

/*
** Usage:   pager_rollback ID
**
** Rollback changes
*/
static int pager_rollback(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  Pager *pPager;
  int rc;
  if( argc!=2 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
       " ID\"", 0);
    return TCL_ERROR;
  }
  if( Tcl_GetInt(interp, argv[1], (int*)&pPager) ) return TCL_ERROR;
  rc = sqlitepager_rollback(pPager);
  if( rc!=SQLITE_OK ){
    Tcl_AppendResult(interp, errorName(rc), 0);
    return TCL_ERROR;
  }
  return TCL_OK;
}

/*
** Usage:   pager_commit ID
**
** Commit all changes
*/
static int pager_commit(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  Pager *pPager;
  int rc;
  if( argc!=2 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
       " ID\"", 0);
    return TCL_ERROR;
  }
  if( Tcl_GetInt(interp, argv[1], (int*)&pPager) ) return TCL_ERROR;
  rc = sqlitepager_commit(pPager);
  if( rc!=SQLITE_OK ){
    Tcl_AppendResult(interp, errorName(rc), 0);
    return TCL_ERROR;
  }
  return TCL_OK;
}

/*
** Usage:   pager_stats ID
**
** Return pager statistics.
*/
static int pager_stats(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  Pager *pPager;
  int i, *a;
  if( argc!=2 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
       " ID\"", 0);
    return TCL_ERROR;
  }
  if( Tcl_GetInt(interp, argv[1], (int*)&pPager) ) return TCL_ERROR;
  a = sqlitepager_stats(pPager);
  for(i=0; i<9; i++){
    static char *zName[] = {
      "ref", "page", "max", "size", "state", "err",
      "hit", "miss", "ovfl",
    };
    char zBuf[100];
    Tcl_AppendElement(interp, zName[i]);
    sprintf(zBuf,"%d",a[i]);
    Tcl_AppendElement(interp, zBuf);
  }
  return TCL_OK;
}

/*
** Usage:   pager_pagecount ID
**
** Return the size of the database file.
*/
static int pager_pagecount(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  Pager *pPager;
  char zBuf[100];
  if( argc!=2 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
       " ID\"", 0);
    return TCL_ERROR;
  }
  if( Tcl_GetInt(interp, argv[1], (int*)&pPager) ) return TCL_ERROR;
  sprintf(zBuf,"%d",sqlitepager_pagecount(pPager));
  Tcl_AppendResult(interp, zBuf, 0);
  return TCL_OK;
}

/*
** Usage:   page_get ID PGNO
**
** Return a pointer to a page from the database.
*/
static int page_get(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  Pager *pPager;
  char zBuf[100];
  void *pPage;
  int pgno;
  int rc;
  if( argc!=3 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
       " ID PGNO\"", 0);
    return TCL_ERROR;
  }
  if( Tcl_GetInt(interp, argv[1], (int*)&pPager) ) return TCL_ERROR;
  if( Tcl_GetInt(interp, argv[2], &pgno) ) return TCL_ERROR;
  rc = sqlitepager_get(pPager, pgno, &pPage);
  if( rc!=SQLITE_OK ){
    Tcl_AppendResult(interp, errorName(rc), 0);
    return TCL_ERROR;
  }
  sprintf(zBuf,"0x%x",(int)pPage);
  Tcl_AppendResult(interp, zBuf, 0);
  return TCL_OK;
}

/*
** Usage:   page_unref PAGE
**
** Drop a pointer to a page.
*/
static int page_unref(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  void *pPage;
  int rc;
  if( argc!=2 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
       " PAGE\"", 0);
    return TCL_ERROR;
  }
  if( Tcl_GetInt(interp, argv[1], (int*)&pPage) ) return TCL_ERROR;
  rc = sqlitepager_unref(pPage);
  if( rc!=SQLITE_OK ){
    Tcl_AppendResult(interp, errorName(rc), 0);
    return TCL_ERROR;
  }
  return TCL_OK;
}

/*
** Usage:   page_read PAGE
**
** Return the content of a page
*/
static int page_read(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  char zBuf[100];
  void *pPage;
  if( argc!=2 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
       " PAGE\"", 0);
    return TCL_ERROR;
  }
  if( Tcl_GetInt(interp, argv[1], (int*)&pPage) ) return TCL_ERROR;
  memcpy(zBuf, pPage, sizeof(zBuf));
  Tcl_AppendResult(interp, zBuf, 0);
  return TCL_OK;
}

/*
** Usage:   page_number PAGE
**
** Return the page number for a page.
*/
static int page_number(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  char zBuf[100];
  void *pPage;
  if( argc!=2 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
       " PAGE\"", 0);
    return TCL_ERROR;
  }
  if( Tcl_GetInt(interp, argv[1], (int*)&pPage) ) return TCL_ERROR;
  sprintf(zBuf, "%d", sqlitepager_pagenumber(pPage));
  Tcl_AppendResult(interp, zBuf, 0);
  return TCL_OK;
}

/*
** Usage:   page_write PAGE DATA
**
** Write something into a page.
*/
static int page_write(
  void *NotUsed,
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int argc,              /* Number of arguments */
  char **argv            /* Text of each argument */
){
  void *pPage;
  int rc;
  if( argc!=3 ){
    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
       " PAGE DATA\"", 0);
    return TCL_ERROR;
  }
  if( Tcl_GetInt(interp, argv[1], (int*)&pPage) ) return TCL_ERROR;
  rc = sqlitepager_write(pPage);
  if( rc!=SQLITE_OK ){
    Tcl_AppendResult(interp, errorName(rc), 0);
    return TCL_ERROR;
  }
  strncpy((char*)pPage, argv[2], SQLITE_PAGE_SIZE-1);
  ((char*)pPage)[SQLITE_PAGE_SIZE-1] = 0;
  return TCL_OK;
}

/*
** Register commands with the TCL interpreter.
*/
int Sqlitetest2_Init(Tcl_Interp *interp){
  Tcl_CreateCommand(interp, "pager_open", pager_open, 0, 0);
  Tcl_CreateCommand(interp, "pager_close", pager_close, 0, 0);
  Tcl_CreateCommand(interp, "pager_commit", pager_commit, 0, 0);
  Tcl_CreateCommand(interp, "pager_rollback", pager_rollback, 0, 0);
  Tcl_CreateCommand(interp, "pager_stats", pager_stats, 0, 0);
  Tcl_CreateCommand(interp, "pager_pagecount", pager_pagecount, 0, 0);
  Tcl_CreateCommand(interp, "page_get", page_get, 0, 0);
  Tcl_CreateCommand(interp, "page_unref", page_unref, 0, 0);
  Tcl_CreateCommand(interp, "page_read", page_read, 0, 0);
  Tcl_CreateCommand(interp, "page_write", page_write, 0, 0);
  Tcl_CreateCommand(interp, "page_number", page_number, 0, 0);
  return TCL_OK;
}

Added test/pager.test.

































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# Copyright (c) 1999, 2000 D. Richard Hipp
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
# 
# You should have received a copy of the GNU General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA  02111-1307, USA.
#
# Author contact information:
#   drh@hwaci.com
#   http://www.hwaci.com/drh/
#
#***********************************************************************
# This file implements regression tests for SQLite library.  The
# focus of this script is page cache subsystem.
#
# $Id: pager.test,v 1.1 2001/04/15 00:37:21 drh Exp $


set testdir [file dirname $argv0]
source $testdir/tester.tcl

if {$dbprefix!="mem:" && [info commands pager_open]!=""} {

# Basic sanity check.  Open and close a pager.
#
do_test pager-1.0 {
  catch {file delete -force ptf1.db}
  catch {file delete -force ptf1.db-journal}
  set v [catch {
    set ::p1 [pager_open ptf1.db 10]
  } msg]
} {0}
do_test pager-1.1 {
  pager_stats $::p1
} {ref 0 page 0 max 10 size -1 state 0 err 0 hit 0 miss 0 ovfl 0}
do_test pager-1.2 {
  pager_pagecount $::p1
} {0}
do_test pager-1.3 {
  pager_stats $::p1
} {ref 0 page 0 max 10 size -1 state 0 err 0 hit 0 miss 0 ovfl 0}
do_test pager-1.4 {
  pager_close $::p1
} {}

# Try to write a few pages.
#
do_test pager-2.1 {
  set v [catch {
    set ::p1 [pager_open ptf1.db 10]
  } msg]
} {0}
do_test pager-2.2 {
  set v [catch {
    set ::g1 [page_get $::p1 0]
  } msg]
  lappend v $msg
} {1 SQLITE_ERROR}
do_test pager-2.3 {
  set v [catch {
    set ::g1 [page_get $::p1 1]
  } msg]
  if {$v} {lappend v $msg}
  set v
} {0}
do_test pager-2.4 {
  pager_stats $::p1
} {ref 1 page 1 max 10 size -1 state 1 err 0 hit 0 miss 1 ovfl 0}
do_test pager-2.5 {
  pager_pagecount $::p1
} {0}
do_test pager-2.6 {
  pager_stats $::p1
} {ref 1 page 1 max 10 size 0 state 1 err 0 hit 0 miss 1 ovfl 0}
do_test pager-2.7 {
  page_number $::g1
} {1}
do_test pager-2.8 {
  page_read $::g1
} {}
do_test pager-2.9 {
  page_unref $::g1
} {}
do_test pager-2.10 {
  pager_stats $::p1
} {ref 0 page 0 max 10 size -1 state 0 err 0 hit 0 miss 1 ovfl 0}
do_test pager-2.11 {
  set ::g1 [page_get $::p1 1]
  expr {$::g1!=0}
} {1}
do_test pager-2.12 {
  page_number $::g1
} {1}
do_test pager-2.13 {
  pager_stats $::p1
} {ref 1 page 1 max 10 size -1 state 1 err 0 hit 0 miss 2 ovfl 0}
do_test pager-2.14 {
  set v [catch {
    page_write $::g1 "Page-One"
  } msg]
  lappend v $msg
} {0 {}}
do_test pager-2.15 {
  pager_stats $::p1
} {ref 1 page 1 max 10 size 0 state 2 err 0 hit 0 miss 2 ovfl 0}
do_test pager-2.16 {
  page_read $::g1
} {Page-One}
do_test pager-2.17 {
  set v [catch {
    pager_commit $::p1
  } msg]
  lappend v $msg
} {0 {}}
do_test pager-2.20 {
  pager_stats $::p1
} {ref 1 page 1 max 10 size -1 state 1 err 0 hit 0 miss 2 ovfl 0}
do_test pager-2.19 {
  pager_pagecount $::p1
} {1}
do_test pager-2.21 {
  pager_stats $::p1
} {ref 1 page 1 max 10 size 1 state 1 err 0 hit 0 miss 2 ovfl 0}
do_test pager-2.22 {
  page_unref $::g1
} {}
do_test pager-2.23 {
  pager_stats $::p1
} {ref 0 page 0 max 10 size -1 state 0 err 0 hit 0 miss 2 ovfl 0}
do_test pager-2.24 {
  set v [catch {
    page_get $::p1 1
  } ::g1]
  if {$v} {lappend v $::g1}
  set v
} {0}
do_test pager-2.25 {
  page_read $::g1
} {Page-One}
do_test pager-2.26 {
  set v [catch {
    page_write $::g1 {page-one}
  } msg]
  lappend v $msg
} {0 {}}
do_test pager-2.27 {
  page_read $::g1
} {page-one}
do_test pager-2.28 {
  set v [catch {
    pager_rollback $::p1
  } msg]
  lappend v $msg
} {0 {}}
do_test pager-2.29 {
  page_read $::g1
} {Page-One}

do_test pager-2.99 {
  pager_close $::p1
} {}


} ;# end if( not mem: and has pager_open command );

finish_test

Changes to www/changes.tcl.

13
14
15
16
17
18
19
20

21
22
23
24
25
26
27


proc chng {date desc} {
  puts "<DT><B>$date</B></DT>"
  puts "<DD><P><UL>$desc</UL></P></DD>"
}

chng {2001 Apr 12 (1.0.31)} {

<li>More robust handling of out-of-memory errors.</li>
<li>New tests added to the test suite.</li>
}

chng {2001 Apr 6 (1.0.30)} {
<li>Remove the <b>sqlite_encoding</b> TCL variable that was introduced
    in the previous version.</li>







|
>







13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28


proc chng {date desc} {
  puts "<DT><B>$date</B></DT>"
  puts "<DD><P><UL>$desc</UL></P></DD>"
}

chng {2001 Apr 14 (1.0.31)} {
<li>Pager subsystem added but not yet used.</li>
<li>More robust handling of out-of-memory errors.</li>
<li>New tests added to the test suite.</li>
}

chng {2001 Apr 6 (1.0.30)} {
<li>Remove the <b>sqlite_encoding</b> TCL variable that was introduced
    in the previous version.</li>