go-mysql-server:基于内存的go实现mysql server

2023-09-06 19:24:52 浏览数 (1)

https://github.com/dolthub/go-mysql-server是用golang实现的基于内存的mysql server。主要应用于两个场景:作为golang单测的mysql替换,作为内置的基于内存的数据库。MySQL Fake 常见实现方式有两种:SQLite 以及 go-mysql-server。因为大家都基本遵循了 SQL 92 标准,在没有用到比较复杂的,或者 MySQL 特有的一些语法特性时,我们可以用 SQLite 来作为一个平替。但是SQLite底层是C语言实现的,github.com/mattn/go-sqlite3仅仅做了cgo的一层封装。用 SQLite 来存取数据时,你会发现跟 MySQL 不同,它只依赖一个文件进行读取和写入,非常轻量级。我们可以在单测执行结束的时候清理掉这个文件即可。SQLite 还提供了内存的模式,这样我们就能完全不依赖存储,直接用 SQLite 来验证我们的语句是否正确。

go-mysql-server 是一个用 Golang 实现的,和 MySQL 完全兼容的数据库,能够用于golang的测试环境,它可以启动一个内存级别的mysql db,初始化一些数据, 可以让被测试对象的db连接指向该内存db。这样做测试的好处是:没有很夸张的mock成本;不用担心产生的脏数据问题;能顺带着测出 DAL 层sql不符合预期的问题。和 SQLite 相比,它进一步规避了很多 SQLite 和 MySQL 语法不兼容的问题(虽然哪怕是 MySQL 自身,5.7 和 8.0 的语法也不相同)。下面体验下

代码语言:javascript复制
package main

import (
  "time"

  sqle "github.com/dolthub/go-mysql-server"
  "github.com/dolthub/go-mysql-server/memory"
  "github.com/dolthub/go-mysql-server/server"
  "github.com/dolthub/go-mysql-server/sql"
  "github.com/dolthub/go-mysql-server/sql/information_schema"
)

// Example of how to implement a MySQL server based on a Engine:
//
// ```
// > mysql --host=127.0.0.1 --port=3306 -u root mydb -e "SELECT * FROM mytable"
//  ---------- ------------------- ------------------------------- --------------------- 
// | name     | email             | phone_numbers                 | created_at          |
//  ---------- ------------------- ------------------------------- --------------------- 
// | John Doe | john@doe.com      | ["555-555-555"]               | 2018-04-18 09:41:13 |
// | John Doe | johnalt@doe.com   | []                            | 2018-04-18 09:41:13 |
// | Jane Doe | jane@doe.com      | []                            | 2018-04-18 09:41:13 |
// | Evil Bob | evilbob@gmail.com | ["555-666-555","666-666-666"] | 2018-04-18 09:41:13 |
//  ---------- ------------------- ------------------------------- --------------------- 
// ```
func main() {
  engine := sqle.NewDefault(
    sql.NewDatabaseProvider(
      createTestDatabase(),
      information_schema.NewInformationSchemaDatabase(),
    ))
  engine.Analyzer.Catalog.MySQLDb.AddRootAccount()
  config := server.Config{
    Protocol: "tcp",
    Address:  "localhost:3306",
  }
  s, err := server.NewDefaultServer(config, engine)
  if err != nil {
    panic(err)
  }
  s.Start()
}

func createTestDatabase() *memory.Database {
  const (
    dbName    = "mydb"
    tableName = "mytable"
  )
  db := memory.NewDatabase(dbName)
  table := memory.NewTable(tableName, sql.NewPrimaryKeySchema(sql.Schema{
    {Name: "name", Type: sql.Text, Nullable: false, Source: tableName},
    {Name: "email", Type: sql.Text, Nullable: false, Source: tableName},
    {Name: "phone_numbers", Type: sql.JSON, Nullable: false, Source: tableName},
    // {Name: "phone_numbers", Type: sql, Nullable: false, Source: tableName},
    {Name: "created_at", Type: sql.Timestamp, Nullable: false, Source: tableName},
  }), &memory.ForeignKeyCollection{})

  db.AddTable(tableName, table)
  ctx := sql.NewEmptyContext()
  table.Insert(ctx, sql.NewRow("John Doe", "john@doe.com", sql.JSONDocument{Val: []string{"555-555-555"}}, time.Now()))
  table.Insert(ctx, sql.NewRow("John Doe", "johnalt@doe.com", sql.JSONDocument{Val: []string{}}, time.Now()))
  table.Insert(ctx, sql.NewRow("Jane Doe", "jane@doe.com", sql.JSONDocument{Val: []string{}}, time.Now()))
  table.Insert(ctx, sql.NewRow("Evil Bob", "evilbob@gmail.com", sql.JSONDocument{Val: []string{"555-666-555", "666-666-666"}}, time.Now()))
  return db
}

官方的例子会有问题,原因详见https://github.com/dolthub/go-mysql-server/issues/1357

代码语言:javascript复制
panic: Actual Value Type: []string, Expected Value Type: sql.JSONValue

解决后,我们就可以启动我们的server

代码语言:javascript复制
% go run ./main.go
INFO[0007] NewConnection                                 DisableClientMultiStatements=false connectionID=1

尝试连接

代码语言:javascript复制
%  mysql --host=127.0.0.1 --port=3306 -u root
Welcome to the MySQL monitor.  Commands end with ; or g.
Your MySQL connection id is 1
Server version: 5.7.9-Vitess

Copyright (c) 2000, 2022, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or 'h' for help. Type 'c' to clear the current input statement.

mysql> show databases;
 -------------------- 
| Database           |
 -------------------- 
| information_schema |
| mydb               |
| mysql              |
 -------------------- 
3 rows in set (0.01 sec)

数据查询

代码语言:javascript复制
mysql> use mydb;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> SELECT * FROM mytable;
 ---------- ------------------- ------------------------------- ---------------------------- 
| name     | email             | phone_numbers                 | created_at                 |
 ---------- ------------------- ------------------------------- ---------------------------- 
| John Doe | john@doe.com      | ["555-555-555"]               | 2023-04-16 00:43:21.392759 |
| John Doe | johnalt@doe.com   | []                            | 2023-04-16 00:43:21.392781 |
| Jane Doe | jane@doe.com      | []                            | 2023-04-16 00:43:21.392784 |
| Evil Bob | evilbob@gmail.com | ["555-666-555","666-666-666"] | 2023-04-16 00:43:21.392786 |
 ---------- ------------------- ------------------------------- ---------------------------- 
4 rows in set (0.00 sec)

0 人点赞