基于RocksDB编写一个简单的SQL数据库|得物技术

一、前言

数据库DBMS是当前互联网开发者最熟悉的基础设施之一,很多后端开发者的入门项目就是一个简单的基于MySQL的数据管理系统。笔者一直想自己编写一个简单的SQL数据库,正好最近正在学习RocksDB和Zig语言的内容,就想利用这个机会作为学习成果的检验,于是就有了这个小项目。

话不多说,先来看看这个小项目成品的效果:
在这里插入图片描述

当然这个小小的数据库只支持创建表、插入和简单查询的能力,并不包含复杂SQL、索引、多租户、事务等高级功能,有兴趣的同学可以自行扩展。

二、什么是RocksDB

RocksDB是由Facebook开发的一款高效的嵌入式键值存储引擎,基于Google的LevelDB进行了多项优化。它主要用于快速存储和高并发读写场景,特别适合在闪存等快速存储介质上运行。

RocksDB是C++开发的,不过它提供了一套C语言API,为不会C++的开发者提供了便利。

三、什么是Zig语言

Zig语言是一种新兴的系统编程语言,由Andrew Kelley于2015年开始开发。其设计目标是改进C语言,并借鉴Rust等其他语言的优点。Zig强调强健性、最佳性和可维护性,并致力于提供高效的手动内存管理和编译时特性。

Zig语言对开发者来说最好的一点就是简单(特别相比Rust来说,Rust笔者已经学了好几次都没能入门),如果你熟悉Rust或者C语言,那么上手Zig只需要2~3天,就算完全没有C家族语言的经验,2周也足够学这门语言。

Zig语言有这些值得关注的特点:

  • 并没有提供类似Rust的生命周期管理,所以需要手动管理内存和C一样;
  • Zig支持编译时执行代码(comptime),允许开发者在编译期间进行类型操作和函数调用,从而提高性能和灵活性(Go语言开发者默泪);
  • 可以无缝衔接C语言的API,继承C生态的库非常简单;
  • 强大的编译工具ZigCC,ZigCC 是一个强大的工具,旨在简化 C/C++ 项目的编译过程,同时提供现代编程语言的优势。它不仅提高了编译效率,还通过增量编译和高效的二进制生成,帮助开发者更好地管理和维护他们的代码库。

四、项目结构

为了实现这个简单SQL数据库,我们可以按照如下的架构来对这个项目做功能分层:

在这里插入图片描述

接下来,我们就分模块详解介绍。

五、实现解析

RocksDB Layer

我们第一步先完成对RocksDB库的桥接,RocksDB虽然提供了C的API,但是并没有过多的文档说明,好在RocksDB提供了一些使用的例子,我们可以根据这些例子知道这些API的用法。

Zig集成C语言的三方库非常容易:

  • 首先,我们把RocksDB的API声明加入项目的include目录

在这里插入图片描述

  • 第二步,我们添加一个RocksDB.zig文件,调用header中的API,由于这个项目比较简单,所以我们只需要set/get/iteration这几个简单的API:
const std = @import("std");
const rdb = @cImport(@cInclude("rocksdb.h"));
pub const Iter = @import("iter.zig").Iter;

pub fn init(allocator: std.mem.Allocator, dir: []const u8) !Self {
    const options: ?*rdb.rocksdb_options_t = rdb.rocksdb_options_create();
    rdb.rocksdb_options_set_create_if_missing(options, 1);
    var err: ?[*:0]u8 = null;
    const db = rdb.rocksdb_open(options, dir.ptr, &err);
    const r = Self{
        .db = db.?,
        .allocator = allocator,
        .dir = dir,
    };
    if (err) |errStr| {
        std.log.err("Failed to open RocksDB: {s}.\n", .{errStr});
        return RocksdbErrors.RocksDBOpenError;
    }
    return r;
}

pub fn deinit(self: Self) void {
    rdb.rocksdb_close(self.db);
}

pub fn set(self: Self, key: []const u8, value: []const u8) !void {
    const writeOptions = rdb.rocksdb_writeoptions_create();
    var err: ?[*:0]u8 = null;
    rdb.rocksdb_put(
        self.db,
        writeOptions,
        key.ptr,
        key.len,
        value.ptr,
        value.len,
        &err,
    );
    if (err) |errStr| {
        std.log.err("Failed to write RocksDB: {s}.\n", .{errStr});
        return RocksdbErrors.RocksDBWriteError;
    }
}

pub fn get(self: Self, key: []const u8, buf: *std.ArrayList(u8)) !void {
    const readOptions = rdb.rocksdb_readoptions_create();
    var value_length: usize = 0;
    var err: ?[*:0]u8 = null;
    const v = rdb.rocksdb_get(
        self.db,
        readOptions,
        key.ptr,
        key.len,
        &value_length,
        &err,
    );
    if (v == null) {
        return;
    }

    if (err) |errStr| {
        std.log.err("Failed to read RocksDB: {s}.\n", .{errStr});
        return RocksdbErrors.RocksDBReadError;
    }
    for (0..value_length) |i| {
        try buf.append(v[i]);
    }
}

pub fn getIter(self: Self, prefix: []const u8) !Iter {
    return Iter.init(self.db, prefix);
}
  • 第三步,我们需要在build.zig中添加RocksDB三方库的link,关于Zigcc的具体细节可以参考这篇文章(https://ziglang.org/learn/build-system/)。
const rocksdb_unit_tests = b.addTest(.{
        .root_source_file = b.path("src/Rocksdb.zig"),
        .target = target,
        .optimize = optimize,
    });
    rocksdb_unit_tests.linkLibC();
    rocksdb_unit_tests.addIncludePath(LazyPath{ .cwd_relative = "./include" });
    rocksdb_unit_tests.linkSystemLibrary("rocksdb");
    rocksdb_unit_tests.addLibraryPath(LazyPath{ .cwd_relative = "/opt/homebrew/lib" });

    const run_rocksdb_unit_tests = b.addRunArtifact(rocksdb_unit_tests);

Lexer

在完成了RocksDB的桥接层后,我们可以着手编写SQL语句的词法分析器。顾名思义,词法分析就是将用户输入的SQL语句转换为一组Token的过程。

  • 第一步,我们需要总结一下SQL语句中总共有哪些分词:

在这里插入图片描述

分析上述的几个SQL语句,我们可以将Token分为如下分类:

pub const Kind = enum {
    unknown,
    // ----------- 保留关键字 ------------
    select_keyword, // select
    create_table_keyword, // create table
    insert_keyword, // insert into
    values_keyword, // values
    from_keyword, // from
    where_keyword,// where

    // ----------- 运算符关键字 -------------
    plus_operator,// +
    equal_operator,// =
    lt_operator,// <
    gt_operator, // >
    concat_operator, // ||

    // ---------- 符号关键字 -------------
    left_paren_syntax, // (
    right_paren_syntax, // )
    comma_syntax, // ,

    identifier, // 普通标识符
    integer, // 整型
    string, // 字符串

    pub fn toString(self: Kind) []const u8 {
        return @tagName(self);
    }
};

const BUILTINS = [_]Builtin{
    .{ .name = "CREATE TABLE", .Kind = Token.Kind.create_table_keyword },
    .{ .name = "INSERT INTO", .Kind = Token.Kind.insert_keyword },
    .{ .name = "SELECT", .Kind = Token.Kind.select_keyword },
    .{ .name = "VALUES", .Kind = Token.Kind.values_keyword },
    .{ .name = "WHERE", .Kind = Token.Kind.where_keyword },
    .{ .name = "FROM", .Kind = Token.Kind.from_keyword },
    .{ .name = "||", .Kind = Token.Kind.concat_operator },
    .{ .name = "=", .Kind = Token.Kind.equal_operator },
    .{ .name = "+", .Kind = Token.Kind.plus_operator },
    .{ .name = "<", .Kind = Token.Kind.lt_operator },
    .{ .name = ">", .Kind = Token.Kind.gt_operator },
    .{ .name = "(", .Kind = Token.Kind.left_paren_syntax },
    .{ .name = ")", .Kind = Token.Kind.right_paren_syntax },
    .{ .name = ",", .Kind = Token.Kind.comma_syntax },
};

  • 第二步,我们定义一下Token的数据结构:
start: u64,
end: u64,
kind: Kind,
source: []const u8,

pub fn init(start: u64, end: u64, kind: Kind, source: []const u8) Self {
    return Self{
        .start = start,
        .end = end,
        .kind = kind,
        .source = source,
    };
}

pub fn getKind(self: Self) Kind {
    return self.kind;
}

pub fn string(self: Self) []const u8 {
    return self.source[self.start..self.end];
}
  • 第三步,我们需要完成上述种类中三类关键字的解析:
fn nextKeyword(self: *LexIterator) ?Token {
    var longest_len: usize = 0;
    var kind = Token.Kind.unknown;
    for (BUILTINS) |builtin| {
        if (self.index + builtin.name.len > self.source.len) continue;
        // 大小写不敏感
        if (asciiCaseInsensitiveEqual(self.source[self.index .. self.index + builtin.name.len], builtin.name)) {
            longest_len = builtin.name.len;
            kind = builtin.Kind;
            break;
        }
    }
    // 由于我们关键字是按长度倒排序的,所以匹配到的一定是最长的keyword
    if (longest_len == 0) return null;
    defer self.index += longest_len;
    return Token.init(self.index, self.index + longest_len, kind, self.source);
}
  • 第四步,完成整型、字符串和普通标识符解析:
fn nextInteger(self: *LexIterator) ?Token {
    var end = self.index;
    var i = self.index;
    while (i < self.source.len and self.source[i] >= '0' and self.source[i] <= '9') {
        end += 1;
        i += 1;
    }
    if (self.index == end) return null;
    defer self.index = end;
    return Token.init(self.index, end, Token.Kind.integer, self.source);
}

fn nextString(self: *LexIterator) ?Token {
    var i = self.index;
    if (self.source[i] != '\'') return null;
    i += 1;

    const start = i;
    var end = i;
    while (i < self.source.len and self.source[i] != '\'') {
        end += 1;
        i += 1;
    }
    if (self.source[i] == '\'') i += 1;
    if (start == end) return null;
    defer self.index = i;
    return Token.init(start, end, Token.Kind.string, self.source);
}

fn nextIdentifier(self: *LexIterator) ?Token {
    var i = self.index;
    var end = self.index;
    while (i < self.source.len and ((self.source[i] >= 'a' and self.source[i] <= 'z') or (self.source[i] >= 'A' and self.source[i] <= 'Z') or self.source[i] == '*')) {
        i += 1;
        end += 1;
    }
    if (self.index == end) return null;

    defer self.index = end;
    return Token.init(self.index, end, Token.Kind.identifier, self.source);
}
  • 第五步,按照关键字>整型>字符串>普通标识符的优先级完成Lexer
pub fn hasNext(self: *LexIterator) bool {
    self.index = eatWhitespace(self.source, self.index);
    return self.index < self.source.len;
}

pub fn next(self: *LexIterator) !Token {
    // std.debug.print("index: {d}, len: {d}, src: {s}\n", .{ self.index, self.source.len, self.source[self.index..] });
    self.index = eatWhitespace(self.source, self.index);
    if (self.index >= self.source.len) return error.OutOfSource;
    if (self.nextKeyword()) |token| {
       
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值