iOS App使用SQLite之句柄的定义及数据库的基本操作
句柄
要操纵一个数据库你就得有一个这个数据库的句柄(又碰到这个难以理解的词了,不过确实还没得一个更好的词来替代它)。其实你跟本不需要去在乎这个词叫什么,你只要搞清楚他是一个什么玩意儿。就如同鞋子为什么叫鞋子,仔细想想确实也难以理解,不过清楚他的功能就OK了,不是吗?
句柄在很多地方我们见到过,最常见的就是文件的句柄,我们要操纵一个文件,我们就要取得一个文件的句柄。句柄是个什么东东呢?其实很简单,句柄是一个东东的描述,他被定义为一个结构体,这个结构体可能会包含要描述的东东的具体信息,比如位置、大小、类型等等。我们有了这个描述信息我们就能去找到这个东东,然后操纵它。
一句话:句柄是物体的描述结构体。
我们来看看sqlite的句柄是怎么定义的(不要被吓到了,代码直接跳过就好):
structsqlite3{ sqlite3_vfs*pVfs;/*OSInterface*/ intnDb;/*Numberofbackendscurrentlyinuse*/ Db*aDb;/*Allbackends*/ intflags;/*Miscellaneousflags.Seebelow*/ unsignedintopenFlags;/*Flagspassedtosqlite3_vfs.xOpen()*/ interrCode;/*Mostrecenterrorcode(SQLITE_*)*/ interrMask;/*&resultcodeswiththisbeforereturning*/ u8autoCommit;/*Theauto-commitflag.*/ u8temp_store;/*1:file2:memory0:default*/ u8mallocFailed;/*Trueifwehaveseenamallocfailure*/ u8dfltLockMode;/*Defaultlocking-modeforattacheddbs*/ signedcharnextAutovac;/*AutovacsettingafterVACUUMif>=0*/ u8suppressErr;/*Donotissueerrormessagesiftrue*/ u8vtabOnConflict;/*Valuetoreturnfors3_vtab_on_conflict()*/ intnextPagesize;/*PagesizeafterVACUUMif>0*/ intnTable;/*Numberoftablesinthedatabase*/ CollSeq*pDfltColl;/*Thedefaultcollatingsequence(BINARY)*/ i64lastRowid;/*ROWIDofmostrecentinsert(seeabove)*/ u32magic;/*Magicnumberfordetectlibrarymisuse*/ intnChange;/*Valuereturnedbysqlite3_changes()*/ intnTotalChange;/*Valuereturnedbysqlite3_total_changes()*/ sqlite3_mutex*mutex;/*Connectionmutex*/ intaLimit[SQLITE_N_LIMIT];/*Limits*/ structsqlite3InitInfo{/*Informationusedduringinitialization*/ intiDb;/*Whenbackisbeinginitialized*/ intnewTnum;/*Rootpageoftablebeinginitialized*/ u8busy;/*TRUEifcurrentlyinitializing*/ u8orphanTrigger;/*LaststatementisorphanedTEMPtrigger*/ }init; intnExtension;/*Numberofloadedextensions*/ void**aExtension;/*Arrayofsharedlibraryhandles*/ structVdbe*pVdbe;/*Listofactivevirtualmachines*/ intactiveVdbeCnt;/*NumberofVDBEscurrentlyexecuting*/ intwriteVdbeCnt;/*NumberofactiveVDBEsthatarewriting*/ intvdbeExecCnt;/*NumberofnestedcallstoVdbeExec()*/ void(*xTrace)(void*,constchar*);/*Tracefunction*/ void*pTraceArg;/*Argumenttothetracefunction*/ void(*xProfile)(void*,constchar*,u64);/*Profilingfunction*/ void*pProfileArg;/*Argumenttoprofilefunction*/ void*pCommitArg;/*ArgumenttoxCommitCallback()*/ int(*xCommitCallback)(void*);/*Invokedateverycommit.*/ void*pRollbackArg;/*ArgumenttoxRollbackCallback()*/ void(*xRollbackCallback)(void*);/*Invokedateverycommit.*/ void*pUpdateArg; void(*xUpdateCallback)(void*,int,constchar*,constchar*,sqlite_int64); #ifndefSQLITE_OMIT_WAL int(*xWalCallback)(void*,sqlite3*,constchar*,int); void*pWalArg; #endif void(*xCollNeeded)(void*,sqlite3*,inteTextRep,constchar*); void(*xCollNeeded16)(void*,sqlite3*,inteTextRep,constvoid*); void*pCollNeededArg; sqlite3_value*pErr;/*Mostrecenterrormessage*/ char*zErrMsg;/*Mostrecenterrormessage(UTF-8encoded)*/ char*zErrMsg16;/*Mostrecenterrormessage(UTF-16encoded)*/ union{ volatileintisInterrupted;/*Trueifsqlite3_interrupthasbeencalled*/ doublenotUsed1;/*Spacer*/ }u1; Lookasidelookaside;/*Lookasidemallocconfiguration*/ #ifndefSQLITE_OMIT_AUTHORIZATION int(*xAuth)(void*,int,constchar*,constchar*,constchar*,constchar*); /*Accessauthorizationfunction*/ void*pAuthArg;/*1stargumenttotheaccessauthfunction*/ #endif #ifndefSQLITE_OMIT_PROGRESS_CALLBACK int(*xProgress)(void*);/*Theprogresscallback*/ void*pProgressArg;/*Argumenttotheprogresscallback*/ intnProgressOps;/*Numberofopcodesforprogresscallback*/ #endif #ifndefSQLITE_OMIT_VIRTUALTABLE HashaModule;/*populatedbysqlite3_create_module()*/ VtabCtx*pVtabCtx;/*Contextforactivevtabconnect/create*/ VTable**aVTrans;/*Virtualtableswithopentransactions*/ intnVTrans;/*AllocatedsizeofaVTrans*/ VTable*pDisconnect;/*Disconnecttheseinnextsqlite3_prepare()*/ #endif FuncDefHashaFunc;/*Hashtableofconnectionfunctions*/ HashaCollSeq;/*Allcollatingsequences*/ BusyHandlerbusyHandler;/*Busycallback*/ intbusyTimeout;/*Busyhandlertimeout,inmsec*/ DbaDbStatic[2];/*Staticspaceforthe2defaultbackends*/ Savepoint*pSavepoint;/*Listofactivesavepoints*/ intnSavepoint;/*Numberofnon-transactionsavepoints*/ intnStatement;/*Numberofnestedstatement-transactions*/ u8isTransactionSavepoint;/*TrueiftheoutermostsavepointisaTS*/ i64nDeferredCons;/*Netdeferredconstraintsthistransaction.*/ int*pnBytesFreed;/*IfnotNULL,incrementthisinDbFree()*/ #ifdefSQLITE_ENABLE_UNLOCK_NOTIFY /*ThefollowingvariablesareallprotectedbytheSTATIC_MASTER **mutex,notbysqlite3.mutex.Theyareusedbycodeinnotify.c. ** **WhenX.pUnlockConnection==Y,thatmeansthatXiswaitingforYto **unlocksothatitcanproceed. ** **WhenX.pBlockingConnection==Y,thatmeansthatsomethingthatXtried **triedtodorecentlyfailedwithanSQLITE_LOCKEDerrorduetolocks **heldbyY. */ sqlite3*pBlockingConnection;/*ConnectionthatcausedSQLITE_LOCKED*/ sqlite3*pUnlockConnection;/*Connectiontowatchforunlock*/ void*pUnlockArg;/*ArgumenttoxUnlockNotify*/ void(*xUnlockNotify)(void**,int);/*Unlocknotifycallback*/ sqlite3*pNextBlocked;/*Nextinlistofallblockedconnections*/ #endif }; typedefstructsqlite3sqlite3;//
是不是被吓到了,没关系,这段代码本来就是我贴出来吓唬你的,我也没认真研究过这个代码,也不想去研究,除非哪天有人出高价请我去研究,我现在只知道怎么用就好了。
这个sqlite3结构体就是被用来描述我们磁盘里的数据库文件的,有了这个描述符我们就可以对这个数据库进行各种操作了,操作的具体内情我们不必要了解,我们只需要知道怎么去调用API就好了,当然有时候还是要了解一点点内情的,这个以后碰到再讲。
我用这么长的话加一大段吓人的代码只有一个目的:不要对句柄有恐惧感。
好了,开始我们的正题,sqlite里面你要操纵数据库我们先得创建一个句柄,然后后面所有对数据库得操作都会用到这个句柄。
sqlite3*pdb;
就这么简单,这样一个ssqlite的句柄我们就创建完成了,我们以后针对数据库的操作都离不开它了。
打开、关闭、创建数据库
我创建了一个句柄,但是我怎么知道这个句柄指向的是磁盘上哪个数据库文件呢?我们只是创建了一个指针,指向一个sqlite3类型的结构体。里面的数据都是空的或者默认的。我们接下来要做的就是去为这个结构体申请内存并填充这个结构体。是不是感觉又被吓到了?这个结构体这么庞大,我们自己去填充还不的猴年马月啊。一切都不用害怕,并不需要我们显式的去填充它,我们只需要告诉它一些最基本的信息,然后调用API让API去帮我们填充。
目前我们只需要告诉这个结构体一个信息,就是数据库文件的路径。然后调用sqlite3_open函数就OK了。
SQLITE_APIintsqlite3_open(//SQLITE_API是个宏,用来标注API的不用理它 constchar*zFilename, sqlite3**ppDb );
第一个参数是路径,constchar*型,第二个参数是数据库句柄的指针,注意是一个二级指针,我们声明的一级指针,所以我们还要加一个取地址符&,请看我下面的实例:
intret=sqlite3_open(getFilePath(),&pdb);//
不要奇怪,其实打开数据库就是获得数据库的一些信息,然后方便我们操纵它,不是吗?通过这个函数我们把数据库与句柄pdb绑定在一起了,我们以就通过这个pdb来操纵数据库了。在这个函数中第一个参数是数据库文件的路径(包括文件名),我一般会把路径写成一个函数,这样遵循编码原则(这里用到的原则是一个函数尽量只做一件事情),建议你也这样,好处你自己可以慢慢体会到。我获取路径的函数如下你可以参考一下:
constchar*getFilePath(){ return[[NSStringstringWithFormat:@"%@/Documents/db",NSHomeDirectory()]UTF8String]; //不好意思这里用到了与IOS相关的一点东西,不过这个也没办法,除非我写成绝对路径,但是那样是行不通的 //不过也没关系,你移植到别处肯定也是要改路径的咯,所以移植的时候改一下就好了 }
还有一点就是返回值,返回值我们用来判断是否执行成功。sqlite中帮我们定义了一系列的宏,用来作为返回值:
#defineSQLITE_OK0/*Successfulresult*/ /*beginning-of-error-codes*/ #defineSQLITE_ERROR1/*SQLerrorormissingdatabase*/ #defineSQLITE_INTERNAL2/*InternallogicerrorinSQLite*/ #defineSQLITE_PERM3/*Accesspermissiondenied*/ #defineSQLITE_ABORT4/*Callbackroutinerequestedanabort*/ #defineSQLITE_BUSY5/*Thedatabasefileislocked*/ #defineSQLITE_LOCKED6/*Atableinthedatabaseislocked*/ #defineSQLITE_NOMEM7/*Amalloc()failed*/ #defineSQLITE_READONLY8/*Attempttowriteareadonlydatabase*/ #defineSQLITE_INTERRUPT9/*Operationterminatedbysqlite3_interrupt()*/ #defineSQLITE_IOERR10/*SomekindofdiskI/Oerroroccurred*/ #defineSQLITE_CORRUPT11/*Thedatabasediskimageismalformed*/ #defineSQLITE_NOTFOUND12/*Unknownopcodeinsqlite3_file_control()*/ #defineSQLITE_FULL13/*Insertionfailedbecausedatabaseisfull*/ #defineSQLITE_CANTOPEN14/*Unabletoopenthedatabasefile*/ #defineSQLITE_PROTOCOL15/*Databaselockprotocolerror*/ #defineSQLITE_EMPTY16/*Databaseisempty*/ #defineSQLITE_SCHEMA17/*Thedatabaseschemachanged*/ #defineSQLITE_TOOBIG18/*StringorBLOBexceedssizelimit*/ #defineSQLITE_CONSTRAINT19/*Abortduetoconstraintviolation*/ #defineSQLITE_MISMATCH20/*Datatypemismatch*/ #defineSQLITE_MISUSE21/*Libraryusedincorrectly*/ #defineSQLITE_NOLFS22/*UsesOSfeaturesnotsupportedonhost*/ #defineSQLITE_AUTH23/*Authorizationdenied*/ #defineSQLITE_FORMAT24/*Auxiliarydatabaseformaterror*/ #defineSQLITE_RANGE25/*2ndparametertosqlite3_bindoutofrange*/ #defineSQLITE_NOTADB26/*Fileopenedthatisnotadatabasefile*/ #defineSQLITE_ROW100/*sqlite3_step()hasanotherrowready*/ #defineSQLITE_DONE101/*sqlite3_step()hasfinishedexecuting*/ /*end-of-error-codes*/
有了这些我们就可以更加方便地调试我们的代码了。
好了,进入正题,上面这个函数就是在数据库存在的情况下打开数据库,不存在的情况下创建数据库,数据库的名字可以随便乱取,只要是ASCII字符就好了,因为sqlite数据库本来就是一个ASCII文件(所以它的安全性还是不大行的,不过作为本地数据库没啥问题)。
数据库打开后我们就可以进行一系列的增删查改的操作了,操作完毕我们就要关闭数据库,不是么?否则怎么释放资源呢?
关闭也很简单:
SQLITE_APIintsqlite3_close(sqlite3*db);
这个就不用解释了吧,直接看我的实例:
sqlite3_close(pdb);//这里就可以不去考虑返回值了