├── .github └── workflows │ └── reviewdog.yml ├── .gitignore ├── .goreleaser.yml ├── .travis.yml ├── .travis ├── docker.cnf └── wait_mysql.sh ├── Gopkg.lock ├── Gopkg.toml ├── LICENSE ├── Makefile ├── README.md ├── docker-compose.yml ├── internal └── getters │ ├── binary.go │ ├── constant.go │ ├── date.go │ ├── datetime.go │ ├── decimal.go │ ├── enum.go │ ├── getters.go │ ├── int.go │ ├── samples.go │ ├── string.go │ ├── time.go │ └── year.go ├── main.go ├── main_test.go ├── runtests.sh ├── tableparser ├── tableparser.go ├── tableparser_test.go └── testdata │ ├── indexes.json │ ├── table001.json │ ├── table002.json │ ├── table003.json │ ├── trigers-5.7.1.json │ ├── trigers-5.7.2.json │ ├── trigers-8.0.0.json │ └── triggers.json ├── testdata ├── sakila.film.json └── schema │ └── sakila.sql ├── tests ├── rental.json ├── sakila-db │ ├── sakila-data.sql │ └── sakila-schema.sql └── table001.json └── testutils └── testutils.go /.github/workflows/reviewdog.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the master branch 7 | on: 8 | push: 9 | branches: [ master ] 10 | pull_request: 11 | branches: [ master ] 12 | 13 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 14 | jobs: 15 | # This workflow contains a single job called "build" 16 | build: 17 | # The type of runner that the job will run on 18 | runs-on: ubuntu-latest 19 | 20 | # Steps represent a sequence of tasks that will be executed as part of the job 21 | steps: 22 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 23 | - uses: actions/checkout@v2 24 | 25 | # Runs a single command using the runners shell 26 | - name: Run golangci-lint with reviewdog 27 | uses: reviewdog/action-golangci-lint@v1.1.3 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | random_data_load* 2 | vendor/ 3 | bin/* 4 | dist/* 5 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # This is an example goreleaser.yaml file with some sane defaults. 2 | # Make sure to check the documentation at http://goreleaser.com 3 | before: 4 | hooks: 5 | # you may remove this if you don't use vgo 6 | # - go mod tidy 7 | # you may remove this if you don't need go generate 8 | # - go generate ./... 9 | builds: 10 | - env: 11 | - CGO_ENABLED=0 12 | archives: 13 | - replacements: 14 | darwin: Darwin 15 | linux: Linux 16 | windows: Windows 17 | 386: i386 18 | amd64: x86_64 19 | checksum: 20 | name_template: 'checksums.txt' 21 | snapshot: 22 | name_template: "{{ .Tag }}-next" 23 | changelog: 24 | sort: asc 25 | filters: 26 | exclude: 27 | - '^docs:' 28 | - '^test:' 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: go 3 | 4 | matrix: 5 | include: 6 | - env: DB=MYSQL5.7 7 | sudo: required 8 | dist: trusty 9 | go: 1.14.x 10 | services: 11 | - docker 12 | before_install: 13 | - go get golang.org/x/tools/cmd/cover 14 | - go get -u github.com/golang/dep/cmd/dep 15 | - dep ensure 16 | - docker pull mysql:5.7 17 | - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_ALLOW_EMPTY_PASSWORD=yes mysql:5.7 --innodb_log_file_size=256MB 18 | --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB 19 | - sleep 30 20 | - cp .travis/docker.cnf ~/.my.cnf 21 | - .travis/wait_mysql.sh 22 | - mysql < testdata/schema/sakila.sql 23 | before_script: 24 | - export TEST_DSN="root:@tcp(127.0.0.1:3307)/sakila?parseTime=true" 25 | 26 | - env: DB=MYSQL5.6 27 | sudo: required 28 | dist: trusty 29 | go: 1.14.x 30 | services: 31 | - docker 32 | before_install: 33 | - go get golang.org/x/tools/cmd/cover 34 | - go get -u github.com/golang/dep/cmd/dep 35 | - dep ensure 36 | - docker pull mysql:5.6 37 | - docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_ALLOW_EMPTY_PASSWORD=yes mysql:5.6 38 | --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB 39 | - sleep 30 40 | - cp .travis/docker.cnf ~/.my.cnf 41 | - .travis/wait_mysql.sh 42 | - mysql < testdata/schema/sakila.sql 43 | before_script: 44 | - export TEST_DSN="root@tcp(127.0.0.1:3307)/sakila?parseTime=true" 45 | 46 | script: 47 | - go test -v -race ./... 48 | -------------------------------------------------------------------------------- /.travis/docker.cnf: -------------------------------------------------------------------------------- 1 | [client] 2 | user = root 3 | host = 127.0.0.1 4 | port = 3307 5 | -------------------------------------------------------------------------------- /.travis/wait_mysql.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | while : 3 | do 4 | sleep 3 5 | if mysql -e 'select version()'; then 6 | break 7 | fi 8 | done 9 | -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | branch = "master" 6 | digest = "1:125f098c5294814b5d9ce2194f03a435c35a6208f029e61681bed9a893d68133" 7 | name = "github.com/alecthomas/template" 8 | packages = [ 9 | ".", 10 | "parse", 11 | ] 12 | pruneopts = "UT" 13 | revision = "fb15b899a75114aa79cc930e33c46b577cc664b1" 14 | 15 | [[projects]] 16 | branch = "master" 17 | digest = "1:f780e2e814981de9dbdc2ca49d6c91d4aa4e2a1639ebc4106fafb0dc54fa5bd8" 18 | name = "github.com/alecthomas/units" 19 | packages = ["."] 20 | pruneopts = "UT" 21 | revision = "f65c72e2690dc4b403c8bd637baf4611cd4c069b" 22 | 23 | [[projects]] 24 | digest = "1:b06d27db2f87588507f245345f329b4192809e72ab32385a26a6eb25988eb82e" 25 | name = "github.com/corpix/uarand" 26 | packages = ["."] 27 | pruneopts = "UT" 28 | revision = "031be390f409fb4bac8fb299e3bcd101479f89f8" 29 | 30 | [[projects]] 31 | digest = "1:2d8307a7345570ece92d85e80f17c9de719fa44a11f5ccd828ab2bb4a328314c" 32 | name = "github.com/go-ini/ini" 33 | packages = ["."] 34 | pruneopts = "UT" 35 | revision = "700781759788472518f4ea52321519b066061a7b" 36 | version = "v1.48.0" 37 | 38 | [[projects]] 39 | digest = "1:ec6f9bf5e274c833c911923c9193867f3f18788c461f76f05f62bb1510e0ae65" 40 | name = "github.com/go-sql-driver/mysql" 41 | packages = ["."] 42 | pruneopts = "UT" 43 | revision = "72cd26f257d44c1114970e19afddcd812016007e" 44 | version = "v1.4.1" 45 | 46 | [[projects]] 47 | digest = "1:e6e8ee8a9aa4efb33290bd5e6615822635884bc007923359e3a10b57eff36bb3" 48 | name = "github.com/gosuri/uilive" 49 | packages = ["."] 50 | pruneopts = "UT" 51 | revision = "4512d98b127f3f3a1b7c3cf1104969fdd17b31d9" 52 | version = "v0.0.3" 53 | 54 | [[projects]] 55 | digest = "1:f6ae4b3c3d4411bffa0b8045fff29974e1eb866e1828786972059f89a130aecf" 56 | name = "github.com/gosuri/uiprogress" 57 | packages = [ 58 | ".", 59 | "util/strutil", 60 | ] 61 | pruneopts = "UT" 62 | revision = "d0567a9d84a1c40dd7568115ea66f4887bf57b33" 63 | version = "0.0.1" 64 | 65 | [[projects]] 66 | digest = "1:88e0b0baeb9072f0a4afbcf12dda615fc8be001d1802357538591155998da21b" 67 | name = "github.com/hashicorp/go-version" 68 | packages = ["."] 69 | pruneopts = "UT" 70 | revision = "ac23dc3fea5d1a983c43f6a0f6e2c13f0195d8bd" 71 | version = "v1.2.0" 72 | 73 | [[projects]] 74 | branch = "master" 75 | digest = "1:22725c01ecd8ed0c0f0078944305a57053340d92878b02db925c660cc4accf64" 76 | name = "github.com/icrowley/fake" 77 | packages = ["."] 78 | pruneopts = "UT" 79 | revision = "4178557ae428460c3780a381c824a1f3aceb6325" 80 | 81 | [[projects]] 82 | digest = "1:31e761d97c76151dde79e9d28964a812c46efc5baee4085b86f68f0c654450de" 83 | name = "github.com/konsorten/go-windows-terminal-sequences" 84 | packages = ["."] 85 | pruneopts = "UT" 86 | revision = "f55edac94c9bbba5d6182a4be46d86a2c9b5b50e" 87 | version = "v1.0.2" 88 | 89 | [[projects]] 90 | digest = "1:ca955a9cd5b50b0f43d2cc3aeb35c951473eeca41b34eb67507f1dbcc0542394" 91 | name = "github.com/kr/pretty" 92 | packages = ["."] 93 | pruneopts = "UT" 94 | revision = "73f6ac0b30a98e433b289500d779f50c1a6f0712" 95 | version = "v0.1.0" 96 | 97 | [[projects]] 98 | digest = "1:15b5cc79aad436d47019f814fde81a10221c740dc8ddf769221a65097fb6c2e9" 99 | name = "github.com/kr/text" 100 | packages = ["."] 101 | pruneopts = "UT" 102 | revision = "e2ffdb16a802fe2bb95e2e35ff34f0e53aeef34f" 103 | version = "v0.1.0" 104 | 105 | [[projects]] 106 | digest = "1:d62282425ffb75047679d7e2c3b980eea7f82c05ef5fb9142ee617ebac6e7432" 107 | name = "github.com/mattn/go-isatty" 108 | packages = ["."] 109 | pruneopts = "UT" 110 | revision = "88ba11cfdc67c7588b30042edf244b2875f892b6" 111 | version = "v0.0.10" 112 | 113 | [[projects]] 114 | digest = "1:cf31692c14422fa27c83a05292eb5cbe0fb2775972e8f1f8446a71549bd8980b" 115 | name = "github.com/pkg/errors" 116 | packages = ["."] 117 | pruneopts = "UT" 118 | revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4" 119 | version = "v0.8.1" 120 | 121 | [[projects]] 122 | digest = "1:04457f9f6f3ffc5fea48e71d62f2ca256637dee0a04d710288e27e05c8b41976" 123 | name = "github.com/sirupsen/logrus" 124 | packages = ["."] 125 | pruneopts = "UT" 126 | revision = "839c75faf7f98a33d445d181f3018b5c3409a45e" 127 | version = "v1.4.2" 128 | 129 | [[projects]] 130 | branch = "master" 131 | digest = "1:7c927f17d868be652a4cfe7de23e4292dea5b14d974a1d536e3b7cb7e79fd695" 132 | name = "golang.org/x/sys" 133 | packages = ["unix"] 134 | pruneopts = "UT" 135 | revision = "b09406accb4736d857a32bf9444cd7edae2ffa79" 136 | 137 | [[projects]] 138 | digest = "1:c25289f43ac4a68d88b02245742347c94f1e108c534dda442188015ff80669b3" 139 | name = "google.golang.org/appengine" 140 | packages = ["cloudsql"] 141 | pruneopts = "UT" 142 | revision = "971852bfffca25b069c31162ae8f247a3dba083b" 143 | version = "v1.6.5" 144 | 145 | [[projects]] 146 | digest = "1:c06d9e11d955af78ac3bbb26bd02e01d2f61f689e1a3bce2ef6fb683ef8a7f2d" 147 | name = "gopkg.in/alecthomas/kingpin.v2" 148 | packages = ["."] 149 | pruneopts = "UT" 150 | revision = "947dcec5ba9c011838740e680966fd7087a71d0d" 151 | version = "v2.2.6" 152 | 153 | [solve-meta] 154 | analyzer-name = "dep" 155 | analyzer-version = 1 156 | input-imports = [ 157 | "github.com/go-ini/ini", 158 | "github.com/go-sql-driver/mysql", 159 | "github.com/gosuri/uiprogress", 160 | "github.com/hashicorp/go-version", 161 | "github.com/icrowley/fake", 162 | "github.com/kr/pretty", 163 | "github.com/pkg/errors", 164 | "github.com/sirupsen/logrus", 165 | "gopkg.in/alecthomas/kingpin.v2", 166 | ] 167 | solver-name = "gps-cdcl" 168 | solver-version = 1 169 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | 28 | [[constraint]] 29 | name = "github.com/go-ini/ini" 30 | version = "1.48.0" 31 | 32 | [[constraint]] 33 | name = "github.com/go-sql-driver/mysql" 34 | version = "1.4.1" 35 | 36 | [[constraint]] 37 | name = "github.com/gosuri/uiprogress" 38 | version = "0.0.1" 39 | 40 | [[constraint]] 41 | name = "github.com/hashicorp/go-version" 42 | version = "1.2.0" 43 | 44 | [[constraint]] 45 | branch = "master" 46 | name = "github.com/icrowley/fake" 47 | 48 | [[constraint]] 49 | name = "github.com/kr/pretty" 50 | version = "0.1.0" 51 | 52 | [[constraint]] 53 | name = "github.com/pkg/errors" 54 | version = "0.8.1" 55 | 56 | [[constraint]] 57 | name = "github.com/sirupsen/logrus" 58 | version = "1.4.2" 59 | 60 | [[constraint]] 61 | name = "gopkg.in/alecthomas/kingpin.v2" 62 | version = "2.2.6" 63 | 64 | [prune] 65 | go-tests = true 66 | unused-packages = true 67 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2019 Percona LLC 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GO := go 2 | pkgs = $(shell basename `git rev-parse --show-toplevel`) 3 | VERSION ?=$(shell git describe --abbrev=0) 4 | BUILD ?=$(shell date +%FT%T%z) 5 | GOVERSION ?=$(shell go version | cut --delimiter=" " -f3) 6 | COMMIT ?=$(shell git rev-parse HEAD) 7 | BRANCH ?=$(shell git rev-parse --abbrev-ref HEAD) 8 | GOPATH ?=${HOME}/go 9 | 10 | MAKE_TARS = '' 11 | CUR_DIR=$(shell pwd) 12 | BIN_DIR=${CUR_DIR}/build 13 | LDFLAGS="-X main.Version=${VERSION} -X main.Build=${BUILD} -X main.Commit=${COMMIT} -X main.Branch=${BRANCH} -X main.GoVersion=${GOVERSION} -s -w" 14 | 15 | ifeq (${GOPATH},) 16 | $(error GOPATH is not set) 17 | endif 18 | 19 | ifeq (,$(wildcard ${GOPATH}/src)) 20 | $(error Invalid GOPATH. There is no src dir in the GOPATH) 21 | endif 22 | 23 | ifeq ($(findstring ${GOPATH},${CUR_DIR}), ) 24 | $(error Wrong directorry for the project. It must be in $GOPATH/github/Percona-Lab/mysql_random_data_load) 25 | endif 26 | 27 | $(info ) 28 | $(info GOPATH..........: ${GOPATH}) 29 | $(info Build directory.: ${BIN_DIR}) 30 | $(info ) 31 | 32 | .PHONY: all style format build test vet tarball linux-amd64 33 | 34 | default: prepare 35 | @$(info Cleaning old tar files in ${BIN_DIR}) 36 | @rm -f ${BIN_DIR}/mysql_random_data_load_*.tar.gz 37 | @echo 38 | @$(info Building in ${BIN_DIR}) 39 | @go build -ldflags ${LDFLAGS} -o ${BIN_DIR}/mysql_random_data_load main.go 40 | 41 | prepare: 42 | @$(info Checking if ${BIN_DIR} exists) 43 | @mkdir -p ${BIN_DIR} 44 | 45 | all: clean darwin-amd64-tar linux-amd64-tar 46 | 47 | clean: prepare 48 | @$(info Cleaning binaries and tar.gz files in dir ${BIN_DIR}) 49 | @rm -f ${BIN_DIR}/mysql_random_data_load 50 | @rm -f ${BIN_DIR}/mysql_random_data_load_*.tar.gz 51 | 52 | linux-amd64: prepare 53 | @echo "Building linux/amd64 binaries in ${BIN_DIR}" 54 | @GOOS=linux GOARCH=amd64 go build -ldflags ${LDFLAGS} -o ${BIN_DIR}/mysql_random_data_load main.go 55 | 56 | linux-amd64-tar: linux-amd64 57 | @tar cvzf ${BIN_DIR}/mysql_random_data_load_linux_amd64.tar.gz -C ${BIN_DIR} mysql_random_data_load 58 | 59 | linux-386: prepare 60 | @echo "Building linux/386 binaries in ${BIN_DIR}" 61 | @GOOS=linux GOARCH=386 go build -ldflags ${LDFLAGS} -o ${BIN_DIR}/mysql_random_data_load main.go 62 | 63 | linux-386-tar: linux-386 64 | @tar cvzf ${BIN_DIR}/mysql_random_data_load_linux_386.tar.gz -C ${BIN_DIR} mysql_random_data_load 65 | 66 | darwin-amd64: 67 | @echo "Building darwin/amd64 binaries in ${BIN_DIR}" 68 | @mkdir -p ${BIN_DIR} 69 | @GOOS=darwin GOARCH=amd64 go build -ldflags ${LDFLAGS} -o ${BIN_DIR}/mysql_random_data_load main.go 70 | 71 | darwin-amd64-tar: darwin-amd64 72 | @tar cvzf ${BIN_DIR}/mysql_random_data_load_darwin_amd64.tar.gz -C ${BIN_DIR} mysql_random_data_load 73 | 74 | style: 75 | @echo ">> checking code style" 76 | @! gofmt -d $(shell find . -path ./vendor -prune -o -name '*.go' -print) | grep '^' 77 | 78 | test: 79 | @echo ">> running tests" 80 | @./runtests.sh 81 | 82 | clean-tests: 83 | @$(info Cleaning up docker containers used for tests) 84 | @docker-compose down 85 | 86 | format: 87 | @echo ">> formatting code" 88 | @$(GO) fmt $(pkgs) 89 | 90 | vet: 91 | @echo ">> vetting code" 92 | @$(GO) vet $(pkgs) 93 | 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Random data generator for MySQL 2 | [![Build Status](https://travis-ci.org/Percona-Lab/mysql_random_data_load.svg?branch=master)](https://travis-ci.org/Percona-Lab/mysql_random_data_load) 3 | 4 | Many times in my job I need to generate random data for a specific table in order to reproduce an issue. 5 | After writing many random generators for every table, I decided to write a random data generator, able to get the table structure and generate random data for it. 6 | Plase take into consideration that this is the first version and it doesn't support all field types yet! 7 | 8 | **NOTICE** 9 | This is an early stage project. 10 | 11 | ## Supported fields: 12 | 13 | |Field type|Generated values| 14 | |----------|----------------| 15 | |tinyint|0 ~ 0xFF| 16 | |smallint|0 ~ 0XFFFF| 17 | |mediumint|0 ~ 0xFFFFFF| 18 | |int - integer|0 ~ 0xFFFFFFFF| 19 | |bigint|0 ~ 0xFFFFFFFFFFFFFFFF| 20 | |float|0 ~ 1e8| 21 | |decimal(m,n)|0 ~ 10^(m-n)| 22 | |double|0 ~ 1000| 23 | |char(n)|up to n random chars| 24 | |varchar(n)|up to n random chars| 25 | |date|NOW() - 1 year ~ NOW()| 26 | |datetime|NOW() - 1 year ~ NOW()| 27 | |timestamp|NOW() - 1 year ~ NOW()| 28 | |time|00:00:00 ~ 23:59:59| 29 | |year|Current year - 1 ~ current year| 30 | |tinyblob|up to 100 chars random paragraph| 31 | |tinytext|up to 100 chars random paragraph| 32 | |blob|up to 100 chars random paragraph| 33 | |text|up to 100 chars random paragraph| 34 | |mediumblob|up to 100 chars random paragraph| 35 | |mediumtext|up to 100 chars random paragraph| 36 | |longblob|up to 100 chars random paragraph| 37 | |longtext|up to 100 chars random paragraph| 38 | |varbinary|up to 100 chars random paragraph| 39 | |enum|A random item from the valid items list| 40 | |set|A random item from the valid items list| 41 | 42 | ### How strings are generated 43 | 44 | - If field size < 10 the program generates a random "first name" 45 | - If the field size > 10 and < 30 the program generates a random "full name" 46 | - If the field size > 30 the program generates a "lorem ipsum" paragraph having up to 100 chars. 47 | 48 | The program can detect if a field accepts NULLs and if it does, it will generate NULLs ramdomly (~ 10 % of the values). 49 | 50 | ## Usage 51 | `mysql_random_data_load [options...]` 52 | 53 | ## Options 54 | |Option|Description| 55 | |------|-----------| 56 | |--bulk-size|Number of rows per INSERT statement (Default: 1000)| 57 | |--debug|Show some debug information| 58 | |--fk-samples-factor|Percentage used to get random samples for foreign keys fields. Default 0.3| 59 | |--host|Host name/ip| 60 | |--max-fk-samples|Maximum number of samples for fields having foreign keys constarints. Default: 100| 61 | |--max-retries|Maximum number of rows to retry in case of errors. See duplicated keys. Deafult: 100| 62 | |--no-progressbar|Skip showing the progress bar. Default: false| 63 | |--password|Password| 64 | |--port|Port number| 65 | |--Print|Print queries to the standard output instead of inserting them into the db| 66 | |--user|Username| 67 | |--version|Show version and exit| 68 | 69 | ## Foreign keys support 70 | If a field has Foreign Keys constraints, `random-data-load` will get up to `--max-fk-samples` random samples from the referenced tables in order to insert valid values for the field. 71 | The number of samples to get follows this rules: 72 | **1.** Get the aproximate number of rows in the referenced table using the `rows` field in: 73 | ``` 74 | EXPLAIN SELECT COUNT(*) FROM . 75 | ``` 76 | **1.1** If the number of rows is less than `max-fk-samples`, all rows are retrieved from the referenced table using this query: 77 | ``` 78 | SELECT FROM . 79 | ``` 80 | **1.2** If the number of rows is greater than `max-fk-samples`, samples are retrieved from the referenced table using this query: 81 | ``` 82 | SELECT FROM . WHERE RAND() <= LIMIT 83 | ``` 84 | 85 | ### Example 86 | ``` 87 | CREATE DATABASE IF NOT EXISTS test; 88 | 89 | CREATE TABLE `test`.`t3` ( 90 | `id` int(11) NOT NULL AUTO_INCREMENT, 91 | `tcol01` tinyint(4) DEFAULT NULL, 92 | `tcol02` smallint(6) DEFAULT NULL, 93 | `tcol03` mediumint(9) DEFAULT NULL, 94 | `tcol04` int(11) DEFAULT NULL, 95 | `tcol05` bigint(20) DEFAULT NULL, 96 | `tcol06` float DEFAULT NULL, 97 | `tcol07` double DEFAULT NULL, 98 | `tcol08` decimal(10,2) DEFAULT NULL, 99 | `tcol09` date DEFAULT NULL, 100 | `tcol10` datetime DEFAULT NULL, 101 | `tcol11` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 102 | `tcol12` time DEFAULT NULL, 103 | `tcol13` year(4) DEFAULT NULL, 104 | `tcol14` varchar(100) DEFAULT NULL, 105 | `tcol15` char(2) DEFAULT NULL, 106 | `tcol16` blob, 107 | `tcol17` text, 108 | `tcol18` mediumtext, 109 | `tcol19` mediumblob, 110 | `tcol20` longblob, 111 | `tcol21` longtext, 112 | `tcol22` mediumtext, 113 | `tcol23` varchar(3) DEFAULT NULL, 114 | `tcol24` varbinary(10) DEFAULT NULL, 115 | `tcol25` enum('a','b','c') DEFAULT NULL, 116 | `tcol26` set('red','green','blue') DEFAULT NULL, 117 | `tcol27` float(5,3) DEFAULT NULL, 118 | `tcol28` double(4,2) DEFAULT NULL, 119 | PRIMARY KEY (`id`) 120 | ) ENGINE=InnoDB; 121 | ``` 122 | To generate 100K random rows, just run: 123 | ``` 124 | mysql_random_data_load test t3 100000 --user=root --password=root 125 | ``` 126 | ``` 127 | mysql> select * from t3 limit 1\G 128 | *************************** 1. row *************************** 129 | id: 1 130 | tcol01: 10 131 | tcol02: 173 132 | tcol03: 1700 133 | tcol04: 13498 134 | tcol05: 33239373 135 | tcol06: 44846.4 136 | tcol07: 5300.23 137 | tcol08: 11360967.75 138 | tcol09: 2017-09-04 139 | tcol10: 2016-11-02 23:11:25 140 | tcol11: 2017-03-03 08:11:40 141 | tcol12: 03:19:39 142 | tcol13: 2017 143 | tcol14: repellat maxime nostrum provident maiores ut quo voluptas. 144 | tcol15: Th 145 | tcol16: Walter 146 | tcol17: quo repellat accusamus quidem odi 147 | tcol18: esse laboriosam nobis libero aut dolores e 148 | tcol19: Carlos Willia 149 | tcol20: et nostrum iusto ipsa sunt recusa 150 | tcol21: a accusantium laboriosam voluptas facilis. 151 | tcol22: laudantium quo unde molestiae consequatur magnam. 152 | tcol23: Pet 153 | tcol24: Richard 154 | tcol25: c 155 | tcol26: green 156 | tcol27: 47.430 157 | tcol28: 6.12 158 | 1 row in set (0.00 sec) 159 | ``` 160 | 161 | ## How to download the precompiled binaries 162 | 163 | There are binaries available for each version for Linux and Darwin. You can find compiled binaries for each version in the releases tab: 164 | 165 | https://github.com/Percona-Lab/mysql_random_data_load/releases 166 | 167 | ## To do 168 | - [ ] Add suport for all data types. 169 | - [X] Add supporrt for foreign keys. 170 | - [ ] Support config files to override default values/ranges. 171 | - [ ] Support custom functions via LUA plugins. 172 | 173 | ## Version history 174 | 175 | #### 0.1.10 176 | - Fixed argument validations 177 | - Fixed ~/.my.cnf loading 178 | 179 | #### 0.1.10 180 | - Fixed connection parameters for MySQL 5.7 (set driver's AllowNativePasswords: true) 181 | 182 | #### 0.1.9 183 | - Added support for bunary and varbinary columns 184 | - By default, read connection params from ${HOME}/.my.cnf 185 | 186 | #### 0.1.8 187 | - Fixed error for triggers created with MySQL 5.6 188 | - Added Travis-CI 189 | - Code clean up 190 | 191 | #### 0.1.7 192 | - Support for MySQL 8.0 193 | - Added --print parameter 194 | - Added --version parameter 195 | - Removed qps parameter 196 | 197 | #### 0.1.6 198 | - Improved generation speed (up to 50% faster) 199 | - Improved support for TokuDB (Thanks Agustin Gallego) 200 | - Code refactored 201 | - Improved debug logging 202 | - Added Query Per Seconds support (experimental) 203 | 204 | #### 0.1.5 205 | - Fixed handling of NULL collation for index parser 206 | 207 | #### 0.1.4 208 | - Fixed handling of time columns 209 | - Improved support of GENERATED columns 210 | 211 | #### 0.1.3 212 | - Fixed handling of nulls 213 | 214 | #### 0.1.2 215 | - New table parser able to retrieve all the information for fields, indexes and foreign keys constraints. 216 | - Support for foreign keys constraints 217 | - Added some tests 218 | 219 | #### 0.1.1 220 | - Fixed random data generation 221 | 222 | #### 0.1.0 223 | - Initial version 224 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | mysql5.6: 4 | image: ${MYSQL_IMAGE:-mysql:5.6} 5 | ports: 6 | - ${MYSQL_HOST:-127.0.0.1}:${MYSQL_PORT:-3306}:3306 7 | environment: 8 | - MYSQL_ALLOW_EMPTY_PASSWORD=yes 9 | # MariaDB >= 10.0.12 doesn't enable Performance Schema by default so we need to do it manually 10 | # https://mariadb.com/kb/en/mariadb/performance-schema-overview/#activating-the-performance-schema 11 | command: --performance-schema --secure-file-priv="" 12 | volumes: 13 | - ./testdata/schema/:/docker-entrypoint-initdb.d/:rw 14 | mysql5.7: 15 | image: ${MYSQL_IMAGE:-mysql:5.7} 16 | ports: 17 | - ${MYSQL_HOST:-127.0.0.1}:${MYSQL_PORT:-3307}:3306 18 | environment: 19 | - MYSQL_ALLOW_EMPTY_PASSWORD=yes 20 | # MariaDB >= 10.0.12 doesn't enable Performance Schema by default so we need to do it manually 21 | # https://mariadb.com/kb/en/mariadb/performance-schema-overview/#activating-the-performance-schema 22 | command: --performance-schema --secure-file-priv="" 23 | volumes: 24 | - ./testdata/schema/:/docker-entrypoint-initdb.d/:rw 25 | mysql8.0: 26 | image: ${MYSQL_IMAGE:-mysql:8.0.3} 27 | ports: 28 | - ${MYSQL_HOST:-127.0.0.1}:${MYSQL_PORT:-3308}:3306 29 | environment: 30 | - MYSQL_ALLOW_EMPTY_PASSWORD=yes 31 | # MariaDB >= 10.0.12 doesn't enable Performance Schema by default so we need to do it manually 32 | # https://mariadb.com/kb/en/mariadb/performance-schema-overview/#activating-the-performance-schema 33 | command: --performance-schema --secure-file-priv="" 34 | volumes: 35 | - ./testdata/schema/:/docker-entrypoint-initdb.d/:rw 36 | -------------------------------------------------------------------------------- /internal/getters/binary.go: -------------------------------------------------------------------------------- 1 | package getters 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | 7 | "github.com/icrowley/fake" 8 | ) 9 | 10 | // RandomBinary getter 11 | type RandomBinary struct { 12 | name string 13 | maxSize int64 14 | allowNull bool 15 | } 16 | 17 | func (r *RandomBinary) Value() interface{} { 18 | if r.allowNull && rand.Int63n(100) < nilFrequency { 19 | return nil 20 | } 21 | var s string 22 | maxSize := uint64(r.maxSize) 23 | if maxSize == 0 { 24 | maxSize = uint64(rand.Int63n(100)) 25 | } 26 | 27 | if maxSize <= 10 { 28 | s = fake.FirstName() 29 | } else if maxSize < 30 { 30 | s = fake.FullName() 31 | } else { 32 | s = fake.Sentence() 33 | } 34 | if len(s) > int(maxSize) { 35 | s = s[:int(maxSize)] 36 | } 37 | return s 38 | } 39 | 40 | func (r *RandomBinary) String() string { 41 | v := r.Value() 42 | if v == nil { 43 | return NULL 44 | } 45 | return v.(string) 46 | } 47 | 48 | func (r *RandomBinary) Quote() string { 49 | v := r.Value() 50 | if v == nil { 51 | return NULL 52 | } 53 | return fmt.Sprintf("%q", v) 54 | } 55 | 56 | func NewRandomBinary(name string, maxSize int64, allowNull bool) *RandomBinary { 57 | return &RandomBinary{name, maxSize, allowNull} 58 | } 59 | -------------------------------------------------------------------------------- /internal/getters/constant.go: -------------------------------------------------------------------------------- 1 | package getters 2 | 3 | import "fmt" 4 | 5 | // Constant Getter. Used for debugging 6 | type Constant struct { 7 | value interface{} 8 | } 9 | 10 | func (r *Constant) Value() interface{} { 11 | return r.value 12 | } 13 | 14 | func (r *Constant) String() string { 15 | return fmt.Sprintf("%s", r.Value()) 16 | } 17 | 18 | func (r *Constant) Quote() string { 19 | return fmt.Sprintf("%q", r.Value()) 20 | } 21 | 22 | func NewConstant(value interface{}) *Constant { 23 | return &Constant{value} 24 | } 25 | -------------------------------------------------------------------------------- /internal/getters/date.go: -------------------------------------------------------------------------------- 1 | package getters 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "time" 7 | ) 8 | 9 | type RandomDate struct { 10 | name string 11 | allowNull bool 12 | } 13 | 14 | func (r *RandomDate) Value() interface{} { 15 | var randomSeconds time.Duration 16 | for i := 0; i < 10 && randomSeconds != 0; i++ { 17 | randomSeconds = time.Duration(rand.Int63n(int64(oneYear)) + rand.Int63n(100)) 18 | } 19 | d := time.Now().Add(-1 * randomSeconds) 20 | return d 21 | } 22 | 23 | func (r *RandomDate) String() string { 24 | d := r.Value().(time.Time) 25 | return d.Format("2006-01-02 15:03:04") 26 | } 27 | 28 | func (r *RandomDate) Quote() string { 29 | d := r.Value().(time.Time) 30 | return fmt.Sprintf("'%s'", d.Format("2006-01-02 15:03:04")) 31 | } 32 | 33 | func NewRandomDate(name string, allowNull bool) *RandomDate { 34 | return &RandomDate{name, allowNull} 35 | } 36 | 37 | type RandomDateInRange struct { 38 | name string 39 | min string 40 | max string 41 | allowNull bool 42 | } 43 | 44 | func (r *RandomDateInRange) Value() interface{} { 45 | rand.Seed(time.Now().UnixNano()) 46 | var randomSeconds int64 47 | randomSeconds = rand.Int63n(oneYear) + rand.Int63n(100) 48 | d := time.Now().Add(-1 * time.Duration(randomSeconds) * time.Second) 49 | return d 50 | } 51 | 52 | func (r *RandomDateInRange) String() string { 53 | d := r.Value().(time.Time) 54 | return d.Format("2006-01-02 15:03:04") 55 | } 56 | 57 | func (r *RandomDateInRange) Quote() string { 58 | d := r.Value().(time.Time) 59 | return fmt.Sprintf("'%s'", d.Format("2006-01-02 15:03:04")) 60 | } 61 | 62 | func NewRandomDateInRange(name string, min, max string, allowNull bool) *RandomDateInRange { 63 | if min == "" { 64 | t := time.Now().Add(-1 * time.Duration(oneYear) * time.Second) 65 | min = t.Format("2006-01-02") 66 | } 67 | return &RandomDateInRange{name, min, max, allowNull} 68 | } 69 | -------------------------------------------------------------------------------- /internal/getters/datetime.go: -------------------------------------------------------------------------------- 1 | package getters 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "time" 7 | ) 8 | 9 | type RandomDateTimeInRange struct { 10 | min string 11 | max string 12 | allowNull bool 13 | } 14 | 15 | // Value returns a random time.Time in the range specified by the New method 16 | func (r *RandomDateTimeInRange) Value() interface{} { 17 | rand.Seed(time.Now().UnixNano()) 18 | randomSeconds := rand.Int63n(oneYear) 19 | d := time.Now().Add(-1 * time.Duration(randomSeconds) * time.Second) 20 | return d 21 | } 22 | 23 | func (r *RandomDateTimeInRange) String() string { 24 | d := r.Value().(time.Time) 25 | return d.Format("2006-01-02 15:03:04") 26 | } 27 | 28 | // Quote returns the value quoted for MySQL 29 | func (r *RandomDateTimeInRange) Quote() string { 30 | d := r.Value().(time.Time) 31 | return fmt.Sprintf("'%s'", d.Format("2006-01-02 15:03:04")) 32 | } 33 | 34 | // NewRandomDateTimeInRange returns a new random date in the specified range 35 | func NewRandomDateTimeInRange(name string, min, max string, allowNull bool) *RandomDateInRange { 36 | if min == "" { 37 | t := time.Now().Add(-1 * time.Duration(oneYear) * time.Second) 38 | min = t.Format("2006-01-02") 39 | } 40 | return &RandomDateInRange{name, min, max, allowNull} 41 | } 42 | 43 | // NewRandomDateTime returns a new random datetime between Now() and Now() - 1 year 44 | func NewRandomDateTime(name string, allowNull bool) *RandomDateInRange { 45 | return &RandomDateInRange{name, "", "", allowNull} 46 | } 47 | -------------------------------------------------------------------------------- /internal/getters/decimal.go: -------------------------------------------------------------------------------- 1 | package getters 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | ) 7 | 8 | // RandomDecimal holds unexported data for decimal values 9 | type RandomDecimal struct { 10 | name string 11 | size int64 12 | allowNull bool 13 | } 14 | 15 | func (r *RandomDecimal) Value() interface{} { 16 | size := r.size 17 | if size > 10 { 18 | size = 10 19 | } 20 | f := rand.Float64() * float64(rand.Int63n(int64(size))) 21 | return f 22 | } 23 | 24 | func (r *RandomDecimal) String() string { 25 | return fmt.Sprintf("%0f", r.Value()) 26 | } 27 | 28 | func (r *RandomDecimal) Quote() string { 29 | return r.String() 30 | } 31 | 32 | func NewRandomDecimal(name string, size int64, allowNull bool) *RandomDecimal { 33 | return &RandomDecimal{name, size, allowNull} 34 | } 35 | -------------------------------------------------------------------------------- /internal/getters/enum.go: -------------------------------------------------------------------------------- 1 | package getters 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | ) 7 | 8 | // RandomEnum Getter 9 | type RandomEnum struct { 10 | allowedValues []string 11 | allowNull bool 12 | } 13 | 14 | func (r *RandomEnum) Value() interface{} { 15 | if r.allowNull && rand.Int63n(100) < nilFrequency { 16 | return nil 17 | } 18 | i := rand.Int63n(int64(len(r.allowedValues))) 19 | return r.allowedValues[i] 20 | } 21 | 22 | func (r *RandomEnum) String() string { 23 | if v := r.Value(); v != nil { 24 | return v.(string) 25 | } 26 | return "NULL" 27 | } 28 | 29 | func (r *RandomEnum) Quote() string { 30 | if v := r.Value(); v != nil { 31 | return fmt.Sprintf("%q", v) 32 | } 33 | return "NULL" 34 | } 35 | 36 | func NewRandomEnum(allowedValues []string, allowNull bool) *RandomEnum { 37 | return &RandomEnum{allowedValues, allowNull} 38 | } 39 | -------------------------------------------------------------------------------- /internal/getters/getters.go: -------------------------------------------------------------------------------- 1 | package getters 2 | 3 | // All types defined here satisfy the Getter interface 4 | // type Getter interface { 5 | // Value() interface{} 6 | // Quote() string 7 | // String() string 8 | // } 9 | 10 | const ( 11 | nilFrequency = 10 12 | oneYear = int64(60 * 60 * 24 * 365) 13 | NULL = "NULL" 14 | ) 15 | -------------------------------------------------------------------------------- /internal/getters/int.go: -------------------------------------------------------------------------------- 1 | package getters 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | ) 7 | 8 | type RandomInt struct { 9 | name string 10 | mask int64 11 | allowNull bool 12 | } 13 | 14 | func (r *RandomInt) Value() interface{} { 15 | return rand.Int63n(r.mask) 16 | } 17 | 18 | func (r *RandomInt) String() string { 19 | return fmt.Sprintf("%d", r.Value()) 20 | } 21 | 22 | func (r *RandomInt) Quote() string { 23 | return r.String() 24 | } 25 | 26 | func NewRandomInt(name string, mask int64, allowNull bool) *RandomInt { 27 | return &RandomInt{name, mask, allowNull} 28 | } 29 | 30 | type RandomIntRange struct { 31 | name string 32 | min int64 33 | max int64 34 | allowNull bool 35 | } 36 | 37 | func (r *RandomIntRange) Value() interface{} { 38 | limit := r.max - r.min + 1 39 | return r.min + rand.Int63n(limit) 40 | } 41 | 42 | func (r *RandomIntRange) String() string { 43 | return fmt.Sprintf("%d", r.Value()) 44 | } 45 | 46 | func (r *RandomIntRange) Quote() string { 47 | return r.String() 48 | } 49 | 50 | func NewRandomIntRange(name string, min, max int64, allowNull bool) *RandomIntRange { 51 | return &RandomIntRange{name, min, max, allowNull} 52 | } 53 | -------------------------------------------------------------------------------- /internal/getters/samples.go: -------------------------------------------------------------------------------- 1 | package getters 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | ) 7 | 8 | type RandomSample struct { 9 | name string 10 | samples []interface{} 11 | allowNull bool 12 | } 13 | 14 | func (r *RandomSample) Value() interface{} { 15 | if r.allowNull && rand.Int63n(100) < nilFrequency { 16 | return nil 17 | } 18 | pos := rand.Int63n(int64(len(r.samples))) 19 | return r.samples[pos] 20 | } 21 | 22 | func (r *RandomSample) String() string { 23 | v := r.Value() 24 | if v == nil { 25 | return NULL 26 | } 27 | return fmt.Sprintf("%v", v) 28 | } 29 | 30 | func (r *RandomSample) Quote() string { 31 | v := r.Value() 32 | if v == nil { 33 | return NULL 34 | } 35 | switch v.(type) { 36 | case string: 37 | return fmt.Sprintf("%q", v) 38 | default: 39 | return fmt.Sprintf("%v", v) 40 | } 41 | } 42 | 43 | func NewRandomSample(name string, samples []interface{}, allowNull bool) *RandomSample { 44 | r := &RandomSample{name, samples, allowNull} 45 | return r 46 | } 47 | -------------------------------------------------------------------------------- /internal/getters/string.go: -------------------------------------------------------------------------------- 1 | package getters 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | 7 | "github.com/icrowley/fake" 8 | ) 9 | 10 | // RandomString getter 11 | type RandomString struct { 12 | name string 13 | maxSize int64 14 | allowNull bool 15 | } 16 | 17 | func (r *RandomString) Value() interface{} { 18 | if r.allowNull && rand.Int63n(100) < nilFrequency { 19 | return nil 20 | } 21 | var s string 22 | maxSize := uint64(r.maxSize) 23 | if maxSize == 0 { 24 | maxSize = uint64(rand.Int63n(100)) 25 | } 26 | 27 | if maxSize <= 10 { 28 | s = fake.FirstName() 29 | } else if maxSize < 30 { 30 | s = fake.FullName() 31 | } else { 32 | s = fake.Sentence() 33 | } 34 | if len(s) > int(maxSize) { 35 | s = s[:int(maxSize)] 36 | } 37 | return s 38 | } 39 | 40 | func (r *RandomString) String() string { 41 | v := r.Value() 42 | if v == nil { 43 | return NULL 44 | } 45 | return v.(string) 46 | } 47 | 48 | func (r *RandomString) Quote() string { 49 | v := r.Value() 50 | if v == nil { 51 | return NULL 52 | } 53 | return fmt.Sprintf("%q", v) 54 | } 55 | 56 | func NewRandomString(name string, maxSize int64, allowNull bool) *RandomString { 57 | return &RandomString{name, maxSize, allowNull} 58 | } 59 | -------------------------------------------------------------------------------- /internal/getters/time.go: -------------------------------------------------------------------------------- 1 | package getters 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | ) 7 | 8 | // RandomTime Getter 9 | type RandomTime struct { 10 | allowNull bool 11 | } 12 | 13 | func (r *RandomTime) Value() interface{} { 14 | if r.allowNull && rand.Int63n(100) < nilFrequency { 15 | return nil 16 | } 17 | h := rand.Int63n(24) 18 | m := rand.Int63n(60) 19 | s := rand.Int63n(60) 20 | return fmt.Sprintf("%02d:%02d:%02d", h, m, s) 21 | } 22 | 23 | func (r *RandomTime) String() string { 24 | if r == nil { 25 | return NULL 26 | } 27 | return r.Value().(string) 28 | } 29 | 30 | func (r *RandomTime) Quote() string { 31 | if r == nil { 32 | return NULL 33 | } 34 | return fmt.Sprintf("%q", r.Value()) 35 | } 36 | 37 | func NewRandomTime(allowNull bool) *RandomTime { 38 | return &RandomTime{allowNull} 39 | } 40 | -------------------------------------------------------------------------------- /internal/getters/year.go: -------------------------------------------------------------------------------- 1 | package getters 2 | 3 | func NewRandomYear(name string, format int, allowNull bool) *RandomIntRange { 4 | if format == 2 { 5 | return &RandomIntRange{name, 01, 99, allowNull} 6 | } 7 | return &RandomIntRange{name, 1901, 2155, allowNull} 8 | } 9 | 10 | func NewRandomYearRange(name string, min, max int64, allowNull bool) *RandomIntRange { 11 | return &RandomIntRange{name, min, max, allowNull} 12 | } 13 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "net/url" 7 | "os" 8 | "os/user" 9 | "runtime" 10 | "strings" 11 | "sync" 12 | "time" 13 | 14 | "github.com/Percona-Lab/mysql_random_data_load/internal/getters" 15 | "github.com/Percona-Lab/mysql_random_data_load/tableparser" 16 | "github.com/go-ini/ini" 17 | "github.com/go-sql-driver/mysql" 18 | "github.com/gosuri/uiprogress" 19 | "github.com/kr/pretty" 20 | 21 | log "github.com/sirupsen/logrus" 22 | kingpin "gopkg.in/alecthomas/kingpin.v2" 23 | ) 24 | 25 | type cliOptions struct { 26 | app *kingpin.Application 27 | 28 | // Arguments 29 | Schema *string 30 | TableName *string 31 | Rows *int 32 | // Flags 33 | BulkSize *int 34 | ConfigFile *string 35 | Debug *bool 36 | Factor *float64 37 | Host *string 38 | MaxRetries *int 39 | MaxThreads *int 40 | NoProgress *bool 41 | Pass *string 42 | Port *int 43 | Print *bool 44 | Samples *int64 45 | User *string 46 | Version *bool 47 | } 48 | 49 | type mysqlOptions struct { 50 | Host string 51 | Password string 52 | Port int 53 | Sock string 54 | User string 55 | } 56 | 57 | var ( 58 | opts *cliOptions 59 | 60 | validFunctions = []string{"int", "string", "date", "date_in_range"} 61 | maxValues = map[string]int64{ 62 | "tinyint": 0XF, 63 | "smallint": 0xFF, 64 | "mediumint": 0x7FFFF, 65 | "int": 0x7FFFFFFF, 66 | "integer": 0x7FFFFFFF, 67 | "float": 0x7FFFFFFF, 68 | "decimal": 0x7FFFFFFF, 69 | "double": 0x7FFFFFFF, 70 | "bigint": 0x7FFFFFFFFFFFFFFF, 71 | } 72 | 73 | Version = "0.0.0." 74 | Commit = "" 75 | Branch = "branch-name" 76 | Build = "2017-01-01" 77 | GoVersion = "1.9.2" 78 | ) 79 | 80 | type getter interface { 81 | Value() interface{} 82 | Quote() string 83 | String() string 84 | } 85 | type insertValues []getter 86 | type insertFunction func(*sql.DB, string, chan int, chan bool, *sync.WaitGroup) 87 | 88 | const ( 89 | defaultMySQLConfigSection = "client" 90 | defaultConfigFile = "~/.my.cnf" 91 | defaultBulkSize = 1000 92 | ) 93 | 94 | func main() { 95 | 96 | opts, err := processCliParams() 97 | if err != nil { 98 | log.Fatal(err.Error()) 99 | } 100 | 101 | if *opts.Version { 102 | fmt.Printf("Version : %s\n", Version) 103 | fmt.Printf("Commit : %s\n", Commit) 104 | fmt.Printf("Branch : %s\n", Branch) 105 | fmt.Printf("Build : %s\n", Build) 106 | fmt.Printf("Go version: %s\n", GoVersion) 107 | return 108 | } 109 | 110 | address := *opts.Host 111 | net := "unix" 112 | if address != "localhost" { 113 | net = "tcp" 114 | } 115 | if *opts.Port != 0 { 116 | address = fmt.Sprintf("%s:%d", address, *opts.Port) 117 | } 118 | 119 | dsn := mysql.Config{ 120 | User: *opts.User, 121 | Passwd: *opts.Pass, 122 | Addr: address, 123 | Net: net, 124 | DBName: "", 125 | ParseTime: true, 126 | AllowNativePasswords: true, 127 | } 128 | 129 | db, err := sql.Open("mysql", dsn.FormatDSN()) 130 | if err != nil { 131 | panic(err) 132 | } 133 | db.SetMaxOpenConns(100) 134 | 135 | // SET TimeZone to UTC to avoid errors due to random dates & daylight saving valid values 136 | if _, err = db.Exec(`SET @@session.time_zone = "+00:00"`); err != nil { 137 | log.Printf("Cannot set time zone to UTC: %s\n", err) 138 | db.Close() 139 | os.Exit(1) 140 | } 141 | 142 | table, err := tableparser.NewTable(db, *opts.Schema, *opts.TableName) 143 | if err != nil { 144 | log.Printf("cannot get table %s struct: %s", *opts.TableName, err) 145 | db.Close() 146 | os.Exit(1) 147 | } 148 | 149 | log.SetFormatter(&log.TextFormatter{FullTimestamp: true}) 150 | if *opts.Debug { 151 | log.SetLevel(log.DebugLevel) 152 | *opts.NoProgress = true 153 | } 154 | log.Debug(pretty.Sprint(table)) 155 | 156 | if len(table.Triggers) > 0 { 157 | log.Warnf("There are triggers on the %s table that might affect this process:", *opts.TableName) 158 | for _, t := range table.Triggers { 159 | log.Warnf("Trigger %q, %s %s", t.Trigger, t.Timing, t.Event) 160 | log.Warnf("Statement: %s", t.Statement) 161 | } 162 | } 163 | 164 | if *opts.Rows < 1 { 165 | db.Close() // golint:noerror 166 | log.Warnf("Number of rows < 1. There is nothing to do. Exiting") 167 | os.Exit(1) 168 | } 169 | 170 | if *opts.BulkSize > *opts.Rows { 171 | *opts.BulkSize = *opts.Rows 172 | } 173 | if *opts.BulkSize < 1 { 174 | *opts.BulkSize = defaultBulkSize 175 | } 176 | 177 | if opts.MaxThreads == nil { 178 | *opts.MaxThreads = runtime.NumCPU() * 10 179 | } 180 | 181 | if *opts.MaxThreads < 1 { 182 | *opts.MaxThreads = 1 183 | } 184 | 185 | if !*opts.Print { 186 | log.Info("Starting") 187 | } 188 | 189 | // Example: want 11 rows with bulksize 4: 190 | // count = int(11 / 4) = 2 -> 2 bulk inserts having 4 rows each = 8 rows 191 | // We need to run this insert twice: 192 | // INSERT INTO table (f1, f2) VALUES (?, ?), (?, ?), (?, ?), (?, ?) 193 | // remainder = rows - count = 11 - 8 = 3 194 | // And then, we need to run this insert once to complete 11 rows 195 | // INSERT INTO table (f1, f2) VALUES (?, ?), (?, ?), (?, ?) 196 | newLineOnEachRow := false 197 | count := *opts.Rows / *opts.BulkSize 198 | remainder := *opts.Rows - count**opts.BulkSize 199 | semaphores := makeSemaphores(*opts.MaxThreads) 200 | rowValues := makeValueFuncs(db, table.Fields, *opts.Samples) 201 | log.Debugf("Must run %d bulk inserts having %d rows each", count, *opts.BulkSize) 202 | 203 | runInsertFunc := runInsert 204 | if *opts.Print { 205 | *opts.MaxThreads = 1 206 | *opts.NoProgress = true 207 | newLineOnEachRow = true 208 | runInsertFunc = func(db *sql.DB, insertQuery string, resultsChan chan int, sem chan bool, wg *sync.WaitGroup) { 209 | fmt.Println(insertQuery) 210 | resultsChan <- *opts.BulkSize 211 | sem <- true 212 | wg.Done() 213 | } 214 | } 215 | 216 | bar := uiprogress.AddBar(*opts.Rows).AppendCompleted().PrependElapsed() 217 | if !*opts.NoProgress { 218 | uiprogress.Start() 219 | } 220 | 221 | okCount, err := run(db, table, bar, semaphores, rowValues, count, *opts.BulkSize, runInsertFunc, newLineOnEachRow) 222 | if err != nil { 223 | log.Errorln(err) 224 | } 225 | var okrCount, okiCount int // remainder & individual inserts OK count 226 | if remainder > 0 { 227 | log.Debugf("Must run 1 extra bulk insert having %d rows, to complete %d rows", remainder, *opts.Rows) 228 | okrCount, err = run(db, table, bar, semaphores, rowValues, 1, remainder, runInsertFunc, newLineOnEachRow) 229 | if err != nil { 230 | log.Errorln(err) 231 | } 232 | } 233 | 234 | // If there were errors and at this point we have less rows than *rows, 235 | // retry adding individual rows (no bulk inserts) 236 | totalOkCount := okCount + okrCount 237 | retries := 0 238 | if totalOkCount < *opts.Rows { 239 | log.Debugf("Running extra %d individual inserts (duplicated keys?)", *opts.Rows-totalOkCount) 240 | } 241 | for totalOkCount < *opts.Rows && retries < *opts.MaxRetries { 242 | okiCount, err = run(db, table, bar, semaphores, rowValues, *opts.Rows-totalOkCount, 1, runInsertFunc, newLineOnEachRow) 243 | if err != nil { 244 | log.Errorf("Cannot run extra insert: %s", err) 245 | } 246 | 247 | retries++ 248 | totalOkCount += okiCount 249 | } 250 | 251 | time.Sleep(500 * time.Millisecond) // Let the progress bar to update 252 | if !*opts.Print { 253 | log.Printf("%d rows inserted", totalOkCount) 254 | } 255 | db.Close() 256 | } 257 | 258 | func run(db *sql.DB, table *tableparser.Table, bar *uiprogress.Bar, sem chan bool, 259 | rowValues insertValues, count, bulkSize int, insertFunc insertFunction, newLineOnEachRow bool) (int, error) { 260 | if count == 0 { 261 | return 0, nil 262 | } 263 | var wg sync.WaitGroup 264 | insertQuery := generateInsertStmt(table) 265 | rowsChan := make(chan []getter, 1000) 266 | okRowsChan := countRowsOK(count, bar) 267 | 268 | go generateInsertData(count*bulkSize, rowValues, rowsChan) 269 | defaultSeparator1 := "" 270 | if newLineOnEachRow { 271 | defaultSeparator1 = "\n" 272 | } 273 | 274 | i := 0 275 | rowsCount := 0 276 | sep1, sep2 := defaultSeparator1, "" 277 | 278 | for i < count { 279 | rowData := <-rowsChan 280 | rowsCount++ 281 | insertQuery += sep1 + " (" 282 | for _, field := range rowData { 283 | insertQuery += sep2 + field.Quote() 284 | sep2 = ", " 285 | } 286 | insertQuery += ")" 287 | sep1 = ", " 288 | if newLineOnEachRow { 289 | sep1 += "\n" 290 | } 291 | sep2 = "" 292 | if rowsCount < bulkSize { 293 | continue 294 | } 295 | 296 | insertQuery += ";\n" 297 | <-sem 298 | wg.Add(1) 299 | go insertFunc(db, insertQuery, okRowsChan, sem, &wg) 300 | 301 | insertQuery = generateInsertStmt(table) 302 | sep1, sep2 = defaultSeparator1, "" 303 | rowsCount = 0 304 | i++ 305 | } 306 | 307 | wg.Wait() 308 | okCount := <-okRowsChan 309 | return okCount, nil 310 | } 311 | 312 | func makeSemaphores(count int) chan bool { 313 | sem := make(chan bool, count) 314 | for i := 0; i < count; i++ { 315 | sem <- true 316 | } 317 | return sem 318 | } 319 | 320 | // This go-routine keeps track of how many rows were actually inserted 321 | // by the bulk inserts since one or more rows could generate duplicated 322 | // keys so, not allways the number of inserted rows = number of rows in 323 | // the bulk insert 324 | 325 | func countRowsOK(count int, bar *uiprogress.Bar) chan int { 326 | var totalOk int 327 | resultsChan := make(chan int, 10000) 328 | go func() { 329 | for i := 0; i < count; i++ { 330 | okCount := <-resultsChan 331 | for j := 0; j < okCount; j++ { 332 | bar.Incr() 333 | } 334 | totalOk += okCount 335 | } 336 | resultsChan <- totalOk 337 | }() 338 | return resultsChan 339 | } 340 | 341 | // generateInsertData will generate 'rows' items, where each item in the channel has 'bulkSize' rows. 342 | // For example: 343 | // We need to load 6 rows using a bulk insert having 2 rows per insert, like this: 344 | // INSERT INTO table (f1, f2, f3) VALUES (?, ?, ?), (?, ?, ?) 345 | // 346 | // This function will put into rowsChan 3 elements, each one having the values for 2 rows: 347 | // rowsChan <- [ v1-1, v1-2, v1-3, v2-1, v2-2, v2-3 ] 348 | // rowsChan <- [ v3-1, v3-2, v3-3, v4-1, v4-2, v4-3 ] 349 | // rowsChan <- [ v1-5, v5-2, v5-3, v6-1, v6-2, v6-3 ] 350 | // 351 | func generateInsertData(count int, values insertValues, rowsChan chan []getter) { 352 | for i := 0; i < count; i++ { 353 | insertRow := make([]getter, 0, len(values)) 354 | for _, val := range values { 355 | insertRow = append(insertRow, val) 356 | } 357 | rowsChan <- insertRow 358 | } 359 | } 360 | 361 | func generateInsertStmt(table *tableparser.Table) string { 362 | fields := getFieldNames(table.Fields) 363 | query := fmt.Sprintf("INSERT IGNORE INTO %s.%s (%s) VALUES ", 364 | backticks(table.Schema), 365 | backticks(table.Name), 366 | strings.Join(fields, ","), 367 | ) 368 | return query 369 | } 370 | 371 | func runInsert(db *sql.DB, insertQuery string, resultsChan chan int, sem chan bool, wg *sync.WaitGroup) { 372 | result, err := db.Exec(insertQuery) 373 | if err != nil { 374 | log.Debugf("Cannot run insert: %s", err) 375 | resultsChan <- 0 376 | sem <- true 377 | wg.Done() 378 | return 379 | } 380 | 381 | rowsAffected, err := result.RowsAffected() 382 | if err != nil { 383 | log.Errorf("Cannot get rows affected after insert: %s", err) 384 | } 385 | resultsChan <- int(rowsAffected) 386 | sem <- true 387 | wg.Done() 388 | } 389 | 390 | // makeValueFuncs returns an array of functions to generate all the values needed for a single row 391 | func makeValueFuncs(conn *sql.DB, fields []tableparser.Field, samples int64) insertValues { 392 | var values []getter 393 | for _, field := range fields { 394 | if !field.IsNullable && field.ColumnKey == "PRI" && strings.Contains(field.Extra, "auto_increment") { 395 | continue 396 | } 397 | if field.Constraint != nil { 398 | samples, err := getSamples(conn, field.Constraint.ReferencedTableSchema, 399 | field.Constraint.ReferencedTableName, 400 | field.Constraint.ReferencedColumnName, 401 | samples, field.DataType) 402 | if err != nil { 403 | log.Printf("cannot get samples for field %q: %s\n", field.ColumnName, err) 404 | continue 405 | } 406 | values = append(values, getters.NewRandomSample(field.ColumnName, samples, field.IsNullable)) 407 | continue 408 | } 409 | maxValue := maxValues["bigint"] 410 | if m, ok := maxValues[field.DataType]; ok { 411 | maxValue = m 412 | } 413 | switch field.DataType { 414 | case "tinyint", "smallint", "mediumint", "int", "integer", "bigint": 415 | values = append(values, getters.NewRandomInt(field.ColumnName, maxValue, field.IsNullable)) 416 | case "float", "decimal", "double": 417 | values = append(values, getters.NewRandomDecimal(field.ColumnName, 418 | field.NumericPrecision.Int64-field.NumericScale.Int64, field.IsNullable)) 419 | case "char", "varchar": 420 | values = append(values, getters.NewRandomString(field.ColumnName, 421 | field.CharacterMaximumLength.Int64, field.IsNullable)) 422 | case "date": 423 | values = append(values, getters.NewRandomDate(field.ColumnName, field.IsNullable)) 424 | case "datetime", "timestamp": 425 | values = append(values, getters.NewRandomDateTime(field.ColumnName, field.IsNullable)) 426 | case "tinyblob", "tinytext", "blob", "text", "mediumtext", "mediumblob", "longblob", "longtext": 427 | values = append(values, getters.NewRandomString(field.ColumnName, 428 | field.CharacterMaximumLength.Int64, field.IsNullable)) 429 | case "time": 430 | values = append(values, getters.NewRandomTime(field.IsNullable)) 431 | case "year": 432 | values = append(values, getters.NewRandomIntRange(field.ColumnName, int64(time.Now().Year()-1), 433 | int64(time.Now().Year()), field.IsNullable)) 434 | case "enum", "set": 435 | values = append(values, getters.NewRandomEnum(field.SetEnumVals, field.IsNullable)) 436 | case "binary", "varbinary": 437 | values = append(values, getters.NewRandomBinary(field.ColumnName, field.CharacterMaximumLength.Int64, field.IsNullable)) 438 | default: 439 | log.Printf("cannot get field type: %s: %s\n", field.ColumnName, field.DataType) 440 | } 441 | } 442 | 443 | return values 444 | } 445 | 446 | func getFieldNames(fields []tableparser.Field) []string { 447 | var fieldNames []string 448 | for _, field := range fields { 449 | if !isSupportedType(field.DataType) { 450 | continue 451 | } 452 | if !field.IsNullable && field.ColumnKey == "PRI" && 453 | strings.Contains(field.Extra, "auto_increment") { 454 | continue 455 | } 456 | fieldNames = append(fieldNames, backticks(field.ColumnName)) 457 | } 458 | return fieldNames 459 | } 460 | 461 | func getSamples(conn *sql.DB, schema, table, field string, samples int64, dataType string) ([]interface{}, error) { 462 | var count int64 463 | var query string 464 | 465 | queryCount := fmt.Sprintf("SELECT COUNT(*) FROM `%s`.`%s`", schema, table) 466 | if err := conn.QueryRow(queryCount).Scan(&count); err != nil { 467 | return nil, fmt.Errorf("cannot get count for table %q: %s", table, err) 468 | } 469 | 470 | if count < samples { 471 | query = fmt.Sprintf("SELECT `%s` FROM `%s`.`%s`", field, schema, table) 472 | } else { 473 | query = fmt.Sprintf("SELECT `%s` FROM `%s`.`%s` WHERE RAND() <= .3 LIMIT %d", 474 | field, schema, table, samples) 475 | } 476 | 477 | rows, err := conn.Query(query) 478 | if err != nil { 479 | return nil, fmt.Errorf("cannot get samples: %s, %s", query, err) 480 | } 481 | defer rows.Close() 482 | 483 | values := []interface{}{} 484 | 485 | for rows.Next() { 486 | var err error 487 | var val interface{} 488 | 489 | switch dataType { 490 | case "tinyint", "smallint", "mediumint", "int", "integer", "bigint", "year": 491 | var v int64 492 | err = rows.Scan(&v) 493 | val = v 494 | case "char", "varchar", "blob", "text", "mediumtext", 495 | "mediumblob", "longblob", "longtext": 496 | var v string 497 | err = rows.Scan(&v) 498 | val = v 499 | case "binary", "varbinary": 500 | var v []rune 501 | err = rows.Scan(&v) 502 | val = v 503 | case "float", "decimal", "double": 504 | var v float64 505 | err = rows.Scan(&v) 506 | val = v 507 | case "date", "time", "datetime", "timestamp": 508 | var v time.Time 509 | err = rows.Scan(&v) 510 | val = v 511 | } 512 | if err != nil { 513 | return nil, fmt.Errorf("cannot scan sample: %s", err) 514 | } 515 | values = append(values, val) 516 | } 517 | if err := rows.Err(); err != nil { 518 | return nil, fmt.Errorf("cannot get samples: %s", err) 519 | } 520 | return values, nil 521 | } 522 | 523 | func backticks(val string) string { 524 | if strings.HasPrefix(val, "`") && strings.HasSuffix(val, "`") { 525 | return url.QueryEscape(val) 526 | } 527 | return "`" + url.QueryEscape(val) + "`" 528 | } 529 | 530 | func isSupportedType(fieldType string) bool { 531 | supportedTypes := map[string]bool{ 532 | "tinyint": true, 533 | "smallint": true, 534 | "mediumint": true, 535 | "int": true, 536 | "integer": true, 537 | "bigint": true, 538 | "float": true, 539 | "decimal": true, 540 | "double": true, 541 | "char": true, 542 | "varchar": true, 543 | "date": true, 544 | "datetime": true, 545 | "timestamp": true, 546 | "time": true, 547 | "year": true, 548 | "tinyblob": true, 549 | "tinytext": true, 550 | "blob": true, 551 | "text": true, 552 | "mediumblob": true, 553 | "mediumtext": true, 554 | "longblob": true, 555 | "longtext": true, 556 | "binary": true, 557 | "varbinary": true, 558 | "enum": true, 559 | "set": true, 560 | } 561 | _, ok := supportedTypes[fieldType] 562 | return ok 563 | } 564 | 565 | func processCliParams() (*cliOptions, error) { 566 | app := kingpin.New("mysql_random_data_loader", "MySQL Random Data Loader") 567 | 568 | opts := &cliOptions{ 569 | app: app, 570 | BulkSize: app.Flag("bulk-size", "Number of rows per insert statement").Default(fmt.Sprintf("%d", defaultBulkSize)).Int(), 571 | ConfigFile: app.Flag("config-file", "MySQL config file").Default(expandHomeDir(defaultConfigFile)).String(), 572 | Debug: app.Flag("debug", "Log debugging information").Bool(), 573 | Factor: app.Flag("fk-samples-factor", "Percentage used to get random samples for foreign keys fields").Default("0.3").Float64(), 574 | Host: app.Flag("host", "Host name/IP").Short('h').String(), 575 | MaxRetries: app.Flag("max-retries", "Number of rows to insert").Default("100").Int(), 576 | MaxThreads: app.Flag("max-threads", "Maximum number of threads to run inserts").Default("1").Int(), 577 | NoProgress: app.Flag("no-progress", "Show progress bar").Default("false").Bool(), 578 | Pass: app.Flag("password", "Password").Short('p').String(), 579 | Port: app.Flag("port", "Port").Short('P').Int(), 580 | Print: app.Flag("print", "Print queries to the standard output instead of inserting them into the db").Bool(), 581 | Samples: app.Flag("max-fk-samples", "Maximum number of samples for foreign keys fields").Default("100").Int64(), 582 | User: app.Flag("user", "User").Short('u').String(), 583 | Version: app.Flag("version", "Show version and exit").Bool(), 584 | 585 | Schema: app.Arg("database", "Database").Required().String(), 586 | TableName: app.Arg("table", "Table").Required().String(), 587 | Rows: app.Arg("rows", "Number of rows to insert").Required().Int(), 588 | } 589 | _, err := app.Parse(os.Args[1:]) 590 | 591 | if err != nil { 592 | return nil, err 593 | } 594 | 595 | if mysqlOpts, err := readMySQLConfigFile(*opts.ConfigFile); err == nil { 596 | checkMySQLParams(opts, mysqlOpts) 597 | } 598 | 599 | return opts, nil 600 | } 601 | 602 | func checkMySQLParams(opts *cliOptions, mysqlOpts *mysqlOptions) { 603 | if *opts.Host == "" && mysqlOpts.Host != "" { 604 | *opts.Host = mysqlOpts.Host 605 | } 606 | 607 | if *opts.Port == 0 && mysqlOpts.Port != 0 { 608 | *opts.Port = mysqlOpts.Port 609 | } 610 | 611 | if *opts.User == "" && mysqlOpts.User != "" { 612 | *opts.User = mysqlOpts.User 613 | } 614 | 615 | if *opts.Pass == "" && mysqlOpts.Password != "" { 616 | *opts.Pass = mysqlOpts.Password 617 | } 618 | } 619 | 620 | func readMySQLConfigFile(filename string) (*mysqlOptions, error) { 621 | cfg, err := ini.Load(expandHomeDir(filename)) 622 | if err != nil { 623 | return nil, err 624 | } 625 | 626 | section := cfg.Section(defaultMySQLConfigSection) 627 | port, _ := section.Key("port").Int() 628 | 629 | mysqlOpts := &mysqlOptions{ 630 | Host: section.Key("host").String(), 631 | Port: port, 632 | User: section.Key("user").String(), 633 | Password: section.Key("password").String(), 634 | } 635 | 636 | return mysqlOpts, nil 637 | } 638 | 639 | func expandHomeDir(dir string) string { 640 | if !strings.HasPrefix(dir, "~") { 641 | return dir 642 | } 643 | u, err := user.Current() 644 | if err != nil { 645 | return dir 646 | } 647 | return u.HomeDir + strings.TrimPrefix(dir, "~") 648 | } 649 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "sync" 7 | "testing" 8 | "time" 9 | 10 | "github.com/Percona-Lab/mysql_random_data_load/internal/getters" 11 | "github.com/Percona-Lab/mysql_random_data_load/tableparser" 12 | tu "github.com/Percona-Lab/mysql_random_data_load/testutils" 13 | ) 14 | 15 | func TestGetSamples(t *testing.T) { 16 | conn := tu.GetMySQLConnection(t) 17 | var wantRows int64 = 100 18 | samples, err := getSamples(conn, "sakila", "inventory", "inventory_id", wantRows, "int") 19 | tu.Ok(t, err, "error getting samples") 20 | _, ok := samples[0].(int64) 21 | tu.Assert(t, ok, "Wrong data type.") 22 | tu.Assert(t, int64(len(samples)) == wantRows, 23 | "Wrong number of samples. Have %d, want 100.", len(samples)) 24 | } 25 | 26 | func TestGenerateInsertData(t *testing.T) { 27 | wantRows := 3 28 | 29 | values := []getter{ 30 | getters.NewRandomInt("f1", 100, false), 31 | getters.NewRandomString("f2", 10, false), 32 | getters.NewRandomDate("f3", false), 33 | } 34 | 35 | rowsChan := make(chan []getter, 100) 36 | count := 0 37 | wg := &sync.WaitGroup{} 38 | wg.Add(1) 39 | 40 | go func() { 41 | for { 42 | select { 43 | case <-time.After(10 * time.Millisecond): 44 | wg.Done() 45 | return 46 | case row := <-rowsChan: 47 | if reflect.TypeOf(row[0]).String() != "*getters.RandomInt" { 48 | fmt.Printf("Expected '*getters.RandomInt' for field [0], got %q\n", reflect.TypeOf(row[0]).String()) 49 | t.Fail() 50 | } 51 | if reflect.TypeOf(row[1]).String() != "*getters.RandomString" { 52 | fmt.Printf("Expected '*getters.RandomString' for field [1], got %q\n", reflect.TypeOf(row[1]).String()) 53 | t.Fail() 54 | } 55 | if reflect.TypeOf(row[2]).String() != "*getters.RandomDate" { 56 | fmt.Printf("Expected '*getters.RandomDate' for field [2], got %q\n", reflect.TypeOf(row[2]).String()) 57 | t.Fail() 58 | } 59 | count++ 60 | } 61 | } 62 | }() 63 | 64 | generateInsertData(wantRows, values, rowsChan) 65 | 66 | wg.Wait() 67 | tu.Assert(t, count == 3, "Invalid number of rows") 68 | } 69 | 70 | func TestGenerateInsertStmt(t *testing.T) { 71 | var table *tableparser.Table 72 | tu.LoadJson(t, "sakila.film.json", &table) 73 | want := "INSERT IGNORE INTO `sakila`.`film` " + 74 | "(`title`,`description`,`release_year`,`language_id`," + 75 | "`original_language_id`,`rental_duration`,`rental_rate`," + 76 | "`length`,`replacement_cost`,`rating`,`special_features`," + 77 | "`last_update`) VALUES " 78 | 79 | query := generateInsertStmt(table) 80 | tu.Equals(t, want, query) 81 | } 82 | -------------------------------------------------------------------------------- /runtests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker-compose up -d 3 | wait_mysql() { 4 | local PORT=$1 5 | while : 6 | do 7 | sleep 3 8 | if mysql -P ${PORT} -e 'select version()' &>/dev/null; then 9 | break 10 | fi 11 | done 12 | } 13 | 14 | for PORT in 3306 3307 3308; do 15 | echo "##########################" 16 | echo "### MySQL at port ${PORT}" 17 | echo "##########################" 18 | wait_mysql $PORT 19 | export TEST_DSN="root:@tcp(127.1:${PORT})/sakila?parseTime=true" 20 | go test -v ./... 21 | done 22 | 23 | -------------------------------------------------------------------------------- /tableparser/tableparser.go: -------------------------------------------------------------------------------- 1 | package tableparser 2 | 3 | import ( 4 | "database/sql" 5 | "database/sql/driver" 6 | "fmt" 7 | "net/url" 8 | "regexp" 9 | "strings" 10 | "time" 11 | 12 | "github.com/pkg/errors" 13 | log "github.com/sirupsen/logrus" 14 | ) 15 | 16 | type NullTime struct { 17 | Time time.Time 18 | Valid bool // Valid is true if Time is not NULL 19 | } 20 | 21 | // Scan implements the Scanner interface. 22 | func (nt *NullTime) Scan(value interface{}) error { 23 | nt.Time, nt.Valid = value.(time.Time) 24 | return nil 25 | } 26 | 27 | // Value implements the driver Valuer interface. 28 | func (nt NullTime) Value() (driver.Value, error) { 29 | if !nt.Valid { 30 | return nil, nil 31 | } 32 | return nt.Time, nil 33 | } 34 | 35 | // Table holds the table definition with all fields, indexes and triggers 36 | type Table struct { 37 | Schema string 38 | Name string 39 | Fields []Field 40 | Indexes map[string]Index 41 | //TODO Include complete indexes information 42 | Constraints []Constraint 43 | Triggers []Trigger 44 | // 45 | conn *sql.DB 46 | } 47 | 48 | // Index holds the basic index information 49 | type Index struct { 50 | Name string 51 | Fields []string 52 | Unique bool 53 | Visible bool 54 | Expression string // MySQL 8.0.16+ 55 | } 56 | 57 | // IndexField holds raw index information as defined in INFORMATION_SCHEMA table 58 | type IndexField struct { 59 | KeyName string 60 | SeqInIndex int 61 | ColumnName string 62 | Collation sql.NullString 63 | Cardinality sql.NullInt64 64 | SubPart sql.NullInt64 65 | Packed sql.NullString 66 | Null string 67 | IndexType string 68 | Comment string 69 | IndexComment string 70 | NonUnique bool 71 | Visible string // MySQL 8.0+ 72 | Expression sql.NullString // MySQL 8.0.16+ 73 | } 74 | 75 | // Constraint holds Foreign Keys information 76 | type Constraint struct { 77 | ConstraintName string 78 | ColumnName string 79 | ReferencedTableSchema string 80 | ReferencedTableName string 81 | ReferencedColumnName string 82 | } 83 | 84 | // Field holds raw field information as defined in INFORMATION_SCHEMA 85 | type Field struct { 86 | TableCatalog string 87 | TableSchema string 88 | TableName string 89 | ColumnName string 90 | OrdinalPosition int 91 | ColumnDefault sql.NullString 92 | IsNullable bool 93 | DataType string 94 | CharacterMaximumLength sql.NullInt64 95 | CharacterOctetLength sql.NullInt64 96 | NumericPrecision sql.NullInt64 97 | NumericScale sql.NullInt64 98 | DatetimePrecision sql.NullInt64 99 | CharacterSetName sql.NullString 100 | CollationName sql.NullString 101 | ColumnType string 102 | ColumnKey string 103 | Extra string 104 | Privileges string 105 | ColumnComment string 106 | GenerationExpression string 107 | SetEnumVals []string 108 | Constraint *Constraint 109 | SrsID sql.NullString 110 | } 111 | 112 | // Trigger holds raw trigger information as defined in INFORMATION_SCHEMA 113 | type Trigger struct { 114 | Trigger string 115 | Event string 116 | Table string 117 | Statement string 118 | Timing string 119 | Created NullTime 120 | SQLMode string 121 | Definer string 122 | CharacterSetClient string 123 | CollationConnection string 124 | DatabaseCollation string 125 | } 126 | 127 | func NewTable(db *sql.DB, schema, tableName string) (*Table, error) { 128 | table := &Table{ 129 | Schema: url.QueryEscape(schema), 130 | Name: url.QueryEscape(tableName), 131 | conn: db, 132 | } 133 | 134 | var err error 135 | table.Indexes, err = getIndexes(db, table.Schema, table.Name) 136 | if err != nil { 137 | return nil, err 138 | } 139 | table.Constraints, err = getConstraints(db, table.Schema, table.Name) 140 | if err != nil { 141 | return nil, err 142 | } 143 | table.Triggers, err = getTriggers(db, table.Schema, table.Name) 144 | if err != nil { 145 | return nil, err 146 | } 147 | 148 | err = table.parse() 149 | if err != nil { 150 | return nil, err 151 | } 152 | table.conn = nil // to save memory since it is not going to be used again 153 | return table, nil 154 | } 155 | 156 | func (t *Table) parse() error { 157 | // +--------------------------- field type 158 | // | +---------------- field size / enum values: decimal(10,2) or enum('a','b') 159 | // | | +---------- extra info (unsigned, etc) 160 | // | | | 161 | re := regexp.MustCompile(`^(.*?)(?:\((.*?)\)(.*))?$`) 162 | 163 | query := "SELECT * FROM `information_schema`.`COLUMNS` WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION" 164 | 165 | constraints := constraintsAsMap(t.Constraints) 166 | 167 | rows, err := t.conn.Query(query, t.Schema, t.Name) 168 | if err != nil { 169 | return err 170 | } 171 | defer rows.Close() 172 | 173 | cols, err := rows.Columns() 174 | if err != nil { 175 | return errors.Wrap(err, "Cannot get column names") 176 | } 177 | 178 | for rows.Next() { 179 | var f Field 180 | var allowNull string 181 | fields := makeScanRecipients(&f, &allowNull, cols) 182 | err := rows.Scan(fields...) 183 | if err != nil { 184 | log.Errorf("Cannot get table fields: %s", err) 185 | continue 186 | } 187 | 188 | allowedValues := []string{} 189 | if f.DataType == "enum" || f.DataType == "set" { 190 | m := re.FindStringSubmatch(f.ColumnType) 191 | if len(m) < 2 { 192 | continue 193 | } 194 | vals := strings.Split(m[2], ",") 195 | for _, val := range vals { 196 | val = strings.TrimPrefix(val, "'") 197 | val = strings.TrimSuffix(val, "'") 198 | allowedValues = append(allowedValues, val) 199 | } 200 | } 201 | 202 | f.SetEnumVals = allowedValues 203 | f.IsNullable = allowNull == "YES" 204 | f.Constraint = constraints[f.ColumnName] 205 | 206 | t.Fields = append(t.Fields, f) 207 | } 208 | 209 | if rows.Err() != nil { 210 | return rows.Err() 211 | } 212 | return nil 213 | } 214 | 215 | func makeScanRecipients(f *Field, allowNull *string, cols []string) []interface{} { 216 | fields := []interface{}{ 217 | &f.TableCatalog, 218 | &f.TableSchema, 219 | &f.TableName, 220 | &f.ColumnName, 221 | &f.OrdinalPosition, 222 | &f.ColumnDefault, 223 | &allowNull, 224 | &f.DataType, 225 | &f.CharacterMaximumLength, 226 | &f.CharacterOctetLength, 227 | &f.NumericPrecision, 228 | &f.NumericScale, 229 | } 230 | 231 | if len(cols) > 19 { // MySQL 5.5 does not have "DATETIME_PRECISION" field 232 | fields = append(fields, &f.DatetimePrecision) 233 | } 234 | 235 | fields = append(fields, &f.CharacterSetName, &f.CollationName, &f.ColumnType, &f.ColumnKey, &f.Extra, &f.Privileges, &f.ColumnComment) 236 | 237 | if len(cols) > 20 && cols[20] == "GENERATION_EXPRESSION" { // MySQL 5.7+ "GENERATION_EXPRESSION" field 238 | fields = append(fields, &f.GenerationExpression) 239 | } 240 | if len(cols) > 21 && cols[21] == "SRS_ID" { // MySQL 8.0+ "SRS ID" field 241 | fields = append(fields, &f.SrsID) 242 | } 243 | 244 | return fields 245 | } 246 | 247 | // FieldNames returns an string array with the table's field names 248 | func (t *Table) FieldNames() []string { 249 | fields := []string{} 250 | for _, field := range t.Fields { 251 | fields = append(fields, field.ColumnName) 252 | } 253 | return fields 254 | } 255 | 256 | func getIndexes(db *sql.DB, schema, tableName string) (map[string]Index, error) { 257 | query := fmt.Sprintf("SHOW INDEXES FROM `%s`.`%s`", schema, tableName) 258 | rows, err := db.Query(query) 259 | if err != nil { 260 | return nil, err 261 | } 262 | 263 | indexes := make(map[string]Index) 264 | 265 | for rows.Next() { 266 | var i IndexField 267 | var table, visible string 268 | fields := []interface{}{&table, &i.NonUnique, &i.KeyName, &i.SeqInIndex, 269 | &i.ColumnName, &i.Collation, &i.Cardinality, &i.SubPart, 270 | &i.Packed, &i.Null, &i.IndexType, &i.Comment, &i.IndexComment, 271 | } 272 | 273 | cols, err := rows.Columns() 274 | if err == nil && len(cols) >= 14 && cols[13] == "Visible" { 275 | fields = append(fields, &i.Visible) 276 | } 277 | if err == nil && len(cols) >= 15 && cols[14] == "Expression" { 278 | fields = append(fields, &i.Expression) 279 | } 280 | 281 | err = rows.Scan(fields...) 282 | if err != nil { 283 | return nil, fmt.Errorf("cannot read indexes: %s", err) 284 | } 285 | if index, ok := indexes[i.KeyName]; !ok { 286 | indexes[i.KeyName] = Index{ 287 | Name: i.KeyName, 288 | Unique: !i.NonUnique, 289 | Fields: []string{i.ColumnName}, 290 | Visible: i.Visible == "YES" || visible == "", 291 | Expression: i.Expression.String, 292 | } 293 | 294 | } else { 295 | index.Fields = append(index.Fields, i.ColumnName) 296 | index.Unique = index.Unique || !i.NonUnique 297 | } 298 | } 299 | if err := rows.Close(); err != nil { 300 | return nil, errors.Wrap(err, "Cannot close query rows at getIndexes") 301 | } 302 | 303 | return indexes, nil 304 | } 305 | 306 | func getConstraints(db *sql.DB, schema, tableName string) ([]Constraint, error) { 307 | query := "SELECT tc.CONSTRAINT_NAME, " + 308 | "kcu.COLUMN_NAME, " + 309 | "kcu.REFERENCED_TABLE_SCHEMA, " + 310 | "kcu.REFERENCED_TABLE_NAME, " + 311 | "kcu.REFERENCED_COLUMN_NAME " + 312 | "FROM information_schema.TABLE_CONSTRAINTS tc " + 313 | "LEFT JOIN information_schema.KEY_COLUMN_USAGE kcu " + 314 | "ON tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME " + 315 | "WHERE tc.CONSTRAINT_TYPE = 'FOREIGN KEY' " + 316 | fmt.Sprintf("AND tc.TABLE_SCHEMA = '%s' ", schema) + 317 | fmt.Sprintf("AND tc.TABLE_NAME = '%s'", tableName) 318 | rows, err := db.Query(query) 319 | if err != nil { 320 | return nil, err 321 | } 322 | defer rows.Close() 323 | 324 | constraints := []Constraint{} 325 | 326 | for rows.Next() { 327 | var c Constraint 328 | err := rows.Scan(&c.ConstraintName, &c.ColumnName, &c.ReferencedTableSchema, 329 | &c.ReferencedTableName, &c.ReferencedColumnName) 330 | if err != nil { 331 | return nil, fmt.Errorf("cannot read constraints: %s", err) 332 | } 333 | constraints = append(constraints, c) 334 | } 335 | 336 | return constraints, nil 337 | } 338 | 339 | func getTriggers(db *sql.DB, schema, tableName string) ([]Trigger, error) { 340 | query := fmt.Sprintf("SHOW TRIGGERS FROM `%s` LIKE '%s'", schema, tableName) 341 | rows, err := db.Query(query) 342 | if err != nil { 343 | return nil, err 344 | } 345 | defer rows.Close() 346 | 347 | triggers := []Trigger{} 348 | 349 | for rows.Next() { 350 | var t Trigger 351 | err := rows.Scan(&t.Trigger, &t.Event, &t.Table, &t.Statement, &t.Timing, 352 | &t.Created, &t.SQLMode, &t.Definer, &t.CharacterSetClient, &t.CollationConnection, 353 | &t.DatabaseCollation) 354 | if err != nil { 355 | return nil, fmt.Errorf("cannot read trigger: %s", err) 356 | } 357 | triggers = append(triggers, t) 358 | } 359 | 360 | return triggers, nil 361 | } 362 | 363 | func constraintsAsMap(constraints []Constraint) map[string]*Constraint { 364 | m := make(map[string]*Constraint) 365 | for _, c := range constraints { 366 | m[c.ColumnName] = &Constraint{ 367 | ConstraintName: c.ConstraintName, 368 | ColumnName: c.ColumnName, 369 | ReferencedTableSchema: c.ReferencedTableSchema, 370 | ReferencedTableName: c.ReferencedTableName, 371 | ReferencedColumnName: c.ReferencedColumnName, 372 | } 373 | } 374 | return m 375 | } 376 | -------------------------------------------------------------------------------- /tableparser/tableparser_test.go: -------------------------------------------------------------------------------- 1 | package tableparser 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | tu "github.com/Percona-Lab/mysql_random_data_load/testutils" 8 | _ "github.com/go-sql-driver/mysql" 9 | version "github.com/hashicorp/go-version" 10 | log "github.com/sirupsen/logrus" 11 | ) 12 | 13 | func TestParse(t *testing.T) { 14 | db := tu.GetMySQLConnection(t) 15 | v := tu.GetMinorVersion(t, db) 16 | var want *Table 17 | 18 | // Patch part of version is stripped by GetMinorVersion so for these test 19 | // it is .0 20 | sampleFiles := map[string]string{ 21 | "5.6.0": "table001.json", 22 | "5.7.0": "table002.json", 23 | "8.0.0": "table003.json", 24 | } 25 | sampleFile, ok := sampleFiles[v.String()] 26 | if !ok { 27 | t.Fatalf("Unknown MySQL version %s", v.String()) 28 | } 29 | 30 | table, err := NewTable(db, "sakila", "film") 31 | if err != nil { 32 | t.Error(err) 33 | } 34 | if tu.UpdateSamples() { 35 | tu.WriteJson(t, sampleFile, table) 36 | } 37 | tu.LoadJson(t, sampleFile, &want) 38 | 39 | tu.Equals(t, table, want) 40 | } 41 | 42 | func TestGetIndexes(t *testing.T) { 43 | db := tu.GetMySQLConnection(t) 44 | want := make(map[string]Index) 45 | tu.LoadJson(t, "indexes.json", &want) 46 | 47 | idx, err := getIndexes(db, "sakila", "film_actor") 48 | if tu.UpdateSamples() { 49 | tu.WriteJson(t, "indexes.json", idx) 50 | } 51 | tu.Ok(t, err) 52 | tu.Equals(t, idx, want) 53 | } 54 | 55 | func TestGetTriggers(t *testing.T) { 56 | db := tu.GetMySQLConnection(t) 57 | want := []Trigger{} 58 | v572, _ := version.NewVersion("5.7.2") 59 | v800, _ := version.NewVersion("8.0.0") 60 | 61 | sampleFile := "trigers-8.0.0.json" 62 | if tu.GetVersion(t, db).LessThan(v800) { 63 | sampleFile = "trigers-5.7.2.json" 64 | } 65 | if tu.GetVersion(t, db).LessThan(v572) { 66 | sampleFile = "trigers-5.7.1.json" 67 | } 68 | 69 | tu.LoadJson(t, sampleFile, &want) 70 | 71 | triggers, err := getTriggers(db, "sakila", "rental") 72 | // fake timestamp to make it constant/testeable 73 | triggers[0].Created.Time = time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) 74 | 75 | if tu.UpdateSamples() { 76 | log.Info("Updating sample file: " + sampleFile) 77 | tu.WriteJson(t, sampleFile, triggers) 78 | } 79 | tu.Ok(t, err) 80 | tu.Equals(t, triggers, want) 81 | } 82 | -------------------------------------------------------------------------------- /tableparser/testdata/indexes.json: -------------------------------------------------------------------------------- 1 | { 2 | "PRIMARY": { 3 | "Name": "PRIMARY", 4 | "Fields": [ 5 | "actor_id" 6 | ], 7 | "Unique": true, 8 | "Visible": true 9 | }, 10 | "idx_fk_film_id": { 11 | "Name": "idx_fk_film_id", 12 | "Fields": [ 13 | "film_id" 14 | ], 15 | "Unique": false, 16 | "Visible": true 17 | } 18 | } -------------------------------------------------------------------------------- /tableparser/testdata/table001.json: -------------------------------------------------------------------------------- 1 | { 2 | "Schema": "sakila", 3 | "Name": "film", 4 | "Fields": [ 5 | { 6 | "TableCatalog": "def", 7 | "TableSchema": "sakila", 8 | "TableName": "film", 9 | "ColumnName": "film_id", 10 | "OrdinalPosition": 1, 11 | "ColumnDefault": { 12 | "String": "", 13 | "Valid": false 14 | }, 15 | "IsNullable": false, 16 | "DataType": "smallint", 17 | "CharacterMaximumLength": { 18 | "Int64": 0, 19 | "Valid": false 20 | }, 21 | "CharacterOctetLength": { 22 | "Int64": 0, 23 | "Valid": false 24 | }, 25 | "NumericPrecision": { 26 | "Int64": 5, 27 | "Valid": true 28 | }, 29 | "NumericScale": { 30 | "Int64": 0, 31 | "Valid": true 32 | }, 33 | "DatetimePrecision": { 34 | "Int64": 0, 35 | "Valid": false 36 | }, 37 | "CharacterSetName": { 38 | "String": "", 39 | "Valid": false 40 | }, 41 | "CollationName": { 42 | "String": "", 43 | "Valid": false 44 | }, 45 | "ColumnType": "smallint(5) unsigned", 46 | "ColumnKey": "PRI", 47 | "Extra": "auto_increment", 48 | "Privileges": "select,insert,update,references", 49 | "ColumnComment": "", 50 | "GenerationExpression": "", 51 | "SetEnumVals": [], 52 | "Constraint": null, 53 | "SrsID": { 54 | "String": "", 55 | "Valid": false 56 | } 57 | }, 58 | { 59 | "TableCatalog": "def", 60 | "TableSchema": "sakila", 61 | "TableName": "film", 62 | "ColumnName": "title", 63 | "OrdinalPosition": 2, 64 | "ColumnDefault": { 65 | "String": "", 66 | "Valid": false 67 | }, 68 | "IsNullable": false, 69 | "DataType": "varchar", 70 | "CharacterMaximumLength": { 71 | "Int64": 255, 72 | "Valid": true 73 | }, 74 | "CharacterOctetLength": { 75 | "Int64": 765, 76 | "Valid": true 77 | }, 78 | "NumericPrecision": { 79 | "Int64": 0, 80 | "Valid": false 81 | }, 82 | "NumericScale": { 83 | "Int64": 0, 84 | "Valid": false 85 | }, 86 | "DatetimePrecision": { 87 | "Int64": 0, 88 | "Valid": false 89 | }, 90 | "CharacterSetName": { 91 | "String": "utf8", 92 | "Valid": true 93 | }, 94 | "CollationName": { 95 | "String": "utf8_general_ci", 96 | "Valid": true 97 | }, 98 | "ColumnType": "varchar(255)", 99 | "ColumnKey": "MUL", 100 | "Extra": "", 101 | "Privileges": "select,insert,update,references", 102 | "ColumnComment": "", 103 | "GenerationExpression": "", 104 | "SetEnumVals": [], 105 | "Constraint": null, 106 | "SrsID": { 107 | "String": "", 108 | "Valid": false 109 | } 110 | }, 111 | { 112 | "TableCatalog": "def", 113 | "TableSchema": "sakila", 114 | "TableName": "film", 115 | "ColumnName": "description", 116 | "OrdinalPosition": 3, 117 | "ColumnDefault": { 118 | "String": "", 119 | "Valid": false 120 | }, 121 | "IsNullable": false, 122 | "DataType": "text", 123 | "CharacterMaximumLength": { 124 | "Int64": 65535, 125 | "Valid": true 126 | }, 127 | "CharacterOctetLength": { 128 | "Int64": 65535, 129 | "Valid": true 130 | }, 131 | "NumericPrecision": { 132 | "Int64": 0, 133 | "Valid": false 134 | }, 135 | "NumericScale": { 136 | "Int64": 0, 137 | "Valid": false 138 | }, 139 | "DatetimePrecision": { 140 | "Int64": 0, 141 | "Valid": false 142 | }, 143 | "CharacterSetName": { 144 | "String": "utf8", 145 | "Valid": true 146 | }, 147 | "CollationName": { 148 | "String": "utf8_general_ci", 149 | "Valid": true 150 | }, 151 | "ColumnType": "text", 152 | "ColumnKey": "", 153 | "Extra": "", 154 | "Privileges": "select,insert,update,references", 155 | "ColumnComment": "", 156 | "GenerationExpression": "", 157 | "SetEnumVals": [], 158 | "Constraint": null, 159 | "SrsID": { 160 | "String": "", 161 | "Valid": false 162 | } 163 | }, 164 | { 165 | "TableCatalog": "def", 166 | "TableSchema": "sakila", 167 | "TableName": "film", 168 | "ColumnName": "release_year", 169 | "OrdinalPosition": 4, 170 | "ColumnDefault": { 171 | "String": "", 172 | "Valid": false 173 | }, 174 | "IsNullable": false, 175 | "DataType": "year", 176 | "CharacterMaximumLength": { 177 | "Int64": 0, 178 | "Valid": false 179 | }, 180 | "CharacterOctetLength": { 181 | "Int64": 0, 182 | "Valid": false 183 | }, 184 | "NumericPrecision": { 185 | "Int64": 0, 186 | "Valid": false 187 | }, 188 | "NumericScale": { 189 | "Int64": 0, 190 | "Valid": false 191 | }, 192 | "DatetimePrecision": { 193 | "Int64": 0, 194 | "Valid": false 195 | }, 196 | "CharacterSetName": { 197 | "String": "", 198 | "Valid": false 199 | }, 200 | "CollationName": { 201 | "String": "", 202 | "Valid": false 203 | }, 204 | "ColumnType": "year(4)", 205 | "ColumnKey": "", 206 | "Extra": "", 207 | "Privileges": "select,insert,update,references", 208 | "ColumnComment": "", 209 | "GenerationExpression": "", 210 | "SetEnumVals": [], 211 | "Constraint": null, 212 | "SrsID": { 213 | "String": "", 214 | "Valid": false 215 | } 216 | }, 217 | { 218 | "TableCatalog": "def", 219 | "TableSchema": "sakila", 220 | "TableName": "film", 221 | "ColumnName": "language_id", 222 | "OrdinalPosition": 5, 223 | "ColumnDefault": { 224 | "String": "", 225 | "Valid": false 226 | }, 227 | "IsNullable": false, 228 | "DataType": "tinyint", 229 | "CharacterMaximumLength": { 230 | "Int64": 0, 231 | "Valid": false 232 | }, 233 | "CharacterOctetLength": { 234 | "Int64": 0, 235 | "Valid": false 236 | }, 237 | "NumericPrecision": { 238 | "Int64": 3, 239 | "Valid": true 240 | }, 241 | "NumericScale": { 242 | "Int64": 0, 243 | "Valid": true 244 | }, 245 | "DatetimePrecision": { 246 | "Int64": 0, 247 | "Valid": false 248 | }, 249 | "CharacterSetName": { 250 | "String": "", 251 | "Valid": false 252 | }, 253 | "CollationName": { 254 | "String": "", 255 | "Valid": false 256 | }, 257 | "ColumnType": "tinyint(3) unsigned", 258 | "ColumnKey": "MUL", 259 | "Extra": "", 260 | "Privileges": "select,insert,update,references", 261 | "ColumnComment": "", 262 | "GenerationExpression": "", 263 | "SetEnumVals": [], 264 | "Constraint": { 265 | "ConstraintName": "fk_film_language", 266 | "ColumnName": "language_id", 267 | "ReferencedTableSchema": "sakila", 268 | "ReferencedTableName": "language", 269 | "ReferencedColumnName": "language_id" 270 | }, 271 | "SrsID": { 272 | "String": "", 273 | "Valid": false 274 | } 275 | }, 276 | { 277 | "TableCatalog": "def", 278 | "TableSchema": "sakila", 279 | "TableName": "film", 280 | "ColumnName": "original_language_id", 281 | "OrdinalPosition": 6, 282 | "ColumnDefault": { 283 | "String": "", 284 | "Valid": false 285 | }, 286 | "IsNullable": false, 287 | "DataType": "tinyint", 288 | "CharacterMaximumLength": { 289 | "Int64": 0, 290 | "Valid": false 291 | }, 292 | "CharacterOctetLength": { 293 | "Int64": 0, 294 | "Valid": false 295 | }, 296 | "NumericPrecision": { 297 | "Int64": 3, 298 | "Valid": true 299 | }, 300 | "NumericScale": { 301 | "Int64": 0, 302 | "Valid": true 303 | }, 304 | "DatetimePrecision": { 305 | "Int64": 0, 306 | "Valid": false 307 | }, 308 | "CharacterSetName": { 309 | "String": "", 310 | "Valid": false 311 | }, 312 | "CollationName": { 313 | "String": "", 314 | "Valid": false 315 | }, 316 | "ColumnType": "tinyint(3) unsigned", 317 | "ColumnKey": "MUL", 318 | "Extra": "", 319 | "Privileges": "select,insert,update,references", 320 | "ColumnComment": "", 321 | "GenerationExpression": "", 322 | "SetEnumVals": [], 323 | "Constraint": { 324 | "ConstraintName": "fk_film_language_original", 325 | "ColumnName": "original_language_id", 326 | "ReferencedTableSchema": "sakila", 327 | "ReferencedTableName": "language", 328 | "ReferencedColumnName": "language_id" 329 | }, 330 | "SrsID": { 331 | "String": "", 332 | "Valid": false 333 | } 334 | }, 335 | { 336 | "TableCatalog": "def", 337 | "TableSchema": "sakila", 338 | "TableName": "film", 339 | "ColumnName": "rental_duration", 340 | "OrdinalPosition": 7, 341 | "ColumnDefault": { 342 | "String": "3", 343 | "Valid": true 344 | }, 345 | "IsNullable": false, 346 | "DataType": "tinyint", 347 | "CharacterMaximumLength": { 348 | "Int64": 0, 349 | "Valid": false 350 | }, 351 | "CharacterOctetLength": { 352 | "Int64": 0, 353 | "Valid": false 354 | }, 355 | "NumericPrecision": { 356 | "Int64": 3, 357 | "Valid": true 358 | }, 359 | "NumericScale": { 360 | "Int64": 0, 361 | "Valid": true 362 | }, 363 | "DatetimePrecision": { 364 | "Int64": 0, 365 | "Valid": false 366 | }, 367 | "CharacterSetName": { 368 | "String": "", 369 | "Valid": false 370 | }, 371 | "CollationName": { 372 | "String": "", 373 | "Valid": false 374 | }, 375 | "ColumnType": "tinyint(3) unsigned", 376 | "ColumnKey": "", 377 | "Extra": "", 378 | "Privileges": "select,insert,update,references", 379 | "ColumnComment": "", 380 | "GenerationExpression": "", 381 | "SetEnumVals": [], 382 | "Constraint": null, 383 | "SrsID": { 384 | "String": "", 385 | "Valid": false 386 | } 387 | }, 388 | { 389 | "TableCatalog": "def", 390 | "TableSchema": "sakila", 391 | "TableName": "film", 392 | "ColumnName": "rental_rate", 393 | "OrdinalPosition": 8, 394 | "ColumnDefault": { 395 | "String": "4.99", 396 | "Valid": true 397 | }, 398 | "IsNullable": false, 399 | "DataType": "decimal", 400 | "CharacterMaximumLength": { 401 | "Int64": 0, 402 | "Valid": false 403 | }, 404 | "CharacterOctetLength": { 405 | "Int64": 0, 406 | "Valid": false 407 | }, 408 | "NumericPrecision": { 409 | "Int64": 4, 410 | "Valid": true 411 | }, 412 | "NumericScale": { 413 | "Int64": 2, 414 | "Valid": true 415 | }, 416 | "DatetimePrecision": { 417 | "Int64": 0, 418 | "Valid": false 419 | }, 420 | "CharacterSetName": { 421 | "String": "", 422 | "Valid": false 423 | }, 424 | "CollationName": { 425 | "String": "", 426 | "Valid": false 427 | }, 428 | "ColumnType": "decimal(4,2)", 429 | "ColumnKey": "", 430 | "Extra": "", 431 | "Privileges": "select,insert,update,references", 432 | "ColumnComment": "", 433 | "GenerationExpression": "", 434 | "SetEnumVals": [], 435 | "Constraint": null, 436 | "SrsID": { 437 | "String": "", 438 | "Valid": false 439 | } 440 | }, 441 | { 442 | "TableCatalog": "def", 443 | "TableSchema": "sakila", 444 | "TableName": "film", 445 | "ColumnName": "length", 446 | "OrdinalPosition": 9, 447 | "ColumnDefault": { 448 | "String": "", 449 | "Valid": false 450 | }, 451 | "IsNullable": false, 452 | "DataType": "smallint", 453 | "CharacterMaximumLength": { 454 | "Int64": 0, 455 | "Valid": false 456 | }, 457 | "CharacterOctetLength": { 458 | "Int64": 0, 459 | "Valid": false 460 | }, 461 | "NumericPrecision": { 462 | "Int64": 5, 463 | "Valid": true 464 | }, 465 | "NumericScale": { 466 | "Int64": 0, 467 | "Valid": true 468 | }, 469 | "DatetimePrecision": { 470 | "Int64": 0, 471 | "Valid": false 472 | }, 473 | "CharacterSetName": { 474 | "String": "", 475 | "Valid": false 476 | }, 477 | "CollationName": { 478 | "String": "", 479 | "Valid": false 480 | }, 481 | "ColumnType": "smallint(5) unsigned", 482 | "ColumnKey": "", 483 | "Extra": "", 484 | "Privileges": "select,insert,update,references", 485 | "ColumnComment": "", 486 | "GenerationExpression": "", 487 | "SetEnumVals": [], 488 | "Constraint": null, 489 | "SrsID": { 490 | "String": "", 491 | "Valid": false 492 | } 493 | }, 494 | { 495 | "TableCatalog": "def", 496 | "TableSchema": "sakila", 497 | "TableName": "film", 498 | "ColumnName": "replacement_cost", 499 | "OrdinalPosition": 10, 500 | "ColumnDefault": { 501 | "String": "19.99", 502 | "Valid": true 503 | }, 504 | "IsNullable": false, 505 | "DataType": "decimal", 506 | "CharacterMaximumLength": { 507 | "Int64": 0, 508 | "Valid": false 509 | }, 510 | "CharacterOctetLength": { 511 | "Int64": 0, 512 | "Valid": false 513 | }, 514 | "NumericPrecision": { 515 | "Int64": 5, 516 | "Valid": true 517 | }, 518 | "NumericScale": { 519 | "Int64": 2, 520 | "Valid": true 521 | }, 522 | "DatetimePrecision": { 523 | "Int64": 0, 524 | "Valid": false 525 | }, 526 | "CharacterSetName": { 527 | "String": "", 528 | "Valid": false 529 | }, 530 | "CollationName": { 531 | "String": "", 532 | "Valid": false 533 | }, 534 | "ColumnType": "decimal(5,2)", 535 | "ColumnKey": "", 536 | "Extra": "", 537 | "Privileges": "select,insert,update,references", 538 | "ColumnComment": "", 539 | "GenerationExpression": "", 540 | "SetEnumVals": [], 541 | "Constraint": null, 542 | "SrsID": { 543 | "String": "", 544 | "Valid": false 545 | } 546 | }, 547 | { 548 | "TableCatalog": "def", 549 | "TableSchema": "sakila", 550 | "TableName": "film", 551 | "ColumnName": "rating", 552 | "OrdinalPosition": 11, 553 | "ColumnDefault": { 554 | "String": "G", 555 | "Valid": true 556 | }, 557 | "IsNullable": false, 558 | "DataType": "enum", 559 | "CharacterMaximumLength": { 560 | "Int64": 5, 561 | "Valid": true 562 | }, 563 | "CharacterOctetLength": { 564 | "Int64": 15, 565 | "Valid": true 566 | }, 567 | "NumericPrecision": { 568 | "Int64": 0, 569 | "Valid": false 570 | }, 571 | "NumericScale": { 572 | "Int64": 0, 573 | "Valid": false 574 | }, 575 | "DatetimePrecision": { 576 | "Int64": 0, 577 | "Valid": false 578 | }, 579 | "CharacterSetName": { 580 | "String": "utf8", 581 | "Valid": true 582 | }, 583 | "CollationName": { 584 | "String": "utf8_general_ci", 585 | "Valid": true 586 | }, 587 | "ColumnType": "enum('G','PG','PG-13','R','NC-17')", 588 | "ColumnKey": "", 589 | "Extra": "", 590 | "Privileges": "select,insert,update,references", 591 | "ColumnComment": "", 592 | "GenerationExpression": "", 593 | "SetEnumVals": [ 594 | "G", 595 | "PG", 596 | "PG-13", 597 | "R", 598 | "NC-17" 599 | ], 600 | "Constraint": null, 601 | "SrsID": { 602 | "String": "", 603 | "Valid": false 604 | } 605 | }, 606 | { 607 | "TableCatalog": "def", 608 | "TableSchema": "sakila", 609 | "TableName": "film", 610 | "ColumnName": "special_features", 611 | "OrdinalPosition": 12, 612 | "ColumnDefault": { 613 | "String": "", 614 | "Valid": false 615 | }, 616 | "IsNullable": false, 617 | "DataType": "set", 618 | "CharacterMaximumLength": { 619 | "Int64": 54, 620 | "Valid": true 621 | }, 622 | "CharacterOctetLength": { 623 | "Int64": 162, 624 | "Valid": true 625 | }, 626 | "NumericPrecision": { 627 | "Int64": 0, 628 | "Valid": false 629 | }, 630 | "NumericScale": { 631 | "Int64": 0, 632 | "Valid": false 633 | }, 634 | "DatetimePrecision": { 635 | "Int64": 0, 636 | "Valid": false 637 | }, 638 | "CharacterSetName": { 639 | "String": "utf8", 640 | "Valid": true 641 | }, 642 | "CollationName": { 643 | "String": "utf8_general_ci", 644 | "Valid": true 645 | }, 646 | "ColumnType": "set('Trailers','Commentaries','Deleted Scenes','Behind the Scenes')", 647 | "ColumnKey": "", 648 | "Extra": "", 649 | "Privileges": "select,insert,update,references", 650 | "ColumnComment": "", 651 | "GenerationExpression": "", 652 | "SetEnumVals": [ 653 | "Trailers", 654 | "Commentaries", 655 | "Deleted Scenes", 656 | "Behind the Scenes" 657 | ], 658 | "Constraint": null, 659 | "SrsID": { 660 | "String": "", 661 | "Valid": false 662 | } 663 | }, 664 | { 665 | "TableCatalog": "def", 666 | "TableSchema": "sakila", 667 | "TableName": "film", 668 | "ColumnName": "last_update", 669 | "OrdinalPosition": 13, 670 | "ColumnDefault": { 671 | "String": "CURRENT_TIMESTAMP", 672 | "Valid": true 673 | }, 674 | "IsNullable": false, 675 | "DataType": "timestamp", 676 | "CharacterMaximumLength": { 677 | "Int64": 0, 678 | "Valid": false 679 | }, 680 | "CharacterOctetLength": { 681 | "Int64": 0, 682 | "Valid": false 683 | }, 684 | "NumericPrecision": { 685 | "Int64": 0, 686 | "Valid": false 687 | }, 688 | "NumericScale": { 689 | "Int64": 0, 690 | "Valid": false 691 | }, 692 | "DatetimePrecision": { 693 | "Int64": 0, 694 | "Valid": true 695 | }, 696 | "CharacterSetName": { 697 | "String": "", 698 | "Valid": false 699 | }, 700 | "CollationName": { 701 | "String": "", 702 | "Valid": false 703 | }, 704 | "ColumnType": "timestamp", 705 | "ColumnKey": "", 706 | "Extra": "on update CURRENT_TIMESTAMP", 707 | "Privileges": "select,insert,update,references", 708 | "ColumnComment": "", 709 | "GenerationExpression": "", 710 | "SetEnumVals": [], 711 | "Constraint": null, 712 | "SrsID": { 713 | "String": "", 714 | "Valid": false 715 | } 716 | } 717 | ], 718 | "Indexes": { 719 | "PRIMARY": { 720 | "Name": "PRIMARY", 721 | "Fields": [ 722 | "film_id" 723 | ], 724 | "Unique": true, 725 | "Visible": true 726 | }, 727 | "idx_fk_language_id": { 728 | "Name": "idx_fk_language_id", 729 | "Fields": [ 730 | "language_id" 731 | ], 732 | "Unique": false, 733 | "Visible": true 734 | }, 735 | "idx_fk_original_language_id": { 736 | "Name": "idx_fk_original_language_id", 737 | "Fields": [ 738 | "original_language_id" 739 | ], 740 | "Unique": false, 741 | "Visible": true 742 | }, 743 | "idx_title": { 744 | "Name": "idx_title", 745 | "Fields": [ 746 | "title" 747 | ], 748 | "Unique": false, 749 | "Visible": true 750 | } 751 | }, 752 | "Constraints": [ 753 | { 754 | "ConstraintName": "fk_film_language", 755 | "ColumnName": "language_id", 756 | "ReferencedTableSchema": "sakila", 757 | "ReferencedTableName": "language", 758 | "ReferencedColumnName": "language_id" 759 | }, 760 | { 761 | "ConstraintName": "fk_film_language_original", 762 | "ColumnName": "original_language_id", 763 | "ReferencedTableSchema": "sakila", 764 | "ReferencedTableName": "language", 765 | "ReferencedColumnName": "language_id" 766 | } 767 | ], 768 | "Triggers": [] 769 | } -------------------------------------------------------------------------------- /tableparser/testdata/table002.json: -------------------------------------------------------------------------------- 1 | { 2 | "Schema": "sakila", 3 | "Name": "film", 4 | "Fields": [ 5 | { 6 | "TableCatalog": "def", 7 | "TableSchema": "sakila", 8 | "TableName": "film", 9 | "ColumnName": "film_id", 10 | "OrdinalPosition": 1, 11 | "ColumnDefault": { 12 | "String": "", 13 | "Valid": false 14 | }, 15 | "IsNullable": false, 16 | "DataType": "smallint", 17 | "CharacterMaximumLength": { 18 | "Int64": 0, 19 | "Valid": false 20 | }, 21 | "CharacterOctetLength": { 22 | "Int64": 0, 23 | "Valid": false 24 | }, 25 | "NumericPrecision": { 26 | "Int64": 5, 27 | "Valid": true 28 | }, 29 | "NumericScale": { 30 | "Int64": 0, 31 | "Valid": true 32 | }, 33 | "DatetimePrecision": { 34 | "Int64": 0, 35 | "Valid": false 36 | }, 37 | "CharacterSetName": { 38 | "String": "", 39 | "Valid": false 40 | }, 41 | "CollationName": { 42 | "String": "", 43 | "Valid": false 44 | }, 45 | "ColumnType": "smallint(5) unsigned", 46 | "ColumnKey": "PRI", 47 | "Extra": "auto_increment", 48 | "Privileges": "select,insert,update,references", 49 | "ColumnComment": "", 50 | "GenerationExpression": "", 51 | "SetEnumVals": [], 52 | "Constraint": null, 53 | "SrsID": { 54 | "String": "", 55 | "Valid": false 56 | } 57 | }, 58 | { 59 | "TableCatalog": "def", 60 | "TableSchema": "sakila", 61 | "TableName": "film", 62 | "ColumnName": "title", 63 | "OrdinalPosition": 2, 64 | "ColumnDefault": { 65 | "String": "", 66 | "Valid": false 67 | }, 68 | "IsNullable": false, 69 | "DataType": "varchar", 70 | "CharacterMaximumLength": { 71 | "Int64": 255, 72 | "Valid": true 73 | }, 74 | "CharacterOctetLength": { 75 | "Int64": 765, 76 | "Valid": true 77 | }, 78 | "NumericPrecision": { 79 | "Int64": 0, 80 | "Valid": false 81 | }, 82 | "NumericScale": { 83 | "Int64": 0, 84 | "Valid": false 85 | }, 86 | "DatetimePrecision": { 87 | "Int64": 0, 88 | "Valid": false 89 | }, 90 | "CharacterSetName": { 91 | "String": "utf8", 92 | "Valid": true 93 | }, 94 | "CollationName": { 95 | "String": "utf8_general_ci", 96 | "Valid": true 97 | }, 98 | "ColumnType": "varchar(255)", 99 | "ColumnKey": "MUL", 100 | "Extra": "", 101 | "Privileges": "select,insert,update,references", 102 | "ColumnComment": "", 103 | "GenerationExpression": "", 104 | "SetEnumVals": [], 105 | "Constraint": null, 106 | "SrsID": { 107 | "String": "", 108 | "Valid": false 109 | } 110 | }, 111 | { 112 | "TableCatalog": "def", 113 | "TableSchema": "sakila", 114 | "TableName": "film", 115 | "ColumnName": "description", 116 | "OrdinalPosition": 3, 117 | "ColumnDefault": { 118 | "String": "", 119 | "Valid": false 120 | }, 121 | "IsNullable": false, 122 | "DataType": "text", 123 | "CharacterMaximumLength": { 124 | "Int64": 65535, 125 | "Valid": true 126 | }, 127 | "CharacterOctetLength": { 128 | "Int64": 65535, 129 | "Valid": true 130 | }, 131 | "NumericPrecision": { 132 | "Int64": 0, 133 | "Valid": false 134 | }, 135 | "NumericScale": { 136 | "Int64": 0, 137 | "Valid": false 138 | }, 139 | "DatetimePrecision": { 140 | "Int64": 0, 141 | "Valid": false 142 | }, 143 | "CharacterSetName": { 144 | "String": "utf8", 145 | "Valid": true 146 | }, 147 | "CollationName": { 148 | "String": "utf8_general_ci", 149 | "Valid": true 150 | }, 151 | "ColumnType": "text", 152 | "ColumnKey": "", 153 | "Extra": "", 154 | "Privileges": "select,insert,update,references", 155 | "ColumnComment": "", 156 | "GenerationExpression": "", 157 | "SetEnumVals": [], 158 | "Constraint": null, 159 | "SrsID": { 160 | "String": "", 161 | "Valid": false 162 | } 163 | }, 164 | { 165 | "TableCatalog": "def", 166 | "TableSchema": "sakila", 167 | "TableName": "film", 168 | "ColumnName": "release_year", 169 | "OrdinalPosition": 4, 170 | "ColumnDefault": { 171 | "String": "", 172 | "Valid": false 173 | }, 174 | "IsNullable": false, 175 | "DataType": "year", 176 | "CharacterMaximumLength": { 177 | "Int64": 0, 178 | "Valid": false 179 | }, 180 | "CharacterOctetLength": { 181 | "Int64": 0, 182 | "Valid": false 183 | }, 184 | "NumericPrecision": { 185 | "Int64": 0, 186 | "Valid": false 187 | }, 188 | "NumericScale": { 189 | "Int64": 0, 190 | "Valid": false 191 | }, 192 | "DatetimePrecision": { 193 | "Int64": 0, 194 | "Valid": false 195 | }, 196 | "CharacterSetName": { 197 | "String": "", 198 | "Valid": false 199 | }, 200 | "CollationName": { 201 | "String": "", 202 | "Valid": false 203 | }, 204 | "ColumnType": "year(4)", 205 | "ColumnKey": "", 206 | "Extra": "", 207 | "Privileges": "select,insert,update,references", 208 | "ColumnComment": "", 209 | "GenerationExpression": "", 210 | "SetEnumVals": [], 211 | "Constraint": null, 212 | "SrsID": { 213 | "String": "", 214 | "Valid": false 215 | } 216 | }, 217 | { 218 | "TableCatalog": "def", 219 | "TableSchema": "sakila", 220 | "TableName": "film", 221 | "ColumnName": "language_id", 222 | "OrdinalPosition": 5, 223 | "ColumnDefault": { 224 | "String": "", 225 | "Valid": false 226 | }, 227 | "IsNullable": false, 228 | "DataType": "tinyint", 229 | "CharacterMaximumLength": { 230 | "Int64": 0, 231 | "Valid": false 232 | }, 233 | "CharacterOctetLength": { 234 | "Int64": 0, 235 | "Valid": false 236 | }, 237 | "NumericPrecision": { 238 | "Int64": 3, 239 | "Valid": true 240 | }, 241 | "NumericScale": { 242 | "Int64": 0, 243 | "Valid": true 244 | }, 245 | "DatetimePrecision": { 246 | "Int64": 0, 247 | "Valid": false 248 | }, 249 | "CharacterSetName": { 250 | "String": "", 251 | "Valid": false 252 | }, 253 | "CollationName": { 254 | "String": "", 255 | "Valid": false 256 | }, 257 | "ColumnType": "tinyint(3) unsigned", 258 | "ColumnKey": "MUL", 259 | "Extra": "", 260 | "Privileges": "select,insert,update,references", 261 | "ColumnComment": "", 262 | "GenerationExpression": "", 263 | "SetEnumVals": [], 264 | "Constraint": { 265 | "ConstraintName": "fk_film_language", 266 | "ColumnName": "language_id", 267 | "ReferencedTableSchema": "sakila", 268 | "ReferencedTableName": "language", 269 | "ReferencedColumnName": "language_id" 270 | }, 271 | "SrsID": { 272 | "String": "", 273 | "Valid": false 274 | } 275 | }, 276 | { 277 | "TableCatalog": "def", 278 | "TableSchema": "sakila", 279 | "TableName": "film", 280 | "ColumnName": "original_language_id", 281 | "OrdinalPosition": 6, 282 | "ColumnDefault": { 283 | "String": "", 284 | "Valid": false 285 | }, 286 | "IsNullable": false, 287 | "DataType": "tinyint", 288 | "CharacterMaximumLength": { 289 | "Int64": 0, 290 | "Valid": false 291 | }, 292 | "CharacterOctetLength": { 293 | "Int64": 0, 294 | "Valid": false 295 | }, 296 | "NumericPrecision": { 297 | "Int64": 3, 298 | "Valid": true 299 | }, 300 | "NumericScale": { 301 | "Int64": 0, 302 | "Valid": true 303 | }, 304 | "DatetimePrecision": { 305 | "Int64": 0, 306 | "Valid": false 307 | }, 308 | "CharacterSetName": { 309 | "String": "", 310 | "Valid": false 311 | }, 312 | "CollationName": { 313 | "String": "", 314 | "Valid": false 315 | }, 316 | "ColumnType": "tinyint(3) unsigned", 317 | "ColumnKey": "MUL", 318 | "Extra": "", 319 | "Privileges": "select,insert,update,references", 320 | "ColumnComment": "", 321 | "GenerationExpression": "", 322 | "SetEnumVals": [], 323 | "Constraint": { 324 | "ConstraintName": "fk_film_language_original", 325 | "ColumnName": "original_language_id", 326 | "ReferencedTableSchema": "sakila", 327 | "ReferencedTableName": "language", 328 | "ReferencedColumnName": "language_id" 329 | }, 330 | "SrsID": { 331 | "String": "", 332 | "Valid": false 333 | } 334 | }, 335 | { 336 | "TableCatalog": "def", 337 | "TableSchema": "sakila", 338 | "TableName": "film", 339 | "ColumnName": "rental_duration", 340 | "OrdinalPosition": 7, 341 | "ColumnDefault": { 342 | "String": "3", 343 | "Valid": true 344 | }, 345 | "IsNullable": false, 346 | "DataType": "tinyint", 347 | "CharacterMaximumLength": { 348 | "Int64": 0, 349 | "Valid": false 350 | }, 351 | "CharacterOctetLength": { 352 | "Int64": 0, 353 | "Valid": false 354 | }, 355 | "NumericPrecision": { 356 | "Int64": 3, 357 | "Valid": true 358 | }, 359 | "NumericScale": { 360 | "Int64": 0, 361 | "Valid": true 362 | }, 363 | "DatetimePrecision": { 364 | "Int64": 0, 365 | "Valid": false 366 | }, 367 | "CharacterSetName": { 368 | "String": "", 369 | "Valid": false 370 | }, 371 | "CollationName": { 372 | "String": "", 373 | "Valid": false 374 | }, 375 | "ColumnType": "tinyint(3) unsigned", 376 | "ColumnKey": "", 377 | "Extra": "", 378 | "Privileges": "select,insert,update,references", 379 | "ColumnComment": "", 380 | "GenerationExpression": "", 381 | "SetEnumVals": [], 382 | "Constraint": null, 383 | "SrsID": { 384 | "String": "", 385 | "Valid": false 386 | } 387 | }, 388 | { 389 | "TableCatalog": "def", 390 | "TableSchema": "sakila", 391 | "TableName": "film", 392 | "ColumnName": "rental_rate", 393 | "OrdinalPosition": 8, 394 | "ColumnDefault": { 395 | "String": "4.99", 396 | "Valid": true 397 | }, 398 | "IsNullable": false, 399 | "DataType": "decimal", 400 | "CharacterMaximumLength": { 401 | "Int64": 0, 402 | "Valid": false 403 | }, 404 | "CharacterOctetLength": { 405 | "Int64": 0, 406 | "Valid": false 407 | }, 408 | "NumericPrecision": { 409 | "Int64": 4, 410 | "Valid": true 411 | }, 412 | "NumericScale": { 413 | "Int64": 2, 414 | "Valid": true 415 | }, 416 | "DatetimePrecision": { 417 | "Int64": 0, 418 | "Valid": false 419 | }, 420 | "CharacterSetName": { 421 | "String": "", 422 | "Valid": false 423 | }, 424 | "CollationName": { 425 | "String": "", 426 | "Valid": false 427 | }, 428 | "ColumnType": "decimal(4,2)", 429 | "ColumnKey": "", 430 | "Extra": "", 431 | "Privileges": "select,insert,update,references", 432 | "ColumnComment": "", 433 | "GenerationExpression": "", 434 | "SetEnumVals": [], 435 | "Constraint": null, 436 | "SrsID": { 437 | "String": "", 438 | "Valid": false 439 | } 440 | }, 441 | { 442 | "TableCatalog": "def", 443 | "TableSchema": "sakila", 444 | "TableName": "film", 445 | "ColumnName": "length", 446 | "OrdinalPosition": 9, 447 | "ColumnDefault": { 448 | "String": "", 449 | "Valid": false 450 | }, 451 | "IsNullable": false, 452 | "DataType": "smallint", 453 | "CharacterMaximumLength": { 454 | "Int64": 0, 455 | "Valid": false 456 | }, 457 | "CharacterOctetLength": { 458 | "Int64": 0, 459 | "Valid": false 460 | }, 461 | "NumericPrecision": { 462 | "Int64": 5, 463 | "Valid": true 464 | }, 465 | "NumericScale": { 466 | "Int64": 0, 467 | "Valid": true 468 | }, 469 | "DatetimePrecision": { 470 | "Int64": 0, 471 | "Valid": false 472 | }, 473 | "CharacterSetName": { 474 | "String": "", 475 | "Valid": false 476 | }, 477 | "CollationName": { 478 | "String": "", 479 | "Valid": false 480 | }, 481 | "ColumnType": "smallint(5) unsigned", 482 | "ColumnKey": "", 483 | "Extra": "", 484 | "Privileges": "select,insert,update,references", 485 | "ColumnComment": "", 486 | "GenerationExpression": "", 487 | "SetEnumVals": [], 488 | "Constraint": null, 489 | "SrsID": { 490 | "String": "", 491 | "Valid": false 492 | } 493 | }, 494 | { 495 | "TableCatalog": "def", 496 | "TableSchema": "sakila", 497 | "TableName": "film", 498 | "ColumnName": "replacement_cost", 499 | "OrdinalPosition": 10, 500 | "ColumnDefault": { 501 | "String": "19.99", 502 | "Valid": true 503 | }, 504 | "IsNullable": false, 505 | "DataType": "decimal", 506 | "CharacterMaximumLength": { 507 | "Int64": 0, 508 | "Valid": false 509 | }, 510 | "CharacterOctetLength": { 511 | "Int64": 0, 512 | "Valid": false 513 | }, 514 | "NumericPrecision": { 515 | "Int64": 5, 516 | "Valid": true 517 | }, 518 | "NumericScale": { 519 | "Int64": 2, 520 | "Valid": true 521 | }, 522 | "DatetimePrecision": { 523 | "Int64": 0, 524 | "Valid": false 525 | }, 526 | "CharacterSetName": { 527 | "String": "", 528 | "Valid": false 529 | }, 530 | "CollationName": { 531 | "String": "", 532 | "Valid": false 533 | }, 534 | "ColumnType": "decimal(5,2)", 535 | "ColumnKey": "", 536 | "Extra": "", 537 | "Privileges": "select,insert,update,references", 538 | "ColumnComment": "", 539 | "GenerationExpression": "", 540 | "SetEnumVals": [], 541 | "Constraint": null, 542 | "SrsID": { 543 | "String": "", 544 | "Valid": false 545 | } 546 | }, 547 | { 548 | "TableCatalog": "def", 549 | "TableSchema": "sakila", 550 | "TableName": "film", 551 | "ColumnName": "rating", 552 | "OrdinalPosition": 11, 553 | "ColumnDefault": { 554 | "String": "G", 555 | "Valid": true 556 | }, 557 | "IsNullable": false, 558 | "DataType": "enum", 559 | "CharacterMaximumLength": { 560 | "Int64": 5, 561 | "Valid": true 562 | }, 563 | "CharacterOctetLength": { 564 | "Int64": 15, 565 | "Valid": true 566 | }, 567 | "NumericPrecision": { 568 | "Int64": 0, 569 | "Valid": false 570 | }, 571 | "NumericScale": { 572 | "Int64": 0, 573 | "Valid": false 574 | }, 575 | "DatetimePrecision": { 576 | "Int64": 0, 577 | "Valid": false 578 | }, 579 | "CharacterSetName": { 580 | "String": "utf8", 581 | "Valid": true 582 | }, 583 | "CollationName": { 584 | "String": "utf8_general_ci", 585 | "Valid": true 586 | }, 587 | "ColumnType": "enum('G','PG','PG-13','R','NC-17')", 588 | "ColumnKey": "", 589 | "Extra": "", 590 | "Privileges": "select,insert,update,references", 591 | "ColumnComment": "", 592 | "GenerationExpression": "", 593 | "SetEnumVals": [ 594 | "G", 595 | "PG", 596 | "PG-13", 597 | "R", 598 | "NC-17" 599 | ], 600 | "Constraint": null, 601 | "SrsID": { 602 | "String": "", 603 | "Valid": false 604 | } 605 | }, 606 | { 607 | "TableCatalog": "def", 608 | "TableSchema": "sakila", 609 | "TableName": "film", 610 | "ColumnName": "special_features", 611 | "OrdinalPosition": 12, 612 | "ColumnDefault": { 613 | "String": "", 614 | "Valid": false 615 | }, 616 | "IsNullable": false, 617 | "DataType": "set", 618 | "CharacterMaximumLength": { 619 | "Int64": 54, 620 | "Valid": true 621 | }, 622 | "CharacterOctetLength": { 623 | "Int64": 162, 624 | "Valid": true 625 | }, 626 | "NumericPrecision": { 627 | "Int64": 0, 628 | "Valid": false 629 | }, 630 | "NumericScale": { 631 | "Int64": 0, 632 | "Valid": false 633 | }, 634 | "DatetimePrecision": { 635 | "Int64": 0, 636 | "Valid": false 637 | }, 638 | "CharacterSetName": { 639 | "String": "utf8", 640 | "Valid": true 641 | }, 642 | "CollationName": { 643 | "String": "utf8_general_ci", 644 | "Valid": true 645 | }, 646 | "ColumnType": "set('Trailers','Commentaries','Deleted Scenes','Behind the Scenes')", 647 | "ColumnKey": "", 648 | "Extra": "", 649 | "Privileges": "select,insert,update,references", 650 | "ColumnComment": "", 651 | "GenerationExpression": "", 652 | "SetEnumVals": [ 653 | "Trailers", 654 | "Commentaries", 655 | "Deleted Scenes", 656 | "Behind the Scenes" 657 | ], 658 | "Constraint": null, 659 | "SrsID": { 660 | "String": "", 661 | "Valid": false 662 | } 663 | }, 664 | { 665 | "TableCatalog": "def", 666 | "TableSchema": "sakila", 667 | "TableName": "film", 668 | "ColumnName": "last_update", 669 | "OrdinalPosition": 13, 670 | "ColumnDefault": { 671 | "String": "CURRENT_TIMESTAMP", 672 | "Valid": true 673 | }, 674 | "IsNullable": false, 675 | "DataType": "timestamp", 676 | "CharacterMaximumLength": { 677 | "Int64": 0, 678 | "Valid": false 679 | }, 680 | "CharacterOctetLength": { 681 | "Int64": 0, 682 | "Valid": false 683 | }, 684 | "NumericPrecision": { 685 | "Int64": 0, 686 | "Valid": false 687 | }, 688 | "NumericScale": { 689 | "Int64": 0, 690 | "Valid": false 691 | }, 692 | "DatetimePrecision": { 693 | "Int64": 0, 694 | "Valid": true 695 | }, 696 | "CharacterSetName": { 697 | "String": "", 698 | "Valid": false 699 | }, 700 | "CollationName": { 701 | "String": "", 702 | "Valid": false 703 | }, 704 | "ColumnType": "timestamp", 705 | "ColumnKey": "", 706 | "Extra": "on update CURRENT_TIMESTAMP", 707 | "Privileges": "select,insert,update,references", 708 | "ColumnComment": "", 709 | "GenerationExpression": "", 710 | "SetEnumVals": [], 711 | "Constraint": null, 712 | "SrsID": { 713 | "String": "", 714 | "Valid": false 715 | } 716 | } 717 | ], 718 | "Indexes": { 719 | "PRIMARY": { 720 | "Name": "PRIMARY", 721 | "Fields": [ 722 | "film_id" 723 | ], 724 | "Unique": true, 725 | "Visible": true 726 | }, 727 | "idx_fk_language_id": { 728 | "Name": "idx_fk_language_id", 729 | "Fields": [ 730 | "language_id" 731 | ], 732 | "Unique": false, 733 | "Visible": true 734 | }, 735 | "idx_fk_original_language_id": { 736 | "Name": "idx_fk_original_language_id", 737 | "Fields": [ 738 | "original_language_id" 739 | ], 740 | "Unique": false, 741 | "Visible": true 742 | }, 743 | "idx_title": { 744 | "Name": "idx_title", 745 | "Fields": [ 746 | "title" 747 | ], 748 | "Unique": false, 749 | "Visible": true 750 | } 751 | }, 752 | "Constraints": [ 753 | { 754 | "ConstraintName": "fk_film_language", 755 | "ColumnName": "language_id", 756 | "ReferencedTableSchema": "sakila", 757 | "ReferencedTableName": "language", 758 | "ReferencedColumnName": "language_id" 759 | }, 760 | { 761 | "ConstraintName": "fk_film_language_original", 762 | "ColumnName": "original_language_id", 763 | "ReferencedTableSchema": "sakila", 764 | "ReferencedTableName": "language", 765 | "ReferencedColumnName": "language_id" 766 | } 767 | ], 768 | "Triggers": [] 769 | } -------------------------------------------------------------------------------- /tableparser/testdata/table003.json: -------------------------------------------------------------------------------- 1 | { 2 | "Schema": "sakila", 3 | "Name": "film", 4 | "Fields": [ 5 | { 6 | "TableCatalog": "def", 7 | "TableSchema": "sakila", 8 | "TableName": "film", 9 | "ColumnName": "film_id", 10 | "OrdinalPosition": 1, 11 | "ColumnDefault": { 12 | "String": "", 13 | "Valid": false 14 | }, 15 | "IsNullable": false, 16 | "DataType": "smallint", 17 | "CharacterMaximumLength": { 18 | "Int64": 0, 19 | "Valid": false 20 | }, 21 | "CharacterOctetLength": { 22 | "Int64": 0, 23 | "Valid": false 24 | }, 25 | "NumericPrecision": { 26 | "Int64": 5, 27 | "Valid": true 28 | }, 29 | "NumericScale": { 30 | "Int64": 0, 31 | "Valid": true 32 | }, 33 | "DatetimePrecision": { 34 | "Int64": 0, 35 | "Valid": false 36 | }, 37 | "CharacterSetName": { 38 | "String": "", 39 | "Valid": false 40 | }, 41 | "CollationName": { 42 | "String": "", 43 | "Valid": false 44 | }, 45 | "ColumnType": "smallint(5) unsigned", 46 | "ColumnKey": "PRI", 47 | "Extra": "auto_increment", 48 | "Privileges": "select,insert,update,references", 49 | "ColumnComment": "", 50 | "GenerationExpression": "", 51 | "SetEnumVals": [], 52 | "Constraint": null, 53 | "SrsID": { 54 | "String": "", 55 | "Valid": false 56 | } 57 | }, 58 | { 59 | "TableCatalog": "def", 60 | "TableSchema": "sakila", 61 | "TableName": "film", 62 | "ColumnName": "title", 63 | "OrdinalPosition": 2, 64 | "ColumnDefault": { 65 | "String": "", 66 | "Valid": false 67 | }, 68 | "IsNullable": false, 69 | "DataType": "varchar", 70 | "CharacterMaximumLength": { 71 | "Int64": 255, 72 | "Valid": true 73 | }, 74 | "CharacterOctetLength": { 75 | "Int64": 765, 76 | "Valid": true 77 | }, 78 | "NumericPrecision": { 79 | "Int64": 0, 80 | "Valid": false 81 | }, 82 | "NumericScale": { 83 | "Int64": 0, 84 | "Valid": false 85 | }, 86 | "DatetimePrecision": { 87 | "Int64": 0, 88 | "Valid": false 89 | }, 90 | "CharacterSetName": { 91 | "String": "utf8", 92 | "Valid": true 93 | }, 94 | "CollationName": { 95 | "String": "utf8_general_ci", 96 | "Valid": true 97 | }, 98 | "ColumnType": "varchar(255)", 99 | "ColumnKey": "MUL", 100 | "Extra": "", 101 | "Privileges": "select,insert,update,references", 102 | "ColumnComment": "", 103 | "GenerationExpression": "", 104 | "SetEnumVals": [], 105 | "Constraint": null, 106 | "SrsID": { 107 | "String": "", 108 | "Valid": false 109 | } 110 | }, 111 | { 112 | "TableCatalog": "def", 113 | "TableSchema": "sakila", 114 | "TableName": "film", 115 | "ColumnName": "description", 116 | "OrdinalPosition": 3, 117 | "ColumnDefault": { 118 | "String": "", 119 | "Valid": false 120 | }, 121 | "IsNullable": false, 122 | "DataType": "text", 123 | "CharacterMaximumLength": { 124 | "Int64": 65535, 125 | "Valid": true 126 | }, 127 | "CharacterOctetLength": { 128 | "Int64": 65535, 129 | "Valid": true 130 | }, 131 | "NumericPrecision": { 132 | "Int64": 0, 133 | "Valid": false 134 | }, 135 | "NumericScale": { 136 | "Int64": 0, 137 | "Valid": false 138 | }, 139 | "DatetimePrecision": { 140 | "Int64": 0, 141 | "Valid": false 142 | }, 143 | "CharacterSetName": { 144 | "String": "utf8", 145 | "Valid": true 146 | }, 147 | "CollationName": { 148 | "String": "utf8_general_ci", 149 | "Valid": true 150 | }, 151 | "ColumnType": "text", 152 | "ColumnKey": "", 153 | "Extra": "", 154 | "Privileges": "select,insert,update,references", 155 | "ColumnComment": "", 156 | "GenerationExpression": "", 157 | "SetEnumVals": [], 158 | "Constraint": null, 159 | "SrsID": { 160 | "String": "", 161 | "Valid": false 162 | } 163 | }, 164 | { 165 | "TableCatalog": "def", 166 | "TableSchema": "sakila", 167 | "TableName": "film", 168 | "ColumnName": "release_year", 169 | "OrdinalPosition": 4, 170 | "ColumnDefault": { 171 | "String": "", 172 | "Valid": false 173 | }, 174 | "IsNullable": false, 175 | "DataType": "year", 176 | "CharacterMaximumLength": { 177 | "Int64": 0, 178 | "Valid": false 179 | }, 180 | "CharacterOctetLength": { 181 | "Int64": 0, 182 | "Valid": false 183 | }, 184 | "NumericPrecision": { 185 | "Int64": 0, 186 | "Valid": false 187 | }, 188 | "NumericScale": { 189 | "Int64": 0, 190 | "Valid": false 191 | }, 192 | "DatetimePrecision": { 193 | "Int64": 0, 194 | "Valid": false 195 | }, 196 | "CharacterSetName": { 197 | "String": "", 198 | "Valid": false 199 | }, 200 | "CollationName": { 201 | "String": "", 202 | "Valid": false 203 | }, 204 | "ColumnType": "year(4)", 205 | "ColumnKey": "", 206 | "Extra": "", 207 | "Privileges": "select,insert,update,references", 208 | "ColumnComment": "", 209 | "GenerationExpression": "", 210 | "SetEnumVals": [], 211 | "Constraint": null, 212 | "SrsID": { 213 | "String": "", 214 | "Valid": false 215 | } 216 | }, 217 | { 218 | "TableCatalog": "def", 219 | "TableSchema": "sakila", 220 | "TableName": "film", 221 | "ColumnName": "language_id", 222 | "OrdinalPosition": 5, 223 | "ColumnDefault": { 224 | "String": "", 225 | "Valid": false 226 | }, 227 | "IsNullable": false, 228 | "DataType": "tinyint", 229 | "CharacterMaximumLength": { 230 | "Int64": 0, 231 | "Valid": false 232 | }, 233 | "CharacterOctetLength": { 234 | "Int64": 0, 235 | "Valid": false 236 | }, 237 | "NumericPrecision": { 238 | "Int64": 3, 239 | "Valid": true 240 | }, 241 | "NumericScale": { 242 | "Int64": 0, 243 | "Valid": true 244 | }, 245 | "DatetimePrecision": { 246 | "Int64": 0, 247 | "Valid": false 248 | }, 249 | "CharacterSetName": { 250 | "String": "", 251 | "Valid": false 252 | }, 253 | "CollationName": { 254 | "String": "", 255 | "Valid": false 256 | }, 257 | "ColumnType": "tinyint(3) unsigned", 258 | "ColumnKey": "MUL", 259 | "Extra": "", 260 | "Privileges": "select,insert,update,references", 261 | "ColumnComment": "", 262 | "GenerationExpression": "", 263 | "SetEnumVals": [], 264 | "Constraint": { 265 | "ConstraintName": "fk_film_language", 266 | "ColumnName": "language_id", 267 | "ReferencedTableSchema": "sakila", 268 | "ReferencedTableName": "language", 269 | "ReferencedColumnName": "language_id" 270 | }, 271 | "SrsID": { 272 | "String": "", 273 | "Valid": false 274 | } 275 | }, 276 | { 277 | "TableCatalog": "def", 278 | "TableSchema": "sakila", 279 | "TableName": "film", 280 | "ColumnName": "original_language_id", 281 | "OrdinalPosition": 6, 282 | "ColumnDefault": { 283 | "String": "", 284 | "Valid": false 285 | }, 286 | "IsNullable": false, 287 | "DataType": "tinyint", 288 | "CharacterMaximumLength": { 289 | "Int64": 0, 290 | "Valid": false 291 | }, 292 | "CharacterOctetLength": { 293 | "Int64": 0, 294 | "Valid": false 295 | }, 296 | "NumericPrecision": { 297 | "Int64": 3, 298 | "Valid": true 299 | }, 300 | "NumericScale": { 301 | "Int64": 0, 302 | "Valid": true 303 | }, 304 | "DatetimePrecision": { 305 | "Int64": 0, 306 | "Valid": false 307 | }, 308 | "CharacterSetName": { 309 | "String": "", 310 | "Valid": false 311 | }, 312 | "CollationName": { 313 | "String": "", 314 | "Valid": false 315 | }, 316 | "ColumnType": "tinyint(3) unsigned", 317 | "ColumnKey": "MUL", 318 | "Extra": "", 319 | "Privileges": "select,insert,update,references", 320 | "ColumnComment": "", 321 | "GenerationExpression": "", 322 | "SetEnumVals": [], 323 | "Constraint": { 324 | "ConstraintName": "fk_film_language_original", 325 | "ColumnName": "original_language_id", 326 | "ReferencedTableSchema": "sakila", 327 | "ReferencedTableName": "language", 328 | "ReferencedColumnName": "language_id" 329 | }, 330 | "SrsID": { 331 | "String": "", 332 | "Valid": false 333 | } 334 | }, 335 | { 336 | "TableCatalog": "def", 337 | "TableSchema": "sakila", 338 | "TableName": "film", 339 | "ColumnName": "rental_duration", 340 | "OrdinalPosition": 7, 341 | "ColumnDefault": { 342 | "String": "3", 343 | "Valid": true 344 | }, 345 | "IsNullable": false, 346 | "DataType": "tinyint", 347 | "CharacterMaximumLength": { 348 | "Int64": 0, 349 | "Valid": false 350 | }, 351 | "CharacterOctetLength": { 352 | "Int64": 0, 353 | "Valid": false 354 | }, 355 | "NumericPrecision": { 356 | "Int64": 3, 357 | "Valid": true 358 | }, 359 | "NumericScale": { 360 | "Int64": 0, 361 | "Valid": true 362 | }, 363 | "DatetimePrecision": { 364 | "Int64": 0, 365 | "Valid": false 366 | }, 367 | "CharacterSetName": { 368 | "String": "", 369 | "Valid": false 370 | }, 371 | "CollationName": { 372 | "String": "", 373 | "Valid": false 374 | }, 375 | "ColumnType": "tinyint(3) unsigned", 376 | "ColumnKey": "", 377 | "Extra": "", 378 | "Privileges": "select,insert,update,references", 379 | "ColumnComment": "", 380 | "GenerationExpression": "", 381 | "SetEnumVals": [], 382 | "Constraint": null, 383 | "SrsID": { 384 | "String": "", 385 | "Valid": false 386 | } 387 | }, 388 | { 389 | "TableCatalog": "def", 390 | "TableSchema": "sakila", 391 | "TableName": "film", 392 | "ColumnName": "rental_rate", 393 | "OrdinalPosition": 8, 394 | "ColumnDefault": { 395 | "String": "4.99", 396 | "Valid": true 397 | }, 398 | "IsNullable": false, 399 | "DataType": "decimal", 400 | "CharacterMaximumLength": { 401 | "Int64": 0, 402 | "Valid": false 403 | }, 404 | "CharacterOctetLength": { 405 | "Int64": 0, 406 | "Valid": false 407 | }, 408 | "NumericPrecision": { 409 | "Int64": 4, 410 | "Valid": true 411 | }, 412 | "NumericScale": { 413 | "Int64": 2, 414 | "Valid": true 415 | }, 416 | "DatetimePrecision": { 417 | "Int64": 0, 418 | "Valid": false 419 | }, 420 | "CharacterSetName": { 421 | "String": "", 422 | "Valid": false 423 | }, 424 | "CollationName": { 425 | "String": "", 426 | "Valid": false 427 | }, 428 | "ColumnType": "decimal(4,2)", 429 | "ColumnKey": "", 430 | "Extra": "", 431 | "Privileges": "select,insert,update,references", 432 | "ColumnComment": "", 433 | "GenerationExpression": "", 434 | "SetEnumVals": [], 435 | "Constraint": null, 436 | "SrsID": { 437 | "String": "", 438 | "Valid": false 439 | } 440 | }, 441 | { 442 | "TableCatalog": "def", 443 | "TableSchema": "sakila", 444 | "TableName": "film", 445 | "ColumnName": "length", 446 | "OrdinalPosition": 9, 447 | "ColumnDefault": { 448 | "String": "", 449 | "Valid": false 450 | }, 451 | "IsNullable": false, 452 | "DataType": "smallint", 453 | "CharacterMaximumLength": { 454 | "Int64": 0, 455 | "Valid": false 456 | }, 457 | "CharacterOctetLength": { 458 | "Int64": 0, 459 | "Valid": false 460 | }, 461 | "NumericPrecision": { 462 | "Int64": 5, 463 | "Valid": true 464 | }, 465 | "NumericScale": { 466 | "Int64": 0, 467 | "Valid": true 468 | }, 469 | "DatetimePrecision": { 470 | "Int64": 0, 471 | "Valid": false 472 | }, 473 | "CharacterSetName": { 474 | "String": "", 475 | "Valid": false 476 | }, 477 | "CollationName": { 478 | "String": "", 479 | "Valid": false 480 | }, 481 | "ColumnType": "smallint(5) unsigned", 482 | "ColumnKey": "", 483 | "Extra": "", 484 | "Privileges": "select,insert,update,references", 485 | "ColumnComment": "", 486 | "GenerationExpression": "", 487 | "SetEnumVals": [], 488 | "Constraint": null, 489 | "SrsID": { 490 | "String": "", 491 | "Valid": false 492 | } 493 | }, 494 | { 495 | "TableCatalog": "def", 496 | "TableSchema": "sakila", 497 | "TableName": "film", 498 | "ColumnName": "replacement_cost", 499 | "OrdinalPosition": 10, 500 | "ColumnDefault": { 501 | "String": "19.99", 502 | "Valid": true 503 | }, 504 | "IsNullable": false, 505 | "DataType": "decimal", 506 | "CharacterMaximumLength": { 507 | "Int64": 0, 508 | "Valid": false 509 | }, 510 | "CharacterOctetLength": { 511 | "Int64": 0, 512 | "Valid": false 513 | }, 514 | "NumericPrecision": { 515 | "Int64": 5, 516 | "Valid": true 517 | }, 518 | "NumericScale": { 519 | "Int64": 2, 520 | "Valid": true 521 | }, 522 | "DatetimePrecision": { 523 | "Int64": 0, 524 | "Valid": false 525 | }, 526 | "CharacterSetName": { 527 | "String": "", 528 | "Valid": false 529 | }, 530 | "CollationName": { 531 | "String": "", 532 | "Valid": false 533 | }, 534 | "ColumnType": "decimal(5,2)", 535 | "ColumnKey": "", 536 | "Extra": "", 537 | "Privileges": "select,insert,update,references", 538 | "ColumnComment": "", 539 | "GenerationExpression": "", 540 | "SetEnumVals": [], 541 | "Constraint": null, 542 | "SrsID": { 543 | "String": "", 544 | "Valid": false 545 | } 546 | }, 547 | { 548 | "TableCatalog": "def", 549 | "TableSchema": "sakila", 550 | "TableName": "film", 551 | "ColumnName": "rating", 552 | "OrdinalPosition": 11, 553 | "ColumnDefault": { 554 | "String": "G", 555 | "Valid": true 556 | }, 557 | "IsNullable": false, 558 | "DataType": "enum", 559 | "CharacterMaximumLength": { 560 | "Int64": 5, 561 | "Valid": true 562 | }, 563 | "CharacterOctetLength": { 564 | "Int64": 15, 565 | "Valid": true 566 | }, 567 | "NumericPrecision": { 568 | "Int64": 0, 569 | "Valid": false 570 | }, 571 | "NumericScale": { 572 | "Int64": 0, 573 | "Valid": false 574 | }, 575 | "DatetimePrecision": { 576 | "Int64": 0, 577 | "Valid": false 578 | }, 579 | "CharacterSetName": { 580 | "String": "utf8", 581 | "Valid": true 582 | }, 583 | "CollationName": { 584 | "String": "utf8_general_ci", 585 | "Valid": true 586 | }, 587 | "ColumnType": "enum('G','PG','PG-13','R','NC-17')", 588 | "ColumnKey": "", 589 | "Extra": "", 590 | "Privileges": "select,insert,update,references", 591 | "ColumnComment": "", 592 | "GenerationExpression": "", 593 | "SetEnumVals": [ 594 | "G", 595 | "PG", 596 | "PG-13", 597 | "R", 598 | "NC-17" 599 | ], 600 | "Constraint": null, 601 | "SrsID": { 602 | "String": "", 603 | "Valid": false 604 | } 605 | }, 606 | { 607 | "TableCatalog": "def", 608 | "TableSchema": "sakila", 609 | "TableName": "film", 610 | "ColumnName": "special_features", 611 | "OrdinalPosition": 12, 612 | "ColumnDefault": { 613 | "String": "", 614 | "Valid": false 615 | }, 616 | "IsNullable": false, 617 | "DataType": "set", 618 | "CharacterMaximumLength": { 619 | "Int64": 54, 620 | "Valid": true 621 | }, 622 | "CharacterOctetLength": { 623 | "Int64": 162, 624 | "Valid": true 625 | }, 626 | "NumericPrecision": { 627 | "Int64": 0, 628 | "Valid": false 629 | }, 630 | "NumericScale": { 631 | "Int64": 0, 632 | "Valid": false 633 | }, 634 | "DatetimePrecision": { 635 | "Int64": 0, 636 | "Valid": false 637 | }, 638 | "CharacterSetName": { 639 | "String": "utf8", 640 | "Valid": true 641 | }, 642 | "CollationName": { 643 | "String": "utf8_general_ci", 644 | "Valid": true 645 | }, 646 | "ColumnType": "set('Trailers','Commentaries','Deleted Scenes','Behind the Scenes')", 647 | "ColumnKey": "", 648 | "Extra": "", 649 | "Privileges": "select,insert,update,references", 650 | "ColumnComment": "", 651 | "GenerationExpression": "", 652 | "SetEnumVals": [ 653 | "Trailers", 654 | "Commentaries", 655 | "Deleted Scenes", 656 | "Behind the Scenes" 657 | ], 658 | "Constraint": null, 659 | "SrsID": { 660 | "String": "", 661 | "Valid": false 662 | } 663 | }, 664 | { 665 | "TableCatalog": "def", 666 | "TableSchema": "sakila", 667 | "TableName": "film", 668 | "ColumnName": "last_update", 669 | "OrdinalPosition": 13, 670 | "ColumnDefault": { 671 | "String": "CURRENT_TIMESTAMP", 672 | "Valid": true 673 | }, 674 | "IsNullable": false, 675 | "DataType": "timestamp", 676 | "CharacterMaximumLength": { 677 | "Int64": 0, 678 | "Valid": false 679 | }, 680 | "CharacterOctetLength": { 681 | "Int64": 0, 682 | "Valid": false 683 | }, 684 | "NumericPrecision": { 685 | "Int64": 0, 686 | "Valid": false 687 | }, 688 | "NumericScale": { 689 | "Int64": 0, 690 | "Valid": false 691 | }, 692 | "DatetimePrecision": { 693 | "Int64": 0, 694 | "Valid": true 695 | }, 696 | "CharacterSetName": { 697 | "String": "", 698 | "Valid": false 699 | }, 700 | "CollationName": { 701 | "String": "", 702 | "Valid": false 703 | }, 704 | "ColumnType": "timestamp", 705 | "ColumnKey": "", 706 | "Extra": "on update CURRENT_TIMESTAMP", 707 | "Privileges": "select,insert,update,references", 708 | "ColumnComment": "", 709 | "GenerationExpression": "", 710 | "SetEnumVals": [], 711 | "Constraint": null, 712 | "SrsID": { 713 | "String": "", 714 | "Valid": false 715 | } 716 | } 717 | ], 718 | "Indexes": { 719 | "PRIMARY": { 720 | "Name": "PRIMARY", 721 | "Fields": [ 722 | "film_id" 723 | ], 724 | "Unique": true, 725 | "Visible": true 726 | }, 727 | "idx_fk_language_id": { 728 | "Name": "idx_fk_language_id", 729 | "Fields": [ 730 | "language_id" 731 | ], 732 | "Unique": false, 733 | "Visible": true 734 | }, 735 | "idx_fk_original_language_id": { 736 | "Name": "idx_fk_original_language_id", 737 | "Fields": [ 738 | "original_language_id" 739 | ], 740 | "Unique": false, 741 | "Visible": true 742 | }, 743 | "idx_title": { 744 | "Name": "idx_title", 745 | "Fields": [ 746 | "title" 747 | ], 748 | "Unique": false, 749 | "Visible": true 750 | } 751 | }, 752 | "Constraints": [ 753 | { 754 | "ConstraintName": "fk_film_language", 755 | "ColumnName": "language_id", 756 | "ReferencedTableSchema": "sakila", 757 | "ReferencedTableName": "language", 758 | "ReferencedColumnName": "language_id" 759 | }, 760 | { 761 | "ConstraintName": "fk_film_language_original", 762 | "ColumnName": "original_language_id", 763 | "ReferencedTableSchema": "sakila", 764 | "ReferencedTableName": "language", 765 | "ReferencedColumnName": "language_id" 766 | } 767 | ], 768 | "Triggers": [] 769 | } -------------------------------------------------------------------------------- /tableparser/testdata/trigers-5.7.1.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Trigger": "rental_date", 4 | "Event": "INSERT", 5 | "Table": "rental", 6 | "Statement": "SET NEW.rental_date = NOW()", 7 | "Timing": "BEFORE", 8 | "Created": { 9 | "Time": "2009-11-10T23:00:00Z", 10 | "Valid": false 11 | }, 12 | "SQLMode": "", 13 | "Definer": "msandbox@%", 14 | "CharacterSetClient": "latin1", 15 | "CollationConnection": "latin1_swedish_ci", 16 | "DatabaseCollation": "latin1_swedish_ci" 17 | } 18 | ] -------------------------------------------------------------------------------- /tableparser/testdata/trigers-5.7.2.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Trigger": "rental_date", 4 | "Event": "INSERT", 5 | "Table": "rental", 6 | "Statement": "SET NEW.rental_date = NOW()", 7 | "Timing": "BEFORE", 8 | "Created": { 9 | "Time": "2009-11-10T23:00:00Z", 10 | "Valid": true 11 | }, 12 | "SQLMode": "", 13 | "Definer": "msandbox@%", 14 | "CharacterSetClient": "latin1", 15 | "CollationConnection": "latin1_swedish_ci", 16 | "DatabaseCollation": "latin1_swedish_ci" 17 | } 18 | ] -------------------------------------------------------------------------------- /tableparser/testdata/trigers-8.0.0.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Trigger": "rental_date", 4 | "Event": "INSERT", 5 | "Table": "rental", 6 | "Statement": "SET NEW.rental_date = NOW()", 7 | "Timing": "BEFORE", 8 | "Created": { 9 | "Time": "2009-11-10T23:00:00Z", 10 | "Valid": true 11 | }, 12 | "SQLMode": "", 13 | "Definer": "msandbox@%", 14 | "CharacterSetClient": "latin1", 15 | "CollationConnection": "latin1_swedish_ci", 16 | "DatabaseCollation": "utf8mb4_0900_ai_ci" 17 | } 18 | ] -------------------------------------------------------------------------------- /tableparser/testdata/triggers.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /testdata/sakila.film.json: -------------------------------------------------------------------------------- 1 | { 2 | "Schema": "sakila", 3 | "Name": "film", 4 | "Fields": [ 5 | { 6 | "TableCatalog": "def", 7 | "TableSchema": "sakila", 8 | "TableName": "film", 9 | "ColumnName": "film_id", 10 | "OrdinalPosition": 1, 11 | "ColumnDefault": { 12 | "String": "", 13 | "Valid": false 14 | }, 15 | "IsNullable": false, 16 | "DataType": "smallint", 17 | "CharacterMaximumLength": { 18 | "Int64": 0, 19 | "Valid": false 20 | }, 21 | "CharacterOctetLength": { 22 | "Int64": 0, 23 | "Valid": false 24 | }, 25 | "NumericPrecision": { 26 | "Int64": 5, 27 | "Valid": true 28 | }, 29 | "NumericScale": { 30 | "Int64": 0, 31 | "Valid": true 32 | }, 33 | "DatetimePrecision": { 34 | "Int64": 0, 35 | "Valid": false 36 | }, 37 | "CharacterSetName": { 38 | "String": "", 39 | "Valid": false 40 | }, 41 | "CollationName": { 42 | "String": "", 43 | "Valid": false 44 | }, 45 | "ColumnType": "smallint(5) unsigned", 46 | "ColumnKey": "PRI", 47 | "Extra": "auto_increment", 48 | "Privileges": "select,insert,update,references", 49 | "ColumnComment": "", 50 | "GenerationExpression": "", 51 | "SetEnumVals": [], 52 | "Constraint": null 53 | }, 54 | { 55 | "TableCatalog": "def", 56 | "TableSchema": "sakila", 57 | "TableName": "film", 58 | "ColumnName": "title", 59 | "OrdinalPosition": 2, 60 | "ColumnDefault": { 61 | "String": "", 62 | "Valid": false 63 | }, 64 | "IsNullable": false, 65 | "DataType": "varchar", 66 | "CharacterMaximumLength": { 67 | "Int64": 255, 68 | "Valid": true 69 | }, 70 | "CharacterOctetLength": { 71 | "Int64": 765, 72 | "Valid": true 73 | }, 74 | "NumericPrecision": { 75 | "Int64": 0, 76 | "Valid": false 77 | }, 78 | "NumericScale": { 79 | "Int64": 0, 80 | "Valid": false 81 | }, 82 | "DatetimePrecision": { 83 | "Int64": 0, 84 | "Valid": false 85 | }, 86 | "CharacterSetName": { 87 | "String": "utf8", 88 | "Valid": true 89 | }, 90 | "CollationName": { 91 | "String": "utf8_general_ci", 92 | "Valid": true 93 | }, 94 | "ColumnType": "varchar(255)", 95 | "ColumnKey": "MUL", 96 | "Extra": "", 97 | "Privileges": "select,insert,update,references", 98 | "ColumnComment": "", 99 | "GenerationExpression": "", 100 | "SetEnumVals": [], 101 | "Constraint": null 102 | }, 103 | { 104 | "TableCatalog": "def", 105 | "TableSchema": "sakila", 106 | "TableName": "film", 107 | "ColumnName": "description", 108 | "OrdinalPosition": 3, 109 | "ColumnDefault": { 110 | "String": "", 111 | "Valid": false 112 | }, 113 | "IsNullable": true, 114 | "DataType": "text", 115 | "CharacterMaximumLength": { 116 | "Int64": 65535, 117 | "Valid": true 118 | }, 119 | "CharacterOctetLength": { 120 | "Int64": 65535, 121 | "Valid": true 122 | }, 123 | "NumericPrecision": { 124 | "Int64": 0, 125 | "Valid": false 126 | }, 127 | "NumericScale": { 128 | "Int64": 0, 129 | "Valid": false 130 | }, 131 | "DatetimePrecision": { 132 | "Int64": 0, 133 | "Valid": false 134 | }, 135 | "CharacterSetName": { 136 | "String": "utf8", 137 | "Valid": true 138 | }, 139 | "CollationName": { 140 | "String": "utf8_general_ci", 141 | "Valid": true 142 | }, 143 | "ColumnType": "text", 144 | "ColumnKey": "", 145 | "Extra": "", 146 | "Privileges": "select,insert,update,references", 147 | "ColumnComment": "", 148 | "GenerationExpression": "", 149 | "SetEnumVals": [], 150 | "Constraint": null 151 | }, 152 | { 153 | "TableCatalog": "def", 154 | "TableSchema": "sakila", 155 | "TableName": "film", 156 | "ColumnName": "release_year", 157 | "OrdinalPosition": 4, 158 | "ColumnDefault": { 159 | "String": "", 160 | "Valid": false 161 | }, 162 | "IsNullable": true, 163 | "DataType": "year", 164 | "CharacterMaximumLength": { 165 | "Int64": 0, 166 | "Valid": false 167 | }, 168 | "CharacterOctetLength": { 169 | "Int64": 0, 170 | "Valid": false 171 | }, 172 | "NumericPrecision": { 173 | "Int64": 0, 174 | "Valid": false 175 | }, 176 | "NumericScale": { 177 | "Int64": 0, 178 | "Valid": false 179 | }, 180 | "DatetimePrecision": { 181 | "Int64": 0, 182 | "Valid": false 183 | }, 184 | "CharacterSetName": { 185 | "String": "", 186 | "Valid": false 187 | }, 188 | "CollationName": { 189 | "String": "", 190 | "Valid": false 191 | }, 192 | "ColumnType": "year(4)", 193 | "ColumnKey": "", 194 | "Extra": "", 195 | "Privileges": "select,insert,update,references", 196 | "ColumnComment": "", 197 | "GenerationExpression": "", 198 | "SetEnumVals": [], 199 | "Constraint": null 200 | }, 201 | { 202 | "TableCatalog": "def", 203 | "TableSchema": "sakila", 204 | "TableName": "film", 205 | "ColumnName": "language_id", 206 | "OrdinalPosition": 5, 207 | "ColumnDefault": { 208 | "String": "", 209 | "Valid": false 210 | }, 211 | "IsNullable": false, 212 | "DataType": "tinyint", 213 | "CharacterMaximumLength": { 214 | "Int64": 0, 215 | "Valid": false 216 | }, 217 | "CharacterOctetLength": { 218 | "Int64": 0, 219 | "Valid": false 220 | }, 221 | "NumericPrecision": { 222 | "Int64": 3, 223 | "Valid": true 224 | }, 225 | "NumericScale": { 226 | "Int64": 0, 227 | "Valid": true 228 | }, 229 | "DatetimePrecision": { 230 | "Int64": 0, 231 | "Valid": false 232 | }, 233 | "CharacterSetName": { 234 | "String": "", 235 | "Valid": false 236 | }, 237 | "CollationName": { 238 | "String": "", 239 | "Valid": false 240 | }, 241 | "ColumnType": "tinyint(3) unsigned", 242 | "ColumnKey": "MUL", 243 | "Extra": "", 244 | "Privileges": "select,insert,update,references", 245 | "ColumnComment": "", 246 | "GenerationExpression": "", 247 | "SetEnumVals": [], 248 | "Constraint": { 249 | "ConstraintName": "fk_film_language", 250 | "ColumnName": "language_id", 251 | "ReferencedTableSchema": "sakila", 252 | "ReferencedTableName": "language", 253 | "ReferencedColumnName": "language_id" 254 | } 255 | }, 256 | { 257 | "TableCatalog": "def", 258 | "TableSchema": "sakila", 259 | "TableName": "film", 260 | "ColumnName": "original_language_id", 261 | "OrdinalPosition": 6, 262 | "ColumnDefault": { 263 | "String": "", 264 | "Valid": false 265 | }, 266 | "IsNullable": true, 267 | "DataType": "tinyint", 268 | "CharacterMaximumLength": { 269 | "Int64": 0, 270 | "Valid": false 271 | }, 272 | "CharacterOctetLength": { 273 | "Int64": 0, 274 | "Valid": false 275 | }, 276 | "NumericPrecision": { 277 | "Int64": 3, 278 | "Valid": true 279 | }, 280 | "NumericScale": { 281 | "Int64": 0, 282 | "Valid": true 283 | }, 284 | "DatetimePrecision": { 285 | "Int64": 0, 286 | "Valid": false 287 | }, 288 | "CharacterSetName": { 289 | "String": "", 290 | "Valid": false 291 | }, 292 | "CollationName": { 293 | "String": "", 294 | "Valid": false 295 | }, 296 | "ColumnType": "tinyint(3) unsigned", 297 | "ColumnKey": "MUL", 298 | "Extra": "", 299 | "Privileges": "select,insert,update,references", 300 | "ColumnComment": "", 301 | "GenerationExpression": "", 302 | "SetEnumVals": [], 303 | "Constraint": { 304 | "ConstraintName": "fk_film_language_original", 305 | "ColumnName": "original_language_id", 306 | "ReferencedTableSchema": "sakila", 307 | "ReferencedTableName": "language", 308 | "ReferencedColumnName": "language_id" 309 | } 310 | }, 311 | { 312 | "TableCatalog": "def", 313 | "TableSchema": "sakila", 314 | "TableName": "film", 315 | "ColumnName": "rental_duration", 316 | "OrdinalPosition": 7, 317 | "ColumnDefault": { 318 | "String": "3", 319 | "Valid": true 320 | }, 321 | "IsNullable": false, 322 | "DataType": "tinyint", 323 | "CharacterMaximumLength": { 324 | "Int64": 0, 325 | "Valid": false 326 | }, 327 | "CharacterOctetLength": { 328 | "Int64": 0, 329 | "Valid": false 330 | }, 331 | "NumericPrecision": { 332 | "Int64": 3, 333 | "Valid": true 334 | }, 335 | "NumericScale": { 336 | "Int64": 0, 337 | "Valid": true 338 | }, 339 | "DatetimePrecision": { 340 | "Int64": 0, 341 | "Valid": false 342 | }, 343 | "CharacterSetName": { 344 | "String": "", 345 | "Valid": false 346 | }, 347 | "CollationName": { 348 | "String": "", 349 | "Valid": false 350 | }, 351 | "ColumnType": "tinyint(3) unsigned", 352 | "ColumnKey": "", 353 | "Extra": "", 354 | "Privileges": "select,insert,update,references", 355 | "ColumnComment": "", 356 | "GenerationExpression": "", 357 | "SetEnumVals": [], 358 | "Constraint": null 359 | }, 360 | { 361 | "TableCatalog": "def", 362 | "TableSchema": "sakila", 363 | "TableName": "film", 364 | "ColumnName": "rental_rate", 365 | "OrdinalPosition": 8, 366 | "ColumnDefault": { 367 | "String": "4.99", 368 | "Valid": true 369 | }, 370 | "IsNullable": false, 371 | "DataType": "decimal", 372 | "CharacterMaximumLength": { 373 | "Int64": 0, 374 | "Valid": false 375 | }, 376 | "CharacterOctetLength": { 377 | "Int64": 0, 378 | "Valid": false 379 | }, 380 | "NumericPrecision": { 381 | "Int64": 4, 382 | "Valid": true 383 | }, 384 | "NumericScale": { 385 | "Int64": 2, 386 | "Valid": true 387 | }, 388 | "DatetimePrecision": { 389 | "Int64": 0, 390 | "Valid": false 391 | }, 392 | "CharacterSetName": { 393 | "String": "", 394 | "Valid": false 395 | }, 396 | "CollationName": { 397 | "String": "", 398 | "Valid": false 399 | }, 400 | "ColumnType": "decimal(4,2)", 401 | "ColumnKey": "", 402 | "Extra": "", 403 | "Privileges": "select,insert,update,references", 404 | "ColumnComment": "", 405 | "GenerationExpression": "", 406 | "SetEnumVals": [], 407 | "Constraint": null 408 | }, 409 | { 410 | "TableCatalog": "def", 411 | "TableSchema": "sakila", 412 | "TableName": "film", 413 | "ColumnName": "length", 414 | "OrdinalPosition": 9, 415 | "ColumnDefault": { 416 | "String": "", 417 | "Valid": false 418 | }, 419 | "IsNullable": true, 420 | "DataType": "smallint", 421 | "CharacterMaximumLength": { 422 | "Int64": 0, 423 | "Valid": false 424 | }, 425 | "CharacterOctetLength": { 426 | "Int64": 0, 427 | "Valid": false 428 | }, 429 | "NumericPrecision": { 430 | "Int64": 5, 431 | "Valid": true 432 | }, 433 | "NumericScale": { 434 | "Int64": 0, 435 | "Valid": true 436 | }, 437 | "DatetimePrecision": { 438 | "Int64": 0, 439 | "Valid": false 440 | }, 441 | "CharacterSetName": { 442 | "String": "", 443 | "Valid": false 444 | }, 445 | "CollationName": { 446 | "String": "", 447 | "Valid": false 448 | }, 449 | "ColumnType": "smallint(5) unsigned", 450 | "ColumnKey": "", 451 | "Extra": "", 452 | "Privileges": "select,insert,update,references", 453 | "ColumnComment": "", 454 | "GenerationExpression": "", 455 | "SetEnumVals": [], 456 | "Constraint": null 457 | }, 458 | { 459 | "TableCatalog": "def", 460 | "TableSchema": "sakila", 461 | "TableName": "film", 462 | "ColumnName": "replacement_cost", 463 | "OrdinalPosition": 10, 464 | "ColumnDefault": { 465 | "String": "19.99", 466 | "Valid": true 467 | }, 468 | "IsNullable": false, 469 | "DataType": "decimal", 470 | "CharacterMaximumLength": { 471 | "Int64": 0, 472 | "Valid": false 473 | }, 474 | "CharacterOctetLength": { 475 | "Int64": 0, 476 | "Valid": false 477 | }, 478 | "NumericPrecision": { 479 | "Int64": 5, 480 | "Valid": true 481 | }, 482 | "NumericScale": { 483 | "Int64": 2, 484 | "Valid": true 485 | }, 486 | "DatetimePrecision": { 487 | "Int64": 0, 488 | "Valid": false 489 | }, 490 | "CharacterSetName": { 491 | "String": "", 492 | "Valid": false 493 | }, 494 | "CollationName": { 495 | "String": "", 496 | "Valid": false 497 | }, 498 | "ColumnType": "decimal(5,2)", 499 | "ColumnKey": "", 500 | "Extra": "", 501 | "Privileges": "select,insert,update,references", 502 | "ColumnComment": "", 503 | "GenerationExpression": "", 504 | "SetEnumVals": [], 505 | "Constraint": null 506 | }, 507 | { 508 | "TableCatalog": "def", 509 | "TableSchema": "sakila", 510 | "TableName": "film", 511 | "ColumnName": "rating", 512 | "OrdinalPosition": 11, 513 | "ColumnDefault": { 514 | "String": "G", 515 | "Valid": true 516 | }, 517 | "IsNullable": true, 518 | "DataType": "enum", 519 | "CharacterMaximumLength": { 520 | "Int64": 5, 521 | "Valid": true 522 | }, 523 | "CharacterOctetLength": { 524 | "Int64": 15, 525 | "Valid": true 526 | }, 527 | "NumericPrecision": { 528 | "Int64": 0, 529 | "Valid": false 530 | }, 531 | "NumericScale": { 532 | "Int64": 0, 533 | "Valid": false 534 | }, 535 | "DatetimePrecision": { 536 | "Int64": 0, 537 | "Valid": false 538 | }, 539 | "CharacterSetName": { 540 | "String": "utf8", 541 | "Valid": true 542 | }, 543 | "CollationName": { 544 | "String": "utf8_general_ci", 545 | "Valid": true 546 | }, 547 | "ColumnType": "enum('G','PG','PG-13','R','NC-17')", 548 | "ColumnKey": "", 549 | "Extra": "", 550 | "Privileges": "select,insert,update,references", 551 | "ColumnComment": "", 552 | "GenerationExpression": "", 553 | "SetEnumVals": [ 554 | "G", 555 | "PG", 556 | "PG-13", 557 | "R", 558 | "NC-17" 559 | ], 560 | "Constraint": null 561 | }, 562 | { 563 | "TableCatalog": "def", 564 | "TableSchema": "sakila", 565 | "TableName": "film", 566 | "ColumnName": "special_features", 567 | "OrdinalPosition": 12, 568 | "ColumnDefault": { 569 | "String": "", 570 | "Valid": false 571 | }, 572 | "IsNullable": true, 573 | "DataType": "set", 574 | "CharacterMaximumLength": { 575 | "Int64": 54, 576 | "Valid": true 577 | }, 578 | "CharacterOctetLength": { 579 | "Int64": 162, 580 | "Valid": true 581 | }, 582 | "NumericPrecision": { 583 | "Int64": 0, 584 | "Valid": false 585 | }, 586 | "NumericScale": { 587 | "Int64": 0, 588 | "Valid": false 589 | }, 590 | "DatetimePrecision": { 591 | "Int64": 0, 592 | "Valid": false 593 | }, 594 | "CharacterSetName": { 595 | "String": "utf8", 596 | "Valid": true 597 | }, 598 | "CollationName": { 599 | "String": "utf8_general_ci", 600 | "Valid": true 601 | }, 602 | "ColumnType": "set('Trailers','Commentaries','Deleted Scenes','Behind the Scenes')", 603 | "ColumnKey": "", 604 | "Extra": "", 605 | "Privileges": "select,insert,update,references", 606 | "ColumnComment": "", 607 | "GenerationExpression": "", 608 | "SetEnumVals": [ 609 | "Trailers", 610 | "Commentaries", 611 | "Deleted Scenes", 612 | "Behind the Scenes" 613 | ], 614 | "Constraint": null 615 | }, 616 | { 617 | "TableCatalog": "def", 618 | "TableSchema": "sakila", 619 | "TableName": "film", 620 | "ColumnName": "last_update", 621 | "OrdinalPosition": 13, 622 | "ColumnDefault": { 623 | "String": "CURRENT_TIMESTAMP", 624 | "Valid": true 625 | }, 626 | "IsNullable": false, 627 | "DataType": "timestamp", 628 | "CharacterMaximumLength": { 629 | "Int64": 0, 630 | "Valid": false 631 | }, 632 | "CharacterOctetLength": { 633 | "Int64": 0, 634 | "Valid": false 635 | }, 636 | "NumericPrecision": { 637 | "Int64": 0, 638 | "Valid": false 639 | }, 640 | "NumericScale": { 641 | "Int64": 0, 642 | "Valid": false 643 | }, 644 | "DatetimePrecision": { 645 | "Int64": 0, 646 | "Valid": true 647 | }, 648 | "CharacterSetName": { 649 | "String": "", 650 | "Valid": false 651 | }, 652 | "CollationName": { 653 | "String": "", 654 | "Valid": false 655 | }, 656 | "ColumnType": "timestamp", 657 | "ColumnKey": "", 658 | "Extra": "on update CURRENT_TIMESTAMP", 659 | "Privileges": "select,insert,update,references", 660 | "ColumnComment": "", 661 | "GenerationExpression": "", 662 | "SetEnumVals": [], 663 | "Constraint": null 664 | } 665 | ], 666 | "Indexes": { 667 | "PRIMARY": { 668 | "Name": "PRIMARY", 669 | "Unique": true, 670 | "Fields": [ 671 | "film_id" 672 | ] 673 | }, 674 | "idx_fk_language_id": { 675 | "Name": "idx_fk_language_id", 676 | "Unique": false, 677 | "Fields": [ 678 | "language_id" 679 | ] 680 | }, 681 | "idx_fk_original_language_id": { 682 | "Name": "idx_fk_original_language_id", 683 | "Unique": false, 684 | "Fields": [ 685 | "original_language_id" 686 | ] 687 | }, 688 | "idx_title": { 689 | "Name": "idx_title", 690 | "Unique": false, 691 | "Fields": [ 692 | "title" 693 | ] 694 | } 695 | }, 696 | "Constraints": [ 697 | { 698 | "ConstraintName": "fk_film_language", 699 | "ColumnName": "language_id", 700 | "ReferencedTableSchema": "sakila", 701 | "ReferencedTableName": "language", 702 | "ReferencedColumnName": "language_id" 703 | }, 704 | { 705 | "ConstraintName": "fk_film_language_original", 706 | "ColumnName": "original_language_id", 707 | "ReferencedTableSchema": "sakila", 708 | "ReferencedTableName": "language", 709 | "ReferencedColumnName": "language_id" 710 | } 711 | ], 712 | "Triggers": [] 713 | } -------------------------------------------------------------------------------- /testdata/schema/sakila.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Percona-Lab/mysql_random_data_load/de8eef03fddd476b8c197a4b54c13889efc91ea8/testdata/schema/sakila.sql -------------------------------------------------------------------------------- /tests/rental.json: -------------------------------------------------------------------------------- 1 | { 2 | "Schema": "sakila", 3 | "Name": "rental", 4 | "Fields": [ 5 | { 6 | "TableCatalog": "def", 7 | "TableSchema": "sakila", 8 | "TableName": "rental", 9 | "ColumnName": "rental_id", 10 | "OrdinalPosition": 1, 11 | "ColumnDefault": { 12 | "String": "", 13 | "Valid": false 14 | }, 15 | "IsNullable": false, 16 | "DataType": "int", 17 | "CharacterMaximumLength": { 18 | "Int64": 0, 19 | "Valid": false 20 | }, 21 | "CharacterOctetLength": { 22 | "Int64": 0, 23 | "Valid": false 24 | }, 25 | "NumericPrecision": { 26 | "Int64": 10, 27 | "Valid": true 28 | }, 29 | "NumericScale": { 30 | "Int64": 0, 31 | "Valid": true 32 | }, 33 | "DatetimePrecision": { 34 | "Int64": 0, 35 | "Valid": false 36 | }, 37 | "CharacterSetName": { 38 | "String": "", 39 | "Valid": false 40 | }, 41 | "CollationName": { 42 | "String": "", 43 | "Valid": false 44 | }, 45 | "ColumnType": "int(11)", 46 | "ColumnKey": "PRI", 47 | "Extra": "auto_increment", 48 | "Privileges": "select,insert,update,references", 49 | "ColumnComment": "", 50 | "GenerationExpression": "", 51 | "SetEnumVals": [], 52 | "Constraint": null 53 | }, 54 | { 55 | "TableCatalog": "def", 56 | "TableSchema": "sakila", 57 | "TableName": "rental", 58 | "ColumnName": "rental_date", 59 | "OrdinalPosition": 2, 60 | "ColumnDefault": { 61 | "String": "", 62 | "Valid": false 63 | }, 64 | "IsNullable": false, 65 | "DataType": "datetime", 66 | "CharacterMaximumLength": { 67 | "Int64": 0, 68 | "Valid": false 69 | }, 70 | "CharacterOctetLength": { 71 | "Int64": 0, 72 | "Valid": false 73 | }, 74 | "NumericPrecision": { 75 | "Int64": 0, 76 | "Valid": false 77 | }, 78 | "NumericScale": { 79 | "Int64": 0, 80 | "Valid": false 81 | }, 82 | "DatetimePrecision": { 83 | "Int64": 0, 84 | "Valid": true 85 | }, 86 | "CharacterSetName": { 87 | "String": "", 88 | "Valid": false 89 | }, 90 | "CollationName": { 91 | "String": "", 92 | "Valid": false 93 | }, 94 | "ColumnType": "datetime", 95 | "ColumnKey": "MUL", 96 | "Extra": "", 97 | "Privileges": "select,insert,update,references", 98 | "ColumnComment": "", 99 | "GenerationExpression": "", 100 | "SetEnumVals": [], 101 | "Constraint": null 102 | }, 103 | { 104 | "TableCatalog": "def", 105 | "TableSchema": "sakila", 106 | "TableName": "rental", 107 | "ColumnName": "inventory_id", 108 | "OrdinalPosition": 3, 109 | "ColumnDefault": { 110 | "String": "", 111 | "Valid": false 112 | }, 113 | "IsNullable": false, 114 | "DataType": "mediumint", 115 | "CharacterMaximumLength": { 116 | "Int64": 0, 117 | "Valid": false 118 | }, 119 | "CharacterOctetLength": { 120 | "Int64": 0, 121 | "Valid": false 122 | }, 123 | "NumericPrecision": { 124 | "Int64": 7, 125 | "Valid": true 126 | }, 127 | "NumericScale": { 128 | "Int64": 0, 129 | "Valid": true 130 | }, 131 | "DatetimePrecision": { 132 | "Int64": 0, 133 | "Valid": false 134 | }, 135 | "CharacterSetName": { 136 | "String": "", 137 | "Valid": false 138 | }, 139 | "CollationName": { 140 | "String": "", 141 | "Valid": false 142 | }, 143 | "ColumnType": "mediumint(8) unsigned", 144 | "ColumnKey": "MUL", 145 | "Extra": "", 146 | "Privileges": "select,insert,update,references", 147 | "ColumnComment": "", 148 | "GenerationExpression": "", 149 | "SetEnumVals": [], 150 | "Constraint": { 151 | "ConstraintName": "fk_rental_inventory", 152 | "ColumnName": "inventory_id", 153 | "ReferencedTableSchema": "sakila", 154 | "ReferencedTableName": "inventory", 155 | "ReferencedColumnName": "inventory_id" 156 | } 157 | }, 158 | { 159 | "TableCatalog": "def", 160 | "TableSchema": "sakila", 161 | "TableName": "rental", 162 | "ColumnName": "customer_id", 163 | "OrdinalPosition": 4, 164 | "ColumnDefault": { 165 | "String": "", 166 | "Valid": false 167 | }, 168 | "IsNullable": false, 169 | "DataType": "smallint", 170 | "CharacterMaximumLength": { 171 | "Int64": 0, 172 | "Valid": false 173 | }, 174 | "CharacterOctetLength": { 175 | "Int64": 0, 176 | "Valid": false 177 | }, 178 | "NumericPrecision": { 179 | "Int64": 5, 180 | "Valid": true 181 | }, 182 | "NumericScale": { 183 | "Int64": 0, 184 | "Valid": true 185 | }, 186 | "DatetimePrecision": { 187 | "Int64": 0, 188 | "Valid": false 189 | }, 190 | "CharacterSetName": { 191 | "String": "", 192 | "Valid": false 193 | }, 194 | "CollationName": { 195 | "String": "", 196 | "Valid": false 197 | }, 198 | "ColumnType": "smallint(5) unsigned", 199 | "ColumnKey": "MUL", 200 | "Extra": "", 201 | "Privileges": "select,insert,update,references", 202 | "ColumnComment": "", 203 | "GenerationExpression": "", 204 | "SetEnumVals": [], 205 | "Constraint": { 206 | "ConstraintName": "fk_rental_customer", 207 | "ColumnName": "customer_id", 208 | "ReferencedTableSchema": "sakila", 209 | "ReferencedTableName": "customer", 210 | "ReferencedColumnName": "customer_id" 211 | } 212 | }, 213 | { 214 | "TableCatalog": "def", 215 | "TableSchema": "sakila", 216 | "TableName": "rental", 217 | "ColumnName": "return_date", 218 | "OrdinalPosition": 5, 219 | "ColumnDefault": { 220 | "String": "", 221 | "Valid": false 222 | }, 223 | "IsNullable": true, 224 | "DataType": "datetime", 225 | "CharacterMaximumLength": { 226 | "Int64": 0, 227 | "Valid": false 228 | }, 229 | "CharacterOctetLength": { 230 | "Int64": 0, 231 | "Valid": false 232 | }, 233 | "NumericPrecision": { 234 | "Int64": 0, 235 | "Valid": false 236 | }, 237 | "NumericScale": { 238 | "Int64": 0, 239 | "Valid": false 240 | }, 241 | "DatetimePrecision": { 242 | "Int64": 0, 243 | "Valid": true 244 | }, 245 | "CharacterSetName": { 246 | "String": "", 247 | "Valid": false 248 | }, 249 | "CollationName": { 250 | "String": "", 251 | "Valid": false 252 | }, 253 | "ColumnType": "datetime", 254 | "ColumnKey": "", 255 | "Extra": "", 256 | "Privileges": "select,insert,update,references", 257 | "ColumnComment": "", 258 | "GenerationExpression": "", 259 | "SetEnumVals": [], 260 | "Constraint": null 261 | }, 262 | { 263 | "TableCatalog": "def", 264 | "TableSchema": "sakila", 265 | "TableName": "rental", 266 | "ColumnName": "staff_id", 267 | "OrdinalPosition": 6, 268 | "ColumnDefault": { 269 | "String": "", 270 | "Valid": false 271 | }, 272 | "IsNullable": false, 273 | "DataType": "tinyint", 274 | "CharacterMaximumLength": { 275 | "Int64": 0, 276 | "Valid": false 277 | }, 278 | "CharacterOctetLength": { 279 | "Int64": 0, 280 | "Valid": false 281 | }, 282 | "NumericPrecision": { 283 | "Int64": 3, 284 | "Valid": true 285 | }, 286 | "NumericScale": { 287 | "Int64": 0, 288 | "Valid": true 289 | }, 290 | "DatetimePrecision": { 291 | "Int64": 0, 292 | "Valid": false 293 | }, 294 | "CharacterSetName": { 295 | "String": "", 296 | "Valid": false 297 | }, 298 | "CollationName": { 299 | "String": "", 300 | "Valid": false 301 | }, 302 | "ColumnType": "tinyint(3) unsigned", 303 | "ColumnKey": "MUL", 304 | "Extra": "", 305 | "Privileges": "select,insert,update,references", 306 | "ColumnComment": "", 307 | "GenerationExpression": "", 308 | "SetEnumVals": [], 309 | "Constraint": { 310 | "ConstraintName": "fk_rental_staff", 311 | "ColumnName": "staff_id", 312 | "ReferencedTableSchema": "sakila", 313 | "ReferencedTableName": "staff", 314 | "ReferencedColumnName": "staff_id" 315 | } 316 | }, 317 | { 318 | "TableCatalog": "def", 319 | "TableSchema": "sakila", 320 | "TableName": "rental", 321 | "ColumnName": "last_update", 322 | "OrdinalPosition": 7, 323 | "ColumnDefault": { 324 | "String": "CURRENT_TIMESTAMP", 325 | "Valid": true 326 | }, 327 | "IsNullable": false, 328 | "DataType": "timestamp", 329 | "CharacterMaximumLength": { 330 | "Int64": 0, 331 | "Valid": false 332 | }, 333 | "CharacterOctetLength": { 334 | "Int64": 0, 335 | "Valid": false 336 | }, 337 | "NumericPrecision": { 338 | "Int64": 0, 339 | "Valid": false 340 | }, 341 | "NumericScale": { 342 | "Int64": 0, 343 | "Valid": false 344 | }, 345 | "DatetimePrecision": { 346 | "Int64": 0, 347 | "Valid": true 348 | }, 349 | "CharacterSetName": { 350 | "String": "", 351 | "Valid": false 352 | }, 353 | "CollationName": { 354 | "String": "", 355 | "Valid": false 356 | }, 357 | "ColumnType": "timestamp", 358 | "ColumnKey": "", 359 | "Extra": "on update CURRENT_TIMESTAMP", 360 | "Privileges": "select,insert,update,references", 361 | "ColumnComment": "", 362 | "GenerationExpression": "", 363 | "SetEnumVals": [], 364 | "Constraint": null 365 | } 366 | ], 367 | "Indexes": [ 368 | { 369 | "NonUnique": false, 370 | "KeyName": "PRIMARY", 371 | "SeqInIndex": 1, 372 | "ColumnName": "rental_id", 373 | "Collation": "A", 374 | "Cardinality": 137529, 375 | "SubPart": { 376 | "Int64": 0, 377 | "Valid": false 378 | }, 379 | "Packed": { 380 | "String": "", 381 | "Valid": false 382 | }, 383 | "Null": "", 384 | "IndexType": "BTREE", 385 | "Comment": "", 386 | "IndexComment": "" 387 | }, 388 | { 389 | "NonUnique": false, 390 | "KeyName": "rental_date", 391 | "SeqInIndex": 1, 392 | "ColumnName": "rental_date", 393 | "Collation": "A", 394 | "Cardinality": 88, 395 | "SubPart": { 396 | "Int64": 0, 397 | "Valid": false 398 | }, 399 | "Packed": { 400 | "String": "", 401 | "Valid": false 402 | }, 403 | "Null": "", 404 | "IndexType": "BTREE", 405 | "Comment": "", 406 | "IndexComment": "" 407 | }, 408 | { 409 | "NonUnique": false, 410 | "KeyName": "rental_date", 411 | "SeqInIndex": 2, 412 | "ColumnName": "inventory_id", 413 | "Collation": "A", 414 | "Cardinality": 5129, 415 | "SubPart": { 416 | "Int64": 0, 417 | "Valid": false 418 | }, 419 | "Packed": { 420 | "String": "", 421 | "Valid": false 422 | }, 423 | "Null": "", 424 | "IndexType": "BTREE", 425 | "Comment": "", 426 | "IndexComment": "" 427 | }, 428 | { 429 | "NonUnique": false, 430 | "KeyName": "rental_date", 431 | "SeqInIndex": 3, 432 | "ColumnName": "customer_id", 433 | "Collation": "A", 434 | "Cardinality": 139228, 435 | "SubPart": { 436 | "Int64": 0, 437 | "Valid": false 438 | }, 439 | "Packed": { 440 | "String": "", 441 | "Valid": false 442 | }, 443 | "Null": "", 444 | "IndexType": "BTREE", 445 | "Comment": "", 446 | "IndexComment": "" 447 | }, 448 | { 449 | "NonUnique": true, 450 | "KeyName": "idx_fk_inventory_id", 451 | "SeqInIndex": 1, 452 | "ColumnName": "inventory_id", 453 | "Collation": "A", 454 | "Cardinality": 270, 455 | "SubPart": { 456 | "Int64": 0, 457 | "Valid": false 458 | }, 459 | "Packed": { 460 | "String": "", 461 | "Valid": false 462 | }, 463 | "Null": "", 464 | "IndexType": "BTREE", 465 | "Comment": "", 466 | "IndexComment": "" 467 | }, 468 | { 469 | "NonUnique": true, 470 | "KeyName": "idx_fk_customer_id", 471 | "SeqInIndex": 1, 472 | "ColumnName": "customer_id", 473 | "Collation": "A", 474 | "Cardinality": 381, 475 | "SubPart": { 476 | "Int64": 0, 477 | "Valid": false 478 | }, 479 | "Packed": { 480 | "String": "", 481 | "Valid": false 482 | }, 483 | "Null": "", 484 | "IndexType": "BTREE", 485 | "Comment": "", 486 | "IndexComment": "" 487 | }, 488 | { 489 | "NonUnique": true, 490 | "KeyName": "idx_fk_staff_id", 491 | "SeqInIndex": 1, 492 | "ColumnName": "staff_id", 493 | "Collation": "A", 494 | "Cardinality": 1, 495 | "SubPart": { 496 | "Int64": 0, 497 | "Valid": false 498 | }, 499 | "Packed": { 500 | "String": "", 501 | "Valid": false 502 | }, 503 | "Null": "", 504 | "IndexType": "BTREE", 505 | "Comment": "", 506 | "IndexComment": "" 507 | } 508 | ], 509 | "Constraints": [ 510 | { 511 | "ConstraintName": "fk_rental_customer", 512 | "ColumnName": "customer_id", 513 | "ReferencedTableSchema": "sakila", 514 | "ReferencedTableName": "customer", 515 | "ReferencedColumnName": "customer_id" 516 | }, 517 | { 518 | "ConstraintName": "fk_rental_inventory", 519 | "ColumnName": "inventory_id", 520 | "ReferencedTableSchema": "sakila", 521 | "ReferencedTableName": "inventory", 522 | "ReferencedColumnName": "inventory_id" 523 | }, 524 | { 525 | "ConstraintName": "fk_rental_staff", 526 | "ColumnName": "staff_id", 527 | "ReferencedTableSchema": "sakila", 528 | "ReferencedTableName": "staff", 529 | "ReferencedColumnName": "staff_id" 530 | } 531 | ] 532 | } -------------------------------------------------------------------------------- /tests/table001.json: -------------------------------------------------------------------------------- 1 | { 2 | "Schema": "sakila", 3 | "Name": "film", 4 | "Fields": [ 5 | { 6 | "TableCatalog": "def", 7 | "TableSchema": "sakila", 8 | "TableName": "film", 9 | "ColumnName": "film_id", 10 | "OrdinalPosition": 1, 11 | "ColumnDefault": { 12 | "String": "", 13 | "Valid": false 14 | }, 15 | "IsNullable": false, 16 | "DataType": "smallint", 17 | "CharacterMaximumLength": { 18 | "Int64": 0, 19 | "Valid": false 20 | }, 21 | "CharacterOctetLength": { 22 | "Int64": 0, 23 | "Valid": false 24 | }, 25 | "NumericPrecision": { 26 | "Int64": 5, 27 | "Valid": true 28 | }, 29 | "NumericScale": { 30 | "Int64": 0, 31 | "Valid": true 32 | }, 33 | "DatetimePrecision": { 34 | "Int64": 0, 35 | "Valid": false 36 | }, 37 | "CharacterSetName": { 38 | "String": "", 39 | "Valid": false 40 | }, 41 | "CollationName": { 42 | "String": "", 43 | "Valid": false 44 | }, 45 | "ColumnType": "smallint(5) unsigned", 46 | "ColumnKey": "PRI", 47 | "Extra": "auto_increment", 48 | "Privileges": "select,insert,update,references", 49 | "ColumnComment": "", 50 | "GenerationExpression": "", 51 | "SetEnumVals": [], 52 | "Constraint": null 53 | }, 54 | { 55 | "TableCatalog": "def", 56 | "TableSchema": "sakila", 57 | "TableName": "film", 58 | "ColumnName": "title", 59 | "OrdinalPosition": 2, 60 | "ColumnDefault": { 61 | "String": "", 62 | "Valid": false 63 | }, 64 | "IsNullable": false, 65 | "DataType": "varchar", 66 | "CharacterMaximumLength": { 67 | "Int64": 255, 68 | "Valid": true 69 | }, 70 | "CharacterOctetLength": { 71 | "Int64": 765, 72 | "Valid": true 73 | }, 74 | "NumericPrecision": { 75 | "Int64": 0, 76 | "Valid": false 77 | }, 78 | "NumericScale": { 79 | "Int64": 0, 80 | "Valid": false 81 | }, 82 | "DatetimePrecision": { 83 | "Int64": 0, 84 | "Valid": false 85 | }, 86 | "CharacterSetName": { 87 | "String": "utf8", 88 | "Valid": true 89 | }, 90 | "CollationName": { 91 | "String": "utf8_general_ci", 92 | "Valid": true 93 | }, 94 | "ColumnType": "varchar(255)", 95 | "ColumnKey": "MUL", 96 | "Extra": "", 97 | "Privileges": "select,insert,update,references", 98 | "ColumnComment": "", 99 | "GenerationExpression": "", 100 | "SetEnumVals": [], 101 | "Constraint": null 102 | }, 103 | { 104 | "TableCatalog": "def", 105 | "TableSchema": "sakila", 106 | "TableName": "film", 107 | "ColumnName": "description", 108 | "OrdinalPosition": 3, 109 | "ColumnDefault": { 110 | "String": "", 111 | "Valid": false 112 | }, 113 | "IsNullable": true, 114 | "DataType": "text", 115 | "CharacterMaximumLength": { 116 | "Int64": 65535, 117 | "Valid": true 118 | }, 119 | "CharacterOctetLength": { 120 | "Int64": 65535, 121 | "Valid": true 122 | }, 123 | "NumericPrecision": { 124 | "Int64": 0, 125 | "Valid": false 126 | }, 127 | "NumericScale": { 128 | "Int64": 0, 129 | "Valid": false 130 | }, 131 | "DatetimePrecision": { 132 | "Int64": 0, 133 | "Valid": false 134 | }, 135 | "CharacterSetName": { 136 | "String": "utf8", 137 | "Valid": true 138 | }, 139 | "CollationName": { 140 | "String": "utf8_general_ci", 141 | "Valid": true 142 | }, 143 | "ColumnType": "text", 144 | "ColumnKey": "", 145 | "Extra": "", 146 | "Privileges": "select,insert,update,references", 147 | "ColumnComment": "", 148 | "GenerationExpression": "", 149 | "SetEnumVals": [], 150 | "Constraint": null 151 | }, 152 | { 153 | "TableCatalog": "def", 154 | "TableSchema": "sakila", 155 | "TableName": "film", 156 | "ColumnName": "release_year", 157 | "OrdinalPosition": 4, 158 | "ColumnDefault": { 159 | "String": "", 160 | "Valid": false 161 | }, 162 | "IsNullable": true, 163 | "DataType": "year", 164 | "CharacterMaximumLength": { 165 | "Int64": 0, 166 | "Valid": false 167 | }, 168 | "CharacterOctetLength": { 169 | "Int64": 0, 170 | "Valid": false 171 | }, 172 | "NumericPrecision": { 173 | "Int64": 0, 174 | "Valid": false 175 | }, 176 | "NumericScale": { 177 | "Int64": 0, 178 | "Valid": false 179 | }, 180 | "DatetimePrecision": { 181 | "Int64": 0, 182 | "Valid": false 183 | }, 184 | "CharacterSetName": { 185 | "String": "", 186 | "Valid": false 187 | }, 188 | "CollationName": { 189 | "String": "", 190 | "Valid": false 191 | }, 192 | "ColumnType": "year(4)", 193 | "ColumnKey": "", 194 | "Extra": "", 195 | "Privileges": "select,insert,update,references", 196 | "ColumnComment": "", 197 | "GenerationExpression": "", 198 | "SetEnumVals": [], 199 | "Constraint": null 200 | }, 201 | { 202 | "TableCatalog": "def", 203 | "TableSchema": "sakila", 204 | "TableName": "film", 205 | "ColumnName": "language_id", 206 | "OrdinalPosition": 5, 207 | "ColumnDefault": { 208 | "String": "", 209 | "Valid": false 210 | }, 211 | "IsNullable": false, 212 | "DataType": "tinyint", 213 | "CharacterMaximumLength": { 214 | "Int64": 0, 215 | "Valid": false 216 | }, 217 | "CharacterOctetLength": { 218 | "Int64": 0, 219 | "Valid": false 220 | }, 221 | "NumericPrecision": { 222 | "Int64": 3, 223 | "Valid": true 224 | }, 225 | "NumericScale": { 226 | "Int64": 0, 227 | "Valid": true 228 | }, 229 | "DatetimePrecision": { 230 | "Int64": 0, 231 | "Valid": false 232 | }, 233 | "CharacterSetName": { 234 | "String": "", 235 | "Valid": false 236 | }, 237 | "CollationName": { 238 | "String": "", 239 | "Valid": false 240 | }, 241 | "ColumnType": "tinyint(3) unsigned", 242 | "ColumnKey": "MUL", 243 | "Extra": "", 244 | "Privileges": "select,insert,update,references", 245 | "ColumnComment": "", 246 | "GenerationExpression": "", 247 | "SetEnumVals": [], 248 | "Constraint": { 249 | "ConstraintName": "fk_film_language_original", 250 | "ColumnName": "original_language_id", 251 | "ReferencedTableSchema": "sakila", 252 | "ReferencedTableName": "language", 253 | "ReferencedColumnName": "language_id" 254 | } 255 | }, 256 | { 257 | "TableCatalog": "def", 258 | "TableSchema": "sakila", 259 | "TableName": "film", 260 | "ColumnName": "original_language_id", 261 | "OrdinalPosition": 6, 262 | "ColumnDefault": { 263 | "String": "", 264 | "Valid": false 265 | }, 266 | "IsNullable": true, 267 | "DataType": "tinyint", 268 | "CharacterMaximumLength": { 269 | "Int64": 0, 270 | "Valid": false 271 | }, 272 | "CharacterOctetLength": { 273 | "Int64": 0, 274 | "Valid": false 275 | }, 276 | "NumericPrecision": { 277 | "Int64": 3, 278 | "Valid": true 279 | }, 280 | "NumericScale": { 281 | "Int64": 0, 282 | "Valid": true 283 | }, 284 | "DatetimePrecision": { 285 | "Int64": 0, 286 | "Valid": false 287 | }, 288 | "CharacterSetName": { 289 | "String": "", 290 | "Valid": false 291 | }, 292 | "CollationName": { 293 | "String": "", 294 | "Valid": false 295 | }, 296 | "ColumnType": "tinyint(3) unsigned", 297 | "ColumnKey": "MUL", 298 | "Extra": "", 299 | "Privileges": "select,insert,update,references", 300 | "ColumnComment": "", 301 | "GenerationExpression": "", 302 | "SetEnumVals": [], 303 | "Constraint": { 304 | "ConstraintName": "fk_film_language_original", 305 | "ColumnName": "original_language_id", 306 | "ReferencedTableSchema": "sakila", 307 | "ReferencedTableName": "language", 308 | "ReferencedColumnName": "language_id" 309 | } 310 | }, 311 | { 312 | "TableCatalog": "def", 313 | "TableSchema": "sakila", 314 | "TableName": "film", 315 | "ColumnName": "rental_duration", 316 | "OrdinalPosition": 7, 317 | "ColumnDefault": { 318 | "String": "3", 319 | "Valid": true 320 | }, 321 | "IsNullable": false, 322 | "DataType": "tinyint", 323 | "CharacterMaximumLength": { 324 | "Int64": 0, 325 | "Valid": false 326 | }, 327 | "CharacterOctetLength": { 328 | "Int64": 0, 329 | "Valid": false 330 | }, 331 | "NumericPrecision": { 332 | "Int64": 3, 333 | "Valid": true 334 | }, 335 | "NumericScale": { 336 | "Int64": 0, 337 | "Valid": true 338 | }, 339 | "DatetimePrecision": { 340 | "Int64": 0, 341 | "Valid": false 342 | }, 343 | "CharacterSetName": { 344 | "String": "", 345 | "Valid": false 346 | }, 347 | "CollationName": { 348 | "String": "", 349 | "Valid": false 350 | }, 351 | "ColumnType": "tinyint(3) unsigned", 352 | "ColumnKey": "", 353 | "Extra": "", 354 | "Privileges": "select,insert,update,references", 355 | "ColumnComment": "", 356 | "GenerationExpression": "", 357 | "SetEnumVals": [], 358 | "Constraint": null 359 | }, 360 | { 361 | "TableCatalog": "def", 362 | "TableSchema": "sakila", 363 | "TableName": "film", 364 | "ColumnName": "rental_rate", 365 | "OrdinalPosition": 8, 366 | "ColumnDefault": { 367 | "String": "4.99", 368 | "Valid": true 369 | }, 370 | "IsNullable": false, 371 | "DataType": "decimal", 372 | "CharacterMaximumLength": { 373 | "Int64": 0, 374 | "Valid": false 375 | }, 376 | "CharacterOctetLength": { 377 | "Int64": 0, 378 | "Valid": false 379 | }, 380 | "NumericPrecision": { 381 | "Int64": 4, 382 | "Valid": true 383 | }, 384 | "NumericScale": { 385 | "Int64": 2, 386 | "Valid": true 387 | }, 388 | "DatetimePrecision": { 389 | "Int64": 0, 390 | "Valid": false 391 | }, 392 | "CharacterSetName": { 393 | "String": "", 394 | "Valid": false 395 | }, 396 | "CollationName": { 397 | "String": "", 398 | "Valid": false 399 | }, 400 | "ColumnType": "decimal(4,2)", 401 | "ColumnKey": "", 402 | "Extra": "", 403 | "Privileges": "select,insert,update,references", 404 | "ColumnComment": "", 405 | "GenerationExpression": "", 406 | "SetEnumVals": [], 407 | "Constraint": null 408 | }, 409 | { 410 | "TableCatalog": "def", 411 | "TableSchema": "sakila", 412 | "TableName": "film", 413 | "ColumnName": "length", 414 | "OrdinalPosition": 9, 415 | "ColumnDefault": { 416 | "String": "", 417 | "Valid": false 418 | }, 419 | "IsNullable": true, 420 | "DataType": "smallint", 421 | "CharacterMaximumLength": { 422 | "Int64": 0, 423 | "Valid": false 424 | }, 425 | "CharacterOctetLength": { 426 | "Int64": 0, 427 | "Valid": false 428 | }, 429 | "NumericPrecision": { 430 | "Int64": 5, 431 | "Valid": true 432 | }, 433 | "NumericScale": { 434 | "Int64": 0, 435 | "Valid": true 436 | }, 437 | "DatetimePrecision": { 438 | "Int64": 0, 439 | "Valid": false 440 | }, 441 | "CharacterSetName": { 442 | "String": "", 443 | "Valid": false 444 | }, 445 | "CollationName": { 446 | "String": "", 447 | "Valid": false 448 | }, 449 | "ColumnType": "smallint(5) unsigned", 450 | "ColumnKey": "", 451 | "Extra": "", 452 | "Privileges": "select,insert,update,references", 453 | "ColumnComment": "", 454 | "GenerationExpression": "", 455 | "SetEnumVals": [], 456 | "Constraint": null 457 | }, 458 | { 459 | "TableCatalog": "def", 460 | "TableSchema": "sakila", 461 | "TableName": "film", 462 | "ColumnName": "replacement_cost", 463 | "OrdinalPosition": 10, 464 | "ColumnDefault": { 465 | "String": "19.99", 466 | "Valid": true 467 | }, 468 | "IsNullable": false, 469 | "DataType": "decimal", 470 | "CharacterMaximumLength": { 471 | "Int64": 0, 472 | "Valid": false 473 | }, 474 | "CharacterOctetLength": { 475 | "Int64": 0, 476 | "Valid": false 477 | }, 478 | "NumericPrecision": { 479 | "Int64": 5, 480 | "Valid": true 481 | }, 482 | "NumericScale": { 483 | "Int64": 2, 484 | "Valid": true 485 | }, 486 | "DatetimePrecision": { 487 | "Int64": 0, 488 | "Valid": false 489 | }, 490 | "CharacterSetName": { 491 | "String": "", 492 | "Valid": false 493 | }, 494 | "CollationName": { 495 | "String": "", 496 | "Valid": false 497 | }, 498 | "ColumnType": "decimal(5,2)", 499 | "ColumnKey": "", 500 | "Extra": "", 501 | "Privileges": "select,insert,update,references", 502 | "ColumnComment": "", 503 | "GenerationExpression": "", 504 | "SetEnumVals": [], 505 | "Constraint": null 506 | }, 507 | { 508 | "TableCatalog": "def", 509 | "TableSchema": "sakila", 510 | "TableName": "film", 511 | "ColumnName": "last_update", 512 | "OrdinalPosition": 13, 513 | "ColumnDefault": { 514 | "String": "CURRENT_TIMESTAMP", 515 | "Valid": true 516 | }, 517 | "IsNullable": false, 518 | "DataType": "timestamp", 519 | "CharacterMaximumLength": { 520 | "Int64": 0, 521 | "Valid": false 522 | }, 523 | "CharacterOctetLength": { 524 | "Int64": 0, 525 | "Valid": false 526 | }, 527 | "NumericPrecision": { 528 | "Int64": 0, 529 | "Valid": false 530 | }, 531 | "NumericScale": { 532 | "Int64": 0, 533 | "Valid": false 534 | }, 535 | "DatetimePrecision": { 536 | "Int64": 0, 537 | "Valid": true 538 | }, 539 | "CharacterSetName": { 540 | "String": "", 541 | "Valid": false 542 | }, 543 | "CollationName": { 544 | "String": "", 545 | "Valid": false 546 | }, 547 | "ColumnType": "timestamp", 548 | "ColumnKey": "", 549 | "Extra": "on update CURRENT_TIMESTAMP", 550 | "Privileges": "select,insert,update,references", 551 | "ColumnComment": "", 552 | "GenerationExpression": "", 553 | "SetEnumVals": [], 554 | "Constraint": null 555 | } 556 | ], 557 | "Indexes": [ 558 | { 559 | "NonUnique": false, 560 | "KeyName": "PRIMARY", 561 | "SeqInIndex": 1, 562 | "ColumnName": "film_id", 563 | "Collation": "A", 564 | "Cardinality": 1000, 565 | "SubPart": { 566 | "Int64": 0, 567 | "Valid": false 568 | }, 569 | "Packed": { 570 | "String": "", 571 | "Valid": false 572 | }, 573 | "Null": "", 574 | "IndexType": "BTREE", 575 | "Comment": "", 576 | "IndexComment": "" 577 | }, 578 | { 579 | "NonUnique": true, 580 | "KeyName": "idx_title", 581 | "SeqInIndex": 1, 582 | "ColumnName": "title", 583 | "Collation": "A", 584 | "Cardinality": 1000, 585 | "SubPart": { 586 | "Int64": 0, 587 | "Valid": false 588 | }, 589 | "Packed": { 590 | "String": "", 591 | "Valid": false 592 | }, 593 | "Null": "", 594 | "IndexType": "BTREE", 595 | "Comment": "", 596 | "IndexComment": "" 597 | }, 598 | { 599 | "NonUnique": true, 600 | "KeyName": "idx_fk_language_id", 601 | "SeqInIndex": 1, 602 | "ColumnName": "language_id", 603 | "Collation": "A", 604 | "Cardinality": 1, 605 | "SubPart": { 606 | "Int64": 0, 607 | "Valid": false 608 | }, 609 | "Packed": { 610 | "String": "", 611 | "Valid": false 612 | }, 613 | "Null": "", 614 | "IndexType": "BTREE", 615 | "Comment": "", 616 | "IndexComment": "" 617 | }, 618 | { 619 | "NonUnique": true, 620 | "KeyName": "idx_fk_original_language_id", 621 | "SeqInIndex": 1, 622 | "ColumnName": "original_language_id", 623 | "Collation": "A", 624 | "Cardinality": 1, 625 | "SubPart": { 626 | "Int64": 0, 627 | "Valid": false 628 | }, 629 | "Packed": { 630 | "String": "", 631 | "Valid": false 632 | }, 633 | "Null": "YES", 634 | "IndexType": "BTREE", 635 | "Comment": "", 636 | "IndexComment": "" 637 | } 638 | ], 639 | "Constraints": [ 640 | { 641 | "ConstraintName": "fk_film_language", 642 | "ColumnName": "language_id", 643 | "ReferencedTableSchema": "sakila", 644 | "ReferencedTableName": "language", 645 | "ReferencedColumnName": "language_id" 646 | }, 647 | { 648 | "ConstraintName": "fk_film_language_original", 649 | "ColumnName": "original_language_id", 650 | "ReferencedTableSchema": "sakila", 651 | "ReferencedTableName": "language", 652 | "ReferencedColumnName": "language_id" 653 | } 654 | ] 655 | } 656 | -------------------------------------------------------------------------------- /testutils/testutils.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "bufio" 5 | "database/sql" 6 | "encoding/json" 7 | "fmt" 8 | "io/ioutil" 9 | "os" 10 | "os/exec" 11 | "path/filepath" 12 | "reflect" 13 | "regexp" 14 | "runtime" 15 | "strconv" 16 | "strings" 17 | "testing" 18 | 19 | "github.com/go-sql-driver/mysql" 20 | version "github.com/hashicorp/go-version" 21 | ) 22 | 23 | var ( 24 | basedir string 25 | db *sql.DB 26 | ) 27 | 28 | func BaseDir() string { 29 | if basedir != "" { 30 | return basedir 31 | } 32 | out, err := exec.Command("git", "rev-parse", "--show-toplevel").Output() 33 | if err != nil { 34 | return "" 35 | } 36 | 37 | basedir = strings.TrimSpace(string(out)) 38 | return basedir 39 | } 40 | 41 | func GetMySQLConnection(tb testing.TB) *sql.DB { 42 | if db != nil { 43 | return db 44 | } 45 | 46 | dsn := os.Getenv("TEST_DSN") 47 | if dsn == "" { 48 | fmt.Printf("%s TEST_DSN environment variable is empty", caller()) 49 | tb.FailNow() 50 | } 51 | 52 | // Parse the DSN in the env var and ensure it has parseTime & multiStatements enabled 53 | // MultiStatements is required for LoadQueriesFromFile 54 | cfg, err := mysql.ParseDSN(dsn) 55 | if err != nil { 56 | fmt.Printf("%s cannot parse DSN %q: %s", caller(), dsn, err) 57 | tb.FailNow() 58 | } 59 | if cfg.Params == nil { 60 | cfg.Params = make(map[string]string) 61 | } 62 | cfg.ParseTime = true 63 | cfg.MultiStatements = true 64 | cfg.InterpolateParams = true 65 | 66 | db, err := sql.Open("mysql", cfg.FormatDSN()) 67 | if err != nil { 68 | fmt.Printf("%s cannot connect to the db: DSN: %s\n%s", caller(), dsn, err) 69 | tb.FailNow() 70 | } 71 | return db 72 | } 73 | 74 | func UpdateSamples() bool { 75 | us, _ := strconv.ParseBool(os.Getenv("UPDATE_SAMPLES")) 76 | return us 77 | } 78 | 79 | func GetVersion(tb testing.TB, db *sql.DB) *version.Version { 80 | var vs string 81 | err := db.QueryRow("SELECT VERSION()").Scan(&vs) 82 | if err != nil { 83 | fmt.Printf("%s cannot get MySQL version: %s\n\n", caller(), err) 84 | tb.FailNow() 85 | } 86 | v, err := version.NewVersion(vs) 87 | if err != nil { 88 | fmt.Printf("%s cannot get MySQL version: %s\n\n", caller(), err) 89 | tb.FailNow() 90 | } 91 | return v 92 | } 93 | 94 | func GetMinorVersion(tb testing.TB, db *sql.DB) *version.Version { 95 | var vs string 96 | err := db.QueryRow("SELECT VERSION()").Scan(&vs) 97 | if err != nil { 98 | fmt.Printf("%s cannot get MySQL version: %s\n\n", caller(), err) 99 | tb.FailNow() 100 | } 101 | 102 | // Extract only major and minor version 103 | re := regexp.MustCompile(`(\d+)\.(\d+)\.(\d+).*`) 104 | m := re.FindAllStringSubmatch(vs, -1) 105 | vs = fmt.Sprintf("%s.%s", m[0][1], m[0][2]) 106 | 107 | v, err := version.NewVersion(vs) 108 | if err != nil { 109 | fmt.Printf("%s cannot get MySQL version: %s\n\n", caller(), err) 110 | tb.FailNow() 111 | } 112 | return v 113 | } 114 | 115 | func LoadFile(tb testing.TB, filename string) []string { 116 | file := filepath.Join("testdata", filename) 117 | fh, err := os.Open(file) 118 | lines := []string{} 119 | reader := bufio.NewReader(fh) 120 | 121 | line, err := reader.ReadString('\n') 122 | for err == nil { 123 | lines = append(lines, strings.TrimRight(line, "\n")) 124 | line, err = reader.ReadString('\n') 125 | } 126 | return lines 127 | } 128 | 129 | func UpdateSampleFile(tb testing.TB, filename string, lines []string) { 130 | if us, _ := strconv.ParseBool(os.Getenv("UPDATE_SAMPLES")); !us { 131 | return 132 | } 133 | WriteFile(tb, filename, lines) 134 | } 135 | 136 | func UpdateSampleJSON(tb testing.TB, filename string, data interface{}) { 137 | if us, _ := strconv.ParseBool(os.Getenv("UPDATE_SAMPLES")); !us { 138 | return 139 | } 140 | WriteJson(tb, filename, data) 141 | } 142 | 143 | func WriteFile(tb testing.TB, filename string, lines []string) { 144 | file := filepath.Join("testdata", filename) 145 | ofh, err := os.Create(file) 146 | if err != nil { 147 | fmt.Printf("%s cannot load json file %q: %s\n\n", caller(), file, err) 148 | tb.FailNow() 149 | } 150 | for _, line := range lines { 151 | ofh.WriteString(line + "\n") 152 | } 153 | ofh.Close() 154 | } 155 | 156 | func LoadQueriesFromFile(tb testing.TB, filename string) { 157 | conn := GetMySQLConnection(tb) 158 | file := filepath.Join("testdata", filename) 159 | data, err := ioutil.ReadFile(file) 160 | if err != nil { 161 | fmt.Printf("%s cannot load json file %q: %s\n\n", caller(), file, err) 162 | tb.FailNow() 163 | } 164 | _, err = conn.Exec(string(data)) 165 | if err != nil { 166 | fmt.Printf("%s cannot load queries from %q: %s\n\n", caller(), file, err) 167 | tb.FailNow() 168 | } 169 | } 170 | 171 | func LoadJson(tb testing.TB, filename string, dest interface{}) { 172 | file := filepath.Join("testdata", filename) 173 | data, err := ioutil.ReadFile(file) 174 | if err != nil { 175 | fmt.Printf("%s cannot load json file %q: %s\n\n", caller(), file, err) 176 | } 177 | 178 | err = json.Unmarshal(data, dest) 179 | if err != nil { 180 | fmt.Printf("%s cannot unmarshal the contents of %q into %T: %s\n\n", caller(), file, dest, err) 181 | } 182 | } 183 | 184 | func WriteJson(tb testing.TB, filename string, data interface{}) { 185 | file := filepath.Join("testdata", filename) 186 | buf, err := json.MarshalIndent(data, "", " ") 187 | if err != nil { 188 | fmt.Printf("%s cannot marshal %T into %q: %s\n\n", caller(), data, file, err) 189 | tb.FailNow() 190 | } 191 | err = ioutil.WriteFile(file, buf, os.ModePerm) 192 | if err != nil { 193 | fmt.Printf("%s cannot write file %q: %s\n\n", caller(), file, err) 194 | tb.FailNow() 195 | } 196 | } 197 | 198 | // assert fails the test if the condition is false. 199 | func Assert(tb testing.TB, condition bool, msg string, v ...interface{}) { 200 | if !condition { 201 | fmt.Printf("%s "+msg+"\n\n", append([]interface{}{caller()}, v...)...) 202 | tb.FailNow() 203 | } 204 | } 205 | 206 | // ok fails the test if an err is not nil. 207 | func Ok(tb testing.TB, err error, args ...interface{}) { 208 | if err != nil { 209 | msg := fmt.Sprintf("%s: unexpected error: %s\n\n", caller(), err.Error()) 210 | if len(args) > 0 { 211 | msg = fmt.Sprintf("%s: %s "+args[0].(string), append([]interface{}{caller(), err}, args[1:]...)) + "\n\n" 212 | } 213 | fmt.Println(msg) 214 | tb.FailNow() 215 | } 216 | } 217 | 218 | func NotOk(tb testing.TB, err error) { 219 | if err == nil { 220 | fmt.Printf("%s: expected error is nil\n\n", caller()) 221 | tb.FailNow() 222 | } 223 | } 224 | 225 | func IsNil(tb testing.TB, i interface{}, args ...interface{}) { 226 | if i != nil { 227 | msg := fmt.Sprintf("%s: expected nil, got %#v\n\n", caller(), i) 228 | if len(args) > 0 { 229 | msg = fmt.Sprintf("%s: %s "+args[0].(string), append([]interface{}{caller(), i}, args[1:]...)) + "\n" 230 | } 231 | fmt.Println(msg) 232 | tb.FailNow() 233 | } 234 | } 235 | 236 | func NotNil(tb testing.TB, i interface{}) { 237 | if i == nil { 238 | fmt.Printf("%s: expected value, got nil\n", caller()) 239 | tb.FailNow() 240 | } 241 | } 242 | 243 | // equals fails the test if exp is not equal to act. 244 | func Equals(tb testing.TB, exp, act interface{}) { 245 | if !reflect.DeepEqual(exp, act) { 246 | fmt.Printf("%s\n\n\texp: %#v\n\n\tgot: %#v\n\n", caller(), exp, act) 247 | tb.FailNow() 248 | } 249 | } 250 | 251 | // Get the caller's function name and line to show a better error message 252 | func caller() string { 253 | _, file, line, _ := runtime.Caller(2) 254 | return fmt.Sprintf("%s:%d", filepath.Base(file), line) 255 | } 256 | --------------------------------------------------------------------------------