条件过滤和更新
条件过滤和更新包含条件过滤和更新两部分内容,后者是指对数组的更新。
1. 版本要求
- SDK 要求版本大于等于 3.55.0,其中 Batch 类操作则要求 3.64.0 以上的版本(目前最高仅提供3.55.0的SDK,即目前批量操作不支持条件更新和过滤查询)。
- Client 工具使用条件过滤要求版本大于等于 3.55.0。
2. 哪些命令支持条件更新?
主要的增删改查均支持条件更新,详情见各 API 的参数说明,API 的提炼说明如下
- 查询请求支持条件过滤,包括
- Get
- FieldGet
- IndexGet(也就是GetByPartKey)
- Traverse
- ListGet
- ListGetAll
- ListTraverse
- 更新请求支持条件过滤和数组更新操作,包括
- Update
- Replace
- Delete
- FieldSet(也就是FieldUpdate)
- FieldIncrease
- ListReplace
- ListDelete
- 批量查询条件过滤,但要求 3.64.0 以上的版本,包括
- BatchGet
- BatchGetByPartKey
- BatchFieldGet
- ListGetBatch
- 更新请求支持条件过滤和数组更新操作,但要求 3.64.0 以上的版本,包括
- BatchUpdate
- BatchReplace
- BatchDelete
- ListDeleteBatch
- ListUpdateBatch
- ListReplaceBatch
- 两个请求,专门用于操作、查询数组:
- UpdateItem、ListUpdateItem,用于对数组字段进行 PUSH、SET、POP 操作
- Query、ListQuery,用于查询部分数组范围,即数组的 GET 操作
- 历史原因,TDR 协议的 Increase 要求 3.64.0 以上的版本才能支持条件更新,低于 3.64.0 的版本,可以使用 Update 命令,后者可支持字段的更新和自增。
- 历史原因,UpdateByPartKey、DeletePartKey 要求 3.64.0 以上的版本才能支持条件更新
- 历史原因,ListDeleteAll 暂不支持条件过滤
更多关于条件过滤和更新的能力支持,请联系Tcaplus。
3. 示例
TDR 表示例(C++)
USER u = {};
// 设置主键
u.dwId = 1;
strcpy(u.szName, "a");
record->SetData(&u, sizeof(u));
// 前置的条件过滤,先判断gameids是否已经包含101
record->SetCondition("gameids NOT CONTAINS($ = 101)");
// 后置的数组操作,插入101
record->SetOperation("PUSH gameids#[-1][$ = 101]");
// 发送写请求
// ...
// 处理响应返回值
if (ret == COMMON_ERR_CONDITION_NOT_MATCHED) // 条件不满足,说明gameids已经存在101了
{
// ...
}
PB 表示例(C++)
user u;
// 设置主键
u.set_id(1);
u.set_name("a");
// 设置递增的步长
u.set_rank(1);
// 当rank达到上限100之后,不再对rank递增,否则 +1
std::set<std::string> dottedpaths;
dottedpaths.insert("rank");
ret = api.FieldInc(dottedpaths, &u, "", "rank < 100");
这里不做更多详尽的示例,TcaplusDB 的 C++ SDK、Go SDK 和 Client 均支持条件更新,对于 SDK 的部分接口,其
condition
参数填写条件过滤文本,operation
参数填写数组操作文本,详情见各 SDK 的 API 说明。
4. 条件过滤语法
过滤条件是类 SQL 的 WHERE 语句的语法,已支持以下几种过滤能力
- 比较,如
rank > 1
,比较符有>, >=, <, <=, =, ==, !=, <>
,在比较的上下文中,=
也是相等比较,而在其他语境下可能是赋值符号 。 - 逻辑运算,如
rank > 1 AND rank < 10
,运算符有AND, OR, NOT
。 - 位运算,仅支持“与”,如
filter & 8
,当从低往高的第三位为 1 时,该表达式为 true。 CONTAINS
和NOT CONTAINS
,即判断数组是否包含,如"mailbox CONTAINS(title == \"tcaplus\")"
表示要求 mailbox 包含一个 title 等于 "tcaplus" 的元素。CONTAINS 括号中可以是更复杂的子条件。LIKE
和NOT LIKE
,支持对字符串字段模糊匹配,如"name LIKE 'abc%'"
,和 MySQL 语法一样,默认忽略大小写,'%'
通配任意字符串,'_'
匹配任意单字符。IN
和NOT IN
,判断某字段值是否满足多个散列的值,如"name IN ('abc', 'ABC', '123')"
。- 支持嵌套字段,如
mail.title
,mailbox[0].title
,map_field['key'].value
。 - 支持内建函数,当前仅支持
size()
函数,用于获取数组或者 map 字段的大小,如"size(mailbox) = 0"
判断数组是否为空。 - 内建属性
$.LastAccessTime
,该内建属性表示记录的最后更新时间,最小精度为秒,可用于和字符串表示的时间进行比较,如 "2021"、"2021-01-01" 或 "2021-01-01 00:00:00"。 - 当前数组元素的引用
$
,如,"gameids NOT CONTAINS($==101)"
。
完整语法如下
condition_expr ::=
array CONTAINS '(' condition ')'
| array NOT CONTAINS '(' condition ')'
| condition
condition ::=
operand
| operand comparator operand
| condition AND condition
| condition OR condition
| NOT condition
| operand bitwise_op operand
| operand LIKE string
| operand NOT LIKE string
comparator ::=
==
| =
| <
| >
| <=
| >=
| !=
bitwise_op ::=
&
array ::=
identifier
operand ::=
identifier
| number
| string
| $
| $.LastAccessTime
- 语法说明
- identifier: 一个合法的标识名称,在这里是字段名或字段名路径,如
name
或mail.title
。 - number: 整型或浮点数,不支持大整数。
- string: 双引号或者单引号括起来的字符串,支持类 C 语言风格的转义字符,如
"abc\t123"
。
- identifier: 一个合法的标识名称,在这里是字段名或字段名路径,如
- 比较
- 不同精度的整型或浮点型的数值都是可以相互比较的,这和 C++ 语言中是一致的,例如 int16 和 int32 比较,前者的类型会被提升之后再比较,整型和浮点比较,整型则会先被提升为浮点型。
- int 和 uint 可比较,会先比较符号位。
- 浮点型的等值会有精度偏差。
- 字符串也是可比较,按照字母字典序,这和 C++ 中的 std::string 的比较行为是一致的。
- 数值类型和字符串直接不可比较。
- 操作符优先级
- 条件表达式 condition 中,操作符优先级,从高到低为
comparator NOT AND OR
,例如"a==1 OR a>10 AND a<20"
,会先计算 AND 的结果再计算 OR。 - 当然可以使用括号来分隔条件表达式,例如
"(a==1 OR a>10) AND a<20"
则就先计算 OR。
- 条件表达式 condition 中,操作符优先级,从高到低为
- 关键字
- 存在一些语法关键字,如
key
、like
、update
等,若存在表的字段名和这些关键字重名,会语法错误,如 "key > 100",不过可以使用引号 ` 把字段名括起来即可规避,如改为"`key` > 100"。 - 因为 TcaplusDB 兼容 MySQL 协议,原则上关键字和 MySQL 的关键字一样,具体可查询:Keywords and Reserved Words。
- 存在一些语法关键字,如
性能优化建议:
条件过滤的性能跟条件表达式、表的结构定义有关,满足以下规则时,有针对性的性能优化(仅供参考,内部实现可能会调整)
- 当条件表达式只用到 key 字段(包括主键和 index 字段)和记录的属性字段(如 $.LastAccessTime),仅通过记录的主键或索引即可进行判断,无需从存储引擎读取全量数据。
- 对于 SortList 表,当条件表达式只使用了 sort 字段,也有性能优化。
- 对于 SortList 表,表定义的排序字段只有一个,且条件表达式是简单的二元比较(如
"field >= 1"
和"field == 1"
等),使用二分查找,有更好的性能优化。
5. 数组操作语法
TcaplusDB 提供对数组的命令式的操作,具备如下能力
- PUSH 操作:在数组指定位置插入新的元素数据。
- SET 操作:修改数组指定位置的元素数据。
- POP 操作:删除数组中某些下标范围或者满足某些条件的元素。
- GET 查询:指定记录的 key,查询数组返回数组中某些下标范围或者满足某些条件的元素(即仅记录的局部数据)。
TcaplusDB 的 C++ SDK、Go SDK 的部分接口,其 condition
参数是前置的过滤条件,operation
参数是后置的数组操作,详情见各 SDK 的 API 说明。
使用示例
- 在 mailbox 数组尾部插入一个元素(同时对元素内的 title 等字段赋值),这里 -1 表示尾部的数组下标。
"PUSH mailbox #[-1] [title = 'tcaplus', content = '...']"
- 在 gameids 数组头部插入一个元素 101,这里 0 表示头部的数组下标,$ 表示当前操作的数组元素。
"PUSH gameids #[0] [$ = 101]"
- 删除 mailbox 数组下标 0 ~ 10 范围内,且 title 不等于 "tcaplus" 的元素。
"POP mailbox #[0-10] [title != 'tcaplus']"
- 修改指定下标为 1 的元素。
"SET gameids #[1] [$ = 101]"
- 组合操作,在数组头部插入,同时在尾部删除(删除不存在的元素无影响),保证数组大小总是维持在 100 以内。
"PUSH gameids #[0][$=100]; POP gameids #[100]"
完整的操作语法如下
push_expr ::=
PUSH array # '[' index ']' '[' assign_expr [, assign_expr]* ']'
set_expr ::=
SET array # '[' index ']' '[' assign_expr [, assign_expr]* ']'
pop_expr ::=
POP array
| POP array # '[' index_range ']'
| POP array # '[' index_range ']' '[' condition ']'
get_expr ::=
GET array
| GET array # '[' index_range ']'
| GET array # '[' index_range ']' '[' condition ']'
assign_expr ::=
identifier = number | string
| $ = number | string
index ::=
integer
index_range ::=
index [, index_range]*
| index - index [, index_range]*
语法说明
- assign_expr: 赋值表达式,若数组元素是组合类型,则是内部字段的赋值
title = 'tcaplus', content = '...'
,若元素是基本类型,则使用$
引用元素本身,如$=101
。 - index: 数组的下标,其中 0 表示数组头部下标,在不知道数组大小情况下,使用-1 表示尾部的下标。
- index_range: 数组下标范围,如 "
0 - -1
" 表示所有的下标,也可以表示多个不连续的范围,如 "0 - 8, -1
"。 - condition: 嵌套的过滤条件,语法和上一章节记录的条件过滤一致,区别在于,前者的语义上下文是整个记录的数据,这里的是数组元素中的数据。
PUSH/SET 的说明
- 赋值表达式中,当前只支持对整型、浮点、字符串类型的字段赋值。
- 赋值表达式中,不同类型整型、浮点型可以相互赋值,即会进行类型强转,有可能出现截断等情况,类型转换的行为和 C++ 中一致。
- 对于 SET,若数组大小为 N,下标的有效范围是 0 ~ (N - 1) 或 -1。
- 对于 PUSH,若数组大小为 N,下标的有效范围是 0 ~ N 或 -1。
POP 的说明
- POP,删除某些范围或某些满足条件的元素。
- 基于
删除不存在的元素不会报错
的原则,pop 指定一个不存在的下标范围时,不会报错,例如数组大小为 10,8-80
会删除最后 2 个元素。
6. 注意事项
- 参数
condition
和operation
文本最大长度是1023。 - TDR 表的条件过滤和更新依赖于 TDR 的解包(Unpack)能力,而 TDR SDK 支持两种方式写入数据记录,即 Record.SetValue() 和 Record.SetData(),这里不支持 SetValue() 写入的复杂数据类型(结构体、数组)。
- TDR 表中数组字段有对应的 refer 字段(数组的实际大小),对数组的 PUSH、POP 操作会自动修改这个 refer 字段。