0202: 表结构
struct 定义
表名字,每列的名字,每列的类型,主键(primary key):
type Schema struct {
Table string
Cols []Column
PKey []int // 主键是哪些列?
}
type Column struct {
Name string
Type CellType
}比如这个表:
create table `link` (
`time` int64 not null,
`src` string not null,
`dst` string not null,
primary key (`src`, `dst`)
);这样表示:
schema := &Schema{
Table: "link",
Cols: []Column{
{Name: "time", Type: TypeI64},
{Name: "src", Type: TypeStr},
{Name: "dst", Type: TypeStr},
},
PKey: []int{1, 2}, // (src, dst)
}主键
主键(primary key)是一个 row 的唯一 ID,由一列或多列组成。要想操作某个 row,就要先通过主键来查找。所以主键就是 KV 里的 K,而不是主键的列就存储在 V 里。这样就能在 KV 上实现关系型数据库(OLTP 类型)。
除了主键,索引(index)也是用 KV 来实现的。通过索引能查询到主键 K,然后通过主键查询到 V。比如一本书,主键是页码,前面的“目录”和后面的“名词索引”都是索引,通过他们找到页码,然后才能找到内容。还有一种情况,索引本身就包含了足够的信息,不需要将 V 查询出来,就好比看书只看目录。
主键和索引本质都是 KV,区别只是主键是必须的。所以索引又叫“二级索引(secondary index)”,而主键叫做 primary key。一些数据库,比如 SQLite,可以建没有主键的表,这是因为 SQLite 里的表都有一个自动生成的隐藏主键,而暴露给用户的主键则是普通索引,实现上没有 primary 和 secondary 的区别。
将 row 编码成 KV
这样表示数据库里的一行数据:
type Row []Cell
func (schema *Schema) NewRow() Row {
return make(Row, len(schema.Cols))
}Schema.NewRow() 返回一个相应大小的,未初始化的 row。
接下来实现序列化和反序列化:
func (row Row) EncodeKey(schema *Schema) (key []byte)
func (row Row) EncodeVal(schema *Schema) (val []byte)
func (row Row) DecodeKey(schema *Schema, key []byte) (err error)
func (row Row) DecodeVal(schema *Schema, val []byte) (err error)要求:
- 这些函数输入或输出的 row 是跟 schema 匹配的。
- 使用上一步的
Cell.Encode()和Cell.Decode()。 - 按照 schema 里定义的列的顺序。
key 前缀
一个数据库里有多个表,所以序列化后的 key 要用一个前缀来区分。我们用表名来区分:
func (row Row) EncodeKey(schema *Schema) (key []byte) {
key = append([]byte(schema.Table), 0x00)
// TODO
}表名后面加了一个 0x00 分隔,这是为了防止有包含关系的表名造成 key 冲突。比如表名 ab 跟 abc 的前缀分别为 "ab\x00"、"abc\x00"。
如果要实用化,可以分配一个整数 ID 作为前缀,这样不至于浪费空间,还可以更改表名。