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);

results matching ""

    No results matching ""