blob: 0de19469a44818ca958589dae3b0722f97d41f34 [file] [log] [blame]
/*
** 2001 September 15
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
*************************************************************************
** Code for testing the btree.c module in SQLite. This code
** is not included in the SQLite library. It is used for automated
** testing of the SQLite library.
*/
#include "sqliteInt.h"
#include "btreeInt.h"
#if defined(INCLUDE_SQLITE_TCL_H)
# include "sqlite_tcl.h"
#else
# include "tcl.h"
#endif
#include <stdlib.h>
#include <string.h>
extern const char *sqlite3ErrName(int);
/*
** A bogus sqlite3 connection structure for use in the btree
** tests.
*/
static sqlite3 sDb;
static int nRefSqlite3 = 0;
/*
** Usage: btree_open FILENAME NCACHE
**
** Open a new database
*/
static int SQLITE_TCLAPI btree_open(
void *NotUsed,
Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
int argc, /* Number of arguments */
const char **argv /* Text of each argument */
){
Btree *pBt;
int rc, nCache;
char zBuf[100];
int n;
char *zFilename;
if( argc!=3 ){
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
" FILENAME NCACHE FLAGS\"", 0);
return TCL_ERROR;
}
if( Tcl_GetInt(interp, argv[2], &nCache) ) return TCL_ERROR;
nRefSqlite3++;
if( nRefSqlite3==1 ){
sDb.pVfs = sqlite3_vfs_find(0);
sDb.mutex = sqlite3MutexAlloc(SQLITE_MUTEX_RECURSIVE);
sqlite3_mutex_enter(sDb.mutex);
}
n = (int)strlen(argv[1]);
zFilename = sqlite3_malloc( n+2 );
if( zFilename==0 ) return TCL_ERROR;
memcpy(zFilename, argv[1], n+1);
zFilename[n+1] = 0;
rc = sqlite3BtreeOpen(sDb.pVfs, zFilename, &sDb, &pBt, 0,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_MAIN_DB);
sqlite3_free(zFilename);
if( rc!=SQLITE_OK ){
Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
return TCL_ERROR;
}
sqlite3BtreeSetCacheSize(pBt, nCache);
sqlite3_snprintf(sizeof(zBuf), zBuf,"%p", pBt);
Tcl_AppendResult(interp, zBuf, 0);
return TCL_OK;
}
/*
** Usage: btree_close ID
**
** Close the given database.
*/
static int SQLITE_TCLAPI btree_close(
void *NotUsed,
Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
int argc, /* Number of arguments */
const char **argv /* Text of each argument */
){
Btree *pBt;
int rc;
if( argc!=2 ){
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
" ID\"", 0);
return TCL_ERROR;
}
pBt = sqlite3TestTextToPtr(argv[1]);
rc = sqlite3BtreeClose(pBt);
if( rc!=SQLITE_OK ){
Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
return TCL_ERROR;
}
nRefSqlite3--;
if( nRefSqlite3==0 ){
sqlite3_mutex_leave(sDb.mutex);
sqlite3_mutex_free(sDb.mutex);
sDb.mutex = 0;
sDb.pVfs = 0;
}
return TCL_OK;
}
/*
** Usage: btree_begin_transaction ID
**
** Start a new transaction
*/
static int SQLITE_TCLAPI btree_begin_transaction(
void *NotUsed,
Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
int argc, /* Number of arguments */
const char **argv /* Text of each argument */
){
Btree *pBt;
int rc;
if( argc!=2 ){
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
" ID\"", 0);
return TCL_ERROR;
}
pBt = sqlite3TestTextToPtr(argv[1]);
sqlite3BtreeEnter(pBt);
rc = sqlite3BtreeBeginTrans(pBt, 1);
sqlite3BtreeLeave(pBt);
if( rc!=SQLITE_OK ){
Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
return TCL_ERROR;
}
return TCL_OK;
}
/*
** Usage: btree_pager_stats ID
**
** Returns pager statistics
*/
static int SQLITE_TCLAPI btree_pager_stats(
void *NotUsed,
Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
int argc, /* Number of arguments */
const char **argv /* Text of each argument */
){
Btree *pBt;
int i;
int *a;
if( argc!=2 ){
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
" ID\"", 0);
return TCL_ERROR;
}
pBt = sqlite3TestTextToPtr(argv[1]);
/* Normally in this file, with a b-tree handle opened using the
** [btree_open] command it is safe to call sqlite3BtreeEnter() directly.
** But this function is sometimes called with a btree handle obtained
** from an open SQLite connection (using [btree_from_db]). In this case
** we need to obtain the mutex for the controlling SQLite handle before
** it is safe to call sqlite3BtreeEnter().
*/
sqlite3_mutex_enter(pBt->db->mutex);
sqlite3BtreeEnter(pBt);
a = sqlite3PagerStats(sqlite3BtreePager(pBt));
for(i=0; i<11; i++){
static char *zName[] = {
"ref", "page", "max", "size", "state", "err",
"hit", "miss", "ovfl", "read", "write"
};
char zBuf[100];
Tcl_AppendElement(interp, zName[i]);
sqlite3_snprintf(sizeof(zBuf), zBuf,"%d",a[i]);
Tcl_AppendElement(interp, zBuf);
}
sqlite3BtreeLeave(pBt);
/* Release the mutex on the SQLite handle that controls this b-tree */
sqlite3_mutex_leave(pBt->db->mutex);
return TCL_OK;
}
/*
** Usage: btree_cursor ID TABLENUM WRITEABLE
**
** Create a new cursor. Return the ID for the cursor.
*/
static int SQLITE_TCLAPI btree_cursor(
void *NotUsed,
Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
int argc, /* Number of arguments */
const char **argv /* Text of each argument */
){
Btree *pBt;
int iTable;
BtCursor *pCur;
int rc = SQLITE_OK;
int wrFlag;
char zBuf[30];
if( argc!=4 ){
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
" ID TABLENUM WRITEABLE\"", 0);
return TCL_ERROR;
}
pBt = sqlite3TestTextToPtr(argv[1]);
if( Tcl_GetInt(interp, argv[2], &iTable) ) return TCL_ERROR;
if( Tcl_GetBoolean(interp, argv[3], &wrFlag) ) return TCL_ERROR;
if( wrFlag ) wrFlag = BTREE_WRCSR;
pCur = (BtCursor *)ckalloc(sqlite3BtreeCursorSize());
memset(pCur, 0, sqlite3BtreeCursorSize());
sqlite3_mutex_enter(pBt->db->mutex);
sqlite3BtreeEnter(pBt);
#ifndef SQLITE_OMIT_SHARED_CACHE
rc = sqlite3BtreeLockTable(pBt, iTable, !!wrFlag);
#endif
if( rc==SQLITE_OK ){
rc = sqlite3BtreeCursor(pBt, iTable, wrFlag, 0, pCur);
}
sqlite3BtreeLeave(pBt);
sqlite3_mutex_leave(pBt->db->mutex);
if( rc ){
ckfree((char *)pCur);
Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
return TCL_ERROR;
}
sqlite3_snprintf(sizeof(zBuf), zBuf,"%p", pCur);
Tcl_AppendResult(interp, zBuf, 0);
return SQLITE_OK;
}
/*
** Usage: btree_close_cursor ID
**
** Close a cursor opened using btree_cursor.
*/
static int SQLITE_TCLAPI btree_close_cursor(
void *NotUsed,
Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
int argc, /* Number of arguments */
const char **argv /* Text of each argument */
){
BtCursor *pCur;
int rc;
if( argc!=2 ){
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
" ID\"", 0);
return TCL_ERROR;
}
pCur = sqlite3TestTextToPtr(argv[1]);
#if SQLITE_THREADSAFE>0
{
Btree *pBt = pCur->pBtree;
sqlite3_mutex_enter(pBt->db->mutex);
sqlite3BtreeEnter(pBt);
rc = sqlite3BtreeCloseCursor(pCur);
sqlite3BtreeLeave(pBt);
sqlite3_mutex_leave(pBt->db->mutex);
}
#else
rc = sqlite3BtreeCloseCursor(pCur);
#endif
ckfree((char *)pCur);
if( rc ){
Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
return TCL_ERROR;
}
return SQLITE_OK;
}
/*
** Usage: btree_next ID
**
** Move the cursor to the next entry in the table. Return 0 on success
** or 1 if the cursor was already on the last entry in the table or if
** the table is empty.
*/
static int SQLITE_TCLAPI btree_next(
void *NotUsed,
Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
int argc, /* Number of arguments */
const char **argv /* Text of each argument */
){
BtCursor *pCur;
int rc;
int res = 0;
char zBuf[100];
if( argc!=2 ){
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
" ID\"", 0);
return TCL_ERROR;
}
pCur = sqlite3TestTextToPtr(argv[1]);
sqlite3BtreeEnter(pCur->pBtree);
rc = sqlite3BtreeNext(pCur, 0);
if( rc==SQLITE_DONE ){
res = 1;
rc = SQLITE_OK;
}
sqlite3BtreeLeave(pCur->pBtree);
if( rc ){
Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
return TCL_ERROR;
}
sqlite3_snprintf(sizeof(zBuf),zBuf,"%d",res);
Tcl_AppendResult(interp, zBuf, 0);
return SQLITE_OK;
}
/*
** Usage: btree_first ID
**
** Move the cursor to the first entry in the table. Return 0 if the
** cursor was left point to something and 1 if the table is empty.
*/
static int SQLITE_TCLAPI btree_first(
void *NotUsed,
Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
int argc, /* Number of arguments */
const char **argv /* Text of each argument */
){
BtCursor *pCur;
int rc;
int res = 0;
char zBuf[100];
if( argc!=2 ){
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
" ID\"", 0);
return TCL_ERROR;
}
pCur = sqlite3TestTextToPtr(argv[1]);
sqlite3BtreeEnter(pCur->pBtree);
rc = sqlite3BtreeFirst(pCur, &res);
sqlite3BtreeLeave(pCur->pBtree);
if( rc ){
Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
return TCL_ERROR;
}
sqlite3_snprintf(sizeof(zBuf),zBuf,"%d",res);
Tcl_AppendResult(interp, zBuf, 0);
return SQLITE_OK;
}
/*
** Usage: btree_eof ID
**
** Return TRUE if the given cursor is not pointing at a valid entry.
** Return FALSE if the cursor does point to a valid entry.
*/
static int SQLITE_TCLAPI btree_eof(
void *NotUsed,
Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
int argc, /* Number of arguments */
const char **argv /* Text of each argument */
){
BtCursor *pCur;
int rc;
char zBuf[50];
if( argc!=2 ){
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
" ID\"", 0);
return TCL_ERROR;
}
pCur = sqlite3TestTextToPtr(argv[1]);
sqlite3BtreeEnter(pCur->pBtree);
rc = sqlite3BtreeEof(pCur);
sqlite3BtreeLeave(pCur->pBtree);
sqlite3_snprintf(sizeof(zBuf),zBuf, "%d", rc);
Tcl_AppendResult(interp, zBuf, 0);
return SQLITE_OK;
}
/*
** Usage: btree_payload_size ID
**
** Return the number of bytes of payload
*/
static int SQLITE_TCLAPI btree_payload_size(
void *NotUsed,
Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
int argc, /* Number of arguments */
const char **argv /* Text of each argument */
){
BtCursor *pCur;
u32 n;
char zBuf[50];
if( argc!=2 ){
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
" ID\"", 0);
return TCL_ERROR;
}
pCur = sqlite3TestTextToPtr(argv[1]);
sqlite3BtreeEnter(pCur->pBtree);
n = sqlite3BtreePayloadSize(pCur);
sqlite3BtreeLeave(pCur->pBtree);
sqlite3_snprintf(sizeof(zBuf),zBuf, "%u", n);
Tcl_AppendResult(interp, zBuf, 0);
return SQLITE_OK;
}
/*
** usage: varint_test START MULTIPLIER COUNT INCREMENT
**
** This command tests the putVarint() and getVarint()
** routines, both for accuracy and for speed.
**
** An integer is written using putVarint() and read back with
** getVarint() and varified to be unchanged. This repeats COUNT
** times. The first integer is START*MULTIPLIER. Each iteration
** increases the integer by INCREMENT.
**
** This command returns nothing if it works. It returns an error message
** if something goes wrong.
*/
static int SQLITE_TCLAPI btree_varint_test(
void *NotUsed,
Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
int argc, /* Number of arguments */
const char **argv /* Text of each argument */
){
u32 start, mult, count, incr;
u64 in, out;
int n1, n2, i, j;
unsigned char zBuf[100];
if( argc!=5 ){
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
" START MULTIPLIER COUNT INCREMENT\"", 0);
return TCL_ERROR;
}
if( Tcl_GetInt(interp, argv[1], (int*)&start) ) return TCL_ERROR;
if( Tcl_GetInt(interp, argv[2], (int*)&mult) ) return TCL_ERROR;
if( Tcl_GetInt(interp, argv[3], (int*)&count) ) return TCL_ERROR;
if( Tcl_GetInt(interp, argv[4], (int*)&incr) ) return TCL_ERROR;
in = start;
in *= mult;
for(i=0; i<(int)count; i++){
char zErr[200];
n1 = putVarint(zBuf, in);
if( n1>9 || n1<1 ){
sqlite3_snprintf(sizeof(zErr), zErr,
"putVarint returned %d - should be between 1 and 9", n1);
Tcl_AppendResult(interp, zErr, 0);
return TCL_ERROR;
}
n2 = getVarint(zBuf, &out);
if( n1!=n2 ){
sqlite3_snprintf(sizeof(zErr), zErr,
"putVarint returned %d and getVarint returned %d", n1, n2);
Tcl_AppendResult(interp, zErr, 0);
return TCL_ERROR;
}
if( in!=out ){
sqlite3_snprintf(sizeof(zErr), zErr,
"Wrote 0x%016llx and got back 0x%016llx", in, out);
Tcl_AppendResult(interp, zErr, 0);
return TCL_ERROR;
}
if( (in & 0xffffffff)==in ){
u32 out32;
n2 = getVarint32(zBuf, out32);
out = out32;
if( n1!=n2 ){
sqlite3_snprintf(sizeof(zErr), zErr,
"putVarint returned %d and GetVarint32 returned %d",
n1, n2);
Tcl_AppendResult(interp, zErr, 0);
return TCL_ERROR;
}
if( in!=out ){
sqlite3_snprintf(sizeof(zErr), zErr,
"Wrote 0x%016llx and got back 0x%016llx from GetVarint32",
in, out);
Tcl_AppendResult(interp, zErr, 0);
return TCL_ERROR;
}
}
/* In order to get realistic timings, run getVarint 19 more times.
** This is because getVarint is called about 20 times more often
** than putVarint.
*/
for(j=0; j<19; j++){
getVarint(zBuf, &out);
}
in += incr;
}
return TCL_OK;
}
/*
** usage: btree_from_db DB-HANDLE
**
** This command returns the btree handle for the main database associated
** with the database-handle passed as the argument. Example usage:
**
** sqlite3 db test.db
** set bt [btree_from_db db]
*/
static int SQLITE_TCLAPI btree_from_db(
void *NotUsed,
Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
int argc, /* Number of arguments */
const char **argv /* Text of each argument */
){
char zBuf[100];
Tcl_CmdInfo info;
sqlite3 *db;
Btree *pBt;
int iDb = 0;
if( argc!=2 && argc!=3 ){
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
" DB-HANDLE ?N?\"", 0);
return TCL_ERROR;
}
if( 1!=Tcl_GetCommandInfo(interp, argv[1], &info) ){
Tcl_AppendResult(interp, "No such db-handle: \"", argv[1], "\"", 0);
return TCL_ERROR;
}
if( argc==3 ){
iDb = atoi(argv[2]);
}
db = *((sqlite3 **)info.objClientData);
assert( db );
pBt = db->aDb[iDb].pBt;
sqlite3_snprintf(sizeof(zBuf), zBuf, "%p", pBt);
Tcl_SetResult(interp, zBuf, TCL_VOLATILE);
return TCL_OK;
}
/*
** Usage: btree_ismemdb ID
**
** Return true if the B-Tree is currently stored entirely in memory.
*/
static int SQLITE_TCLAPI btree_ismemdb(
void *NotUsed,
Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
int argc, /* Number of arguments */
const char **argv /* Text of each argument */
){
Btree *pBt;
int res;
sqlite3_file *pFile;
if( argc!=2 ){
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
" ID\"", 0);
return TCL_ERROR;
}
pBt = sqlite3TestTextToPtr(argv[1]);
sqlite3_mutex_enter(pBt->db->mutex);
sqlite3BtreeEnter(pBt);
pFile = sqlite3PagerFile(sqlite3BtreePager(pBt));
res = (pFile->pMethods==0);
sqlite3BtreeLeave(pBt);
sqlite3_mutex_leave(pBt->db->mutex);
Tcl_SetObjResult(interp, Tcl_NewBooleanObj(res));
return SQLITE_OK;
}
/*
** usage: btree_set_cache_size ID NCACHE
**
** Set the size of the cache used by btree $ID.
*/
static int SQLITE_TCLAPI btree_set_cache_size(
void *NotUsed,
Tcl_Interp *interp, /* The TCL interpreter that invoked this command */
int argc, /* Number of arguments */
const char **argv /* Text of each argument */
){
int nCache;
Btree *pBt;
if( argc!=3 ){
Tcl_AppendResult(
interp, "wrong # args: should be \"", argv[0], " BT NCACHE\"", 0);
return TCL_ERROR;
}
pBt = sqlite3TestTextToPtr(argv[1]);
if( Tcl_GetInt(interp, argv[2], &nCache) ) return TCL_ERROR;
sqlite3_mutex_enter(pBt->db->mutex);
sqlite3BtreeEnter(pBt);
sqlite3BtreeSetCacheSize(pBt, nCache);
sqlite3BtreeLeave(pBt);
sqlite3_mutex_leave(pBt->db->mutex);
return TCL_OK;
}
/*
** usage: btree_insert CSR ?KEY? VALUE
**
** Set the size of the cache used by btree $ID.
*/
static int SQLITE_TCLAPI btree_insert(
ClientData clientData,
Tcl_Interp *interp,
int objc,
Tcl_Obj *const objv[]
){
BtCursor *pCur;
int rc;
BtreePayload x;
if( objc!=4 && objc!=3 ){
Tcl_WrongNumArgs(interp, 1, objv, "?-intkey? CSR KEY VALUE");
return TCL_ERROR;
}
memset(&x, 0, sizeof(x));
if( objc==4 ){
if( Tcl_GetIntFromObj(interp, objv[2], &rc) ) return TCL_ERROR;
x.nKey = rc;
x.pData = (void*)Tcl_GetByteArrayFromObj(objv[3], &x.nData);
}else{
x.pKey = (void*)Tcl_GetByteArrayFromObj(objv[2], &rc);
x.nKey = rc;
}
pCur = (BtCursor*)sqlite3TestTextToPtr(Tcl_GetString(objv[1]));
sqlite3_mutex_enter(pCur->pBtree->db->mutex);
sqlite3BtreeEnter(pCur->pBtree);
rc = sqlite3BtreeInsert(pCur, &x, 0, 0);
sqlite3BtreeLeave(pCur->pBtree);
sqlite3_mutex_leave(pCur->pBtree->db->mutex);
Tcl_ResetResult(interp);
if( rc ){
Tcl_AppendResult(interp, sqlite3ErrName(rc), 0);
return TCL_ERROR;
}
return TCL_OK;
}
/*
** Register commands with the TCL interpreter.
*/
int Sqlitetest3_Init(Tcl_Interp *interp){
static struct {
char *zName;
Tcl_CmdProc *xProc;
} aCmd[] = {
{ "btree_open", (Tcl_CmdProc*)btree_open },
{ "btree_close", (Tcl_CmdProc*)btree_close },
{ "btree_begin_transaction", (Tcl_CmdProc*)btree_begin_transaction },
{ "btree_pager_stats", (Tcl_CmdProc*)btree_pager_stats },
{ "btree_cursor", (Tcl_CmdProc*)btree_cursor },
{ "btree_close_cursor", (Tcl_CmdProc*)btree_close_cursor },
{ "btree_next", (Tcl_CmdProc*)btree_next },
{ "btree_eof", (Tcl_CmdProc*)btree_eof },
{ "btree_payload_size", (Tcl_CmdProc*)btree_payload_size },
{ "btree_first", (Tcl_CmdProc*)btree_first },
{ "btree_varint_test", (Tcl_CmdProc*)btree_varint_test },
{ "btree_from_db", (Tcl_CmdProc*)btree_from_db },
{ "btree_ismemdb", (Tcl_CmdProc*)btree_ismemdb },
{ "btree_set_cache_size", (Tcl_CmdProc*)btree_set_cache_size }
};
int i;
for(i=0; i<sizeof(aCmd)/sizeof(aCmd[0]); i++){
Tcl_CreateCommand(interp, aCmd[i].zName, aCmd[i].xProc, 0, 0);
}
Tcl_CreateObjCommand(interp, "btree_insert", btree_insert, 0, 0);
return TCL_OK;
}