您的位置:首页 > 编程语言 > C语言/C++

介绍SQLite的C/C++接口

2013-05-22 09:31 288 查看
该文章提供SQLite C/C++接口的总体介绍。

SQLite的早期版本很容易学习,因为它们仅仅支持5个C/C++接口。不过随着SQLite的功能的提升,新的C/C++接口被添加进来,因此,现在有200多个不同的APIs。这对于新程序开发者来说是巨大的。幸运的是,SQLite中的大部分C/C++接口是针对性的,并且从不需要考虑。机关具有如此多的入口点,核心API依旧相对简单且易于编码。该文章目标是提供容易理解SQLite如何工作的背景信息。

1 核心对象与接口

SQL数据库引擎的原始工作是运行SQL语句。为了完成这个目的,开发者需要了解两个对象:

数据库连接对象:sqlite3
预处理语句对象:sqlite3_stmt

严格来说,预处理语句对象并不需要,因为便利的封装接口,sqlite3_exec或sqlite3_get_table,可以被使用并且这些便利的封装物封装与隐藏了预处理语句对象。无论如何,对预处理语句的理解对于全面了解SQLite很多必要。

数据库连接与预处理语句对象被下面列出的C/C++程序接口的小集合控制:

sqlite3_open()
sqlite3_prepare()
sqlite3_step()
sqlite3_column()
sqlite3_finalize()
sqlite3_close()

这6个C/C++程序接口与上面列出的两个对象组成SQLite的核心功能。理解它们的开发者将具有使用SQLite的好基础。

注意,上面的程序列表是概念性的而不是实际上的。很多这些程序以多个版本出现。例如,上面的列表显示一个独立的程序名sqlite3_open(),然而事实上有3个独立的程序以稍微不同的方式完成相同的事情:sqlite3_open()、sqlite3_open16()与sqlite3_open_v2()。列表提到sqlite3_column(),不过事实上并不存在该程序。显示在该列表中的”sqlite3_column()"是一个为用于提取多种数据类型的列数据的整个程序家族提供的占位空间。

这里是核心接口做什么的一个总结:

sqlite3_open()该程序打开一个SQLite数据库文件的连接并且返回一个数据库连接对象。这通常是应用程序进行的第一个SQLite API调用,并且也是大多数其它SQLite APIs的前提。许多SQLite接口需要一个指向数据库连接对象的指针作为第一个参数,并且被认为是作用域数据库连接对象的方法。这个程序是数据库连接对象的构造函数。

sqlite3_prepare()这个程序将SQL文本转换为一个预处理语句对象,并且返回指向那个对象的指针。这个接口需要一个由前面调用sqlite3_open()产生的数据库对象指针与一个包含SQL预处理语句的字符串。这个API并不真正运行该SQL语句。它只是为运行准备SQL语句。

考虑每个SQL语句为一个小计算程序。sqlite3_prepare()的目的是编译该代码为目标代码。该预处理语句是对象代码。sqlite3_step()接口然后运行该对象代码获取结果。

注意sqlite3_prepare()的使用对于新应用程序来说是不推荐的。一个新的可选的程序sqlite3_prepare_v2()可以替换使用。

sqlite3_step()这个程序用于运行一个由前面通过sqlite3_prepare()接口产生的预处理语句。该语句运行到结果的第一行可用为止。为了进一步获取结果的第二行,再一次调用sqlite3_step()。继续调用sqlite3_step()

sqlite3_column()这个程序通过sqlite3_step()执行的预处理语句返回结果集中的当前行的一个单独的列。每一次sqlite3_step()都以一个新的结果集行停止,为了查找该行的所有列的值,这个程序可以被调用多次。就像前面提到的,在SQLite API中并没有诸如“sqlite3_column()"的真正函数,反而,我们这里所谓的”sqlite3_column()“是为整个从各种数据类型的结果集中返回值的函数家族的一个占位空格。还存在该家族中的其它程序,返回结果的大小(如果它是一个字符串或BLOB)与结果集中的列数。

sqlite3_column_blob()
sqlite3_column_bytes()
sqlite3_column_bytes16()
sqlite3_column_count()
sqlite3_column_double()
sqlite3_column_int()
sqlite3_column_int64()
sqlite3_column_text()
sqlite3_column_text16()
sqlite3_column_type()
sqlite3_column_value()

sqlite3_finalize()这个程序销毁一个由前面调用sqlite3_prepare()产生的预处理语句。为了避免内存泄漏,每个预处理语句必须调用该程序销毁。

sqlite3_close()这个程序关闭一个由前面调用sqlite3_open()打开的数据库连接。与该连接关联的所有预处理语句都需要在关闭该连接之前结束。



1.1 核心程序与对象的典型应用

想要使用SQLite的应用程序通常在初始化时使用sqlite3_open()创建单独的数据库连接。注意sqlite3_open()可以用于打开已经存在的数据库文件或者创建新的数据库文件。然而很多应用程序使用仅有的一个数据库连接,没有任何原因为什么应用程序不能为了打开多个数据库连接----到相同的数据库或不同的数据库,而多次调用sqlite3_open()。有时一个多线程应用程序可以创建为每个线程创建独立的数据库连接。注意,没有必要为访问两个或多个数据库打开各自的数据库连接。通过使用ATTACH SQL命令,一个单独的数据库连接可以一次访问两个或多个数据库。

许多应用程序在关闭的时候通过调用sqlite3_close()销毁它们的数据库连接。或者,例如,一个应用程序可以在响应File->Open菜单命令时打开数据库连接,在相应File->Close菜单时销毁相应的数据库连接。

为了运行SQL语句,应用程序遵循下面的步骤:

使用sqlite3_prepare()创建一个预处理语句。
通过调用sqlite3_step()执行该预处理语句。
对于查询,使用sqlite3_column()从两次调用sqlite3_step()中提取结果。
使用sqlite3_finalize()销毁预处理语句。

为了高效使用SQLite,上述的步骤是所有人都需要了解的。其它的都是装饰与细节方面.

译者添加的示例代码:

sqlite3* pDb = NULL;
rc = sqlite3_open("demo.db", &pDb);  //打开数据库demo.db
if (rc)
{
printf("error: error_no=%d, error_msg=%s\n",
sqlite3_errcode(pDb), sqlite3_errmsg(pDb));
sqlite3_close(pDb);
return 1;
}

char* pszSql = "select * from Teacher;";
if (sqlite3_prepare(pDb, pszSql, strlen(pszSql), &pStmt, NULL))
{
printf("sqlite3_prepare error: error_no = %d, error_msg = %s\n",
sqlite3_errcode(pDb), sqlite3_errmsg(pDb));
sqlite3_finalize(pStmt);
return 1;
}
printf("sqlite3_prepare successful!\n");
int nRc = sqlite3_step(pStmt);
if (SQLITE_ROW == nRc)
{
int nRowCount = sqlite3_column_count(pStmt);
printf("Index, ");
for (int nColumnIndex = 0; nColumnIndex < nRowCount; ++nColumnIndex)
{
printf("%s", sqlite3_column_name(pStmt, nColumnIndex));
if (nColumnIndex != nRowCount - 1)
{
printf(", ");
}
}
printf("\n");
int nIndex = 0;
while (SQLITE_ROW == nRc)
{
printf("%d: ", nIndex);
for (int nColumnIndex = 0; nColumnIndex < nRowCount; ++nColumnIndex)
{
int nColumnType = sqlite3_column_type(pStmt, nColumnIndex);
switch (nColumnType)
{
case SQLITE_INTEGER:
{
int nResult = sqlite3_column_int(pStmt, nColumnIndex);
printf("%d", nResult);
}
break;
case SQLITE_FLOAT:
{
double dResult = sqlite3_column_double(pStmt, nColumnIndex);
printf("%f", dResult);
}
break;
case SQLITE_TEXT:
{
const unsigned char* pszResult = sqlite3_column_text(pStmt, nColumnIndex);
printf("%s", pszResult);
}
break;
case SQLITE_BLOB:
{
char* pszResult = (char*)sqlite3_column_blob(pStmt, nColumnIndex);
printf("%s", pszResult);
}
break;
case SQLITE_NULL:
default:
{
const unsigned char* pszResult = sqlite3_column_text(pStmt, nColumnIndex);
printf("%s", pszResult);
}
break;
}
if (nColumnIndex != nRowCount - 1)
{
printf(", ");
}
}
printf("\n");
nRc = sqlite3_step(pStmt);
if (SQLITE_ERROR == nRc)
{
break;
}
nIndex ++;
}
}
sqlite3_finalize(pStmt);
sqlite3_close(pDb);


2 核心程序的便利封装

sqlite3_exec()接口是使用一个单独的函数调用执行上面4个步骤的便利封装。一个传递给sqlite3_exec()的回调函数用于处理结果集中的每一行。sqlite3_get_table()是另一个执行上面4步的便利封装.sqlite3_get_table()接口与sqlite3_exec()不同的是:它保存查询结果在堆内存中而不是调用一个回调函数.

需要意识到sqlite3_exec()与sqlite3_get_table()所做的事情都可以用过使用核心程序完成。事实上,这些封装完全依据核心程序实现的.

3 绑定参数与重用预处理语句

在前面的讨论中,假设每个SQL语句都预处理、执行、然后销毁。然而,SQLite准许相同的预处理语句执行多次。这是通过使用下列程序完成的:

sqlite3_reset()
sqlite3_bind()

在预处理语句通过一个或多个sqlite3_step()调用执行以后,它为了被重新执行可以通过调用sqlite3_reset()重设。使用sqlite3_reset()于一个现存的预处理语句而不是创建一个新的预处理语句避免了不必要的sqlite3_prepare()调用。在许多SQL语句中,运行sqlite3_prepare()所需的时间等于或超过sqlite3_step()所需的时间。因此避免调用sqlite3_prepare()可以有一个标志性的性能提高.

即使通常执行完全相同的SQL语句多次是很有用的。更通常一些,有些人希望执行相似的语句。例如,你或许希望通过不同的插入值执行INSERT语句多次。为了适应这类灵活性,SQLite准许SQL语句包含参数,它绑定优先执行的值。这些值可以在后面被改变,并且相同的预处理语句可以使用新值第二次被执行。

SQLite中,无论在什么地方包含字符串常值都是有效的,你可以以下面任何一种方式使用一个参数:

?
?NNN
:AAA
$AAA
@AAA

前面的例子中,NNN是一个整型值,AAA是个标识符。参数初始化时具有值NULL。在第一次调用sqlite3_step()之前或者在sqlite3_reset()之后,应用程序可以调用一次sqlite3_step()接口绑定值给参数。每次调用sqlite3_bind()在相同的参数上重写前面的绑定信息。

准许应用程序预处理多个SQL语句并且按需执行它们。对于未完成的预处理语句,并没有一个严格的限制。

译者添加的示例代码:

char* pszSql = "insert into Teacher (ID, Name) values(?, ?);";
if (sqlite3_prepare(pDb, pszSql, strlen(pszSql), &pStmt, NULL))
{
printf("sqlite3_prepare error: error_no = %d, error_msg = %s\n",
sqlite3_errcode(pDb), sqlite3_errmsg(pDb));
sqlite3_finalize(pStmt);
return 1;
}
for (int i = 0; i < 5; ++i)
{
sqlite3_bind_int(pStmt, 1, i+10);
sqlite3_bind_text(pStmt, 2, "VVVVV", 5, 0);
sqlite3_step(pStmt);
sqlite3_reset(pStmt);
}
sqlite3_finalize(pStmt);


4 配置SQLite

SQLite的默认配置对于大多数应用程序都工作很好。不过有时,开发者为了挤出一点性能或使用一些古怪的特征,希望稍稍调整该配置。

sqlite3_config()接口用于改变SQLite的全局的、进程层次的配置。sqlite3_config()接口必须在任何数据库连接创建之前调用。sqlite3_config()接口准许程序开发者做像下面的事情:

调整SQLite如何进行内存分配,包括为适应安全性、实时性、嵌入式的系统设置可选的内存分配器与应用程序定义的内存分配器。
设置进程级别的错误日志。
指定应用程序定义的页缓存。
调整互斥体(mutex)的使用,因此它们适合于各种各样的线程模型,或者替换应用程序定义的互斥体系统。

在进程级别的配置完成之后,数据库连接可以被创建,单个数据库连接可以使用sqlite3_limit()与sqlite3_db_config()配置。

5 扩展SQLite

SQLite包括可以用于扩展它的功能性的接口。这些程序包括:

sqlite3_create_collation()
sqlite3_create_function()
sqlite3_create_module()
sqlite3_vfs_register()

sqlite3_create_collation()接口用于为文本排序创建新的校对序列.sqlite3_create_module()接口用于竹醋新的虚拟表格实现。sqlite3_vfs_register()接口创建新的VFSes。

sqlite3_create_function()接口创建新的SQL函数,标量或集合。这个新函数的实现通常使用下列额外的接口:

sqlite3_aggregate_context()
sqlite3_result()
sqlite3_user_data()
sqlite3_value()

SQLite的所有内嵌SQL函数都通过精确使用这些相同的接口创建的。参考SQLite源代码,例如尤其是date.c与func.c源文件。

共享库或DLLs可以用作SQLite的可加载扩展。

6 其它接口

该文章仅仅设计基础的SQLite接口。SQLite库包含许多其它的APIs,实现了很有用的没有在这里描述的特征。组成SQLite应用程序编程接口的完整的函数列表可以在C/C++接口说明书中看到。想要了解SQLite接口的完整的、权威性的知识,参考该文档。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: