{"id":18408363,"url":"https://github.com/pingcap/go-randgen","last_synced_at":"2025-04-07T09:32:52.787Z","repository":{"id":35688710,"uuid":"205220053","full_name":"pingcap/go-randgen","owner":"pingcap","description":"a QA tool to random generate sql by bnf pattern","archived":false,"fork":false,"pushed_at":"2023-03-07T03:26:12.000Z","size":2543,"stargazers_count":77,"open_issues_count":12,"forks_count":44,"subscribers_count":93,"default_branch":"master","last_synced_at":"2025-03-22T16:02:22.555Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pingcap.png","metadata":{"files":{"readme":"README-ZH.MD","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2019-08-29T17:51:53.000Z","updated_at":"2025-03-19T11:19:25.000Z","dependencies_parsed_at":"2022-09-07T10:50:17.778Z","dependency_job_id":"14052cb1-6abf-4dde-b835-39d25df941c4","html_url":"https://github.com/pingcap/go-randgen","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pingcap%2Fgo-randgen","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pingcap%2Fgo-randgen/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pingcap%2Fgo-randgen/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pingcap%2Fgo-randgen/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pingcap","download_url":"https://codeload.github.com/pingcap/go-randgen/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247626461,"owners_count":20969307,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-06T03:18:45.111Z","updated_at":"2025-04-07T09:32:52.446Z","avatar_url":"https://github.com/pingcap.png","language":"Go","readme":"# go randgen\n\ngo 版本的 mysql randgen\n\n## go get 安装\n\n```\ngo get -u  github.com/pingcap/go-randgen/cmd/go-randgen\n```\n\n尝试一下：\n\n```\ngo-randgen -h\n```\n\n\n## 编译安装\n\n - 安装 go-bindata 命令行工具\n\n```bash\ngo get -u github.com/jteeuwen/go-bindata/...\n```\n\n - 编译 go-randgen\n \n```bash\nmake all\n```\n\n## 特色\n\n 1. 内置一个默认 zz 文件，也就是说你只给一个 yy 文件，就能自动生成 sql\n 2. 生成 sql 的过程中可以不连接数据库，非常迅速\n 3. 兼容 mysql randgen 的 yy 文件的语法，只要在 yy 文件中没有插入 perl 代码，就可以直接拿过来运行\n 4. 和 mysql randgen 支持嵌入 perl 代码类似，go randgen 支持嵌入 lua 代码\n 5. 纯 Go 实现，设计得非常灵活，非常易于 Hack\n 6. 除了 cmd 包下面的函数，其他包对外暴露的函数的实现全部是无状态，如果需要可以完全当成一个库来调用\n\n## Quick start\n\n\n### gentest\n\n生成测试 window functions 的 sql：\n\n```bash\n# -Y 表示使用的 yy 文件\n# -Q 表示生成的查询数量\n# -B 表示将数据库构造语句与查询语句分开成两个文件存放\n# 这里不需要指定 zz 文件是因为系统自带了一个默认的 zz 文件\n./go-randgen gentest -Y examples/windows.yy -Q 10 -B\n```\n\n在当前目录下看到`output.data.sql`即是生成的 ddl(表结构定义) 和 dml(初始化表中数据)，\n`output.rand.sql`即是根据 yy 文件生成的查询 sql。\n\n上述案例使用的是系统[默认的 zz 文件 ](resource/resource/default.zz.lua)，也可以自己重新写\n，然后通过`-Z`参数指定路径，具体规则见语法手册。\n\n如果你不想生成 ddl，只想根据 yy 生成一些 sql，\n可以使用`--skip-zz`跳过 ddl 的生成，\n不过此时也不允许在 yy 文件中包含表名或者字段相关的关键字。\n\nyy 文件的具体写法也见后面的语法手册\n\n### gendata\n\n根据指定的 zz，往指定的 dsns 中灌入相应数据\n\n```bash\n# 在指定的 dsn 中灌入内置 zz 文件定义的数据\n# 通过逗号分割多个dsn\n./go-randgen gendata --dsns \"root:@tcp(127.0.0.1:3306)/randgen,root:@tcp(127.0.0.1:4000)/randgen\"\n```\n\n### gensql\n\n根据指定的 dsn，解析 yy 文件生成 sql：\n\n```bash\n./go-randgen gensql -Y examples/functions.yy \\  \n             --dsn \"root:@tcp(127.0.0.1:3306)/randgen\" \\ \n             -Q 100\n```\n\n注意`gensql`会假设 dsn 中所有表的字段及类型都是一样的（因为 randgen 生成的数据有这个特点）\n\n### exec\n\n指定两个 dsn，直接将生成的 sql 在两个 dsn 上执行，并 dump 出运行结果不一致的 sql\n\n示例：\n\n```bash\n./go-randgen exec -Y examples/functions.yy \\\n             --dsn1 \"root:@tcp(127.0.0.1:4000)/randgen\" \\\n             --dsn2 \"root:@tcp(127.0.0.1:3306)/randgen\" \\  \n             -Q 100\n```\n\n分别在两个 dsn 中先通过内置的 zz 生成数据，然后通过 functions.yy 中定义的规则随机生成 100 条 sql，\n在两个 dsn 中同时执行，然后对比执行结果是否一致，如果不一致，\n则把相关信息输出到程序执行目录的`dump`目录下\n（可以通过`--dump`选项修改 dump 目录）\n\n如果你想让 go-randgen 一直运行下去，而不是执行有限条 sql 后停止，\n可以将`-Q`设置为负数，比如`-Q -1`.\n\n注意，默认情况下，对比两个 sql 的执行结果是无序的，比如下面两个运行结果，\ngo randgen 会认为他们是一样的：\n\n```sql\nResult1:\n\n+------+------+\n| p    | s    |\n+------+------+\n|    1 | aaa  |\n|    2 | bbb  |\n+------+------+\n\nResult2:\n\n+------+------+\n| p    | s    |\n+------+------+\n|    2 | bbb  |\n|    1 | aaa  |\n+------+------+\n```\n\n如果想要精确到 byte 的有序比较的话，可以添加`--order`选项\n\n`exec`也可以通过`--skip-zz`选项跳过数据生成的过程，此时它会采用\n类似于`gensql`的方式生成 sql 并执行\n\n### 作为一个库\n\n除了 cmd 目录下的包，其他所有包对外暴露的函数的实现都是无状态的，可以很安全地作为\n一个库被反复调用。至于使用的方法，可以参考 cmd 包下相关命令的实现。\n\n示例：通过 yy 的文本内容获得一个 Iterator ，并且生成 10 条 sql\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"github.com/pingcap/go-randgen/grammar\"\n\t\"github.com/pingcap/go-randgen/grammar/sql_generator\"\n\t\"log\"\n)\n\nfunc main() {\n\tyy := `\n{\ni = 1\n}\n\nquery: \n    create\n    \ncreate:\n    CREATE TABLE \n    {print(string.format(\"table%d\", i)); i = i+1}\n    (a int)\n`\n\titerator, err := grammar.NewIter(yy, \"query\", 5, nil, false)\n\tif err != nil {\n\t\tlog.Fatalf(\"get iter err %v\\n\", err)\n\t}\n\n\titerator.Visit(sql_generator.FixedTimesVisitor(func(_ int, sql string) {\n\t\tfmt.Println(sql)\n\t}, 5))\n}\n```\n\n\u003e 注意这里 NewIter 第三个参数传 nil 没有问题是因为示例的 yy 并没有使用关键字，如果\n\u003e 其中使用了 yy 的关键字，则最好使用 gendata.NewKeyfun() 来创建该参数\n\n打印的结果为：\n\n```sql\nCREATE TABLE table1 (a int)\nCREATE TABLE table2 (a int)\nCREATE TABLE table3 (a int)\nCREATE TABLE table4 (a int)\nCREATE TABLE table5 (a int)\n```\n\n\n## 语法手册\n\n### zz 文件\n\n#### 快速入门\n\nzz 文件是一个 lua 脚本,zz 文件会定义三件事情:\n\n 1. 生成哪些表\n 2. 表中哪些字段\n 3. 字段中有哪些数据\n \n以内置的 zz 文件为例：\n\n```lua\n-- 表相关定义\ntables = {\n    -- 生成的表的记录数\n    rows = {10, 20, 30, 90},\n    -- 表的字符编码\n    charsets = {'utf8', 'latin1', 'binary'},\n    -- 表的分区数, 'undef' 表示不分区\n    partitions = {4, 6, 'undef'},\n}\n\n-- 字段相关定义\nfields = {\n    -- 需要测试的数据类型\n    types = {'bigint', 'float', 'double', 'decimal(40, 20)',\n        'char(20)', 'varchar(20)'},\n    -- 所有的上面的数字类型都要测试带符合和不带符号两种\n    sign = {'signed', 'unsigned'}\n}\n\n-- 数据初始化相关定义\ndata = {\n    -- 数字字段的生成方案\n    numbers = {'null', 'tinyint', 'smallint',\n        '12.991', '1.009', '-9.183',\n        'decimal',\n    },\n    -- 字符串字段的生成方案\n    strings = {'null', 'letter', 'english'},\n}\n```\n\n如上所示，在 zz 文件中必须要有三个 Table 类型的变量，分别是**tables**,\n**fields**和**data**.\n\n**tables**中定义表的相关属性，比如示例中的`rows`,`charsets`和`partitions`\n（更多的可定义属性见下面的语法手册），这些属性会被求全组合，每种组合生成一张表，\n所以示例中的 tables 定义共会生成 4(rows)*3(charsets)*3(partitions)=36 张表\n\n**fields**定义表中的字段信息，这些信息同样会被求全组合，每种组合生成一个字段，\n但是上面的示例中生成的字段数目会少于 6(types)*2(sign)=12 个，因为 sign 属性只能\n作用在数字类型的字段上面，对于非数字类型，go-randgen 会自动忽略该属性，\n所以示例中的配置总计生成的字段数为 4(number)*2(sign)+2(char)=10 个\n，注意 randgen 生成的所有表中的字段都是一样的\n\n**data**定义表中的数据，其中 key 代表字段类型\n（具体可定义的字段类型见下面的语法手册）\n，value 是一个数组，代表该类型字段的\n可选值，每生成一条记录，遇到 key 类型的字段时会从 value 随机选择一个作为该条记录的值\n，可选值可以是\"字面量\"或者\"生成器\"，比如上面示例中对于`numbers`的定义，`null`,\n`12.991`等就是字面量，会直接将其作为一个值，而像`tinyint`就是一个生成器，\n如果选到它的话，它会从`-128~127`中随机选择一个值生成（具体有哪些生成器见下面的\n语法手册）\n\n#### tables\n\n| 字段名称    | 含义    |  可选值  |默认值 |\n| --------   | -----   | ---- | ----|\n| rows       | 表的记录数  |  任意大于 0 的数字    |[0, 1, 2, 10, 100] |\n| charsets   | 字符编码    |  'utf8','utf8mb4','ascii','latin1','binary', 'undef' 表示不显式设置字符集|['undef'] |\n| partitions | 分区数      |  任意大于 0 的数字或者 'undef', 'undef' 表示不分区   |['undef'] |\n\n可设置的字段与默认值在源码中见[gendata/tables.go](gendata/tables.go) 的`tablesVars`变量\n\n#### fields\n\n| 字段名称   | 含义    |  可选值  |默认值 |\n| --------  | -----   | ---- | ----|\n| types     | 字段类型  |任意合法的 mysql 类型|['int', 'varchar', 'date', 'time', 'datetime'] |\n| keys      | 索引信息  |'key' 表示加索引,'undef' 表示不加|['undef', 'key']|\n| sign      | 是否带符号|'signed', 'unsigned'|['signed']|\n\n可设置的字段与默认值在源码中见[gendata/fields.go](gendata/fields.go) 的`fieldVars`变量\n\n#### data\n\ndata 的设置是和 mysql randgen 不太一样的地方，除了支持 numbers\n, blobs, temporals, enum, strings 五种梗概类型以外，还可以用\n用更细的类型，比如 decimal，bigint 等等，如果存在更细类型的 key 的话，\n则以更细类型的定义为准。\n\n比如：\n\n```lua\ndata = {\n    numbers = {'null', 'tinyint', 'smallint',\n        '12.991', '1.009', '-9.183',\n        'decimal',\n    },\n    bigint = {100, 10, 3},\n}\n```\n\n上面这个配置，在遇到 bigint 类型的字段时，每次生成数据会从 100, 10, 3 中随机选择一个，而\n不会理会更粗犷的 numbers 的配置。\n\n具体数据类型与梗概数据类型的对应关系见[gendata/data.go](gendata/data.go)\n中的`summaryType`变量。\n\n其中 'tinyint', 'smallint', 'decimal' 都是 go randgen 自带的数据生成规则。\n\ngo randgen 中支持的所有数据生成规则见\n[gendata/generators/register.go](gendata/generators/register.go)\n的`init`函数\n\n### yy 文件\n\n#### 快速入门\n\n一个简单的示例：\n\n```yacc\n\n# 单行注释\n/*\n多行注释\n*/\n\nquery:\n    select\n    | select1\n\nselect:\n    SELECT fields FROM _table\n    \nfields:\n    _field\n    | _field_int\n```\n\n他的一次生成结果可能如下：\n\n```sql\nselect1\nSELECT 随机的一个字段 FROM 随机的一张表\nSELECT 随机的一个整型字段 FROM 随机的一张表\nselect1\n```\n\n\n#### 注释\n\n - 单行注释`#`\n - 多行注释`/**/` \n\n#### 标识符的分类\n\n - 非终结符：由小写字母,数字或者下划线组成，但是不能以数字开头\n - 终结符：大写字母，特殊字符或者数字组成，但是不能以下划线开头\n - 关键字：下划线开头\n\n\u003e 对于写在表达式右边的非终结符，如果找不到对应的产生式，也会退化成终结符\n\n#### 关键字\n\n关键字都是以下划线开头\n\n获取表名和字段名的接口:\n\n - `_table`: 从生成的表中随机选择一张\n - `_field`: 从生成的字段中随机选择一个\n - `_field_int`: 从整型字段中随机选择一个\n - `_field_char`: 从 char 和 varchar 类型字段中随机选择一个\n - `_field_list`: 获取全部字段，以逗号分隔\n - `_field_int_list`: 获取全部整型字段，以逗号分隔\n - `_field_char_list`: 获取全部字符型字段，以逗号分隔\n \n随机生成数据的一些糖 (字符相关的会在两边自动生成双引号):\n\n - `_digit`: 随机生成一个 0-9 的数字\n - `_letter`: 随机生成一个 'a' 到 'z' 之间的字母\n - `_english`: 随机生成一个英文单词\n - `_int`: 随机生成一个整型\n - `_date`: 生成`yyyy-MM-dd`格式的随机日期\n - `_year`: 随机生成一个年份\n - `_time`: 随机生成一个`hh:mm:ss`的随机时间\n - `_datetime`: 随机生成一个`yyyy-MM-dd hh:mm:ss`的随机时间\n\n\n没有写全，代码位于[链接中的 NewKeyfun 方法 ](/gendata/gendata.go)，\n可以自行查看\n\n#### 嵌入 lua 代码\n\n可以在大括号（\"{}\"）的包围中写 lua 代码，调用 print 可以想要的内容拼接到 sql 中\n\n```\nquery:{a = 1}\n    CREATE TABLE \n    {print(string.format(\"t%d\", a))} (a INT)\n```\n\n以上代码始终生成 sql 为`CREATE TABLE t1 (a INT)`\n\n在代码块中可以调用 lua 标准库中的任意函数，比如：\n\n```\n# 每次随机生成 10-20 的随机数\nquery:\n    {print(math.random(10,20))}\n```\n\n\n正常的代码块会在每次分支被运行到的时候执行一遍。\n\ngo randgen 支持在文件的头部插入一个代码块，这个代码块在整个\nsql 执行的过程中只会执行一次，称为**头部代码块**，主要用于变量或者函数的申明：\n\n```\n\n# 头部代码块对后面 sql 生成需要的一些变量或函数的进行申明\n{\ni = 1\na = 100\nfunction add(num1, num2)\n    return num1 + num2    \nend\n}\n\nquery:\n   select\n\nselect:\n   SELECT * FROM _table WHERE where_clause\n   \nwhere_clause:\n   _field_int \u003e {print(i)}\n   | _field_char \u003e {print(a)}\n   | _field_int + _field_int \u003e {print(add(i, a))}\n   \n```\n\n\n通过大括号包围 lua 代码看起来会和 lua 本身的 table 语法相矛盾，\n但是你不用担心，我在解析的时候已经作了处理，可以放心大胆地\n在代码块中使用 table 语法：\n\n```\n{\nf={a=1, b=3}\narr={0,2,3,4}\n}\n\nquery:\n  {print(arr[f.a])} | {print(arr[f.b])}\n```\n\n上面的代码将只会生成\"0\"或者\"3\"（注意 lua 数组的下标是从 1 开始的）\n\n这个示例并没有什么实际意义，只是表达个意思\n\n另一个比较重要的特性是你可以在 lua 代码块中用`_xxx()`的方式调用 yy 关键字：\n\n```\nquery:{table = _table()}\n    BEGIN ; update ; select ; END\n\nupdate:\n    UPDATE {print(table)} SET _field_int = 10\n\nselect:\n    SELECT * FROM {print(table)}\n```\n\n上面这种写法将能够保证 `update` 和 `selet` 的是同一张随即表。\n\n#### 常用模式\n\n - 递归地嵌套子查询\n \n```\nquery:\n    select\n\nselect:\n    SELECT * FROM\n    (select)\n    WHERE _field_int \u003e 10\n    | SELECT * FROM _table WHERE _field_char = _english\n```\n\n - 可能为空的规则\n \n```\norder:\n    ASC\n    |DESC\n    |    # 空规则\n    \n#....省略其他规则\n```\n\n - 生成多条相邻的 sql 语句\n \n有的时候我们希望相关的几条 sql 生成在相邻的位置，比如在测试\nPrepared statement 时，下面例子改自[examples/functions.yy](examples/functions.yy) \n\n```\nquery:\n\tSET @stmt = {print('\"')} select {print('\"')};\n\tPREPARE stmt FROM @stmt_create ; \n\tEXECUTE stmt ;\n\t\nselect:\n    SELECT * FROM _table\n```\n\n此时如果你指定生成的 sql 数量为 3（即`-Q`参数指定为 3）的话，那么就\n会生成如下的 sql\n\n```sql\nSET @stmt = \" SELECT * FROM _table \";\nPREPARE stmt FROM @stmt_create; \nEXECUTE stmt;\n```\n\n指定生成 6 条 sql 的话，就会把上面的 sql 生成两遍。\n\n假如指定生成 sql 数目为 2 的话，那么会生成：\n\n```sql\nSET @stmt = \" SELECT * FROM _table \";\nPREPARE stmt FROM @stmt_create; \n```\n\n从这里我们也可以看出`;`的语义，这个语义继承自 mysql randgen，\n表示一次性生成数条相邻的 sql\n\n - 测试 create 语句时创建名字不冲突的表\n\n\n方案一: 插入 lua 脚本,利用头部代码块\n\n```\n# 申明 i 为 1\n{\ni = 1\n}\n\nquery: \n    create\n    \ncreate:\n    CREATE TABLE \n    {print(string.format(\"table%d\", i)); i = i+1}\n    (a int)\n```\n\n生成结果：\n\n```sql\nCREATE TABLE table1 (a int);\nCREATE TABLE table2 (a int);\nCREATE TABLE table3 (a int);\n......\n```\n\n方案 2：先创建表，然后再把它删除了，利用之前提到的`;`符号\n\n```\nquery:\n    create\n    \ncreate:\n    CREATE TABLE t (a int); DROP TABLE t\n```\n\n生成结果：\n\n```\nCREATE TABLE t (a int);\nDROP TABLE t;\nCREATE TABLE t (a int);\nDROP TABLE t;\n...\n```\n\n## How to hack\n\n相比 mysql randgen，go randgen 最大的特点就是易于 Hack\n，几乎没有任何硬编码，当你觉得缺少什么特性时，可以非常\n方便地自己加上\n\n### hack zz data\n\n如果你觉得 go randgen 在 zz 文件中 data 字段提供的\n数据生成指令不够用时，\n可以进入[gendata/generators/register.go](gendata/generators/register.go)\n的`init`方法里添加。\n\n假设你在里面添加了一个`aaa`指令，除了能够在\nzz 的 data 字段中使用`\"aaa\"`指令外，\n在 yy 文件中也会自动增加一个`_aaa`关键字可以使用\n\n### hack yy key word\n\n如果觉得 yy 中提供的关键字不够用，可以在\n[gendata/gendata.go](gendata/gendata.go)\n中的`NewKeyfun`方法中添加。\n\n\n\n## 与 Mysql randgen 不同的地方\n\n - 不要在规则尾部加分号, mysql randgen 有这个习惯，但是 go randgen 不需要这么做，我们不依赖这个分号\n 来判断不同的规则。当然，你加了也没什么问题，为了兼容 mysql randgen，\n 作了额外的处理\n - 数据初始化时使用`insert`，而不是`insert ignore`，在给 unsigned 类型列\n生成数据时会自动适配非负数，尝试最多 10 次随机，直到生成正数，如果十次都是负数，\n则直接赋予 1\n - zz 文件中 data 的定义可以使用更加精确的数据类型，而不是只有 mysql randgen 中的四种\n - 生成 sql 时可以不连接数据库，利用在生成 ddl 时自动记录下的 schema，非常迅速\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpingcap%2Fgo-randgen","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpingcap%2Fgo-randgen","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpingcap%2Fgo-randgen/lists"}