McoSqlEngine
对于大多数 C++ 应用程序来说,主要的类是 McoSqlEngine。
用于管理事务和执行预编译语句的功能是在 McoSqlSession 类中提供的。
实例化
内存分配器中所介绍的,6.5 及更高版本的内存分配反转在内部是通过静态内存块分配器来完成的。因此,应避免在全局静态内存中实例化 McoSqlEngine。
例如,不要使用以下声明:
McoSqlEngine engine;
在全局静态内存中,引擎应在 main()
函数中按如下方式实例化:
int main(int argc, char** argv)
{
McoSqlEngine engine;
...
}
然后通过调用其 open()
方法来初始化引擎,并通过 close() 方法来关闭引擎:
using namespace McoSql;
char * db_name = "persondb";
#define DATABASE_SIZE 268435456
#define MEMORY_PAGE_SIZE 128
int main(int argc, char* argv[])
{
McoSqlEngine engine;
...
engine.open( db_name, persondb_get_dictionary(), DATABASE_SIZE, MEMORY_PAGE_SIZE);
// 进行正常的数据库处理
engine.close();
return 0;
}
为了说明正常的数据库处理过程,以下示例在一个简单的数据库中创建两条 Person 记录,然后对其进行选择、更新和删除操作:
/* 数据库记录“Person”的模式定义 */
class Person
{
char<64> name;
int4 age;
float weight;
tree<name> by_name;
};
// 描述数据库模式的函数的前向声明
GET_DICTIONARY(persondb);
// 定义与数据库记录“Person”相对应的结构。
struct _Person
{
char const* name;
int age;
float weight;
};
int addPersons()
{
_Person p;
int count = 0;
// 添加两条“Person”记录
p.name = "John Smith";
p.age = 35;
p.weight = 72.1f;
engine.executeStatement("insert into Person %r", &p);
p.name = "Peter Brown";
p.age = 40;
p.weight = 62.1f;
engine.executeStatement("insert into Person %r", &p);
// 列出“Person”表中的内容
QueryResult result(engine.executeQuery("select * from Person order by age"));
showPersons( &result );
// 按姓名搜索“Person”记录
strcpy( name, "John%" );
engine.executeQuery("select * from Person where name like %s", name));
strcpy( name, "%Brown%" );
engine.executeQuery("select * from Person where name like %s", name));
// 根据姓名更新两条“Person”记录
strcpy( name, "John Smith" ); age = 36; weight = 75.2;
count = engine.executeStatement("update Person set age=%i, weight=%f where name=%s",
age, weight, name)
strcpy( name, "Peter Brown"); age = 41; weight = 65.0;
count = engine.executeStatement("update Person set age=%i, weight=%f where name=%s",
age, weight, name)
// 按姓名删除两条“Person”记录
strcpy( name, "John Smith" );
count = engine.executeStatement("delete from Person where name=%s", name);
count = engine.executeStatement("delete from Person”);
// 关闭 SQL 引擎和数据库
engine.close();
return 0;
}
void showPersons(QueryResult * result)
{
Cursor* cursor = (*result)->records();
while (cursor->hasNext())
{
Record* rec = cursor->next();
_Person p;
// 将“Person”记录提取到相应的结构体中
(*result)->extract(rec, &p, sizeof(p));
printf("\t\t%s: Age=%d Weight=%.2f\n", p.name, p.age, p.weight);
}
}
在上述代码示例中,有一些重要的编程要点需要注意:
- 在
mcoapi.h
中定义的宏 GET_DICTIONARY 将get*_dictionary()
函数定义为“extern C”函数(将数据库名称嵌入到函数名称中)。 - 在应用程序中定义的 C 结构体
_Person
与数据库模式中的类 Person 完全对应。该结构体中每个元素的 C 类型以及相应的内存大小,必须与数据库类中定义该字段时使用的类型完全一致。 _Person
字段名称的类型为char const*
,因此要为该字段赋字符串值,我们应将指针值设置为静态字符串的地址,而不是使用字符数组(char[64]
)字段类型,然后执行某种形式的strcpy()
来复制值。
p.name = "John Smith";
- 专门的 SmartESQL %r 语法将结构
_Person
的全部内容复制到 SQL 插入语句中,大大节省了编程工作:
engine.executeStatement("insert into Person %r", &p);
- QueryResult 类提供了一个游标,该游标可方便地被
showPersons()
函数用于遍历结果集。 - 当在像
showPersons()
这样的函数内实例化 QueryResult 对象时,该对象、其游标(结果集)以及与之相关的所有内存会在对象超出作用域时(即函数返回时)由类析构函数释放。 - 再次使用
_Person
结构从通过 SQL 选择语句检索到的数据库记录中提取数据。这之所以可行,是因为该结构中的数据排列与类定义完全对应。 - 对于更新和删除语句,
executeStatement()
方法会返回实际更新或删除的行数。
count = engine.executeStatement("update Person set age=%i, weight=%f where name=%s",age, weight, name)
- 专门的 SmartESQL %s、%i 和 %f 语法,用于直接将字符串、整数和浮点值复制到 SQL 的select、update和delete语句中,将参数列表中的值替换到 SQL 语句中。(请注意,此列表中的参数值最多为 10 个。有关 SmartEDB 格式说明符的完整列表,请参阅参数替换格式说明页面。)
QueryResult用法
C++ 的 QueryResult 主要目的是控制查询的范围,并在查询异常终止时自动释放所有已分配的资源。如果查询的范围(即 QueryResult 对象实例的生命周期)不正确,将会导致运行时错误。这种情况通常发生在实例在创建它的事务之外被使用,或者在数据库实例关闭时仍然存在。以下代码片段演示了错误的用法:
main ()
{
McoSqlEngine engine;
engine.open( ... );
QueryResult result(engine.executeQuery("select * from aRecord"));
Cursor* cursor = result->records();
while (cursor->hasNext())
{
...
} /* while 循环结束 */
engine.close(); /* 数据库实例在此处被销毁。 */
return 0;
/* 由局部变量“result”表示的 QueryResult 类对象将在此处被删除。 */
/* 由于数据库已不存在,QueryResult 析构函数会导致应用程序崩溃。 */
}
注意
由于创建对象的事务上下文被保存在对象内部,因此如果事务不再有效,对该对象句柄的任何操作都会导致错误。在上述代码中,当对象 result
超出作用域时,会调用 QueryResult 的析构函数。但由于数据库已关闭,result
中的事务句柄不再有效,因此尝试关闭事务会导致应用程序崩溃!
为避免这种情况,任何 QueryResult 类的实例在不再需要时都必须立即删除。
- 如果该对象是通过
new
创建的,则可以通过显式调用delete
操作符来删除; - 如果该对象是作为局部变量实例化的,则可以使用
{}
来删除。
以下是两个示例,展示了 QueryResult 类的正确用法:
main ()
{
McoSqlEngine engine;
engine.open( ... );
{
QueryResult result(engine.executeQuery("select * from aRecord"));
Cursor* cursor = result->records();
Record* rec;
while (rec = cursor->next())
{
...
} /* while 循环结束 */
} /* 由局部变量“result”表示的 QueryResult 类对象将在此处被删除。 */
engine.close(); /* 数据库实例在此处被销毁。 */
return 0;
}
请注意,{}
会导致在调用 engine.close()
之前,定义在其中的 QueryResult 结果对象超出作用域,从而在结果中的事务句柄失效之前调用其析构函数。也许更方便地确保 QueryResult 对象作用域正确的方法是在单独的查询方法中实例化它们,如下所示:
void do_query(McoSqlEngine &engine)
{
QueryResult result(engine.executeQuery("select * from aRecord"));
Cursor* cursor = result->records();
Record* rec;
while ( rec = cursor->next())
{
...
} /* while 循环结束 */
return;
} /* 由局部变量“result”表示的 QueryResult 类对象将在此处被删除。 */
main ()
{
McoSqlEngine engine;
engine.open( ... );
do_query(engine);
engine.close(); /* 数据库实例在此处被销毁。 */
return 0;
}
语句级读取一致性的说明
SmartESQL 引擎确保语句级别读取一致性是强制执行的。这意味着数据库运行时保证单个查询返回的所有数据都来自同一时间点——即查询开始的时间。
- 使用 MVCC 事务管理器时,性能不会受到影响。
- 使用 MURSIW 事务管理器时,只读事务通过索引浏览大量对象的 SQL 查询可能会变慢。(例如:
select * from XXX order by key
)。
使用固定大小的结构优化
SmartESQL 允许通过使用“占位符”说明符 %r 和 %R 将结构体作为 SQL 语句参数传递。后者要求用固定大小的结构体替换 %R。当使用固定大小的结构体时,内部 SQL API 实现可以通过避免多次调用 *_get()
或 *_put()
来优化读取或写入操作。
例如,考虑以下结构体:
struct Person
{
char const* name;
int age;
float weight;
};
由于此结构包含一个 char*
元素,所以其大小是可变的。若要将此结构传递给 SQL 语句,则必须使用 %r 格式说明符。例如:
void addPerson(Person* p)
{
// 向表中添加新记录。
// 记录将使用通过“Person”结构体传递的数据进行初始化。
engine.executeStatement("insert into Person %r", p);
}
然而,该结构可以定义为一个固定大小的结构体(即没有可变长度元素),如下所示:
class Person {
char<64> name;
int4 age;
float weight;
tree<name> by_name;
};
现在,可以使用更高效的 %R 指定符将此结构传递给 SQL 语句,如下所示:
void addPerson(Person* p)
{
// 向表中添加新记录。
// 记录将使用通过“Person”结构体传递的数据进行初始化。
engine.executeStatement("insert into Person %R", p);
}
在这种情况下,SmartESQL 的实现能够更高效地存储数据:它不会使用数据访问包装器,而是直接将数据复制到数据库页面。因此,不会有多余的函数调用,目标页面也不会多次被锁定/解锁。
但请注意,这种优化仅适用于只包含固定大小组件的类。
SmartEDB索引和 SQL
索引通常用于优化数据库对象的排序和搜索功能。添加索引能够显著加快搜索速度,并为 select
语句提供增强的筛选功能。在 SQL 数据库中,通常使用标准的 DDL 语句 Create Index...
来引入索引,而在 SmartEDB 数据库中,索引更方便地在数据库模式中定义。例如,以下模式为上述 Person 类的修改版本定义了两个索引:
class Person
{
int4 id;
char<64> name;
int4 age;
float weight;
hash<id> by_id[1000];
tree<name> by_name;
};
尽管并非必需,但这些索引能提升对 Person 表中选择语句的执行效率。名为 by_name
的树形索引将由 SmartESQL 运行时用于优化如上述代码片段中执行的select
语句。
engine.executeQuery("select * from Person where name like %s", name);
根据数据库设计,有时使用诸如 by_id
这样的哈希索引通过唯一标识符查找对象可以获得更好的优化程度,例如在如下语句中:
engine.executeQuery("select * from Person where id = %d", personId);
SmartEDB 提供了丰富的索引类型,可在模式定义中指定,以方便执行各种专门的数据访问操作。特别是,已添加了对标准 SQL 语言的一些扩展,以支持这些特殊索引类型中的一种,即 rtree,该类型用于空间搜索。考虑如下数据库模式:
class Consumer
{
int4 ConsumerId;
double Coordinates1[4];
double Coordinates2[4];
double Coordinates3[4];
hash<ConsumerId> by_id[1000];
rtree <Coordinates1> ridx1;
rtree <Coordinates2> ridx2;
rtree <Coordinates3> ridx3;
};
这里使用 SmartEDB 原生 DDL 定义了 Consumer 类,并为其三个 Coordinates 字段创建了三个 rtree 索引。现在,对任何 Coordinates 字段进行的任何 SQL 搜索都将使用相应的索引。也可以使用扩展 SQL 定义相同的模式,如下所示:
create table Consumer
(
ConsumerId int primary key,
Coordinates1 array(double, 4) using rtree index,
Coordinates2 array(double, 4) using rtree index,
Coordinates3 array (double, 4) using rtree index
);
定义了这些索引之后,我们现在就可以执行空间搜索,查找坐标包含(Contained)、重叠(Overlapping)或接近(Near)指定点的对象。例如,以下脚本可以使用 xSQL 实用程序运行,以创建表、添加一些记录,然后执行空间搜索:
create table Consumer
(
ConsumerId int primary key,
Coordinates1 array(double, 4) using rtree index,
Coordinates2 array(double, 4) using rtree index,
Coordinates3 array (double, 4) using rtree index
);
insert into Consumer(ConsumerId, Coordinates1, Coordinates2, Coordinates3)
values(1, '{1.0, 1.0, 2.0, 2.0}', '{3.0, 3.0, 4.0, 4.0}', '{5.0, 5.0, 6.0, 6.0}');
insert into Consumer(ConsumerId, Coordinates1, Coordinates2, Coordinates3)
values(2, '{10.0, 10.0, 20.0, 20.0}', '{30.0, 30.0, 40.0, 40.0}', '{50.0, 50.0, 60.0, 60.0}');
numformat "%4.1f"
select * from Consumer;
select * from Consumer where Coordinates1 contains '{3.5, 3.5, 3.5, 3.5}' union
select * from Consumer where Coordinates2 contains '{3.5, 3.5, 3.5, 3.5}' union
select * from Consumer where Coordinates3 contains '{3.5, 3.5, 3.5, 3.5}';
select * from Consumer where Coordinates1 overlaps '{3.5, 3.5, 3.5, 3.5}' union
select * from Consumer where Coordinates2 overlaps '{3.5, 3.5, 3.5, 3.5}' union
select * from Consumer where Coordinates3 overlaps '{3.5, 3.5, 3.5, 3.5}';
select * from Consumer where Coordinates1 near '{3.5, 3.5}' union
select * from Consumer where Coordinates2 near '{3.5, 3.5}' union
select * from Consumer where Coordinates3 near '{3.5, 3.5}';
运行此脚本将会产生以下输出:
ConsumerId Coordinates1 Coordinates2 Coordinates3
------------------------------------------------------------------------------
1 [ 1.0, 1.0, 2.0, 2.0] [ 3.0, 3.0, 4.0, 4.0] [ 5.0, 5.0, 6.0, 6.0]
2 [10.0,10.0,20.0,20.0] [30.0,30.0,40.0,40.0] [50.0,50.0,60.0,60.0]
Selected records: 2
ConsumerId Coordinates1 Coordinates2 Coordinates3
------------------------------------------------------------------------------
1 [ 1.0, 1.0, 2.0, 2.0] [ 3.0, 3.0, 4.0, 4.0] [ 5.0, 5.0, 6.0, 6.0]
Selected records: 1
ConsumerId Coordinates1 Coordinates2 Coordinates3
------------------------------------------------------------------------------
1 [ 1.0, 1.0, 2.0, 2.0] [ 3.0, 3.0, 4.0, 4.0] [ 5.0, 5.0, 6.0, 6.0]
Selected records: 1
ConsumerId Coordinates1 Coordinates2 Coordinates3
------------------------------------------------------------------------------
1 [ 1.0, 1.0, 2.0, 2.0] [ 3.0, 3.0, 4.0, 4.0] [ 5.0, 5.0, 6.0, 6.0]
2 [10.0,10.0,20.0,20.0] [30.0,30.0,40.0,40.0] [50.0,50.0,60.0,60.0]
Selected records: 2
请注意此示例中的以下内容:
- 在 Consumer 类中,使用扩展的 SQL 子句通过 rtree 索引为三个坐标字段定义了 rtree 索引。用于 rtree 索引的字段必须声明为维度为 4 的数组,以被视为矩形:例如 array(double, 4)。
- 在插入语句中使用 set 语法指定值,例如 {1.0,1.0,2.0,2.0},以指定矩形的左上角(1.0,1.0)和右下角(2.0,2.0)点。
- 使用 numformat 命令限制浮点数输出(使用标准 C printf 字段宽度和精度规范)。
- 使用 SQL 的 union 构造对字段 Coordinate1、Coordinate2 和 Coordinate3 进行或选择。
- SQL 扩展 Contains、Overlaps 和 Near 实现了空间搜索。
- Contains 和 Overlaps 搜索的结果是记录 1,其 Coordinate2 矩形确实包含并重叠指定为零宽度和高度的矩形的点(3.5,3.5)。
- Near 搜索的结果是按距离点(3.5,3.5)的顺序排列的两条记录。
结构体类型的映射
目前,模式编译器不会为类/表定义生成相应的 C 结构体,开发人员必须在应用程序代码中提供 C 结构体。
下表指定了要为每个 SmartEDB 字段类型使用的 C 结构体类型:
SmartEDB字段类型 | C 结构体类型 |
---|---|
signed<1> | Char |
signed<2> | Short |
signed<4> | Int |
signed<8> | int64_t |
unsigned<1> | unsigned char |
unsigned<2> | unsigned short |
unsigned<4> | unsigned int |
unsigned<8> | int64_t |
float | float |
double | double |
string | char* |
nstring | wchar_t* |
vector | Array* |
array | Array* |
struct | Struct* |
autoid_t | Reference* |
time | time_t |
date | time_t |
McoSqlEngine:open
McoSqlEngine::open()
的原型为:
open(
char const* name,
mco_dictionary_h dictionary,
size_t size,
size_t pageSize = 128,
void* mapAddress = (void*)0x20000000,
size_t maxTransSize = 0,
int flags =
ALLOCATE_MEMORY|
SET_ERROR_HANDLER|
START_MCO_RUNTIME|
SET_SQL_ALLOCATOR|
INITIALIZE_DATABASE,
char const* databaseFile = NULL
);
在内部,McoSqlEngine::open()
执行以下操作:
- 检查本地或共享内存支持。
- 如果本地内存且(标志 & 分配内存)为真,则调用
malloc()
分配DATABASE_SIZE
字节。 - 将 SmartEDB 错误处理程序设置为
McoDatabase::checkStatus
。 - 初始化 SmartEDB 运行时。
- 设置 McoSQL 内存分配器。
- 设置最大事务大小。
- 连接或打开 SmartEDB 数据库。
- 打开 SQL 引擎。
加载和保存数据库镜像
McoSqlEngine::open()
方法的最后一个可选参数是指向数据库文件的路径,数据库应从此文件加载。因此,若要从先前保存在文件中加载数据库,必须指定最后一个参数;
- 默认情况下,此参数的值为
NULL
,数据库不会从磁盘加载。 - 如果该参数不为
NULL
,则 open() 方法会尝试从指定文件加载数据库。 - 如果文件不存在,则初始化一个空数据库。
McoSqlDatabase::save()
方法用于将数据库保存到指定文件中,若要在关闭数据库之前保存数据库图像,请在关闭数据库之前插入对 save()
方法的调用。
保存和加载类数据
可以通过 saveClass()
和 loadClass()
来实现:
void saveClass(char const *filePath, char const *className);
void loadClass(char const *filePath, char const *className = 0);
详细信息,请参阅保存/加载单个类页面。
McoSqlEngine:close
在内部,McoSqlEngine::close()
执行以下操作:
- 释放内存分配器。
- 关闭数据库(
mco_db_disconnect()
和mco_db_close()
)。3 - 如果在初始化期间分配了内存,则释放该内存。
- 终止运行时。