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)

要求:

key 前缀

一个数据库里有多个表,所以序列化后的 key 要用一个前缀来区分。我们用表名来区分:

func (row Row) EncodeKey(schema *Schema) (key []byte) {
    key = append([]byte(schema.Table), 0x00)
    // TODO
}

表名后面加了一个 0x00 分隔,这是为了防止有包含关系的表名造成 key 冲突。比如表名 ababc 的前缀分别为 "ab\x00""abc\x00"

如果要实用化,可以分配一个整数 ID 作为前缀,这样不至于浪费空间,还可以更改表名。