// WCDB 内部的代码示例
template<>
class ExpressionConvertible<Column> final : public std::true_type {
public:
static Expression asExpression(const Column& column);
template<typename T>
class ResultColumnConvertible<T, typename std::enable_if<ExpressionConvertible<T>::value>::type> final
: public std::true_type {
public:
static ResultColumn asResultColumn(const T& t)
return ExpressionConvertible<T>::asExpression(t);
因此,原来的 select
语句可以直接简写为:
WCDB::Column identifierColumm = WCDB::Column("identifier");
WCDB::StatementSelect select =
WCDB::StatementSelect().select(identifierColumm).from("sampleTable");
printf("%s", select.getDescription().data()); // 输出 "SELECT identifier FROM sampleTable"
WCDB 内的 Convertible
类型转换类较多,这里不一一赘述。开发者也无需逐一了解,在使用时再查阅接口即可。
WCDB::Field
严格来说不属于语言集成查询的一部分,它们是语言集成查询和模型绑定结合的产物。
当需要通过对象来操作数据库时,如"get object"或者"update with object"等,WCDB 不仅需要知道查找数据的哪个字段(即 Column
所完成的事情,WCDB::Field
实际继承了Column
),还需要知道这个字段对应模型绑定中的哪个变量。
const WCDB::Field& field = WCDB_FIELD(Sample::identifier);
WCDB::OptionalValueArray<Sample> objects = database.getAllObjectsWithFields<Sample>("sampleTable", field);
而 Field
就是存储了数据库字段和模型绑定字段的映射关系,而且还可以分离这个映射,具体用法可参考高级用法一章的查询重定向。
基于模型绑定,开发者可以完全摆脱通过字符串创建 Column
,更便捷地操作数据库。Field
是继承了Column
来实现的,WINQ中所有使用Column
的地方都可以使用Field
。
WCDB::StatementSelect select =
WCDB::StatementSelect().select(WCDB_FIELD(Sample::identifier)).from("sampleTable")
printf("%s", select.getDescription().data());// 输出 "SELECT identifier FROM sampleTable"
Expression
可以算是 SQL 里最复杂的一个了。它在许多语句中都出现,比如查询语句的 where
、groupBy
、having
、limit
、offset
,更新语句的 value
、where
、limit
、offset
等等。同时,它的完整定义也很长,甚至还包含了递归。
从单个 Expression
的语法角度来看,它支持从数字、字符串、字段创建。而 WCDB 将其扩展为支持所有字段映射类型,包括内建的类型和自定义的类型。
WCDB::Expression expressionInt = WCDB::Expression(1);
WCDB::Expression expressionDouble = WCDB::Expression(2.0);
WCDB::Expression expressionString = WCDB::Expression("3");
WCDB::Expression expressionColumn = WCDB::Expression(WCDB::Column( "identifier"));
除此之外,还有内建的绑定参数 WCDB::BindParameter
也可以转成 Expression
类型,甚至WCDB::StatementSelect
也能转成 Expression
类型,因为有些场景子查询的结果也能作为判断条件。
多个 Expression
之间可以通过函数或运算符的方式进行操作,如:
WCDB::Expression expression = expressionColumn.between(expressionInt, expressionDouble);
printf("%s", expression.getDescription().data()); // 输出 "identifier BETWEEN 1 AND 2.0"
Expression
语法所支持的运算符有十多个,WCDB 基本都支持,但在语义上改为更符合 C++ 的习惯。例如
||
运算符在 SQL 语法中用于字符串链接,而在 WCDB 中则是用于"或"的逻辑运算。
<>
运算符在 SQL 语法中用于不等比较,而在 WCDB 中则是直接使用较为习惯的 !=
运算符。
WCDB::Expression expression1 = expressionInt + expressionDouble;
printf("%s", expression1.getDescription().data()); // 输出 "(1 + 2.0)"
WCDB::Expression expression2 = expressionColumn >= expression1;
printf("%s", expression2.getDescription().data()); // 输出 "(identifier >= (1 + 2.0))"
// 基础类型 -1 可以直接转换为 Expression
WCDB::Expression expression3 = expressionColumn < -1 || expression2;
printf("%s", expression3.getDescription().data()); // 输出 "((identifier < -1) OR (identifier >= (1 + 2.0)))"
WCDB::Expression statementSelect = WCDB::StatementSelect()
.select(WCDB_FIELD(Sample::identifier))
.from("sampleTable")
.where(expression3);
printf("%s", statementSelect.getDescription().data()); // 输出 "SELECT identifier FROM sampleTable WHERE ((identifier < -1) OR (identifier >= (1 + 2.0)))"
显然,每个类都要转换成 Expression
来进行这些操作,虽然也可以,但这就太麻烦了。
WCDB::Expression expression = WCDB::Expression(WCDB_FIELD(Sample::identifier)) > 1;
printf("%s", expression.getDescription().data());
因此,WCDB 将 Expression
支持的函数操作和运算符操作定义成了多种 ExpressionOperable
基类:
ExpressionUnaryOperable
一元运算符;
ExpressionBinaryOperable
二元运算符;
ExpressionInOperable
in操作,包括一系列in和notIn操作;
ExpressionBetweenOperable
between操作
ExpressionCollateOperable
给Expression
指定字符串比对函数,具体见Collation。
CoreFunctionOperable
给Expression
调用SQLite内置函数,包括abs
、hex
、length
等,具体见Core Function。
AggregateFunctionOperable
给Expression
调用SQLite内置聚合函数,包括avg
、count
、max
、min
等,具体见Aggregate Function。
FTSFunctionOperable
给Expression
调用FTS函数,包括highlight
、matchinfo
等。
继承这些ExpressionOperable
基类的类都可以与 Expression
的类似,使用函数或运算符进行语言集成查询,目前包括可以作为表达式左值的Column
、Expression
这两个。也因此,基于模型绑定,开发者可以完全摆脱通过拼装 Expression
,更便捷地操作数据库。
WCDB::StatementSelect select = WCDB::StatementSelect().select(WCDB_FIELD(Sample::identifier))
.from("sampleTable")
.where(WCDB_FIELD(Sample::identifier) < -1 || WCDB_FIELD(Sample::identifier) >= 3.0);
printf("%s", select.getDescription().data()); // 输出 "SELECT identifier FROM sampleTable WHERE ((identifier < -1) OR (identifier >= 3.0))"
Statement
在前文已经接触到不少了,如查询 StatementSelect
、插入 StatementInsert
等。它是一个最基本的完整可被执行的 SQL 语句。
SQLite 共包含 27 种 Statement
,WCDB 都支持了。根据语法规则创建的 Statement
,可以通过HandleOperation::execute()
函数直接执行,也可以使用Database
、Handle
和Table
下面的这些接口获取执行Statement
的结果,
getValueFromStatement
执行Statement
并获取一个结果。
getOneRowFromStatement
执行Statement
并获取一行结果,结果为一维数组。
getOneColumnFromStatement
执行Statement
并获取一列结果,结果为一维数组。
getAllRowsFromStatement
执行Statement
并获取多行结果,以二维数组的方式返回。
试考虑,表中的数据可以想象为一个矩阵的存在,假设其数据如下:
identifier
content
以下是使用Statement
进行查询操作的示例代码:
// 获取所有内容
WCDB::OptionalMultiRows allRows = database.getAllRowsFromStatement(WCDB::StatementSelect().select(Sample::allFields()).from("sampleTable"));
printf("%lld", allRows.value()[2][0].intValue()); // 输出 3
// 获取第二行
WCDB::OptionalOneRow secondRow = database.getOneRowFromStatement(WCDB::StatementSelect().select(Sample::allFields()).from("sampleTable").offset(1));
printf("%lld", secondRow.value()[0].intValue()); // 输出 2
// 获取第二行 content 列的值
WCDB::OptionalOneColumn contentColumn = database.getOneColumnFromStatement(WCDB::StatementSelect().select(WCDB_FIELD(Sample::content)).from("sampleTable"));
printf("%s", contentColumn.value()[3].textValue().data()); // 输出 "sample2"
// 获取 identifier 的最大值
WCDB::OptionalValue maxId = database.getValueFromStatement(WCDB::StatementSelect().select(WCDB_FIELD(Sample::identifier).max()).from("sampleTable"));
printf("%lld", maxId.value().intValue());// 输出 5
// 获取不同的 content 数
WCDB::OptionalValue distinctContentCount = database.getValueFromStatement(WCDB::StatementSelect().select(WCDB_FIELD(Sample::content).count().distinct()).from("sampleTable"));
printf("%lld", distinctContentCount.value().intValue());// 输出 2
Statement
还可以通过 StatementOperation::prepare()
创建 WCDB::PreparedStatement
对象,来精细控制Statement
的执行过程,这个我们会在高级接口的Handle一节详细介绍。
如前文所说,SQL 的复杂性决定了不可能介绍每一个类型及语句。因此,这里将介绍如何将一个已有的 SQL,转写为语言集成查询。开发者可以以此为例子,触类旁通。
对于已有的 SQL:
在语法中确定其所属的 Statement
。
对照对应 Statement
的语法,根据关键字对已有的 SQL 进行断句。
逐个通过语言集成查询的函数调用进行实现。
以如下 SQL 为例:
SELECT min(identifier) FROM sampleTable WHERE (identifier > 0 || identifier / 2 == 0 ) && description NOT NULL ORDER BY identifier ASC LIMIT 1, 100
根据 Statement
的列表,显然这个 SQL 属于 StatementSelect
。
WCDB::StatementSelect statementSelect = WCDB::StatementSelect()
根据 StatementSelect
的语法规则,按关键词(即语法中的大写字母)进行断句:
SELECT min(identifier)
FROM sampleTable
WHERE (identifier > 0 || identifier / 2 == 0 ) && description NOT NULL
ORDER BY identifier ASC
LIMIT 1, 100
根据每个关键词,找到语言集成查询中对应的函数,并完成其参数。
如 SELECT
关键词对应 StatementSelect
的 select()
函数和from()
函数,以及AggregateFunctionOperable
中的min
函数:
statementSelect.select(WCDB_FIELD(Sample::identifier).min().distinct())
statementSelect.from("sampleTable")
WHERE
的参数虽然较复杂,但也都能找到对应的函数:
statementSelect.where((WCDB_FIELD(Sample::identifier) > 0 || WCDB_FIELD(Sample::identifier) / 2 == 0) && WCDB_FIELD(Sample::content).notNull())
其他语句也同理:
statementSelect.order(WCDB_FIELD(Sample::identifier).asOrder(WCTOrderedAscending))
statementSelect.limit(1, 100)
SQLite的官网给出了它支持的全部SQL语法的结构图,还是以最复杂的select语句为例:
WINQ的整体设计思路是把整个select语句包装成一个StatementSelect
对象,把图中的圆角方框的连接点包装成StatementSelect
对象的成员函数,如:with
、select
、from
、where
、groupby
、having
、orderBy
、limit
等等这些函数。
图中直角方框的内容则是设计成StatementSelect
对象的成员函数的入参。一些简单的入参则是支持使用基础类型,比如from
函数可以传入一个字符串表示表名,一些复杂的入参则是继续包装成一个对象,比如前面提到的ResultColumn
和Expression
,其他的还有CommonTableExpression
、Join
、WindowDef
等等这些对象。
所以在编写复杂SQL语句时,可以先找到对应的Statement
对象,然后找到需要调用的方法,最后再根据方法需要的入参传入具体值。按照这个思路,可以根据SQL的语法结构图编写出所有SQLite支持的SQL语句。
[C++-Object-Relational-Mapping-Builtin-Column-Codable-Type]: