https://github.com/dpwgc/kv2doc
一个嵌入式文档数据库实现,基于 Go + BoltDB
https://github.com/dpwgc/kv2doc
boltdb database go nosql
Last synced: about 1 month ago
JSON representation
一个嵌入式文档数据库实现,基于 Go + BoltDB
- Host: GitHub
- URL: https://github.com/dpwgc/kv2doc
- Owner: dpwgc
- License: mit
- Created: 2025-02-14T11:07:31.000Z (over 1 year ago)
- Default Branch: master
- Last Pushed: 2025-02-17T17:41:52.000Z (over 1 year ago)
- Last Synced: 2025-04-26T07:53:16.516Z (about 1 year ago)
- Topics: boltdb, database, go, nosql
- Language: Go
- Homepage: https://gitee.com/dpwgc/kv2doc
- Size: 51.8 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# kv2doc
## 一个嵌入式文档数据库,基于 Go + BoltDB + Expr-lang 实现
### 实现功能
* 支持基本的表结构及文档数据插入/更新/删除/批量增删改操作。
* 支持索引维护及查询(遵循最左前缀原则)。
* 支持简单的条件查询与复杂的嵌套查询。
* 支持列表查询(排序+分页)与滚动查询。
***
### 使用示例
* 安装
```
go get github.com/dpwgc/kv2doc
```
* 代码示例
```go
package main
import (
"fmt"
"github.com/dpwgc/kv2doc"
)
func main() {
// 新建数据库 demo.db
db, _ := kv2doc.NewDB("demo.db")
// 往 test_table 表中插入2条数据(无需建表,插入数据时会自动建表,同时为每一个字段都建立索引)
_, _ = db.Add("test_table", kv2doc.Doc{
"title": "hello world 1",
"type": "1",
})
id, _ := db.Add("test_table", kv2doc.Doc{
"title": "hello world 2",
"type": "2",
})
// 更新第2条数据,新增一个 color 字段
_ = db.Edit("test_table", id, kv2doc.Doc{
"title": "hello world 2",
"type": "2",
"color": "red",
})
// 查询文档,筛选条件:title 以 hello 为前缀, type 要大于 0 且 存在 color 字段,结果集按主键ID排序后,取前10条返回
// 使用 Eq 或 LeftLike 进行查询时,会走最左前缀索引,其他查询方法走全表扫描
documents, _ := db.Query("test_table").
LeftLike("title", "hello").
Must(kv2doc.Expr().Gt("type", "0").Exist("color")).
Desc("_id").
Limit(0, 10).
List()
// 打印查询结果
for _, v := range documents {
fmt.Println(v.ToJson())
}
// 查看Query执行计划
explain := db.Query("test_table").
LeftLike("title", "hello").
Must(kv2doc.Expr().Gt("type", "0").Exist("color")).
Explain()
// 具体执行逻辑
fmt.Println("expr:", explain.Expr)
// 选择了哪个索引
fmt.Println("index:", explain.Index)
// 删除表
_ = db.Drop("test_table")
}
```
***
### 函数说明
| 名称 | 功能 |
|-----------------|---------------------|
| kv2doc.NewDB | 创建/打开一个数据库 |
| kv2doc.ByStore | 创建/打开一个数据库(自定义存储引擎) |
| db.Add | 新增文档(表不存在时自动建表) |
| db.Edit | 编辑文档 |
| db.Delete | 删除文档 |
| db.Bulk | 批量操作(增删改) |
| db.Drop | 删除表 |
| db.Query | 新建查询 |
| Query.Eq | 等于 |
| Query.Ne | 不等于 |
| Query.Gt | 大于 |
| Query.Gte | 大于等于 |
| Query.Lt | 小于 |
| Query.Lte | 小于等于 |
| Query.In | 包含 |
| Query.NotIn | 不包含 |
| Query.Like | 含有 |
| Query.LeftLike | 相同前缀 |
| Query.RightLike | 相同后缀 |
| Query.Exist | 存在 |
| Query.NotExist | 不存在 |
| Query.Must | 交集语句 |
| Query.Should | 并集语句 |
| Query.Asc | 正序 |
| Query.Desc | 倒序 |
| Query.Limit | 分页 |
| Query.One | 返回一个文档 |
| Query.List | 返回多个文档 |
| Query.Count | 返回文档数量 |
| Query.Scroll | 滚动查询文档 |
| Query.Explain | 查看执行计划 |
***
### 存储实现原理
#### 假设有一个文档,Json 格式如下( _id 为文档主键,使用 BoltDB 的 NextSequence 方法获取)
```json
{
"_id": "123",
"title": "hello world",
"type": "1",
"color": "red"
}
```
#### 在保存此文档时,会将该文档的非主键字段拆解成索引,分别存进 BoltDB 键值对中,Key 是字段名 + 字段值 + 主键 id,Value 是主键 id
| key | value |
|-------------------------|-------|
| f/_id/123/123 | 123 |
| f/title/hello world/123 | 123 |
| f/type/1/123 | 123 |
| f/color/red/123 | 123 |
#### 上述表格展示的是字段索引( key 以 f 前缀开头),只有文档 id,没有文档内容。而真正的文档内容,保存在主键 Key 下( key 以 p 前缀开头)
| key | value |
|-----------|-----------------------------------------------------------------------|
| p/_id/123 | { "_id": "123", "title": "hello world", "type": "1", "color": "red" } |
***
### 查询实现原理
#### 查询情况可分为两种:索引扫描 or 全表扫描
#### 索引扫描:
* 如果使用了 Eq(等于)、 LeftLike(前缀相同)或者 In(数组内必须要有共同前缀才能走索引),会按最左前缀原则匹配索引
* 例如:执行 LeftLike("title", "hello").Gt("type", "1"),会先利用 BoltDB 的 Cursor 遍历功能扫描所有前缀为 f/title/hello 的 key
* 然后再根据该索引扫描的结果作其他条件筛选(先根据字段索引 value 中的主键 id 找到文档内容,再判断文档中的 type 字段是否大于 1)
#### 全表扫描:
* 当全表扫描时,会在 BoltDB 中扫描所有前缀为 p 的 key(即所有存放文档内容的主键 key),然后再根据文档内容逐条匹配
***
### 自定义存储实现
#### 可以使用其他带事务功能的键值数据库来充当存储引擎,只需实现下述接口(务必确保所有操作方法都有事务保障),并调用 ByStore 方法生成数据库实例即可
```go
type Store interface {
CreateTable(table string) (err error)
DropTable(table string) (err error)
SetKV(table string, kvs []KV) (err error)
GetKV(table, key string) (kv KV, err error)
ScanKV(table, prefix string, handle func(key string, value []byte) bool) (err error)
NextID(table string) (id string, err error)
}
```
```go
db := kv2doc.ByStore(rocketStore)
db := kv2doc.ByStore(etcdStore)
```