fastdb主要的目标之一就是提供一个灵活并且方便的应用语言接口。任何使用过odbc或者类似的sql接口的人会明白我说的是什么。在fastdb中,一个查询可以用c++写成下面的样子:
dbQuery q;
dbCursor<Contract> contracts;
dbCursor<Supplier> suppliers;
int price, quantity;
q = "(price >=",price,"or quantity >=",quantity,
") and delivery.year=1999";
// input price and quantity values
if (contracts.select(q) != 0) {
do {
printf("%s\n", suppliers.at(contracts->supplier)->company);
} while (contracts.next());
}
Table
Type
|
Description
|
bool
|
boolean type ( true,false )
|
int1
|
one byte signed integer (-128..127)
|
int2
|
two bytes signed integer (-32768..32767)
|
int4
|
four bytes signed integer (-2147483648..2147483647)
|
int8
|
eight bytes signed integer (-2**63..2**63-1)
|
real4
|
four bytes ANSI floating point type
|
real8
|
eight bytes ANSI double precision floating point type
|
char const*
|
zero terminated string
|
dbReference<T>
|
reference to class T
|
dbArray<T>
|
dynamic array of elements of type T
|
TYPE_DESCRIPTOR(
field_list)
构成了这个方法。这个宏的用括号括起来的单一参数是一个类字段描述符的列表。如果要为这个类定义一些方法并使之可以用于对应的数据库,则用宏
CLASS_DESCRIPTOR(
name, field_list)
来代替
TYPE_DESCRIPTOR
。需要类名来取得成员函数的引用。
下面的例子说明了头文件中类型描述符的创建过程:
class dbDateTime {
int4 stamp;
public:
int year() {
return localtime((time_t*)&stamp)->tm_year + 1900;
}
...
CLASS_DESCRIPTOR(dbDateTime,
(KEY(stamp,INDEXED|HASHED),
METHOD(year), METHOD(month), METHOD(day),
METHOD(dayOfYear), METHOD(dayOfWeek),
METHOD(hour), METHOD(minute), METHOD(second)));
};
class Detail {
public:
char const* name;
char const* material;
char const* color;
real4 weight;
dbArray< dbReference<Contract> > contracts;
TYPE_DESCRIPTOR((KEY(name, INDEXED|HASHED),
KEY(material, HASHED),
KEY(color, HASHED),
KEY(weight, INDEXED),
RELATION(contracts, detail)));
};
class Contract {
public:
dbDateTime delivery;
int4 quantity;
int8 price;
dbReference<Detail> detail;
dbReference<Supplier> supplier;
TYPE_DESCRIPTOR((KEY(delivery, HASHED|INDEXED),
KEY(quantity, INDEXED),
KEY(price, INDEXED),
RELATION(detail, contracts),
RELATION(supplier, contracts)));
};
所有数据库中使用的类都要定义类型描述符。除了定义类型描述符外,还必须在C++类和数据库表之间建立一个映像。宏REGISTER(name)就做这个工作。与TYPE_DESCRIPTOR宏不同的是,REGISTER宏应该在实现文件中使用而不是在头文件中。该宏构造一个与类相连的表的描述符。如果你要在一个应用中使用多个数据库,那么就可能使用REGISTER_IN(name,database)宏在一个具体数据库中注册一个表。该宏的database参数应该是一个指向dbDatabase对象的指针。你可以像下面这样注册数据库的表:
REGISTER(Detail);REGISTER(Supplier);REGISTER(Contract);表(以及对应的类)在每一时刻只能对应于一个数据库。当你打开一个数据库,fastdb向数据库中导入所有在应用中定义的类。如果一个同名的类在数据库中已经存在,则会比较描述符在数据库中的类与描述符在应用中的类,如果两个类的定义不同,则fastdb试图将该表转换成新的格式。数值类型之间的任何转换(整形到实型,实型到整形,扩展或者截断)都是允许的。增加字段也很容易,但是只有对空表才可以删除字段(以避免偶然的数据崩溃).装载所有的类描述符后,fastdb就检查在应用程序的类描述符中指定的索引是否存在于数据库中、创建新的索引并且删除不再使用的索引。只有在不超过一个应用程序访问数据库是才可以进行表的重构以及增加/删除索引。所以只有第一个与数据库关联的应用程序可以进行表的转换,所有其余的应用只能向数据库中增加新类。有一个特殊的内部表Metatable,该表包含了数据库中所有其他表的信息。C++程序员不需要访问这个表,因为数据库表的结构是由C++类指定的。但在交互SQL程序中,还是有必要检查这个表来获取记录字段的信息。从版本2.30开始,fastdb支持自增字段(有数据自动赋值的值唯一的字段).要使用自增字段必须:
1.
带上-DAUTOINCREMENT_SUPPROT
标志重新编译
fastdb
和你的应用程序。(在
fastdb makefile
中的
DEFS
变量中增加这个标志)
注意:不带该标记编译的
fastdb
创建的数据库文件与带标记编译的
fastdb
创建的数据库文件不兼容。
2.
如果你要使用初始值非
0
的计数器,则必须给
dbTableDescriptor::initialAutoincrementCount
赋个值。该变量由所有的表共享,因此所有的表都有一个共同初始值的自增计数器。
3.
自增字段必须是
int4
类型,并且必须用
AUTOINCREMENT
标志声明
int4 rid;
char const* name;
...
TYPE_DESCRIPTOR((KEY(rid, AUTOINCREMENT|INDEXED), FIELD(name), ...));
}
// no rec.rid should be specified
rec.name = "John Smith";
insert(rec);
// rec.rid now assigned unique value
int newRecordId = rec.rid; // and can be used to reference this record
5.当记录被删除该值将不会再使用,当事务中止时,表的自增计数器也将回滚。
Query
int price, quantity;
q = "price >=",price,"or quantity >=",quantity;
由于char *可以用来指定一个查询的分片(fraction)(例如"price >=")和一个字符串类型的参数,fastdb使用了一个特别的规则来解决这个模糊性。该规则基于这样一个假定即没有理由把一个查询文本分解成两个字符串如("price",">=")或者指定多于一个的参数序列("color=",color,color).因此fastdb假定第一个字符串是该查询文本的一个分片并且随之转换到操作数模式。在操作数模式中,fastdb认为char * 参数是一个查询参数然后切换回到查询文本模式,依此类推。也可以不用这个“句法糖”(syntax sugar)而是显示的通过dbQuery::append(dbQueryElement::ElementType type, void const* ptr)
方法来构造查询元素。在向查询添加元素之前,必须通过dbQuery::reset()
方法来重置查询('operator='
自动作了这个事情)。
不能使用c++数值常量来作为查询参数,因为参数是通过引用来访问的。但可以使用字符串常量,因为字符串时传值的。有两种方法在一个查询中指定字符串参数:使用一个字符串缓冲或一个指向字符串指针的指针dbQuery q;char* type;char name[256];q = "name=",name,"and type=",&type;scanf("%s", name);type = "A";cursor.select(q);...scanf("%s", name);type = "B";cursor.select(q);...查询变量既不能作为一个参数传给一个函数也不能赋给另一个变量。当fastdb编译查询时,会把编译树存到该对象中。下一次使用该查询时,不需要再次编译并且可以使用已编译好的树。这样节省了一些编译查询的时间。fastdb提供了两个方法来集成数据库中的用户自定义类型。第一种方法-定义类方法-已经讨论过,另一个方法只处理查询构造。程序员要定义方法,该方法并不作确实的运算,而是返回一个表达式(根据预先定义的数据库类型),该方法来执行必要的查询。最好通过例子来说明这点。fastdb没有内置的日期时间类型,而是使用一个普通的c++类dbDateTime。该类定义了方法用来在有序列表中指定日期时间字段和使用通常的运算符来比较两个日期。class dbDateTime {int4 stamp;public:...dbQueryExpression operator == (char const* field) {dbQueryExpression expr;expr = dbComponent(field,"stamp"),"=",stamp;return expr;}dbQueryExpression operator != (char const* field) {dbQueryExpression expr;expr = dbComponent(field,"stamp"),"<>",stamp;return expr;}dbQueryExpression operator < (char const* field) {dbQueryExpression expr;expr = dbComponent(field,"stamp"),">",stamp;return expr;}dbQueryExpression operator <= (char const* field) {dbQueryExpression expr;expr = dbComponent(field,"stamp"),">=",stamp;return expr;}dbQueryExpression operator > (char const* field) {dbQueryExpression expr;expr = dbComponent(field,"stamp"),"<",stamp;return expr;}dbQueryExpression operator >= (char const* field) {dbQueryExpression expr;expr = dbComponent(field,"stamp"),"<=",stamp;return expr;}friend dbQueryExpression between(char const* field, dbDateTime& from,dbDateTime& till){dbQueryExpression expr;expr=dbComponent(field,"stamp"),"between",from.stamp,"and",till.stamp;return expr;}friend dbQueryExpression ascent(char const* field) {dbQueryExpression expr;expr=dbComponent(field,"stamp");return expr;}friend dbQueryExpression descent(char const* field) {dbQueryExpression expr;expr=dbComponent(field,"stamp"),"desc";return expr;}};所有这些方法接受参数作为一个记录的字段的名字,该名字用来构造一个记录组件的全名。使用类dbComponent来作这个事情,该类把结构字段的名字和结构组件的名字组合成一个用'.'符号分隔的复合名字。类
dbQueryExpression
用来收集表达式项,表达式自动的用圆括号括起来,消除了运算符优先级引起的冲突。
假定一个记录包含了一个字段
dbDateTime
类型的字段
delivery
,可以如下构造查询:
dbDateTime from, till;
q1 = between("delivery", from, till),"order by",ascent("delivery");
q2 = till >= "delivery";
除了这些方法外,一些类指定方法也可以用这种方法定义,擂如一个区域类型的overlaps方法。这种方法的好处是数据库引擎可以使用预定义的类型并且可以使用索引和其他的一些优化方法来执行查询。另一方面,这些类的实现的封装已保留,因此程序员在一个类的表示改变时不应该重写所有的查询。
下面这些c++类型可以用作查询参数:
int1 bool int2 char const* int4 char ** int8 char const** real4 dbReference<T> real8 dbArray< dbReference<T> >
Cursor
at(dbReference<T> const& ref)
方法把游标指向引用所指的记录。在这种情况下,选择将只包含一个记录,而
next(),prev()
方法将总是返回
NULL
。由于游标和引用在
fastdb
重视严格类型化的,所有必须的检查可以有编译器静态的进行而不需要动态类型检查。运行时唯一要进行的检查是对空引用的检查。游标中当前记录的对象标识符可以用
currentId()
方法获得。
可以限制
select
语句返回的记录的数目。游标类有两个方法
setSelectionLimit(size_t lim)
和
unsetSelectionLimit()
用来设置
/
取消查询返回的记录数的限制。在某些情况下,程序员可能只需要几个记录或者头几个记录,从而查询的执行时间和消耗的内存大小可以通过限制选择的大小来降低。但如果你指定了被选记录的排序方式,只选择
k
个记录的查询并不返回关键字最小的头
k
个记录,而是返回任意
k
个记录然后排序。
于是所有数据库数据的操作都可以通过游标来进行,唯一的例外是插入操作,fastDB提供了一个重载的插入函数:
template<class T>
dbReference<T> insert(T const& record);
fastdb的c++API为引用类型定义了一个特殊的null变量,可以用null变量与引用比较或者赋给一个引用:
void update(dbReference<Contract> c) {
if (c != null) {
dbCursor<Contract> contract(dbCursorForUpdate);
contract.at(c);
contract->supplier = null;
}
}
查询参数通常跟c++变量绑定。大多数情况下这是方便而且灵活的机制。但在多线程应用中,无法保证同一查询会在同一时刻不被另一线程以不同的参数执行。一个解决的方法是使用同步原语(临界区或者mutex)来排除查询的并发执行。但这种方法会导致性能退化。fastdb可以并行操作读操作而提高了整体系统吞吐量。另一个解决方法是使用延迟参数绑定。如下所示:
dbQuery q;struct QueryParams {int salary;int age;int rank;};void open(){QueryParams* params = (QueryParams*)NULL;q = "salary > ", params->salary, "and age < ", params->age, "and rank =", params->rank;}void find(int salary, int age, int rank){QueryParams params;params.salary = salary;params.age = age;params.rank = rank;dbCursor<Person> cusor;if (cursor.select(q, ¶ms) > 0) {do {cout << cursor->name << NL;} while (cursor.next());}}在这个例子中open函数只为结构中的字段偏移绑定查询变量。然后再find函数中,指向带有参数的结构的真实的指针传递给select结构。find函数可以被多个线程并发执行而只有一个编译好的查询被所有这些线程使用。这种机制从版本2.25开始使用。
Database
dbDatabase类控制应用与数据库的交互,进行数据库并发访问的同步,事务处理,内存分配,出错处理...dbDatabase对象的构造函数允许程序员制定一些数据库参数。
dbDatabase(dbAccessType type = dbAllAccess,
size_t dbInitSize = dbDefaultInitDatabaseSize,
size_t dbExtensionQuantum = dbDefaultExtensionQuantum,
size_t dbInitIndexSize = dbDefaultInitIndexSize,
int nThreads = 1);
支持下面的数据库访问类型:
Access type
|
Description
|
dbDatabase::dbReadOnly
|
Read only mode
|
dbDatabase::dbAllAccess
|
Normal mode
|
dbDatabase::dbConcurrentRead
|
Read only mode in which application can access the database concurrently with application updating the same database in dbConcurrentUpdate mode
|
dbDatabase::dbConcurrentUpdate
|
Mode to be used in conjunction with dbConcurrentRead to perform updates in the database without blocking read applications for a long time
|
在数据库主要用只读模式访问而更新不应该长时间堵塞读操作的情况下应该同时使用
dbConcurrentUpdate
和dbConcurrentRead
模式。在这种模式下更新数据库可以与读访问并发执行(读将不会看到改变的数据直到事务提交)。只有在更新事务提交时,才会设置排他锁然后在当前对象索引自增改变
(incremental change)
之后马上释放掉。
于是你可以使用
dbConcurrentRead
模式启动一个或多个应用而其读事务将并发执行。也可以使用
dbConcurrentUpdate
模式启动一个或多个应用。所有这些应用的事务将通过一个全局的
mutex
来同步。因此这些事务
(
甚至是只读
)
将排他性的执行。但是
dbConcurrentUpdate
模式的应用的事务可以与
dbConcurrentRead
模式的应用的事务并发运行。请参阅
testconc.cpp
例子,里边说明了这些模式的使用方法。
dbConcurrentUpdate
和dbConcurrentRead
模式与其他模式混合使用,也不要在一个进程中同时使用他们(因此不能启动两个线程其中一个用
dbConcurrentUpdate模式打开数据库另一个用dbConcurrentRead模式)。在dbConcurrentUpdate
模式下不要使用dbDatabase::precommit
方法。
dbDatabase::setConcurrency
来指定线程数。
dbParallelScanThreshold
,该字段指定了在使用并行查询后表中记录数的一个阈值,缺省为
1000
。
可以用
open(char const* databaseName, char const* fileName = NULL, unsigned waitLockTimeout = INFINITE)
方法来打开数据库。如果文件名参数省略,则通过数据库名家一个后缀“
.fdb"
来创建一个文件。数据库名应该是由除了‘
\
’之外的任意符号组成的标识符。最后一个参数
waitLockTimeout
可以设置用来防止当工作于该数据库的所有活动进程中的某些进程崩溃时把所有的进程锁住。如果崩溃的进程锁住了数据库,则其他的进程都将无法继续执行。为了防止这种情况,可以指定一个等待该锁的最大延迟,当该延迟过期后,系统将试图进行恢复并继续执行活动的进程。如果数据库成功打开
open
方法返回
true,
否则返回
false
。在后面这种情况,数据库的
handleError
方法将带上
DatabaseOpenError
错误码被调用。一个数据库会话可以用
close
方法中止,该方法隐含的提交当前事务。
在一个多线程的应用中,每一个要访问数据库的线程都应该首先与数据库粘附
(attach).
dbDatabase::attach()
方法分配线程指定数据然后把线程与数据库粘附起来。该方法自动由
open()
方法调用。因此没有理由为打开数据的线程调用
attach()
方法。当该线程工作完毕,应当调用
dbDatabase::detach()
方法。close方法自动调用detach()
方法。
detach()
方法隐含提交当前事务。一个已经分离的线程试图访问数据库时将产生断言错误(
assertion failure
)。
fastdb
可以并行的编译和执行查询,在多处理器系统中显著的提高了性能。但不能并发更新数据库(这是为了尽可能少的日志事务
(log-less transaction)
机制和
0
等待恢复的代价)
.
当一个应用程序试图改变数据库(打开一个更新游标或者在表中插入新记录)时,首先就以排他方式锁住数据库,禁止其他应用程序访问数据库,即使是只读的查询。这样来避免锁住数据库应用程序过长的时间,改变事务应当尽可能的短。在该事务中不能进行堵塞操作(如等待用户的输入)
.
可以使用lock()方法来显示的锁住数据库。锁通常是自动进行的。只有很少的情况下你才需要使用这个方法。它将以排他方式锁住数据库知道当前事务结束。
可以用dbDatabase::backup(char const* file)
方法来备份数据库。备份操作将以共享模式锁住数据然后从内存向指定的文件刷新数据库的映像。因为使用了影子对象索引,数据库文件总是处于一致状态,因此从备份恢复至需要把备份文件改一下名字(如果备份被放到磁带,则首先要把文件恢复到磁盘).
dbDatabase
类也负责处理一些应用的错误,如编译查询时的句法错误,执行查询时的索引越界或者空引用访问。由一个虚方法dbDatabase::handleError
来处理这些错误。
virtual void handleError(dbErrorClass error,char const* msg = NULL,int arg = 0);
Error classes and default handling
| |||
Class
|
Description
|
Argument
|
Default reaction
|
QueryError
|
query compilation error
|
position in query string
|
abort compilation
|
ArithmeticError
|
arithmetic error during division or power operations
|
-
|
terminate application
|
IndexOutOfRangeError
|
index is out if array bounds
|
value of index
|
terminate application
|
DatabaseOpenError
|
error while database opening
|
-
|
open method will return false
|
FileError
|
failure of file IO operation
|
error code
|
terminate application
|
OutOfMemoryError
|
not enough memory for object allocation
|
requested allocation size
|
terminate application
|
Deadlock
|
upgrading lock causes deadlock
|
-
|
terminate application
|
NullReferenceError
|
null reference is accessed during query execution
|
-
|
terminate application
|