WCDB整理

WCDB整理

[TOC]

WCDB

WCDB基于WINQ,引入了Objective-C++代码,因此对于所有引入WCDB的源文件,都需要将其后缀.m改为.mm。为减少影响范围,可以通过Objective-C的category特性将其隔离,达到只在model层使用Objective-C++编译,而不影响controller和view

易用

WCDB通过WINQ和ORM,使得从拼接SQL、获取数据、拼装Object的整个过程,只需要一行代码即可完成

NSArray<Message *> *messages = [wcdb getAllObjectsOfClass:Message.class fromTable:@"message"];

创建数据库

WCTDatabase通过指定路径进行创建。同时,该接口会自动创建路径中未创建的目录。

WCTDatabase* wcdb = [[WCTDatabase alloc] initWithPath:path];

打开数据库

WCDB会在第一次访问数据库时,自动打开数据库,不需要开发者主动操作。

canOpen接口可用于测试数据库能否正常打开,isOpened接口可用于测试数据库是否已打开。

建表与ORM

  • WCDB_IMPLEMENTATION(className)用于定义进行绑定的类
  • WCDB_PROPERTY(propertyName)WCDB_SYNTHESIZE(className, propertyName)用于声明和定义字段。
  • WCDB_SYNTHESIZE(className, propertyName)默认使用属性名作为数据库表的字段名。对于属性名与字段名不同的情况,可以使用WCDB_SYNTHESIZE_COLUMN(className, propertyName, columnName)进行映射。
  • WCDB_PRIMARY_AUTO_INCREMENT(className, propertyName)用于定义主键且自增。
  • WCDB_INDEX(className, indexNameSubfix, propertyName)用于定义索引。

createTableAndIndexesOfName:withClass:该接口使用的是IF NOT EXISTS的SQL,因此可以用重复调用。不需要在每次调用前判断表或索引是否已经存在。

当定义发生变化时,该接口也会对应的增加字段或索引。因此,该接口可用于数据库表的升级

查询

NSArray<Message *> *messages = [wcdb getAllObjectsOfClass:Message.class fromTable:@"message"];

插入

[wcdb insertObject:message into:@"message"];

修改

[wcdb updateAllRowsInTable:@"message" onProperties:Message.modifiedTime withObject:message];

删除

[wcdb deleteAllObjects];

条件语句

WCDB通过WINQ完成条件语句

查询示例

1
2
3
NSArray *messages = [wcdb getObjectsOnResults:{Message.localID, Message.createTime}
fromTable:@"message"
where:Message.localID>1||Message.modifiedTime!=Message.createTime];

自增插入示例

1
2
3
4
message.isAutoIncrement = YES;
[wcdb insertObject:message
onProperties:{Message.localID, Message.content}
into:@"message"];

数值更新示例

1
2
3
4
[wcdb updateRowsInTable:@"message"
onProperty:Message.modifiedTime
withValue:[NSDate date]
where:Message.localID==1];

部分删除示例

1
2
3
[wcdb deleteObjectsFromTable:@"messsage"
where:Message.localID>0&&Message.content!=nil
limit:1];

更多关于增删查改接口的用法,可参考:CRUD教程

特殊语句和核心层接口

WCDB的ObjC层接口封装了绝大部分场景下适用的增删查改语句。但SQL千变万化,接口层不可能覆盖全部场景。对于这种情况,可以通过WINQ的核心层接口进行调用。

对于SQL:EXPLAIN QUERY PLAN CREATE TABLE message(localID INTEGER)

  1. 找到其对应的sql-stmt,然后通过以WCDB::Statement开头的类进行调用。如例子中,其对应的sql-stmt为WCDB::StatementExplainWCDB::StatementCreateTable

  2. 获取字段映射。对于已经定义ORM的字段,可以通过className.propertyName获取,如:Message.localID。对于未定义ORM的字段,可以通过WCDB::Column columnName("columnName")创建,如 WCDB::Column localID("localID")

  3. 根据Statement内的定义,按照与SQL同名的函数调用获得完整的WINQ语句。如例子中,其对应的WINQ语句为

    1
    2
    WCDB::ColumnDefList columnDefList = {WCTSampleORM.identifier.def(WCTColumnTypeInteger32, true)};
    WCDB::StatementExplain statementExplain = WCDB::StatementExplain().explainQueryPlan(WCDB::StatementCreateTable().create("message", columnDefList));
  4. 通过getDescription()打印log,调试确保SQL正确

    1
    NSLog(@"SQL: %s", statementExplain.getDescription().c_str());

执行WINQ

通过exec:执行WINQ statement。

1
[wcdb exec:statement];

获取WINQ运行结果

通过prepare:运行WINQ statement,获得WCTStatement,并以此获取返回值。

1
2
3
4
5
6
7
8
WCTStatement *statement = [wcdb prepare:statementExplain];
if (statement && [statement step]) {
for (int i = 0; i < [statement getCount]; ++i) {
NSString *columnName = [statement getNameAtIndex:i];
WCTValue *value = [statement getValueAtIndex:i];
NSLog(@"%@:%@", columnName, value);
}
}

该接口风格与FMDB类似。

更多关于示例代码,可以参考核心层接口

事务

1
2
3
4
5
6
7
8
BOOL result = [wcdb beginTransaction];
if (!result) {
//failed
}
//do sth...
if (![wcdb commitTransaction]) {
[wcdb rollbackTransaction];
}

便捷事务接口

runTransaction:接口会在commit失败时自动rollback事务。开发者也可以在BLOCK结束时返回YESNO来决定commit或rollback事务,以此减少代码量。

1
2
3
4
[wcdb runTransaction:^BOOL{
//do sth...
return result;//YES to commit transaction and NO to rollback transaction
}];

配置

1
2
3
[wcdb setConfig:^BOOL(std::shared_ptr<WCDB::Handle> handle, WCDB::Error &error) {
return handle->exec(WCDB::StatementPragma().pragma(WCDB::Pragma::CacheSize, -2000));
} forName:@"CacheSizeConfig"]'

关闭数据库

WCDB会自动管理这个过程。对于某一路径的数据库,WCDB会在所有对其的引用释放时,自动关闭数据库,并回收资源。

WCDB提供了close:接口确保完全关闭数据库,并阻塞其他线程的访问。

1
2
3
[wcdb close:^(){
//do something on this closed database
}];

将一个已有的ObjC类进行ORM绑定的过程如下:

  • 定义该类遵循WCTTableCoding协议。可以在类声明上定义,也可以通过文件模版在category内定义。
  • 使用WCDB_PROPERTY宏在头文件声明需要绑定到数据库表的字段。
  • 使用WCDB_IMPLEMENTATIO宏在类文件定义绑定到数据库表的类。
  • 使用WCDB_SYNTHESIZE宏在类文件定义需要绑定到数据库表的字段。

除此之外,WCDB还提供了许多可选的宏,用于定义数据库索引、约束等,如:

  • WCDB_PRIMARY用于定义主键
  • WCDB_INDEX用于定义索引
  • WCDB_UNIQUE用于定义唯一约束
  • WCDB_NOT_NULL用于定义非空约束

基础类

WCDB提供了三个基础类进行数据库操作:WCTDatabaseWCTTableWCTTransaction。它们的接口都是线程安全的。

WCTDatabase

WCTDatabase表示一个数据库,可以进行所有数据库操作,包括增删查改、表操作、事务、文件操作、损坏修复等。

WCDB提供了删除数据库、移动数据库、获取数据库占用空间和使用路径的文件操作接口。

文件操作的最佳实践是确保数据库已关闭。

可加密

WCTDatabase不支持跨线程事务。事务内的操作必须在同一个线程运行完。跨线程事务可以参考WCTTransaction。关于事务的例子,可以参考Sample-transaction

WCTTable

WCTTable表示一个表。它等价于预设了classtableNameWCTDatabase,仅可以进行数据的增删查改等。

WCTTransaction

WCTTransaction表示一个事务。

WCTDatabase的事务不同,WCTTransaction可以在函数和对象之间传递,实现跨线程的事务。

关于事务的例子,可以参考Sample-transaction

基础类共享

对于同一个路径的数据库,不同的WCTDatabaseWCTTableWCTTransaction对象共享同一个WCDB核心。因此,你可以在代码的不同位置、不同线程任意创建不同的基础类对象,WCDB会自动管理它们的共享数据和线程并发。

1
2
3
4
WCTDatabase* database1 = [[WCTDatabase alloc] initWithPath:path];
WCTDatabase* database2 = [[WCTDatabase alloc] initWithPath:path];
database1.tag = 1;
NSLog(@"%d", database2.tag);//print 1

CRUD

WCDB的增删改查分为表操作和数据操作两种。

表操作

表操作包括创建/删除 表/索引、判断表、索引是否存在等。WCTDatabaseWCTransaction都支持表操作的接口。

开发者可以根据ORM的定义创建表或索引:

1
2
BOOL ret = [database createTableAndIndexesOfName:tableName
withClass:WCTSampleTable.class];

也可以通过WINQ自定义表或索引:

1
2
3
4
5
BOOL ret = [database createTableOfName:tableName
withColumnDefList:{
WCTSampleTable.intValue.def(WCTColumnTypeInteger32),
WCTSampleTable.stringValue.def(WCTColumnTypeString)
}];

关于表操作的例子,可以参考Sample-table

数据操作

数据操作分为便捷接口和链式接口两种。WCTDatabaseWCTTableWCTTransaction均支持数据操作接口。

便捷接口

便捷接口的设计原则为,通过一行代码即可完成数据的操作。

插入
  • insertObject:into:insertObjects:into:,插入单个或多个对象
  • insertOrReplaceObject:intoinsertOrReplaceObjects:into,插入单个或多个对象。当对象的主键在数据库内已经存在时,更新数据;否则插入对象。
  • insertObject:onProperties:into:insertObjects:onProperties:into:,插入单个或多个对象的部分属性
  • insertOrReplaceObject:onProperties:intoinsertOrReplaceObjects:onProperties:into,插入单个或多个对象的部分属性。当对象的主键在数据库内已经存在时,更新数据;否则插入对象。
删除
  • deleteAllObjectsFromTable:删除表内的所有数据
  • deleteObjectsFromTable:后可组合接 whereorderBylimitoffset以删除部分数据
更新
  • updateAllRowsInTable:onProperties:withObject:,通过object更新数据库中所有指定列的数据
  • updateRowsInTable:onProperties:withObject:后可组合接 whereorderBylimitoffset以通过object更新指定列的部分数据
  • updateAllRowsInTable:onProperty:withObject:,通过object更新数据库某一列的数据
  • updateRowsInTable:onProperty:withObject:后可组合接 whereorderBylimitoffset以通过object更新某一列的部分数据
  • updateAllRowsInTable:onProperties:withRow:,通过数组更新数据库中的所有指定列的数据
  • updateRowsInTable:onProperties:withRow:后可组合接 whereorderBylimitoffset以通过数组更新指定列的部分数据
  • updateAllRowsInTable:onProperty:withRow:,通过数组更新数据库某一列的数据
  • updateRowsInTable:onProperty:withRow:后可组合接 whereorderBylimitoffset以通过数组更新某一列的部分数
查找
  • getOneObjectOfClass:fromTable:后可接 whereorderBylimitoffset以从数据库中取出一行数据并组合成object
  • getOneObjectOnResults:fromTable:后可接 whereorderBylimitoffset以从数据库中取出一行数据的部分列并组合成object
  • getOneRowOnResults:fromTable:后可接 whereorderBylimitoffset以从数据库中取出一行数据的部分列并组合成数组
  • getOneColumnOnResult:fromTable:后可接 whereorderBylimitoffset以从数据库中取出一列数据并组合成数组
  • getOneDistinctColumnOnResult:fromTable:后可接 whereorderBylimitoffset以从数据库中取出一列数据,并取distinct后组合成数组。
  • getOneValueOnResult:fromTable:后可接 whereorderBylimitoffset以从数据库中取出一行数据的某一列
  • getAllObjectsOfClass:fromTable:,取出所有数据,并组合成object
  • getObjectsOfClass:fromTable:后可接 whereorderBylimitoffset以从数据库中取出一部分数据,并组合成object
  • getAllObjectsOnResults:fromTable:,取出所有数据的指定列,并组合成object
  • getObjectsOnResults:fromTable:后可接whereorderBylimitoffset以从数据库中取出一部分数据的指定列,并组合成object
  • getAllRowsOnResults:fromTable:,取出所有数据的指定列,并组合成数组
  • getRowsOnResults:fromTable:后可接whereorderBylimitoffset以从数据库中取出一部分数据的指定列,并组合成数组

具体例子可直接参考Sample-convenience

链式接口

链式调用是指对象的接口返回一个对象,从而允许在单个语句中将调用链接在一起,而不需要变量来存储中间结果。

WCDB对于增删改查操作,都提供了对应的类以实现链式调用

  • WCTInsert
  • WCTDelete
  • WCTUpdate
  • WCTSelect
  • WCTRowSelect
  • WCTMultiSelect
1
2
3
4
5
6
WCTSelect *select = [database prepareSelectObjectsOnResults:Message.localID.max()
fromTable:@"message"];
NSArray<Message *> *objects = [[[[select where:Message.localID > 0]
groupBy:{Message.content}]
orderBy:Message.createTime.order()]
limit:10].allObjects;

whereorderBylimit等接口的返回值均为self,因此可以通过链式调用,更自然更灵活的写出对应的查询。

开发者可以通过链式接口获取数据库操作的耗时、错误信息;也可以通过遍历逐个生成object。

1
2
3
4
5
6
7
8
9
//Error message
WCTError *error = select.error;
//Performance
int cost = select.cost;
//Iteration
Message *message;
while ((message = [select nextObject])) {
//...
}

关于链式接口的例子,请参考Sample-chaincall

封装SQL

1
2
- (BOOL)exec:(const WCDB::Statement &)statement;
- (WCTStatement *)prepare:(const WCDB::Statement &)statement;

结合WINQ,开发者可以用核心层接口执行其他未封装的复杂SQL。

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
//run unwrapped SQL
//PRAGMA case_sensitive_like=1
[database exec:WCDB::StatementPragma().pragma(WCDB::Pragma::CaseSensitiveLike, true)];
//get value from unwrapped SQL
//PRAGMA case_sensitive
WCTStatement *statement = [database prepare:WCDB::StatementPragma().pragma(WCDB::Pragma::CacheSize)];
if (statement && statement.step) {
NSLog(@"Cache size %@", [statement getValueAtIndex:0]);
}
//complex statement
//EXPLAIN CREATE TABLE message(localID INTEGER PRIMARY KEY ASC, content TEXT);
NSLog(@"Explain:");
WCDB::ColumnDef localIDColumnDef(WCDB::Column("localID"), WCDB::ColumnType::Integer32);
localIDColumnDef.makePrimary(WCDB::OrderTerm::ASC);
WCDB::ColumnDef contentColumnDef(WCDB::Column("content"), WCDB::ColumnType::Text);
WCDB::ColumnDefList columnDefList = {localIDColumnDef, contentColumnDef};
WCDB::StatementCreateTable statementCreate = WCDB::StatementCreateTable().create("message", columnDefList);
WCTStatement *statementExplain = [database prepare:WCDB::StatementExplain().explain(statementCreate)];
if (statementExplain && [statementExplain step]) {
for (int i = 0; i < [statementExplain getCount]; ++i) {
NSString *columnName = [statementExplain getNameAtIndex:i];
WCTValue *value = [statementExplain getValueAtIndex:i];
NSLog(@"%@:%@", columnName, value);
}
}

调试SQL

[WCTStatistics SetGlobalSQLTrace:]会监控所有执行的SQL,该接口可用于调试,确定SQL是否执行正确。

1
2
3
4
//SQL Execution Monitor
[WCTStatistics SetGlobalSQLTrace:^(NSString *sql) {
NSLog(@"SQL: %@", sql);
}];

关于核心层接口的例子,请参考Sample-core

主键自增(Auto Increment)

对于主键自增的类,需要在ORM定义WCDB_PRIMARY_AUTO_INCREMENT(className, propertyName),然后通过isAutoIncrement接口设置自增属性,并通过lastInsertedRowID接口获取插入的RowID

1
2
3
4
5
6
WCTSampleConvenient *object = [[WCTSampleConvenient alloc] init];
object.isAutoIncrement = YES;
object.stringValue = @"Insert auto increment";
[database insertObject:object
into:tableName];
long long lastInsertedRowID = object.lastInsertedRowID;

as重定向

基于ORM的支持,我们可以从数据库直接取出一个Object。然而,有时候需要取出并非是某个字段,而是有一些组合。例如:

1
2
3
4
NSNumber *maxModifiedTime = [database getOneValueOnResult:Message.modifiedTime.max()
fromTable:@"message"];
Message *message = [[Message alloc] init];
message.createTime = [NSDate dateWithTimeIntervalSince1970:maxModifiedTime.doubleValue];

这段代码从数据库中取出了消息的最新的修改时间,并以此将此时间作为消息的创建时间,新建了一个message。这种情况下,就可以使用as重定向。

as重定向,它可以将一个查询结果重定向到某一个字段,如下:

1
2
Message *message = [database getOneObjectOnResults:Message.modifiedTime.max().as(Message.createTime)
fromTable:@"message"];

通过as(Message.createTime)的语法,将查询结果重新指向了createTime。因此只需一行代码便可完成原来的任务。

多表查询

SQLite支持联表查询,在某些特定的场景下,可以起到优化性能、简化表结构的作用。

WCDB同样提供了对应的接口,并在ORM的支持下,通过WCTMultiSelect的链式接口,可以同时从表中取出多个类的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
SELECT contact.nickname, contact_ext.headImg
FROM contact, contact_ext
WHERE contact.name==contact_ext.name
*/
WCTMultiSelect *multiSelect = [[database prepareSelectMultiObjectsOnResults:{
Contact.nickname.inTable(@"contact"),
ContactExt.nickname.inTable(@"contact_ext")
} fromTables:@[ @"contact", @"contact_ext" ]] where:Contact.name.inTable(@"contact") == ContactExt.name.inTable(@"contact_ext")];
while ((multiObject = [multiSelect nextMultiObject])) {
Contact *contact = (Contact *) [multiObject objectForKey:@"contact"];
ContactExt *contact = (ContactExt *) [multiObject objectForKey:@"contact_ext"];
//...
}

ORM

ORM宏

WCDB使用内置的宏来连接类、属性与表、字段。共有三类宏,分别对应数据库的字段、索引和约束。所有宏都定义在WCTCodingMacro.h中。

关于字段、索引、约束的具体描述及用法,请参考SQLite的相关文档:Create TableCreate Index

字段宏

字段宏以WCDB_SYNTHESIZE开头,定义了类属性与字段之间的联系。支持自定义字段名和默认值。

  • WCDB_SYNTHESIZE(className, propertyName)是最简单的用法,它直接使用propertyName作为数据库字段名。
  • WCDB_SYNTHESIZE_COLUMN(className, propertyName, columnName)支持自定义字段名。
  • WCDB_SYNTHESIZE_DEFAULT(className, propertyName, defaultValue)支持自定义字段的默认值。默认值可以是任意的C类型或NSStringNSDataNSNumberNSNull
  • WCDB_SYNTHESIZE_COLUMN_DEFAULT(className, propertyName, columnName, defaultValue)为以上两者的组合。

关于字段宏的例子,请参考WCTSampleORM

索引宏

索引宏以WCDB_INDEX开头,定义了数据库的索引属性。支持定义索引的排序方式。

  • WCDB_INDEX(className, indexSubfixName, propertyName)是最简单的用法,它直接定义某个字段为索引。同时,WCDB会将tableName+indexSubfixName作为该索引的名称。
  • WCDB_INDEX_ASC(className, indexSubfixName, propertyName)定义索引为升序。
  • WCDB_INDEX_DESC(className, indexSubfixName, propertyName)定义索引为降序。
  • WCDB_UNIQUE_INDEX(className, indexSubfixName, propertyName)定义唯一索引。
  • WCDB_UNIQUE_INDEX_ASC(className, indexSubfixName, propertyName)定义唯一索引为升序。
  • WCDB_UNIQUE_INDEX_DESC(className, indexSubfixName, propertyName)定义唯一索引为降序。

多字段索引

WCDB通过indexSubfixName匹配多索引。相同的indexSubfixName会被组合为多字段索引。

1
2
WCDB_INDEX(WCTSampleORMIndex, "_multiIndexSubfix", multiIndexPart1)
WCDB_INDEX(WCTSampleORMIndex, "_multiIndexSubfix", multiIndexPart2)

关于索引宏的例子,请参考WCTSampleORMIndex

约束宏

约束宏包括字段约束和表约束

字段约束

  • 主键约束以WCDB_PRIMARY开头,定义了数据库的主键,支持自定义主键的排序方式、是否自增。

    • WCDB_PRIMARY(className, propertyName)是最基本的用法,它直接使用propertyName作为数据库主键。
    • WCDB_PRIMARY_ASC(className, propertyName)定义主键升序。
    • WCDB_PRIMARY_DESC(className, propertyName)定义主键降序。
    • WCDB_PRIMARY_AUTO_INCREMENT(className, propertyName)定义主键自增。
    • WCDB_PRIMARY_ASC_AUTO_INCREMENT(className, propertyName)是主键自增和升序的组合。
    • WCDB_PRIMARY_DESC_AUTO_INCREMENT(className, propertyName)是主键自增和降序的组合。
  • 非空约束为WCDB_NOT_NULL(className, propertyName),当该字段插入数据为空时,数据库会返回错误。

  • 唯一约束为WCDB_UNIQUE(className, propertyName),当该字段插入数据与其他列冲突时,数据库会返回错误。

关于字段约束的例子,请参考WCTSampleORMColumnConstraint

表约束

  • 多主键约束以WCDB_MULTI_PRIMARY开头,定义了数据库的多主键,支持自定义每个主键的排序方式。
    • WCDB_MULTI_PRIMARY(className, constraintName, propertyName)是最基本的用法,与索引类似,多个主键通过constraintName匹配。
    • WCDB_MULTI_PRIMARY_ASC(className, constraintName, propertyName)定义了多主键propertyName对应的主键升序。
    • WCDB_MULTI_PRIMARY_DESC(className, constraintName, propertyName)定义了多主键中propertyName对应的主键降序。
  • 多字段唯一约束以WCDB_MULTI_UNIQUE开头,定义了数据库的多字段组合唯一,支持自定义每个字段的排序方式。
    • WCDB_MULTI_UNIQUE(className, constraintName, propertyName)是最基本的用法,与索引类似,多个字段通过constraintName匹配。
    • WCDB_MULTI_UNIQUE_ASC(className, constraintName, propertyName)定义了多字段中propertyName对应的字段升序。
    • WCDB_MULTI_UNIQUE_DESC(className, constraintName, propertyName)定义了多字段中propertyName对应的字段降序。

关于表约束的例子,请参考WCTSampleORMTableConstraint

类与数据库对应类型

SQLite数据库的字段有整型、浮点数、字符串、二进制数据等五种类型。WCDB的ORM会自动识别property的类型,并映射到适合的数据库类型。其对应关系为:

C类型 数据库类型
整型(包括但不限于intunsignedlongunsigned longlong longunsigned long long等所有基于整型的C基本类型) 整型(INTEGER)
枚举型(enum及所有基于枚举型的C基本类型) 整型(INTEGER)
浮点数(包括但不限于floatdoublelong double等所有基于浮点型的C基本类型) 浮点型( REAL)
字符串(const char *的C字符串类型) 字符串( TEXT)
Objective-C类型 数据库类型
Objective-C类型 数据库类型
NSDate 整型(INTEGER)
NSNumber 浮点型( REAL)
NSStringNSMutableString 字符串( TEXT)
其他所有符合NSCoding协议的NSObject子类 二进制(BLOB)

自定义类-数据库类型映射

WCDB支持开发者自定义绑定类型。

类只需实现WCTColumnCoding协议,即可支持绑定。

1
2
3
4
5
6
@protocol WCTColumnCoding
@required
+ (instancetype)unarchiveWithWCTValue:(WCTValue *)value; //value could be nil
- (id /* WCTValue* */)archivedWCTValue; //value could be nil
+ (WCTColumnType)columnTypeForWCDB;
@end
  • columnTypeForWCDB接口定义类对应数据库中的类型。
  • unarchiveWithWCTValue:接口定义从数据库类型反序列化到类的转换方式。
  • archivedWCTValue接口定义从类序列化到数据库类型的转换方式。

为了简化定义,WCDB提供了Xcode文件模版来创建类字段绑定。

取消内置类型

上面提到WCDB内置对NSDataNSArray等等常用的Objective-C类型的支持,并且基于NSCoding协议进行序列化和反序列化。这些内置绑定的实现都在 builtin 目录下,这些实现也可以作为例子参考。

若开发者希望自定义基本类型的绑定,可以将内置的绑定关闭。关闭方法为:删除工程文件的Build Settings->Preprocessor Macros下各个scheme的WCDB_BUILTIN_COLUMN_CODING宏。

关于自定义类型的例子,请参考sample_advance

修改字段

对于需要增加的字段,只需在定义处添加,并再次执行createTableAndIndexesOfName:withClass:即可。

1
2
3
WCDB_IMPLEMENTATION(WCTSampleAddColumn)
WCDB_SYNTHESIZE(WCTSampleAddColumn, identifier)
WCDB_SYNTHESIZE(WCTSampleAddColumn, newColumn)// Add a new column

对于需要删除字段,只需将其定义删除即可。

1
2
3
WCDB_IMPLEMENTATION(WCTSampleAddColumn)
WCDB_SYNTHESIZE(WCTSampleAddColumn, identifier)
//WCDB_SYNTHESIZE(WCTSampleAddColumn, deletedColumn)// delete a column

由于SQLite不支持删除字段,因此,删除定义后,WCDB只是将该字段忽略,其旧数据依然存在在数据库内,但新增加的数据基本不会因为该字段产生额外的性能和空间损耗。

由于SQLite不支持修改字段名称,因此WCDB使用WCDB_SYNTHESIZE_COLUMN(className, propertyName, columnName)重新映射宏。

对于已经定义的字段WCDB_SYNTHESIZE(MyClass, myValue)可以修改为WCDB_SYNTHESIZE_COLUMN(MyClass, newMyValue, "myValue")

对于已经定义的字段类型,可以任意修改为其他类型。但旧数据会使用新类型的解析方式进行反序列化,因此需要确保其兼容性。

更多扩展性

由于ORM不可能覆盖所有用法,因此WCDB提供了 core 接口,开发者可以根据自己的需求执行SQL。请参考 核心层接口

如果这些接口仍不满足你的需求,欢迎给我们提 Issue

注意事项

  1. 由于WCDB_SYNTHESIZE(className, propertyName)宏默认使用propertyName作为字段名,因此在修改propertyName后,会导致错误,需用WCDB_SYNTHESIZE_COLUMN(className, newPropertyName, "oldPropertyName")重新映射。
  2. 每个进行ORM的property,都必须实现getter/setter。因为ORM会在初始化时通过objc-runtime获取property的getter/setter的IMP ,以此实现通过object存取数据库的特性。getter/setter不必须是公开的,也可以是私有接口。

全局监控与错误处理

错误处理WCTError

WCTError继承自NSError,包含了WCDB错误的所有信息,以供调试或发现问题。

type表示错误的类型,不同类型的错误其错误码和拥有的信息不同。其对应关系如下

Type Code
SQLite,表示该错误来自SQLite接口 请参考rescode
SystemCall,表示该错误来自系统调用 请参考errno
Core,表示该错误来自WCDB Core层 请参考源码的error.hpp
Interface,表示该错误来自WCDB Interface层 请参考源码的error.hpp
Abort,表示中断,该错误一般是开发错误,应该在发布前修复 /
Warning,表示警告,建议修复 /
SQLiteGlobal,表示该信息来自SQLite的log接口,一般只作为debug log 请参考rescode

其他错误信息通过infoForKey接口获得,包括:

  1. Tag,正在操作的数据库的tag
  2. Operation,正在进行的操作,请参考error.hpp
  3. Extended Code,SQLite的扩展码,请参考rescode
  4. Message,错误信息
  5. SQL,发生错误时正在执行的SQL
  6. Path,发生错误时正在操作的文件的路径。
获取错误

由于便捷接口的设计原则是易用,因此不提供获取错误的方式。错误处理需使用链式接口

1
2
3
4
5
6
WCTSelect *select = [database prepareSelectObjectsOfClass:Message.class
fromTable:@"message"];
NSArray<Message *> *objects = [[[select where:Message.localID > 0]
orderBy:Message.createTime.order()]
limit:10].allObjects;
WCTError *error = select.error;

开发者也可以注册全局的错误接口,以调试、上报、打log

1
2
3
4
//Error Monitor
[WCTStatistics SetGlobalErrorReport:^(WCTError *error) {
NSLog(@"[WCDB]%@", error);
}];

性能监控

WCDB支持获取单次操作的耗时,也支持对单个DB或全局注册统一接口监控性能。

所有性能监控都会有少量的性能损坏,请根据需求开启。

操作耗时

由于便捷接口的设计原则是易用,因此不提供获取错误的方式。操作耗时需使用链式接口。

首先安通过setStatisticsEnabled:打开耗时监控

1
2
3
WCTSelect *select = [database prepareSelectObjectsOfClass:Message.class
fromTable:@"message"];
[select setStatisticsEnabled:YES];//You should call this before all other operations.

在操作执行完成后,通过cost接口获取耗时

1
2
3
4
NSArray<Message *> *objects = [[[select where:Message.localID > 0]
orderBy:Message.createTime.order()]
limit:10].allObjects;
NSLog(@"%f", select.cost);//You should call this after all other operations.
监控耗时

WCDB支持对所有SQL操作进行全局监控,也支持监控单个特定的数据库。

所有监控的返回数据都相同,包括三个数据:

  • Tag,执行操作的数据库的tag
  • sqls,执行的SQL和对应的次数
    • 对于非事务操作,则为单条SQL
    • 对于事务操作,则为该次事务所执行的所有SQL和每个sql执行的次数
  • cost,耗时
全局监控

监控所有db的数据库操作耗时,该接口需要在所有db打开、操作之前调用。

1
2
3
4
5
6
7
[WCTStatistics SetGlobalTrace:^(WCTTag tag, NSDictionary<NSString *, NSNumber *> *sqls, NSInteger cost) {
NSLog(@"Tag: %d", tag);
[sqls enumerateKeysAndObjectsUsingBlock:^(NSString *sql, NSNumber *count, BOOL *) {
NSLog(@"SQL: %@ Count: %d", sql, count.intValue);
}];
NSLog(@"Total cost %ld nanoseconds", (long) cost);
}];
特定数据库监控

对于特定的数据库,该接口会覆盖全局监控的注册。

1
2
3
4
5
6
7
[db setTrace:^(WCTTag tag, NSDictionary<NSString *, NSNumber *> *sqls, NSInteger cost) {
NSLog(@"Tag: %d", tag);
[sqls enumerateKeysAndObjectsUsingBlock:^(NSString *sql, NSNumber *count, BOOL *) {
NSLog(@"SQL: %@ Count: %d", sql, count.intValue);
}];
NSLog(@"Total cost %ld nanoseconds", (long) cost);
}];
操作耗时与监控耗时的不同
  • 操作耗时的cost返回的耗时为浮点数的秒,监控耗时的cost返回的耗时为整型的纳秒。
  • 监控耗时仅包括SQL在SQLite层面的耗时,包括SQL的编译、I/O等。而操作耗时除以上之外,还包括了WCDB层面对类封装等产生的耗时
SQL执行监控

WCDB可以监控所有SQL的执行,以确定代码符合预期

1
2
3
4
//SQL Execution Monitor
[WCTStatistics SetGlobalSQLTrace:^(NSString *sql) {
NSLog(@"SQL: %@", sql);
}];

参考链接:wcdb-wiki