TcaplusDB 的 Schema Free 用法
1. 使用场景
TcaplusDB支持多级嵌套的表结构定义,但和MongoDB这类文档数据库相比,仍然需要用户提供表的Schema定义。
虽然这方面TcaplusDB没有MongoDB那么灵活,但TcaplusDB支持使用Protobuf定义表结构,可以利用Protobuf中的map类型支持一定程度的弱Schema的文档数据模型用法。
2. 使用限制
当前只能通过PB表支持弱Schema的用法。
3. 版本要求
TcaplusDB服务端版本应当>=3.55.0。
3. 使用方法
2.1. PB表定义
例如,对于一个存储用户信息的表,传统的表定义可能是这样:
CREATE TABLE user (id int, name varchar(32), mailbox text, phone varchar(32), gameid int, ...);
考虑到若 user 的不同记录的信息结构上差异比较大,或者 user 表结构可能会频繁增删字段,那么使用这样的表定义更合适,user 记录的其他信息都保存在单独的 info 字段中(json类型):
CREATE TABLE user (id int, name varchar(32), info json);
相应地,在 TcaplusDB 中可以这么定义表结构:
syntax = "proto3";
import "tcaplusservice.optionv1.proto";
message user {
option(tcaplusservice.tcaplus_primary_key) = "id";
int32 id = 1;
string name = 2;
map<string, string> info = 3;
}
约束 & 说明
单记录的上限不超过 10MB
map 类型字段的 value 可以是 protobuf 的任意类型,如 message,这样就可以嵌套
使用 API FieldSet / FieldGet 操作 map 结构时,要求 map 的 key 只能是 string 或整型
使用 API FieldSet / FieldGet 操作 map 结构时,map 的 value 可以是嵌套 message,如
info["sub_message"].field
,但当前不支持嵌套数组或者嵌套的 map,如info["sub_map"]["sub_key"]
或info["sub_message"].sub_map["sub_key"]
TcaplusDB 的这种 map 用法中,有一定的局限性,不能像 json 那般自由地嵌套,map 的 key 和 value 必须在表定义时给定类型,针对不统一的 value 类型,可以考虑使用
map<string, bytes>
2.2. PB表C++ SDK使用示例
2.2.1 插入记录
新插入的记录的值
{
"id": 1,
"name": "user_001",
"phone": "123456789",
"gameid": "1001",
"update_time": "2021-01-01 00:00:00"
}
API 调用
user u; // user 类型是 proto 文件预编译的类型
// 设置主键
u.set_id(1);
u.set_name("user_001");
// 设置 user 的其他信息
(*u.mutable_info())["phone"] = "123456789";
(*u.mutable_info())["gameid"] = "0";
(*u.mutable_info())["update_time"] = "2021-01-01 00:00:00";
api.Set(&u);
2.2.2 新增 & 修改字段
user u; // user 类型是 proto 文件预编译的类型
// 设置主键
u.set_id(1);
// 设置 user 的更新的字段的新值
(*u.mutable_info())["update_time"] = "2021-07-01 00:00:00";
(*u.mutable_info())["gameid"] = "1002";
// 新增字段 'mail',无需修改表结构
(*u.mutable_info())["mail"] = "user_001@xxx.com";
// 设置更新字段的路径
set<string> paths;
// SET操作,覆盖info['update_time']的值,不存在则创建之
paths.insert("SET info['update_time']");
// PUSH操作,不存在则创建之,已存在则报错
paths.insert("PUSH info['gameid']");
// 这里等价于SET info['mail']
paths.insert("info['mail']");
// 执行修改,这是原子操作
api.FieldSet(paths, &u, "", condition);
2.2.3 删除 & 修改字段
user u; // user 类型是 proto 文件预编译的类型
// 设置主键
u.set_id(1);
// 设置 user 的更新的字段的新值
(*u.mutable_info())["update_time"] = "2021-07-01 00:00:00";
// 设置修改字段的路径
set<string> paths;
// 删除 gameid 字段,同时刷新 update_time
paths.insert("info['update_time']");
paths.insert("POP info['gameid']");
// 执行修改,这是原子操作
api.FieldSet(paths, &u);
2.2.4 查询字段
user u; // user 类型是 proto 文件预编译的类型
// 设置主键
u.set_id(1);
// 设置查询字段的路径
set<string> paths;
paths.insert("name");
paths.insert("info['update_time']");
// FieldGet 查询结果填在 u 中
api.FieldGet(paths, &u);
2.3. PB表Go SDK使用示例
2.3.1 插入记录
新插入的记录的值
{
"id": 1,
"name": "user_001",
"phone": "123456789",
"gameid": "1001",
"update_time": "2021-01-01 00:00:00"
}
API 调用
user u; // user 类型是 proto 文件预编译的类型
// 设置主键
u.id = 1;
u.name = "user_001" ;
// 设置 user 的其他信息
u.info["phone"] = "123456789";
u.info["gameid"] = "0";
u.info["update_time"] = "2021-01-01 00:00:00";
api.Set(&u);
2.3.2 新增 & 修改字段
user u; // user 类型是 proto 文件预编译的类型
// 设置主键
u.id = 1;
// 设置 user 的更新的字段的新值
u.info["gameid"] = "1002";
u.info["update_time"] = "2021-07-01 00:00:00";
// 新增字段 'mail',无需修改表结构
u.info["mail"] = "user_001@xxx.com";
// 设置 更新字段的路径
var paths []string;
// SET操作,覆盖info['update_time']的值,不存在则创建之
paths = append(paths, "SET info['update_time']");
// PUSH操作,不存在则创建之,已存在则报错
paths = append(paths, "PUSH info['gameid']");
// 这里等价于SET info['mail']
paths = append(paths, "info['mail']");
// 执行修改,这是原子操作
api.FieldUpdate(&u, paths);
2.3.3 删除 & 修改字段
user u; // user 类型是 proto 文件预编译的类型
// 设置主键
u.id = 1;
// 设置 user 的更新的字段的新值
u.info["update_time"] = "2021-07-01 00:00:00";
// 删除 gameid 字段,同时刷新 update_time
var paths []string;
paths = append(paths, "info['update_time']");
paths = append(paths, "POP info['gameid']");
// 执行修改,这是原子操作
api.FieldUpdate(paths, &u);
2.3.4 查询字段
user u; // user 类型是 proto 文件预编译的类型
// 设置主键
t.id = 1;
// 设置查询字段的路径
var paths []string;
paths = append(paths, "name");
paths = append(paths, "info['update_time']");
// FieldGet 查询结果填在 u 中
api.FieldGet(&u, paths);