├── .codecov.yml ├── .github └── CONTRIBUTING.md ├── .gitignore ├── .golangci.yml ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── auth.go ├── auth_test.go ├── base.go ├── base_test.go ├── conn.go ├── connector.go ├── connector_test.go ├── datatypes.go ├── datatypes_gofuzz.go ├── datatypes_test.go ├── doc.go ├── docker-compose.yml ├── driver.go ├── gofuzzgen_disabled.go ├── gofuzzgen_enabled.go ├── internal ├── mitm-proxy.go ├── proto │ ├── compile.go │ ├── mysqlx.proto │ ├── mysqlx │ │ └── mysqlx.pb.go │ ├── mysqlx_connection.proto │ ├── mysqlx_connection │ │ └── mysqlx_connection.pb.go │ ├── mysqlx_crud.proto │ ├── mysqlx_crud │ │ └── mysqlx_crud.pb.go │ ├── mysqlx_datatypes.proto │ ├── mysqlx_datatypes │ │ └── mysqlx_datatypes.pb.go │ ├── mysqlx_expect.proto │ ├── mysqlx_expect │ │ └── mysqlx_expect.pb.go │ ├── mysqlx_expr.proto │ ├── mysqlx_expr │ │ └── mysqlx_expr.pb.go │ ├── mysqlx_notice.proto │ ├── mysqlx_notice │ │ └── mysqlx_notice.pb.go │ ├── mysqlx_resultset.proto │ ├── mysqlx_resultset │ │ └── mysqlx_resultset.pb.go │ ├── mysqlx_session.proto │ ├── mysqlx_session │ │ └── mysqlx_session.pb.go │ ├── mysqlx_sql.proto │ └── mysqlx_sql │ │ └── mysqlx_sql.pb.go └── wait.go ├── mysqlx_test.go ├── rows.go ├── stmt.go ├── tracking.go ├── tracking_test.go └── tx.go /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | precision: 2 3 | round: down 4 | range: 65...75 5 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Useful links 2 | 3 | * https://dev.mysql.com/worklog/task/?id=8338 4 | * https://dev.mysql.com/worklog/task/?id=8639 5 | * https://dev.mysql.com/worklog/task/?id=9271 6 | * https://dev.mysql.com/worklog/task/?id=10237 7 | * https://dev.mysql.com/worklog/task/?id=10992 8 | * https://dev.mysql.com/doc/internals/en/x-protocol.html 9 | * https://dev.mysql.com/doc/dev/mysql-server/latest/PAGE_PROTOCOL.html 10 | * https://dev.mysql.com/doc/refman/5.5/en/x-plugin.html 11 | * https://dev.mysql.com/doc/refman/5.7/en/x-plugin.html 12 | * https://dev.mysql.com/doc/refman/8.0/en/x-plugin.html 13 | 14 | # Running Man-In-The-Middle proxy 15 | 16 | ``` 17 | docker cp mysqlx:/var/lib/mysql/server-cert.pem internal/ 18 | docker cp mysqlx:/var/lib/mysql/server-key.pem internal/ 19 | ``` 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | Gopkg.toml 3 | Gopkg.lock 4 | go.mod 5 | go.sum 6 | 7 | /go-fuzz/ 8 | 9 | *.pem 10 | *.txt 11 | *.zip 12 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | linters-settings: 3 | goimports: 4 | local-prefixes: github.com/AlekSi/mysqlx 5 | 6 | lll: 7 | line-length: 140 8 | tab-width: 4 9 | 10 | unused: 11 | check-exported: true 12 | 13 | unparam: 14 | algo: cha 15 | check-exported: true 16 | 17 | linters: 18 | enable-all: true 19 | 20 | issues: 21 | exclude-use-default: false 22 | exclude: 23 | # gosec: Duplicated errcheck checks 24 | - 'G104: Errors unhandled' 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: required 3 | language: go 4 | 5 | go: 6 | - 1.10.x 7 | - 1.11.x 8 | - master 9 | 10 | go_import_path: github.com/AlekSi/mysqlx 11 | 12 | env: 13 | global: 14 | - MYSQLX_TEST_DATASOURCE='mysqlx://my_user:my_password@127.0.0.1:33060/?_auth-method=PLAIN&_dial-timeout=1s&time_zone=UTC' 15 | matrix: 16 | - MYSQL_IMAGE=mysql/mysql-server:5.7 17 | # - MYSQL_IMAGE=mysql/mysql-server:8.0 18 | 19 | services: 20 | - docker 21 | 22 | before_install: 23 | - sudo /etc/init.d/mysql stop 24 | 25 | install: 26 | - go get -v -t 27 | 28 | before_script: 29 | - docker --version 30 | - docker-compose --version 31 | - docker-compose up -d 32 | 33 | script: 34 | - make install 35 | 36 | - go run -v internal/wait.go 37 | - make seed 38 | 39 | - make test 40 | - make test-race 41 | - make test-cover 42 | 43 | after_success: 44 | - bash <(curl -s https://codecov.io/bash) -X fix 45 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Not released yet 2 | 3 | * Protobuf files are now generated by [protoc-gen-gofast](https://github.com/gogo/protobuf) to reduce a number 4 | of memory allocation. 5 | 6 | ## v0.1.0 (2018-02-25) 7 | 8 | * First tagged version. 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: test 2 | 3 | export MYSQLX_TEST_DATASOURCE ?= mysqlx://my_user:my_password@127.0.0.1:33060/?_auth-method=PLAIN&_dial-timeout=1s&time_zone=UTC 4 | 5 | install: 6 | go install -v ./... 7 | go test -v -i ./... 8 | 9 | test: install 10 | go test -v -tags gofuzz 11 | 12 | test-race: 13 | go install -v -race ./... 14 | go test -v -race -i ./... 15 | go test -v -race -tags gofuzz 16 | 17 | test-cover: install 18 | go test -v -coverprofile=coverage.txt -covermode=atomic 19 | 20 | cover: test-cover 21 | go tool cover -html=coverage.txt 22 | 23 | bench: test 24 | go test -run=NONE -bench=. -benchtime=3s -count=5 -benchmem | tee bench.txt 25 | 26 | check: install 27 | golangci-lint run 28 | 29 | proto: 30 | go install -v ./vendor/github.com/gogo/protobuf/protoc-gen-gofast 31 | cd internal/proto && go run compile.go 32 | 33 | fuzz: test 34 | env GO111MODULE=off go-fuzz-build -func=FuzzUnmarshalDecimal github.com/AlekSi/mysqlx 35 | env GO111MODULE=off go-fuzz -bin=mysqlx-fuzz.zip -workdir=go-fuzz/unmarshalDecimal 36 | 37 | seed: 38 | docker exec -ti mysqlx sh -c 'mysql < /test_db/mysql/world_x/world_x.sql' 39 | docker exec -ti mysqlx mysql -e "GRANT ALL ON world_x.* TO 'my_user'@'%';" 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mysqlx 2 | 3 | MySQL driver for Go's (golang) `database/sql` package and MySQL X Protocol. 4 | 5 | [![GoDoc](https://godoc.org/github.com/AlekSi/mysqlx?status.svg)](https://godoc.org/github.com/AlekSi/mysqlx) 6 | [![Build Status](https://travis-ci.org/AlekSi/mysqlx.svg?branch=master)](https://travis-ci.org/AlekSi/mysqlx) 7 | [![Codecov](https://codecov.io/gh/AlekSi/mysqlx/branch/master/graph/badge.svg)](https://codecov.io/gh/AlekSi/mysqlx) 8 | [![Go Report Card](https://goreportcard.com/badge/github.com/AlekSi/mysqlx)](https://goreportcard.com/report/github.com/AlekSi/mysqlx) 9 | 10 | It requires Go 1.10+. 11 | 12 | ## Status 13 | 14 | **Alpha quality. Do not use in production!** 15 | 16 | You are, however, is encouraged to try it in development and report bugs. 17 | 18 | ## Data source format 19 | 20 | ``` 21 | mysqlx://username:password@host:port/database?_param=value&session_variable=value&… 22 | ``` 23 | 24 | All query parameters that are not starting with `_` are used as session variables 25 | and are set whenever a connection is opened. 26 | Parameters starting with `_` are listed below: 27 | 28 | * `_auth-method`: `PLAIN` or `MYSQL41` (see [AuthMethod type](https://godoc.org/github.com/AlekSi/mysqlx#AuthMethod)) 29 | * `_dial-timeout` 30 | 31 | ## TODO 32 | 33 | * Real TLS support. 34 | * Binary strings. 35 | * Large uint64. 36 | * More tests for correct connection closing. 37 | * More concurrent tests. 38 | * Benchmarks. 39 | * Charsets. 40 | * Time zones. 41 | * Real prepared statements. 42 | * Named values. 43 | * Expose notices and warnings (?). 44 | -------------------------------------------------------------------------------- /auth.go: -------------------------------------------------------------------------------- 1 | // mysqlx - MySQL driver for Go's database/​sql package and MySQL X Protocol. 2 | // Copyright (c) 2017-2018 Alexey Palazhchenko 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | 8 | package mysqlx 9 | 10 | import ( 11 | "crypto/sha1" 12 | "crypto/sha256" 13 | "fmt" 14 | ) 15 | 16 | // https://github.com/mysql/mysql-server/blob/mysql-5.7.19/rapid/plugin/x/mysqlxtest_src/password_hasher.cc 17 | // https://github.com/mysql/mysql-server/blob/mysql-5.7.19/rapid/plugin/x/mysqlxtest_src/mysql41_hash.cc 18 | func scrambleMySQL41(password string, authData []byte) []byte { 19 | hash1 := sha1.Sum([]byte(password)) 20 | hash2 := sha1.Sum(hash1[:]) 21 | 22 | h := sha1.New() 23 | h.Write(authData) 24 | h.Write(hash2[:]) 25 | res := h.Sum(nil) 26 | 27 | for i := range res { 28 | res[i] ^= hash1[i] 29 | } 30 | return res 31 | } 32 | 33 | func authDataMySQL41(database, username, password string, authData []byte) ([]byte, error) { 34 | if len(authData) != 20 { 35 | return nil, fmt.Errorf("authDataMySQL41: expected authData to has 20 bytes, got %d", len(authData)) 36 | } 37 | 38 | res := database + "\x00" + username + "\x00" 39 | if password == "" { 40 | return []byte(res), nil 41 | } 42 | 43 | res += fmt.Sprintf("*%X", scrambleMySQL41(password, authData)) 44 | return []byte(res), nil 45 | } 46 | 47 | func scrambleSHA256(password string, authData []byte) []byte { 48 | // SHA256(password) XOR SHA256(SHA256(SHA256(password)) + authData) 49 | hash1 := sha256.Sum256([]byte(password)) 50 | hash1h := sha256.Sum256(hash1[:]) 51 | h := sha256.New() 52 | h.Write(hash1h[:]) 53 | h.Write(authData) 54 | hash2 := h.Sum(nil) 55 | res := make([]byte, sha256.Size) 56 | for i := 0; i < sha256.Size; i++ { 57 | res[i] = hash1[i] ^ hash2[i] 58 | } 59 | return res 60 | } 61 | 62 | // https://dev.mysql.com/worklog/task/?id=10992 63 | func authDataSHA256(database, username, password string, authData []byte) ([]byte, error) { 64 | if len(authData) != 20 { 65 | return nil, fmt.Errorf("authDataSHA256: expected authData to has 20 bytes, got %d", len(authData)) 66 | } 67 | 68 | res := database + "\x00" + username + "\x00" 69 | 70 | res += fmt.Sprintf("%X", scrambleSHA256(password, authData)) 71 | return []byte(res), nil 72 | } 73 | -------------------------------------------------------------------------------- /auth_test.go: -------------------------------------------------------------------------------- 1 | // mysqlx - MySQL driver for Go's database/​sql package and MySQL X Protocol. 2 | // Copyright (c) 2017-2018 Alexey Palazhchenko 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | 8 | package mysqlx 9 | 10 | import ( 11 | "encoding/hex" 12 | "testing" 13 | 14 | "github.com/stretchr/testify/assert" 15 | "github.com/stretchr/testify/require" 16 | ) 17 | 18 | func TestAuth(t *testing.T) { 19 | t.Parallel() 20 | 21 | decode := func(s string) []byte { 22 | b, err := hex.DecodeString(s) 23 | require.NoError(t, err) 24 | return b 25 | } 26 | 27 | must := func(b []byte, err error) string { 28 | require.NoError(t, err) 29 | return string(b) 30 | } 31 | 32 | t.Run("MySQL41", func(t *testing.T) { 33 | assert.Equal(t, "\x00root\x00", 34 | must(authDataMySQL41("", "root", "", decode("434169533f3569721167252e59117a645a681500")))) 35 | assert.Equal(t, "world_x\x00root\x00", 36 | must(authDataMySQL41("world_x", "root", "", decode("434169533f3569721167252e59117a645a681500")))) 37 | assert.Equal(t, "\x00my_user\x00*C8B66ADB21E1E674249869852AEBC573DB7A5639", 38 | must(authDataMySQL41("", "my_user", "my_password", decode("434169533f3569721167252e59117a645a681500")))) 39 | assert.Equal(t, "world_x\x00my_user\x00*C8B66ADB21E1E674249869852AEBC573DB7A5639", 40 | must(authDataMySQL41("world_x", "my_user", "my_password", decode("434169533f3569721167252e59117a645a681500")))) 41 | }) 42 | 43 | t.Run("SHA256", func(t *testing.T) { 44 | assert.Equal(t, "\x00root\x0093AB01DB16BDA656A4164855EDFCBE4B5355A6AA726ED094EBBD517E836C31C2", 45 | must(authDataSHA256("", "root", "", decode("6c093446472736302a0b600f446e05212a6e7400")))) 46 | 47 | assert.Equal(t, "world_x\x00root\x0093AB01DB16BDA656A4164855EDFCBE4B5355A6AA726ED094EBBD517E836C31C2", 48 | must(authDataSHA256("world_x", "root", "", decode("6c093446472736302a0b600f446e05212a6e7400")))) 49 | 50 | assert.Equal(t, "\x00my_user\x007773D5FEE8462642B6141EACC3ED4F47104C1DDCDFBB4A0B5602452D4C73200D", 51 | must(authDataSHA256("", "my_user", "my_password", decode("6c093446472736302a0b600f446e05212a6e7400")))) 52 | 53 | assert.Equal(t, "world_x\000my_user\0007773D5FEE8462642B6141EACC3ED4F47104C1DDCDFBB4A0B5602452D4C73200D", 54 | must(authDataSHA256("world_x", "my_user", "my_password", decode("6c093446472736302a0b600f446e05212a6e7400")))) 55 | }) 56 | } 57 | -------------------------------------------------------------------------------- /base.go: -------------------------------------------------------------------------------- 1 | // mysqlx - MySQL driver for Go's database/​sql package and MySQL X Protocol. 2 | // Copyright (c) 2017-2018 Alexey Palazhchenko 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | 8 | package mysqlx 9 | 10 | import ( 11 | "database/sql/driver" 12 | "errors" 13 | "fmt" 14 | ) 15 | 16 | const bug = true 17 | 18 | func bugf(format string, a ...interface{}) error { 19 | msg := fmt.Sprintf(format, a...) + "\nPlease report this bug: https://github.com/AlekSi/mysqlx/issues\n" 20 | if bug { 21 | panic(msg) 22 | } 23 | return errors.New(msg) 24 | } 25 | 26 | // Severity represents Error severity level. 27 | type Severity byte 28 | 29 | const ( 30 | // SeverityError indicates the current message sequence is aborted for the given error 31 | // and the session is ready for more. 32 | SeverityError Severity = 0 33 | 34 | // SeverityFatal indicates the client should not expect the server to continue handling any further messages 35 | // and should close the connection. 36 | SeverityFatal Severity = 1 37 | ) 38 | 39 | func (s Severity) String() string { 40 | switch s { 41 | case SeverityError: 42 | return "ERROR" 43 | case SeverityFatal: 44 | return "FATAL" 45 | default: 46 | return fmt.Sprintf("Severity %d", s) 47 | } 48 | } 49 | 50 | // Error represents MySQL X Protocol error message. 51 | // It's not used for transport-level errors. 52 | type Error struct { 53 | Severity Severity 54 | Code uint32 55 | SQLState string 56 | Msg string 57 | } 58 | 59 | // Error returns error string in a formal similar to mysql and mysqlsh client programs. 60 | func (e *Error) Error() string { 61 | return fmt.Sprintf("%s %d (%s): %s", e.Severity, e.Code, e.SQLState, e.Msg) 62 | } 63 | 64 | type execResult struct { 65 | lastInsertId int64 66 | rowsAffected int64 67 | } 68 | 69 | func (r execResult) LastInsertId() (int64, error) { 70 | return r.lastInsertId, nil 71 | } 72 | 73 | func (r execResult) RowsAffected() (int64, error) { 74 | return r.rowsAffected, nil 75 | } 76 | 77 | // check interfaces 78 | var ( 79 | _ fmt.Stringer = SeverityError 80 | _ error = (*Error)(nil) 81 | _ driver.Result = execResult{} 82 | ) 83 | -------------------------------------------------------------------------------- /base_test.go: -------------------------------------------------------------------------------- 1 | // mysqlx - MySQL driver for Go's database/​sql package and MySQL X Protocol. 2 | // Copyright (c) 2017-2018 Alexey Palazhchenko 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | 8 | package mysqlx 9 | 10 | import ( 11 | "testing" 12 | 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | func TestSeverityStringer(t *testing.T) { 17 | t.Parallel() 18 | 19 | assert.Equal(t, "ERROR", SeverityError.String()) 20 | assert.Equal(t, "FATAL", SeverityFatal.String()) 21 | assert.Equal(t, "Severity 42", Severity(42).String()) 22 | } 23 | -------------------------------------------------------------------------------- /conn.go: -------------------------------------------------------------------------------- 1 | // mysqlx - MySQL driver for Go's database/​sql package and MySQL X Protocol. 2 | // Copyright (c) 2017-2018 Alexey Palazhchenko 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | 8 | package mysqlx 9 | 10 | import ( 11 | "context" 12 | "crypto/tls" 13 | "database/sql" 14 | "database/sql/driver" 15 | "encoding/binary" 16 | "fmt" 17 | "io" 18 | "net" 19 | "sort" 20 | "strings" 21 | "sync" 22 | 23 | "github.com/golang/protobuf/proto" 24 | 25 | "github.com/AlekSi/mysqlx/internal/proto/mysqlx" 26 | "github.com/AlekSi/mysqlx/internal/proto/mysqlx_connection" 27 | "github.com/AlekSi/mysqlx/internal/proto/mysqlx_datatypes" 28 | "github.com/AlekSi/mysqlx/internal/proto/mysqlx_notice" 29 | "github.com/AlekSi/mysqlx/internal/proto/mysqlx_resultset" 30 | "github.com/AlekSi/mysqlx/internal/proto/mysqlx_session" 31 | "github.com/AlekSi/mysqlx/internal/proto/mysqlx_sql" 32 | ) 33 | 34 | // TODO make this configurable? 35 | // It should not be less then 1. 36 | const rowsCap = 1 37 | 38 | // conn is a connection to a database. 39 | // It is not used concurrently by multiple goroutines. 40 | // conn is assumed to be stateful. 41 | type conn struct { 42 | transport net.Conn 43 | tracef func(format string, v ...interface{}) // can't be nil 44 | 45 | closeOnce sync.Once 46 | closeErr error 47 | } 48 | 49 | func newConn(transport net.Conn, traceF func(format string, v ...interface{})) *conn { 50 | if traceF == nil { 51 | traceF = noTrace 52 | } 53 | 54 | c := &conn{ 55 | transport: transport, 56 | tracef: traceF, 57 | } 58 | local, remote := transport.LocalAddr().String(), transport.RemoteAddr().String() 59 | traceF("+++ connection opened: %s->%s", local, remote) 60 | connectionOpened(c) 61 | 62 | return c 63 | } 64 | 65 | func open(ctx context.Context, connector *Connector) (*conn, error) { 66 | if connector.DialTimeout != 0 { 67 | var cancel context.CancelFunc 68 | ctx, cancel = context.WithTimeout(ctx, connector.DialTimeout) 69 | defer cancel() 70 | } 71 | 72 | conn, err := new(net.Dialer).DialContext(ctx, "tcp", connector.hostPort()) 73 | if err != nil { 74 | return nil, err 75 | } 76 | c := newConn(conn, connector.Trace) 77 | defer func() { 78 | if err != nil { 79 | c.close(err) 80 | } 81 | }() 82 | 83 | if err = c.negotiate(ctx); err != nil { 84 | return nil, err 85 | } 86 | if err = c.authenticate(ctx, connector.AuthMethod, connector.Database, connector.Username, connector.Password); err != nil { 87 | return nil, err 88 | } 89 | 90 | // set session variables 91 | keys := make([]string, 0, len(connector.SessionVariables)) 92 | for k := range connector.SessionVariables { 93 | keys = append(keys, k) 94 | } 95 | sort.Strings(keys) 96 | for _, k := range keys { 97 | nv := []driver.NamedValue{{ 98 | Ordinal: 1, 99 | Value: connector.SessionVariables[k], 100 | }} 101 | if _, err = c.ExecContext(ctx, "SET SESSION "+k+" = ?", nv); err != nil { 102 | return nil, err 103 | } 104 | } 105 | 106 | return c, nil 107 | } 108 | 109 | func (c *conn) negotiate(ctx context.Context) error { 110 | if err := c.writeMessage(ctx, &mysqlx_connection.CapabilitiesGet{}); err != nil { 111 | return err 112 | } 113 | m, err := c.readMessage(ctx) 114 | if err != nil { 115 | return err 116 | } 117 | 118 | var tlsFound bool 119 | for _, cap := range m.(*mysqlx_connection.Capabilities).Capabilities { 120 | if cap.GetName() == "tls" { 121 | tlsFound = true 122 | } 123 | } 124 | 125 | // enable TLS if possible 126 | if tlsFound { 127 | cap := &mysqlx_connection.CapabilitiesSet{ 128 | Capabilities: &mysqlx_connection.Capabilities{ 129 | Capabilities: []*mysqlx_connection.Capability{{ 130 | Name: proto.String("tls"), 131 | Value: &mysqlx_datatypes.Any{ 132 | Type: mysqlx_datatypes.Any_SCALAR.Enum(), 133 | Scalar: &mysqlx_datatypes.Scalar{ 134 | Type: mysqlx_datatypes.Scalar_V_BOOL.Enum(), 135 | VBool: proto.Bool(true), 136 | }, 137 | }, 138 | }}, 139 | }, 140 | } 141 | if err := c.writeMessage(ctx, cap); err != nil { 142 | return err 143 | } 144 | if _, err := c.readMessage(ctx); err != nil { 145 | return err 146 | } 147 | // FIXME 148 | tlsConfig := &tls.Config{ 149 | InsecureSkipVerify: true, 150 | } 151 | tlsConn := tls.Client(c.transport, tlsConfig) 152 | if err := tlsConn.Handshake(); err != nil { 153 | tlsConn.Close() 154 | return err 155 | } 156 | c.transport = tlsConn 157 | } 158 | 159 | return nil 160 | } 161 | 162 | func (c *conn) authenticate(ctx context.Context, method AuthMethod, database, username, password string) error { 163 | /* 164 | if err := c.writeMessage(ctx, &mysqlx_connection.CapabilitiesGet{}); err != nil { 165 | return err 166 | } 167 | m, err := c.readMessage(ctx) 168 | if err != nil { 169 | return err 170 | } 171 | 172 | var tlsEnabled, sha256Found, mysql41Found, plainFound bool 173 | for _, cap := range m.(*mysqlx_connection.Capabilities).Capabilities { 174 | switch cap.GetName() { 175 | case "tls": 176 | tlsEnabled = cap.GetValue().GetScalar().GetVBool() 177 | case "authentication.mechanisms": 178 | for _, value := range cap.Value.Array.Value { 179 | s := string(value.Scalar.VString.Value) 180 | switch s { 181 | case "SHA256_MEMORY": 182 | // FIXME 183 | // sha256Found = true 184 | case "MYSQL41": 185 | mysql41Found = true 186 | case "PLAIN": 187 | plainFound = true 188 | } 189 | } 190 | } 191 | } 192 | 193 | err = fmt.Errorf("can't authenticate") 194 | if err != nil && sha256Found { 195 | err = c.authSHA256(ctx, database, username, password) 196 | } 197 | if err != nil && mysql41Found { 198 | err = c.authMySQL41(ctx, database, username, password) 199 | } 200 | if err != nil && plainFound && tlsEnabled { 201 | err = c.authPlain(ctx, database, username, password) 202 | } 203 | return err 204 | */ 205 | 206 | switch method { 207 | case AuthPlain: 208 | return c.authPlain(ctx, database, username, password) 209 | case AuthMySQL41: 210 | return c.authMySQL41(ctx, database, username, password) 211 | default: 212 | return fmt.Errorf("unexpected authentication method %q", method) 213 | } 214 | } 215 | 216 | func (c *conn) authSHA256(ctx context.Context, database, username, password string) error { 217 | if err := c.writeMessage(ctx, &mysqlx_session.AuthenticateStart{ 218 | MechName: proto.String("SHA256_MEMORY"), 219 | }); err != nil { 220 | return err 221 | } 222 | 223 | m, err := c.readMessage(ctx) 224 | if err != nil { 225 | return err 226 | } 227 | cont := m.(*mysqlx_session.AuthenticateContinue) 228 | 229 | authData, err := authDataSHA256(database, username, password, cont.AuthData) 230 | if err != nil { 231 | return err 232 | } 233 | if err = c.writeMessage(ctx, &mysqlx_session.AuthenticateContinue{ 234 | AuthData: authData, 235 | }); err != nil { 236 | return err 237 | } 238 | 239 | if m, err = c.readMessage(ctx); err != nil { 240 | return err 241 | } 242 | switch m := m.(type) { 243 | case *mysqlx.Error: 244 | severity := Severity(m.GetSeverity()) 245 | return &Error{ 246 | Severity: severity, 247 | Code: m.GetCode(), 248 | SQLState: m.GetSqlState(), 249 | Msg: m.GetMsg(), 250 | } 251 | 252 | case *mysqlx_notice.SessionStateChanged: 253 | default: 254 | return bugf("conn.authSHA256: unhandled type %T", m) 255 | } 256 | 257 | if m, err = c.readMessage(ctx); err != nil { 258 | return err 259 | } 260 | _ = m.(*mysqlx_session.AuthenticateOk) 261 | 262 | return nil 263 | } 264 | 265 | func (c *conn) authMySQL41(ctx context.Context, database, username, password string) error { 266 | if err := c.writeMessage(ctx, &mysqlx_session.AuthenticateStart{ 267 | MechName: proto.String("MYSQL41"), 268 | }); err != nil { 269 | return err 270 | } 271 | 272 | m, err := c.readMessage(ctx) 273 | if err != nil { 274 | return err 275 | } 276 | cont := m.(*mysqlx_session.AuthenticateContinue) 277 | 278 | authData, err := authDataMySQL41(database, username, password, cont.AuthData) 279 | if err != nil { 280 | return err 281 | } 282 | if err = c.writeMessage(ctx, &mysqlx_session.AuthenticateContinue{ 283 | AuthData: authData, 284 | }); err != nil { 285 | return err 286 | } 287 | 288 | if m, err = c.readMessage(ctx); err != nil { 289 | return err 290 | } 291 | switch m := m.(type) { 292 | case *mysqlx.Error: 293 | severity := Severity(m.GetSeverity()) 294 | return &Error{ 295 | Severity: severity, 296 | Code: m.GetCode(), 297 | SQLState: m.GetSqlState(), 298 | Msg: m.GetMsg(), 299 | } 300 | 301 | case *mysqlx_notice.SessionStateChanged: 302 | default: 303 | return bugf("conn.authMySQL41: unhandled type %T", m) 304 | } 305 | 306 | if m, err = c.readMessage(ctx); err != nil { 307 | return err 308 | } 309 | _ = m.(*mysqlx_session.AuthenticateOk) 310 | 311 | return nil 312 | } 313 | 314 | func (c *conn) authPlain(ctx context.Context, database, username, password string) error { 315 | if err := c.writeMessage(ctx, &mysqlx_session.AuthenticateStart{ 316 | MechName: proto.String("PLAIN"), 317 | AuthData: []byte(database + "\x00" + username + "\x00" + password), 318 | }); err != nil { 319 | return err 320 | } 321 | 322 | var m proto.Message 323 | var err error 324 | if m, err = c.readMessage(ctx); err != nil { 325 | return err 326 | } 327 | switch m := m.(type) { 328 | case *mysqlx.Error: 329 | severity := Severity(m.GetSeverity()) 330 | return &Error{ 331 | Severity: severity, 332 | Code: m.GetCode(), 333 | SQLState: m.GetSqlState(), 334 | Msg: m.GetMsg(), 335 | } 336 | 337 | case *mysqlx_notice.SessionStateChanged: 338 | default: 339 | return bugf("conn.authPlain: unhandled type %T", m) 340 | } 341 | 342 | if m, err = c.readMessage(ctx); err != nil { 343 | return err 344 | } 345 | switch m := m.(type) { 346 | case *mysqlx_session.AuthenticateOk: 347 | default: 348 | return bugf("conn.authPlain: unhandled type %T", m) 349 | } 350 | 351 | return nil 352 | } 353 | 354 | func (c *conn) close(err error) error { 355 | c.closeOnce.Do(func() { 356 | c.closeErr = err 357 | e := c.transport.Close() 358 | if c.closeErr == nil { 359 | c.closeErr = e 360 | } 361 | connectionClosed(c) 362 | c.tracef("--- connection closed: %s->%s", c.transport.LocalAddr(), c.transport.RemoteAddr()) 363 | }) 364 | 365 | return c.closeErr 366 | } 367 | 368 | // Close invalidates and potentially stops any current prepared statements and transactions, 369 | // marking this connection as no longer in use. 370 | // Because the sql package maintains a free pool of connections and only calls Close when there's 371 | // a surplus of idle connections, it shouldn't be necessary for drivers to do their own connection caching. 372 | func (c *conn) Close() error { 373 | if err := c.writeMessage(context.TODO(), &mysqlx_connection.Close{}); err != nil { 374 | return c.close(err) 375 | } 376 | 377 | // read one next message, but do not check it is mysqlx.Ok 378 | if _, err := c.readMessage(context.TODO()); err != nil { 379 | return c.close(err) 380 | } 381 | 382 | return c.close(nil) 383 | } 384 | 385 | // Begin starts and returns a new transaction. 386 | func (c *conn) Begin() (driver.Tx, error) { 387 | if _, err := c.ExecContext(context.Background(), "BEGIN", nil); err != nil { 388 | return nil, err 389 | } 390 | return &tx{ 391 | c: c, 392 | }, nil 393 | } 394 | 395 | // BeginTx starts and returns a new transaction. 396 | // If the context is canceled by the user the sql package will 397 | // call Tx.Rollback before discarding and closing the connection. 398 | func (c *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) { 399 | var chars []string 400 | switch sql.IsolationLevel(opts.Isolation) { 401 | case sql.LevelDefault: 402 | // nothing 403 | case sql.LevelReadUncommitted: 404 | chars = append(chars, "ISOLATION LEVEL READ UNCOMMITTED") 405 | case sql.LevelReadCommitted: 406 | chars = append(chars, "ISOLATION LEVEL READ COMMITTED") 407 | case sql.LevelRepeatableRead: 408 | chars = append(chars, "ISOLATION LEVEL REPEATABLE READ") 409 | case sql.LevelSnapshot: 410 | // special handling below 411 | case sql.LevelSerializable: 412 | chars = append(chars, "ISOLATION LEVEL SERIALIZABLE") 413 | default: 414 | return nil, bugf("conn.BeginTx: isolation level %d is not supported yet", opts.Isolation) 415 | } 416 | if opts.ReadOnly { 417 | chars = append(chars, "READ ONLY") 418 | } 419 | if chars != nil { 420 | q := "SET TRANSACTION " + strings.Join(chars, ", ") 421 | if _, err := c.ExecContext(ctx, q, nil); err != nil { 422 | return nil, err 423 | } 424 | } 425 | 426 | q := "START TRANSACTION" 427 | if sql.IsolationLevel(opts.Isolation) == sql.LevelSnapshot { 428 | q += " WITH CONSISTENT SNAPSHOT" 429 | } 430 | if _, err := c.ExecContext(ctx, q, nil); err != nil { 431 | return nil, err 432 | } 433 | return &tx{ 434 | c: c, 435 | }, nil 436 | } 437 | 438 | // Prepare returns a prepared statement, bound to this connection. 439 | func (c *conn) Prepare(query string) (driver.Stmt, error) { 440 | return &stmt{ 441 | c: c, 442 | query: query, 443 | }, nil 444 | } 445 | 446 | // PrepareContext returns a prepared statement, bound to this connection. 447 | // context is for the preparation of the statement (and so ignored). 448 | func (c *conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) { 449 | return &stmt{ 450 | c: c, 451 | query: query, 452 | }, nil 453 | } 454 | 455 | // Exec executes a query that doesn't return rows, such as an INSERT or UPDATE. 456 | func (c *conn) Exec(query string, args []driver.Value) (driver.Result, error) { 457 | nv := make([]driver.NamedValue, len(args)) 458 | for i, arg := range args { 459 | nv[i] = driver.NamedValue{ 460 | Ordinal: i + 1, 461 | Value: arg, 462 | } 463 | } 464 | return c.ExecContext(context.Background(), query, nv) 465 | } 466 | 467 | // ExecContext executes a query that doesn't return rows, such as an INSERT or UPDATE. 468 | // It honors the context timeout and return when the context is canceled. 469 | func (c *conn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { 470 | stmt := &mysqlx_sql.StmtExecute{ 471 | Stmt: []byte(query), 472 | } 473 | for i, nv := range args { 474 | if nv.Name != "" { 475 | return nil, bugf("conn.ExecContext: %q - named values are not supported yet", nv.Name) 476 | } 477 | if nv.Ordinal != i+1 { 478 | return nil, bugf("conn.ExecContext: out-of-order values are not supported yet") 479 | } 480 | a, err := marshalValue(nv.Value) 481 | if err != nil { 482 | return nil, err 483 | } 484 | stmt.Args = append(stmt.Args, a) 485 | } 486 | 487 | if err := c.writeMessage(ctx, stmt); err != nil { 488 | return nil, c.close(err) 489 | } 490 | 491 | var result driver.Result = driver.ResultNoRows 492 | for { 493 | m, err := c.readMessage(ctx) 494 | if err != nil { 495 | return nil, c.close(err) 496 | } 497 | 498 | switch m := m.(type) { 499 | case *mysqlx.Error: 500 | severity := Severity(m.GetSeverity()) 501 | 502 | // TODO close connection if severity is FATAL? 503 | 504 | return nil, &Error{ 505 | Severity: severity, 506 | Code: m.GetCode(), 507 | SQLState: m.GetSqlState(), 508 | Msg: m.GetMsg(), 509 | } 510 | 511 | case *mysqlx_notice.Warning: 512 | // TODO expose warnings? 513 | continue 514 | 515 | case *mysqlx_resultset.ColumnMetaData: 516 | continue 517 | 518 | // query with rows 519 | case *mysqlx_resultset.Row: 520 | continue 521 | 522 | // query without rows 523 | case *mysqlx_resultset.FetchDone: 524 | continue 525 | case *mysqlx_notice.SessionStateChanged: 526 | switch m.GetParam() { 527 | case mysqlx_notice.SessionStateChanged_GENERATED_INSERT_ID: 528 | ra, _ := result.RowsAffected() 529 | result = execResult{ 530 | lastInsertId: int64(m.GetValue().GetVUnsignedInt()), 531 | rowsAffected: ra, 532 | } 533 | case mysqlx_notice.SessionStateChanged_ROWS_AFFECTED: 534 | if result == driver.ResultNoRows { 535 | result = driver.RowsAffected(m.GetValue().GetVUnsignedInt()) 536 | } 537 | case mysqlx_notice.SessionStateChanged_PRODUCED_MESSAGE: 538 | // TODO log it? 539 | continue 540 | default: 541 | return nil, bugf("conn.ExecContext: unhandled session state change %v", m) 542 | } 543 | case *mysqlx_sql.StmtExecuteOk: 544 | return result, nil 545 | 546 | default: 547 | return nil, bugf("conn.ExecContext: unhandled type %T", m) 548 | } 549 | } 550 | } 551 | 552 | // Query executes a query that may return rows, such as a SELECT. 553 | func (c *conn) Query(query string, args []driver.Value) (driver.Rows, error) { 554 | nv := make([]driver.NamedValue, len(args)) 555 | for i, arg := range args { 556 | nv[i] = driver.NamedValue{ 557 | Ordinal: i + 1, 558 | Value: arg, 559 | } 560 | } 561 | return c.QueryContext(context.Background(), query, nv) 562 | } 563 | 564 | // QueryContext executes a query that may return rows, such as a SELECT. 565 | // It honors the context timeout and return when the context is canceled. 566 | func (c *conn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { 567 | stmt := &mysqlx_sql.StmtExecute{ 568 | Stmt: []byte(query), 569 | } 570 | for i, nv := range args { 571 | if nv.Name != "" { 572 | return nil, bugf("conn.QueryContext: %q - named values are not supported yet", nv.Name) 573 | } 574 | if nv.Ordinal != i+1 { 575 | return nil, bugf("conn.QueryContext: out-of-order values are not supported yet") 576 | } 577 | a, err := marshalValue(nv.Value) 578 | if err != nil { 579 | return nil, err 580 | } 581 | stmt.Args = append(stmt.Args, a) 582 | } 583 | 584 | if err := c.writeMessage(ctx, stmt); err != nil { 585 | return nil, c.close(err) 586 | } 587 | 588 | rows := rows{ 589 | c: c, 590 | columns: make([]mysqlx_resultset.ColumnMetaData, 0, 1), 591 | rows: make(chan *mysqlx_resultset.Row, rowsCap), 592 | } 593 | for { 594 | m, err := c.readMessage(ctx) 595 | if err != nil { 596 | return nil, c.close(err) 597 | } 598 | 599 | switch m := m.(type) { 600 | case *mysqlx.Error: 601 | severity := Severity(m.GetSeverity()) 602 | 603 | // TODO close connection if severity is FATAL? 604 | 605 | return nil, &Error{ 606 | Severity: severity, 607 | Code: m.GetCode(), 608 | SQLState: m.GetSqlState(), 609 | Msg: m.GetMsg(), 610 | } 611 | 612 | case *mysqlx_resultset.ColumnMetaData: 613 | rows.columns = append(rows.columns, *m) 614 | 615 | // query with rows 616 | case *mysqlx_resultset.Row: 617 | rows.rows <- m 618 | go rows.runReader(ctx) 619 | return &rows, nil 620 | 621 | // query without rows 622 | case *mysqlx_resultset.FetchDone: 623 | continue 624 | case *mysqlx_notice.SessionStateChanged: 625 | switch m.GetParam() { 626 | case mysqlx_notice.SessionStateChanged_ROWS_AFFECTED: 627 | continue 628 | default: 629 | return nil, bugf("conn.QueryContext: unhandled session state change %v", m) 630 | } 631 | case *mysqlx_sql.StmtExecuteOk: 632 | close(rows.rows) 633 | return &rows, nil 634 | 635 | default: 636 | return nil, bugf("conn.QueryContext: unhandled type %T", m) 637 | } 638 | } 639 | } 640 | 641 | // Ping verifies a connection to the database is still alive, establishing a connection if necessary. 642 | // If Ping returns driver.ErrBadConn, sql.DB.Ping and sql.DB.PingContext will remove the Conn from pool. 643 | func (c *conn) Ping(ctx context.Context) error { 644 | if _, err := c.ExecContext(ctx, "SELECT 'ping'", nil); err != nil { 645 | return driver.ErrBadConn 646 | } 647 | return nil 648 | } 649 | 650 | // CheckNamedValue is called before passing arguments to the driver 651 | // and is called in place of any ColumnConverter. CheckNamedValue must do type 652 | // validation and conversion as appropriate for the driver. 653 | func (c *conn) CheckNamedValue(arg *driver.NamedValue) error { 654 | if arg.Name != "" { 655 | return bugf("conn.CheckNamedValue: %q - named values are not supported yet", arg.Name) 656 | } 657 | 658 | // pass everything to datatypes handling (marshalValue and unmarshalValue) 659 | return nil 660 | } 661 | 662 | /* 663 | // ResetSession is called while a connection is in the connection 664 | // pool. No queries will run on this connection until this method returns. 665 | // 666 | // If the connection is bad this should return driver.ErrBadConn to prevent 667 | // the connection from being returned to the connection pool. Any other 668 | // error will be discarded. 669 | func (c *conn) ResetSession(ctx context.Context) error { 670 | // We do not want to reset a session completely by sending mysqlx_session.Reset message, 671 | // because that also reset current selected database, etc. 672 | // We also do not want to ping a database in this method - that will create extra load. 673 | // So, we do nothing. 674 | return nil 675 | } 676 | */ 677 | 678 | // writeMessage writes one protocol message, returns low-level error if any. 679 | func (c *conn) writeMessage(ctx context.Context, m proto.Message) error { 680 | deadline, _ := ctx.Deadline() 681 | if err := c.transport.SetWriteDeadline(deadline); err != nil { 682 | return driver.ErrBadConn 683 | } 684 | 685 | b, err := proto.Marshal(m) 686 | if err != nil { 687 | return err 688 | } 689 | 690 | var t mysqlx.ClientMessages_Type 691 | switch m.(type) { 692 | case *mysqlx_connection.CapabilitiesGet: 693 | t = mysqlx.ClientMessages_CON_CAPABILITIES_GET 694 | case *mysqlx_connection.CapabilitiesSet: 695 | t = mysqlx.ClientMessages_CON_CAPABILITIES_SET 696 | case *mysqlx_connection.Close: 697 | t = mysqlx.ClientMessages_CON_CLOSE 698 | 699 | case *mysqlx_session.AuthenticateStart: 700 | t = mysqlx.ClientMessages_SESS_AUTHENTICATE_START 701 | case *mysqlx_session.AuthenticateContinue: 702 | t = mysqlx.ClientMessages_SESS_AUTHENTICATE_CONTINUE 703 | 704 | case *mysqlx_sql.StmtExecute: 705 | t = mysqlx.ClientMessages_SQL_STMT_EXECUTE 706 | 707 | default: 708 | return bugf("conn.writeMessage: unhandled client message: %T %#v", m, m) 709 | } 710 | 711 | c.tracef(">>> %T %v", m, m) 712 | 713 | var head [5]byte 714 | binary.LittleEndian.PutUint32(head[:], uint32(len(b))+1) 715 | head[4] = byte(t) 716 | _, err = (&net.Buffers{head[:], b}).WriteTo(c.transport) // use writev(2) if available 717 | if err != nil { 718 | return driver.ErrBadConn 719 | } 720 | return nil 721 | } 722 | 723 | // ReadMessage reads and returns one next protocol message, or low-level error. 724 | // Notices are unwrapped: SessionVariableChanged, SessionStateChanged, and Warning are returned, 725 | // and raw Frame is never returned. 726 | // TODO un-export (currently required for mitm-proxy) 727 | func ReadMessage(r io.Reader) (proto.Message, []byte, error) { 728 | var head [5]byte 729 | if _, err := io.ReadFull(r, head[:]); err != nil { 730 | return nil, nil, driver.ErrBadConn 731 | } 732 | 733 | buf := make([]byte, binary.LittleEndian.Uint32(head[:])+4) 734 | copy(buf, head[:]) 735 | if _, err := io.ReadFull(r, buf[5:]); err != nil { 736 | return nil, nil, driver.ErrBadConn 737 | } 738 | 739 | t := mysqlx.ServerMessages_Type(buf[4]) 740 | var m proto.Message 741 | switch t { 742 | case mysqlx.ServerMessages_OK: 743 | m = new(mysqlx.Ok) 744 | case mysqlx.ServerMessages_ERROR: 745 | m = new(mysqlx.Error) 746 | 747 | case mysqlx.ServerMessages_CONN_CAPABILITIES: 748 | m = new(mysqlx_connection.Capabilities) 749 | 750 | case mysqlx.ServerMessages_SESS_AUTHENTICATE_CONTINUE: 751 | m = new(mysqlx_session.AuthenticateContinue) 752 | case mysqlx.ServerMessages_SESS_AUTHENTICATE_OK: 753 | m = new(mysqlx_session.AuthenticateOk) 754 | 755 | case mysqlx.ServerMessages_NOTICE: 756 | m = new(mysqlx_notice.Frame) 757 | 758 | case mysqlx.ServerMessages_RESULTSET_COLUMN_META_DATA: 759 | m = new(mysqlx_resultset.ColumnMetaData) 760 | case mysqlx.ServerMessages_RESULTSET_ROW: 761 | m = new(mysqlx_resultset.Row) 762 | case mysqlx.ServerMessages_RESULTSET_FETCH_DONE: 763 | // TODO short circuit there 764 | m = new(mysqlx_resultset.FetchDone) 765 | // case mysqlx.ServerMessages_RESULTSET_FETCH_SUSPENDED: 766 | // // FIXME what's there? 767 | // case mysqlx.ServerMessages_RESULTSET_FETCH_DONE_MORE_RESULTSETS: 768 | // m = new(mysqlx_resultset.FetchDoneMoreResultsets) 769 | // case mysqlx.ServerMessages_RESULTSET_FETCH_DONE_MORE_OUT_PARAMS: 770 | // m = new(mysqlx_resultset.FetchDoneMoreOutParams) 771 | 772 | case mysqlx.ServerMessages_SQL_STMT_EXECUTE_OK: 773 | // TODO short circuit there 774 | m = new(mysqlx_sql.StmtExecuteOk) 775 | 776 | default: 777 | return nil, nil, bugf("conn.readMessage: unhandled type of server message: %s (%d)", t, t) 778 | } 779 | 780 | if err := proto.Unmarshal(buf[5:], m); err != nil { 781 | return nil, buf, fmt.Errorf("conn.readMessage: %s", err) 782 | } 783 | 784 | // unwrap notice frames, return variable and state changes, skip over warnings 785 | if t == mysqlx.ServerMessages_NOTICE { 786 | f := m.(*mysqlx_notice.Frame) 787 | switch mysqlx_notice.Frame_Type(f.GetType()) { 788 | case mysqlx_notice.Frame_WARNING: 789 | m = new(mysqlx_notice.Warning) 790 | if err := proto.Unmarshal(f.Payload, m); err != nil { 791 | return nil, nil, err 792 | } 793 | case mysqlx_notice.Frame_SESSION_VARIABLE_CHANGED: 794 | m = new(mysqlx_notice.SessionVariableChanged) 795 | if err := proto.Unmarshal(f.Payload, m); err != nil { 796 | return nil, nil, err 797 | } 798 | case mysqlx_notice.Frame_SESSION_STATE_CHANGED: 799 | m = new(mysqlx_notice.SessionStateChanged) 800 | if err := proto.Unmarshal(f.Payload, m); err != nil { 801 | return nil, nil, err 802 | } 803 | default: 804 | return nil, nil, bugf("conn.readMessage: unexpected notice frame type: %v", f) 805 | } 806 | 807 | if f.GetScope() != mysqlx_notice.Frame_LOCAL { 808 | return nil, nil, bugf("conn.readMessage: unexpected notice frame scope: %v", f) 809 | } 810 | } 811 | 812 | return m, buf, nil 813 | } 814 | 815 | func (c *conn) readMessage(ctx context.Context) (proto.Message, error) { 816 | deadline, _ := ctx.Deadline() 817 | if err := c.transport.SetReadDeadline(deadline); err != nil { 818 | return nil, driver.ErrBadConn 819 | } 820 | m, _, err := ReadMessage(c.transport) 821 | if err != nil { 822 | return nil, driver.ErrBadConn 823 | } 824 | c.tracef("<<< %T %v", m, m) 825 | return m, nil 826 | } 827 | 828 | // check interfaces 829 | var ( 830 | _ driver.Conn = (*conn)(nil) 831 | _ driver.ConnBeginTx = (*conn)(nil) 832 | _ driver.ConnPrepareContext = (*conn)(nil) 833 | _ driver.Execer = (*conn)(nil) 834 | _ driver.ExecerContext = (*conn)(nil) 835 | _ driver.Queryer = (*conn)(nil) 836 | _ driver.QueryerContext = (*conn)(nil) 837 | _ driver.Pinger = (*conn)(nil) 838 | _ driver.NamedValueChecker = (*conn)(nil) 839 | // _ driver.SessionResetter = (*conn)(nil) 840 | ) 841 | -------------------------------------------------------------------------------- /connector.go: -------------------------------------------------------------------------------- 1 | // mysqlx - MySQL driver for Go's database/​sql package and MySQL X Protocol. 2 | // Copyright (c) 2017-2018 Alexey Palazhchenko 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | 8 | package mysqlx 9 | 10 | import ( 11 | "context" 12 | "database/sql/driver" 13 | "fmt" 14 | "net" 15 | "net/url" 16 | "strconv" 17 | "strings" 18 | "time" 19 | ) 20 | 21 | type AuthMethod string 22 | 23 | const ( 24 | AuthPlain AuthMethod = "PLAIN" 25 | AuthMySQL41 AuthMethod = "MYSQL41" 26 | ) 27 | 28 | // noTrace is a trace functions which does nothing. 29 | func noTrace(string, ...interface{}) {} 30 | 31 | // Connector implements database/sql/driver.Connector interface. 32 | type Connector struct { 33 | Host string 34 | Port uint16 35 | Database string 36 | Username string 37 | Password string 38 | 39 | AuthMethod AuthMethod 40 | DialTimeout time.Duration 41 | 42 | SessionVariables map[string]string 43 | 44 | Trace func(format string, v ...interface{}) // may be nil 45 | } 46 | 47 | // Connect returns a new connection to the database. 48 | // 49 | // The provided context.Context is for dialing purposes only 50 | // (see net.DialContext) and is not used for other purposes. 51 | // 52 | // The returned connection must be used only by one goroutine at a time. 53 | func (connector *Connector) Connect(ctx context.Context) (driver.Conn, error) { 54 | return open(ctx, connector) 55 | } 56 | 57 | // Driver returns the underlying Driver of the Connector, 58 | // mainly to maintain compatibility with the Driver method on sql.DB. 59 | func (connector *Connector) Driver() driver.Driver { 60 | return Driver 61 | } 62 | 63 | // ParseDataSource returns Connector for given data source. 64 | func ParseDataSource(dataSource string) (*Connector, error) { 65 | u, err := url.Parse(dataSource) 66 | if err != nil { 67 | return nil, err 68 | } 69 | if u.Scheme != "mysqlx" { 70 | return nil, fmt.Errorf("unexpected scheme %s", u.Scheme) 71 | } 72 | connector := &Connector{ 73 | Host: u.Hostname(), 74 | Database: strings.TrimPrefix(u.Path, "/"), 75 | } 76 | 77 | // set port if given 78 | if p := u.Port(); p != "" { 79 | pp, err := strconv.ParseUint(p, 10, 16) 80 | if err != nil { 81 | return nil, err 82 | } 83 | connector.Port = uint16(pp) 84 | } 85 | 86 | // set username and password if they are given 87 | if u.User != nil { 88 | connector.Username = u.User.Username() 89 | connector.Password, _ = u.User.Password() 90 | } 91 | 92 | for k, vs := range u.Query() { 93 | if len(vs) != 1 { 94 | return nil, fmt.Errorf("%d values given for session variable %s: %v", len(vs), k, vs) 95 | } 96 | v := vs[0] 97 | 98 | // set session variables 99 | if !strings.HasPrefix(k, "_") { 100 | if connector.SessionVariables == nil { 101 | connector.SessionVariables = make(map[string]string) 102 | } 103 | connector.SessionVariables[k] = v 104 | continue 105 | } 106 | 107 | switch k { 108 | case "_auth-method": 109 | switch v { 110 | case string(AuthPlain): 111 | connector.AuthMethod = AuthPlain 112 | case string(AuthMySQL41): 113 | connector.AuthMethod = AuthMySQL41 114 | default: 115 | return nil, fmt.Errorf("unexpected value for %q: %q", k, v) 116 | } 117 | 118 | case "_dial-timeout": 119 | connector.DialTimeout, err = time.ParseDuration(v) 120 | if err != nil { 121 | dt, err := strconv.ParseFloat(v, 64) 122 | if err != nil { 123 | return nil, fmt.Errorf("unexpected value for %q: %q", k, v) 124 | } 125 | connector.DialTimeout = time.Duration(dt * float64(time.Second)) 126 | } 127 | 128 | default: 129 | return nil, fmt.Errorf("unexpected parameter %q", k) 130 | } 131 | } 132 | 133 | return connector, nil 134 | } 135 | 136 | func (connector *Connector) hostPort() string { 137 | return net.JoinHostPort(connector.Host, strconv.FormatUint(uint64(connector.Port), 10)) 138 | } 139 | 140 | // URL returns data source as an URL. 141 | func (connector *Connector) URL() *url.URL { 142 | u := &url.URL{ 143 | Scheme: "mysqlx", 144 | Host: connector.hostPort(), 145 | Path: "/" + connector.Database, 146 | } 147 | 148 | if connector.Username != "" { 149 | u.User = url.UserPassword(connector.Username, connector.Password) 150 | } 151 | 152 | q := make(url.Values) 153 | if connector.AuthMethod != "" { 154 | q.Set("_auth-method", string(connector.AuthMethod)) 155 | } 156 | 157 | for k, v := range connector.SessionVariables { 158 | q.Set(k, v) 159 | } 160 | 161 | u.RawQuery = q.Encode() 162 | return u 163 | } 164 | 165 | // check interfaces 166 | var ( 167 | _ driver.Connector = (*Connector)(nil) 168 | ) 169 | -------------------------------------------------------------------------------- /connector_test.go: -------------------------------------------------------------------------------- 1 | // mysqlx - MySQL driver for Go's database/​sql package and MySQL X Protocol. 2 | // Copyright (c) 2017-2018 Alexey Palazhchenko 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | 8 | package mysqlx 9 | 10 | import ( 11 | "testing" 12 | 13 | "github.com/stretchr/testify/assert" 14 | "github.com/stretchr/testify/require" 15 | ) 16 | 17 | func TestParseDataSource(t *testing.T) { 18 | t.Parallel() 19 | 20 | for dataSource, expected := range map[string]*Connector{ 21 | "mysqlx://my_user:my_password@127.0.0.1:33060/world_x?time_zone=UTC": { 22 | Host: "127.0.0.1", 23 | Port: 33060, 24 | Database: "world_x", 25 | Username: "my_user", 26 | Password: "my_password", 27 | SessionVariables: map[string]string{"time_zone": "UTC"}, 28 | }, 29 | } { 30 | t.Run(dataSource, func(t *testing.T) { 31 | t.Parallel() 32 | 33 | actual, err := ParseDataSource(dataSource) 34 | require.NoError(t, err) 35 | assert.Equal(t, expected, actual) 36 | assert.Equal(t, dataSource, actual.URL().String()) 37 | }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /datatypes.go: -------------------------------------------------------------------------------- 1 | // mysqlx - MySQL driver for Go's database/​sql package and MySQL X Protocol. 2 | // Copyright (c) 2017-2018 Alexey Palazhchenko 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | 8 | package mysqlx 9 | 10 | import ( 11 | "bytes" 12 | "database/sql/driver" 13 | "encoding/binary" 14 | "fmt" 15 | "math" 16 | "reflect" 17 | "time" 18 | 19 | "github.com/golang/protobuf/proto" 20 | 21 | "github.com/AlekSi/mysqlx/internal/proto/mysqlx_datatypes" 22 | "github.com/AlekSi/mysqlx/internal/proto/mysqlx_resultset" 23 | ) 24 | 25 | var btoa = []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"} 26 | 27 | func unmarshalDecimal(value []byte) (string, error) { 28 | addToGoFuzzCorpus("unmarshalDecimal", value) 29 | 30 | if len(value) < 2 { 31 | return "", fmt.Errorf("unmarshalDecimal: failed to parse decimal %#v", value) 32 | } 33 | 34 | sign := value[len(value)-1] 35 | var s string 36 | for _, b := range value[1 : len(value)-1] { 37 | h := (b >> 4) & 0x0f 38 | l := b & 0x0f 39 | if h > 9 || l > 9 { 40 | return "", fmt.Errorf("unmarshalDecimal: failed to parse decimal %#v", value) 41 | } 42 | s += btoa[h] + btoa[l] 43 | } 44 | if sign != 0xd0 && sign != 0xc0 { 45 | h := (sign >> 4) & 0x0f 46 | if h > 9 { 47 | return "", fmt.Errorf("unmarshalDecimal: failed to parse decimal %#v", value) 48 | } 49 | s += btoa[h] 50 | sign <<= 4 51 | } 52 | if scale := int(value[0]); scale != 0 { 53 | if scale >= len(s) { 54 | return "", fmt.Errorf("unmarshalDecimal: failed to parse decimal %#v", value) 55 | } 56 | s = s[:len(s)-scale] + "." + s[len(s)-scale:] 57 | } 58 | switch sign { 59 | case 0xd0: 60 | return "-" + s, nil 61 | case 0xc0: 62 | return s, nil 63 | default: 64 | return "", fmt.Errorf("unmarshalDecimal: failed to parse decimal %#v", value) 65 | } 66 | } 67 | 68 | func unmarshalValue(value []byte, column *mysqlx_resultset.ColumnMetaData) (driver.Value, error) { 69 | // NULL -> nil, ignore type 70 | if len(value) == 0 { 71 | return nil, nil 72 | } 73 | 74 | // in order of ColumnMetaData_FieldType values 75 | switch *column.Type { 76 | case mysqlx_resultset.ColumnMetaData_SINT: 77 | // TINY, SHORT, INT24, INT, LONGLONG 78 | i64, n := binary.Varint(value) 79 | if n != len(value) { 80 | return nil, bugf("unmarshalValue: failed to decode %#v as SINT", value) 81 | } 82 | return i64, nil 83 | 84 | case mysqlx_resultset.ColumnMetaData_UINT: 85 | // TINY UNSIGNED, SHORT UNSIGNED, INT24 UNSIGNED, INT UNSIGNED, LONGLONG UNSIGNED, YEAR 86 | u64, n := binary.Uvarint(value) 87 | if n != len(value) { 88 | return nil, bugf("unmarshalValue: failed to decode %#v as UINT", value) 89 | } 90 | return u64, nil 91 | 92 | case mysqlx_resultset.ColumnMetaData_DOUBLE: 93 | // DOUBLE 94 | u64, err := proto.NewBuffer(value).DecodeFixed64() 95 | if err != nil { 96 | return nil, bugf("unmarshalValue: failed to decode %#v as DOUBLE: %s", value, err) 97 | } 98 | return math.Float64frombits(u64), nil 99 | 100 | case mysqlx_resultset.ColumnMetaData_FLOAT: 101 | // FLOAT 102 | u64, err := proto.NewBuffer(value).DecodeFixed32() 103 | if err != nil { 104 | return nil, bugf("unmarshalValue: failed to decode %#v as FLOAT: %s", value, err) 105 | } 106 | return float64(math.Float32frombits(uint32(u64))), nil 107 | 108 | case mysqlx_resultset.ColumnMetaData_BYTES: 109 | // VARCHAR, CHAR, GEOMETRY (and also NULL, but we handle it separately) 110 | return string(value[:len(value)-1]), nil // trim last 0x00 111 | 112 | case mysqlx_resultset.ColumnMetaData_TIME: 113 | // TIME 114 | // FIXME convert to time.Duration? what about range? 115 | // and time.Duration is not a driver.Value! 116 | return nil, bugf("unmarshalValue: unhandled TIME %s, value %#v", column.Type, value) 117 | 118 | case mysqlx_resultset.ColumnMetaData_DATETIME: 119 | // DATE, DATETIME, TIMESTAMP 120 | // year, month and day are mandatory, other parts are optional 121 | r := bytes.NewReader(value) 122 | year, _ := binary.ReadUvarint(r) 123 | month, _ := binary.ReadUvarint(r) 124 | day, err := binary.ReadUvarint(r) 125 | if err != nil { 126 | return nil, bugf("unmarshalValue: failed to decode %#v as DATETIME: %s", value, err) 127 | } 128 | hour, _ := binary.ReadUvarint(r) 129 | min, _ := binary.ReadUvarint(r) 130 | sec, _ := binary.ReadUvarint(r) 131 | usec, _ := binary.ReadUvarint(r) 132 | return time.Date(int(year), time.Month(month), int(day), int(hour), int(min), int(sec), int(usec)*1000, time.UTC), nil 133 | 134 | case mysqlx_resultset.ColumnMetaData_SET: 135 | // SET 136 | return nil, bugf("unmarshalValue: unhandled SET %s, value %#v", column.Type, value) 137 | 138 | case mysqlx_resultset.ColumnMetaData_ENUM: 139 | // ENUM 140 | return string(value[:len(value)-1]), nil // trim last 0x00 141 | 142 | case mysqlx_resultset.ColumnMetaData_BIT: 143 | // BIT 144 | return nil, bugf("unmarshalValue: unhandled BIT %s, value %#v", column.Type, value) 145 | 146 | case mysqlx_resultset.ColumnMetaData_DECIMAL: 147 | // DECIMAL 148 | return unmarshalDecimal(value) 149 | 150 | default: 151 | return nil, bugf("unmarshalValue: unhandled type %s, value %#v", column.Type, value) 152 | } 153 | } 154 | 155 | func marshalValue(value driver.Value) (*mysqlx_datatypes.Any, error) { 156 | // Due to conn.CheckNamedValue passing on everything, value there can be of any type, not only of the one of 157 | // standard driver.Value types. We should handle everything ourselves. 158 | 159 | v := reflect.ValueOf(value) 160 | if v.Kind() == reflect.Ptr { 161 | v = v.Elem() 162 | if v.IsValid() { 163 | value = v.Interface() 164 | } else { 165 | value = nil 166 | } 167 | } 168 | 169 | // nil -> NULL 170 | if value == nil { 171 | return &mysqlx_datatypes.Any{ 172 | Type: mysqlx_datatypes.Any_SCALAR.Enum(), 173 | Scalar: &mysqlx_datatypes.Scalar{ 174 | Type: mysqlx_datatypes.Scalar_V_NULL.Enum(), 175 | }, 176 | }, nil 177 | } 178 | 179 | // in order of Scalar_Type values 180 | switch value := value.(type) { 181 | case int, int8, int16, int32, int64: 182 | // SINT 183 | return &mysqlx_datatypes.Any{ 184 | Type: mysqlx_datatypes.Any_SCALAR.Enum(), 185 | Scalar: &mysqlx_datatypes.Scalar{ 186 | Type: mysqlx_datatypes.Scalar_V_SINT.Enum(), 187 | VSignedInt: proto.Int64(reflect.ValueOf(value).Int()), 188 | }, 189 | }, nil 190 | 191 | case uint, uint8, uint16, uint32, uint64: 192 | // UINT 193 | return &mysqlx_datatypes.Any{ 194 | Type: mysqlx_datatypes.Any_SCALAR.Enum(), 195 | Scalar: &mysqlx_datatypes.Scalar{ 196 | Type: mysqlx_datatypes.Scalar_V_UINT.Enum(), 197 | VUnsignedInt: proto.Uint64(reflect.ValueOf(value).Uint()), 198 | }, 199 | }, nil 200 | 201 | case float64: 202 | // DOUBLE 203 | return &mysqlx_datatypes.Any{ 204 | Type: mysqlx_datatypes.Any_SCALAR.Enum(), 205 | Scalar: &mysqlx_datatypes.Scalar{ 206 | Type: mysqlx_datatypes.Scalar_V_DOUBLE.Enum(), 207 | VDouble: &value, 208 | }, 209 | }, nil 210 | 211 | case float32: 212 | // FLOAT 213 | return &mysqlx_datatypes.Any{ 214 | Type: mysqlx_datatypes.Any_SCALAR.Enum(), 215 | Scalar: &mysqlx_datatypes.Scalar{ 216 | Type: mysqlx_datatypes.Scalar_V_FLOAT.Enum(), 217 | VFloat: &value, 218 | }, 219 | }, nil 220 | 221 | case bool: 222 | // BOOL 223 | return &mysqlx_datatypes.Any{ 224 | Type: mysqlx_datatypes.Any_SCALAR.Enum(), 225 | Scalar: &mysqlx_datatypes.Scalar{ 226 | Type: mysqlx_datatypes.Scalar_V_BOOL.Enum(), 227 | VBool: &value, 228 | }, 229 | }, nil 230 | 231 | case string: 232 | // STRING 233 | return &mysqlx_datatypes.Any{ 234 | Type: mysqlx_datatypes.Any_SCALAR.Enum(), 235 | Scalar: &mysqlx_datatypes.Scalar{ 236 | Type: mysqlx_datatypes.Scalar_V_STRING.Enum(), 237 | VString: &mysqlx_datatypes.Scalar_String{ 238 | Value: []byte(value), 239 | }, 240 | }, 241 | }, nil 242 | 243 | case time.Time: 244 | s := value.Format("2006-01-02 15:04:05.999999999") 245 | return &mysqlx_datatypes.Any{ 246 | Type: mysqlx_datatypes.Any_SCALAR.Enum(), 247 | Scalar: &mysqlx_datatypes.Scalar{ 248 | Type: mysqlx_datatypes.Scalar_V_OCTETS.Enum(), 249 | VOctets: &mysqlx_datatypes.Scalar_Octets{ 250 | Value: []byte(s), 251 | }, 252 | }, 253 | }, nil 254 | 255 | case uintptr: 256 | return nil, fmt.Errorf("marshalValue: unhandled type %T, value %#v", value, value) 257 | 258 | default: 259 | return nil, bugf("marshalValue: unhandled type %T, value %#v", value, value) 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /datatypes_gofuzz.go: -------------------------------------------------------------------------------- 1 | // mysqlx - MySQL driver for Go's database/​sql package and MySQL X Protocol. 2 | // Copyright (c) 2017-2018 Alexey Palazhchenko 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | 8 | // +build gofuzz 9 | 10 | package mysqlx 11 | 12 | func FuzzUnmarshalDecimal(data []byte) int { 13 | _, err := unmarshalDecimal(data) 14 | if err != nil { 15 | return 0 16 | } 17 | return 1 18 | } 19 | -------------------------------------------------------------------------------- /datatypes_test.go: -------------------------------------------------------------------------------- 1 | // mysqlx - MySQL driver for Go's database/​sql package and MySQL X Protocol. 2 | // Copyright (c) 2017-2018 Alexey Palazhchenko 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | 8 | package mysqlx 9 | 10 | import ( 11 | "testing" 12 | 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | type decimalInput struct { 17 | b []byte 18 | expected string 19 | valid bool 20 | } 21 | 22 | var decimalInputs = []decimalInput{ 23 | // valid values from TestQueryData 24 | {[]byte{0x00, 0x9c}, "9", true}, 25 | {[]byte{0x00, 0x9d}, "-9", true}, 26 | {[]byte{0x00, 0x12, 0xc0}, "12", true}, 27 | {[]byte{0x00, 0x12, 0xd0}, "-12", true}, 28 | {[]byte{0x01, 0x09, 0xc0}, "0.9", true}, 29 | {[]byte{0x01, 0x09, 0xd0}, "-0.9", true}, 30 | {[]byte{0x03, 0x12, 0x34, 0x0c}, "12.340", true}, 31 | {[]byte{0x03, 0x12, 0x34, 0x0d}, "-12.340", true}, 32 | {[]byte{0x04, 0x12, 0x34, 0x01, 0xc0}, "12.3401", true}, 33 | {[]byte{0x04, 0x12, 0x34, 0x01, 0xd0}, "-12.3401", true}, 34 | 35 | {nil, "", false}, 36 | {[]byte{}, "", false}, 37 | {[]byte{0x00}, "", false}, 38 | {[]byte{0x00, 0x00}, "", false}, 39 | {[]byte{0x30, 0x30}, "", false}, 40 | {[]byte{0xff, 0xff}, "", false}, 41 | {[]byte{0x30, 0x0a, 0x30}, "", false}, 42 | } 43 | 44 | func TestUnmarshalDecimal(t *testing.T) { 45 | t.Parallel() 46 | 47 | for _, input := range decimalInputs { 48 | d, err := unmarshalDecimal(input.b) 49 | assert.Equal(t, input.valid, err == nil, "%s", err) 50 | assert.Equal(t, input.expected, d) 51 | } 52 | } 53 | 54 | var sink interface{} 55 | 56 | func BenchmarkUnmarshalDecimal(b *testing.B) { 57 | for _, input := range decimalInputs { 58 | b.Run(input.expected, func(b *testing.B) { 59 | for i := 0; i < b.N; i++ { 60 | sink, sink = unmarshalDecimal(input.b) 61 | } 62 | }) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // mysqlx - MySQL driver for Go's database/​sql package and MySQL X Protocol. 2 | // Copyright (c) 2017-2018 Alexey Palazhchenko 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | 8 | // Package mysqlx provides a MySQL driver for Go's database/​sql package and MySQL X Protocol. 9 | package mysqlx 10 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: '3' 3 | 4 | services: 5 | test_db: 6 | image: aleksi/test_db:1.0.0 7 | container_name: test_db 8 | volumes: 9 | - test_db:/test_db:ro 10 | 11 | mysql: 12 | image: ${MYSQL_IMAGE:-mysql/mysql-server:5.7} 13 | container_name: mysqlx 14 | command: --plugin-load=mysqlx=mysqlx.so 15 | environment: 16 | - MYSQL_ALLOW_EMPTY_PASSWORD=yes 17 | - MYSQL_USER=my_user 18 | - MYSQL_PASSWORD=my_password 19 | - MYSQL_ROOT_HOST=% 20 | ports: 21 | - 127.0.0.1:3306:3306 22 | - 127.0.0.1:33060:33060 23 | volumes: 24 | - test_db:/test_db:ro 25 | 26 | volumes: 27 | test_db: 28 | -------------------------------------------------------------------------------- /driver.go: -------------------------------------------------------------------------------- 1 | // mysqlx - MySQL driver for Go's database/​sql package and MySQL X Protocol. 2 | // Copyright (c) 2017-2018 Alexey Palazhchenko 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | 8 | package mysqlx 9 | 10 | import ( 11 | "context" 12 | "database/sql" 13 | "database/sql/driver" 14 | ) 15 | 16 | type driverType struct{} 17 | 18 | // Driver implements database/sql/driver.Driver and database/sql/driver.DriverContext interfaces. 19 | // It has no internal state. 20 | var Driver driverType 21 | 22 | // Open returns a new connection to the database. See README for data source format. 23 | // The returned connection must be used only by one goroutine at a time. 24 | func (d driverType) Open(dataSource string) (driver.Conn, error) { 25 | connector, err := d.OpenConnector(dataSource) 26 | if err != nil { 27 | return nil, err 28 | } 29 | return open(context.Background(), connector.(*Connector)) 30 | } 31 | 32 | // OpenConnector returns Connector for a given data source. 33 | func (d driverType) OpenConnector(dataSource string) (driver.Connector, error) { 34 | connector, err := ParseDataSource(dataSource) 35 | if err != nil { 36 | return nil, err 37 | } 38 | return connector, nil 39 | } 40 | 41 | func init() { 42 | sql.Register("mysqlx", Driver) 43 | } 44 | 45 | // check interfaces 46 | var ( 47 | _ driver.Driver = Driver 48 | _ driver.DriverContext = Driver 49 | ) 50 | -------------------------------------------------------------------------------- /gofuzzgen_disabled.go: -------------------------------------------------------------------------------- 1 | // mysqlx - MySQL driver for Go's database/​sql package and MySQL X Protocol. 2 | // Copyright (c) 2017-2018 Alexey Palazhchenko 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | 8 | // +build !gofuzz 9 | 10 | package mysqlx 11 | 12 | func addToGoFuzzCorpus(name string, data []byte) {} 13 | -------------------------------------------------------------------------------- /gofuzzgen_enabled.go: -------------------------------------------------------------------------------- 1 | // mysqlx - MySQL driver for Go's database/​sql package and MySQL X Protocol. 2 | // Copyright (c) 2017-2018 Alexey Palazhchenko 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | 8 | // +build gofuzz 9 | 10 | package mysqlx 11 | 12 | import ( 13 | "crypto/sha1" 14 | "fmt" 15 | "io/ioutil" 16 | "os" 17 | "path/filepath" 18 | ) 19 | 20 | func addToGoFuzzCorpus(name string, data []byte) { 21 | path := filepath.Join("go-fuzz", name, "corpus") 22 | _ = os.MkdirAll(path, 0777) 23 | 24 | path = filepath.Join(path, fmt.Sprintf("test-%x", sha1.Sum(data))) 25 | if err := ioutil.WriteFile(path, data, 0666); err != nil { 26 | panic(err) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /internal/mitm-proxy.go: -------------------------------------------------------------------------------- 1 | // mysqlx - MySQL driver for Go's database/​sql package and MySQL X Protocol. 2 | // Copyright (c) 2017-2018 Alexey Palazhchenko 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | 8 | // +build ignore 9 | 10 | package main 11 | 12 | import ( 13 | "crypto/tls" 14 | "encoding/binary" 15 | "flag" 16 | "fmt" 17 | "io" 18 | "log" 19 | "net" 20 | "os" 21 | 22 | "github.com/golang/protobuf/proto" 23 | 24 | driver "github.com/AlekSi/mysqlx" 25 | "github.com/AlekSi/mysqlx/internal/proto/mysqlx" 26 | "github.com/AlekSi/mysqlx/internal/proto/mysqlx_connection" 27 | "github.com/AlekSi/mysqlx/internal/proto/mysqlx_session" 28 | "github.com/AlekSi/mysqlx/internal/proto/mysqlx_sql" 29 | ) 30 | 31 | func readClientMessage(r io.Reader) (proto.Message, []byte, error) { 32 | var head [5]byte 33 | if _, err := io.ReadFull(r, head[:]); err != nil { 34 | return nil, nil, err 35 | } 36 | 37 | buf := make([]byte, binary.LittleEndian.Uint32(head[:])+4) 38 | copy(buf, head[:]) 39 | if _, err := io.ReadFull(r, buf[5:]); err != nil { 40 | return nil, nil, err 41 | } 42 | 43 | t := mysqlx.ClientMessages_Type(buf[4]) 44 | var m proto.Message 45 | switch t { 46 | case mysqlx.ClientMessages_CON_CAPABILITIES_GET: 47 | m = new(mysqlx_connection.CapabilitiesGet) 48 | case mysqlx.ClientMessages_CON_CAPABILITIES_SET: 49 | m = new(mysqlx_connection.CapabilitiesSet) 50 | case mysqlx.ClientMessages_SESS_AUTHENTICATE_START: 51 | m = new(mysqlx_session.AuthenticateStart) 52 | case mysqlx.ClientMessages_SESS_AUTHENTICATE_CONTINUE: 53 | m = new(mysqlx_session.AuthenticateContinue) 54 | case mysqlx.ClientMessages_SQL_STMT_EXECUTE: 55 | m = new(mysqlx_sql.StmtExecute) 56 | default: 57 | return nil, nil, fmt.Errorf("readClientMessage: unhandled type of client message: %s (%d)", t, t) 58 | } 59 | 60 | if err := proto.Unmarshal(buf[5:], m); err != nil { 61 | return nil, buf, fmt.Errorf("readClientMessage: %s", err) 62 | } 63 | 64 | return m, buf, nil 65 | } 66 | 67 | func main() { 68 | log.SetFlags(0) 69 | 70 | listenF := flag.String("listen", "127.0.0.1:33061", "listen on that address") 71 | connectF := flag.String("connect", "127.0.0.1:33060", "connect to that address") 72 | serverCertF := flag.String("server-cert", "server-cert.pem", "server certificate") 73 | serverKeyF := flag.String("server-key", "server-key.pem", "server private key") 74 | flag.Parse() 75 | 76 | var tlsClientConfig *tls.Config 77 | if *serverCertF != "" || *serverKeyF != "" { 78 | cert, err := tls.LoadX509KeyPair(*serverCertF, *serverKeyF) 79 | if err != nil { 80 | log.Fatal(err) 81 | } 82 | tlsClientConfig = &tls.Config{ 83 | Certificates: []tls.Certificate{cert}, 84 | } 85 | } 86 | 87 | log.Printf("Listening on %s...", *listenF) 88 | l, err := net.Listen("tcp", *listenF) 89 | if err != nil { 90 | log.Fatal(err) 91 | } 92 | 93 | client, err := l.Accept() 94 | if err != nil { 95 | log.Fatal(err) 96 | } 97 | log.Printf("Accepted connection from %s to %s.", client.RemoteAddr(), client.LocalAddr()) 98 | 99 | log.Printf("Connecting to %s...", *connectF) 100 | server, err := net.Dial("tcp", *connectF) 101 | if err != nil { 102 | log.Fatal(err) 103 | } 104 | log.Printf("Connected to %s from %s.", server.RemoteAddr(), server.LocalAddr()) 105 | 106 | startTLSClient := make(chan struct{}) 107 | startTLSServer := make(chan struct{}) 108 | 109 | // read from client, write to server 110 | go func() { 111 | prefix := fmt.Sprintf( 112 | "\n%s -> %s -> %s -> %s:\n", 113 | client.RemoteAddr(), client.LocalAddr(), server.LocalAddr(), server.RemoteAddr(), 114 | ) 115 | logger := log.New(os.Stderr, prefix, log.Flags()) 116 | for { 117 | m, b, err := readClientMessage(client) 118 | if err != nil { 119 | logger.Fatal(err) 120 | } 121 | 122 | msg := fmt.Sprintf("%T\n%s", m, m) 123 | switch auth := m.(type) { 124 | case *mysqlx_session.AuthenticateStart: 125 | msg += fmt.Sprintf("\nAuthData = %x", auth.AuthData) 126 | case *mysqlx_session.AuthenticateContinue: 127 | msg += fmt.Sprintf("\nAuthData = %x", auth.AuthData) 128 | case *mysqlx_session.AuthenticateOk: 129 | msg += fmt.Sprintf("\nAuthData = %x", auth.AuthData) 130 | } 131 | logger.Print(msg) 132 | 133 | var tlsRequested bool 134 | if set, ok := m.(*mysqlx_connection.CapabilitiesSet); ok { 135 | for _, cap := range set.GetCapabilities().GetCapabilities() { 136 | if cap.GetName() == "tls" && cap.GetValue().GetScalar().GetVBool() { 137 | tlsRequested = true 138 | break 139 | } 140 | } 141 | } 142 | 143 | if tlsRequested { 144 | close(startTLSClient) 145 | } 146 | 147 | _, err = server.Write(b) 148 | if err != nil { 149 | logger.Fatal(err) 150 | } 151 | 152 | if tlsRequested { 153 | <-startTLSServer 154 | startTLSServer = nil 155 | 156 | logger.Printf("Establishing TLS connection...") 157 | tlsClient := tls.Server(client, tlsClientConfig) 158 | if err = tlsClient.Handshake(); err != nil { 159 | logger.Panic(err) 160 | } 161 | client = tlsClient 162 | logger.Printf("TLS connection established.") 163 | } 164 | } 165 | }() 166 | 167 | // read from server, write to client 168 | go func() { 169 | prefix := fmt.Sprintf( 170 | "\n%s <- %s <- %s <- %s:\n", 171 | client.RemoteAddr(), client.LocalAddr(), server.LocalAddr(), server.RemoteAddr(), 172 | ) 173 | logger := log.New(os.Stderr, prefix, log.Flags()) 174 | for { 175 | m, b, err := driver.ReadMessage(server) 176 | if err != nil { 177 | logger.Fatal(err) 178 | } 179 | 180 | msg := fmt.Sprintf("%T\n%s", m, m) 181 | switch auth := m.(type) { 182 | case *mysqlx_session.AuthenticateStart: 183 | msg += fmt.Sprintf("\nAuthData = %x", auth.AuthData) 184 | case *mysqlx_session.AuthenticateContinue: 185 | msg += fmt.Sprintf("\nAuthData = %x", auth.AuthData) 186 | case *mysqlx_session.AuthenticateOk: 187 | msg += fmt.Sprintf("\nAuthData = %x", auth.AuthData) 188 | } 189 | logger.Print(msg) 190 | 191 | _, err = client.Write(b) 192 | if err != nil { 193 | logger.Fatal(err) 194 | } 195 | 196 | _, ok := m.(*mysqlx.Ok) 197 | if ok { 198 | select { 199 | case <-startTLSClient: 200 | close(startTLSServer) 201 | startTLSClient = nil 202 | 203 | logger.Printf("Establishing TLS connection...") 204 | tlsServer := tls.Client(server, &tls.Config{ 205 | InsecureSkipVerify: true, 206 | }) 207 | if err = tlsServer.Handshake(); err != nil { 208 | logger.Panic(err) 209 | } 210 | server = tlsServer 211 | logger.Printf("TLS connection established.") 212 | default: 213 | } 214 | } 215 | } 216 | }() 217 | 218 | select {} 219 | } 220 | -------------------------------------------------------------------------------- /internal/proto/compile.go: -------------------------------------------------------------------------------- 1 | // +build ignore 2 | 3 | package main 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "log" 9 | "os" 10 | "os/exec" 11 | "path/filepath" 12 | "strings" 13 | ) 14 | 15 | func run(name string, arg ...string) { 16 | cmd := exec.Command(name, arg...) 17 | cmd.Stdout = os.Stdout 18 | cmd.Stderr = os.Stderr 19 | log.Print(strings.Join(cmd.Args, " ")) 20 | if err := cmd.Run(); err != nil { 21 | log.Fatal(err) 22 | } 23 | } 24 | 25 | func main() { 26 | log.SetFlags(0) 27 | flag.Usage = func() { 28 | fmt.Fprintf(os.Stderr, "%s invokes protoc protobuf compiler with right flags.\n", os.Args[0]) 29 | } 30 | flag.Parse() 31 | 32 | files, err := filepath.Glob("*.proto") 33 | if err != nil { 34 | log.Fatal(err) 35 | } 36 | 37 | mapping := make([]string, len(files)) 38 | commands := make([][]string, len(files)) 39 | for i, f := range files { 40 | packageName := strings.TrimSuffix(f, filepath.Ext(f)) 41 | if err = os.RemoveAll(packageName); err != nil { 42 | log.Fatal(err) 43 | } 44 | if err = os.MkdirAll(packageName, 0755); err != nil { 45 | log.Fatal(err) 46 | } 47 | 48 | // go get -u github.com/gogo/protobuf/protoc-gen-gofast 49 | mapping[i] = fmt.Sprintf("M%s=github.com/AlekSi/mysqlx/internal/proto/%s", f, packageName) 50 | commands[i] = []string{"protoc", "--gofast_out=import_path=" + packageName + ",%s:" + packageName, f} 51 | } 52 | 53 | // for _, m := range mapping { 54 | // log.Print(m) 55 | // } 56 | 57 | m := strings.Join(mapping, ",") 58 | for _, c := range commands { 59 | c[1] = fmt.Sprintf(c[1], m) 60 | run(c[0], c[1:]...) 61 | } 62 | 63 | run("gofmt", "-w", "-s", ".") 64 | } 65 | -------------------------------------------------------------------------------- /internal/proto/mysqlx.proto: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License, version 2.0, 6 | * as published by the Free Software Foundation. 7 | * 8 | * This program is also distributed with certain software (including 9 | * but not limited to OpenSSL) that is licensed under separate terms, 10 | * as designated in a particular file or component or in included license 11 | * documentation. The authors of MySQL hereby grant you an additional 12 | * permission to link the program and your derivative works with the 13 | * separately licensed software that they have included with MySQL. 14 | * 15 | * This program is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License, version 2.0, for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program; if not, write to the Free Software 22 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 23 | */ 24 | 25 | // tell protobuf 3.0 to use protobuf 2.x rules 26 | syntax = "proto2"; 27 | 28 | // ifdef PROTOBUF_LITE: option optimize_for = LITE_RUNTIME; 29 | 30 | package Mysqlx; 31 | option java_package = "com.mysql.cj.x.protobuf"; 32 | 33 | import "google/protobuf/descriptor.proto"; // comment_out_if PROTOBUF_LITE 34 | 35 | // style-guide: 36 | // 37 | // see https://developers.google.com/protocol-buffers/docs/style 38 | // 39 | // message CamelCaseMsg { 40 | // enum CamelCaseEnum { 41 | // FIRST_VALUE = 1; 42 | // } 43 | // required CamelCaseEnum some_enum = 1; 44 | // } 45 | // 46 | 47 | 48 | // IDs of messages that can be sent from client to the server 49 | // 50 | // .. note:: 51 | // this message is never sent on the wire. It is only used to let ``protoc`` 52 | // 53 | // * generate constants 54 | // * check for uniqueness 55 | message ClientMessages { 56 | enum Type { 57 | CON_CAPABILITIES_GET = 1; 58 | CON_CAPABILITIES_SET = 2; 59 | CON_CLOSE = 3; 60 | 61 | SESS_AUTHENTICATE_START = 4; 62 | SESS_AUTHENTICATE_CONTINUE = 5; 63 | SESS_RESET = 6; 64 | SESS_CLOSE = 7; 65 | 66 | SQL_STMT_EXECUTE = 12; 67 | 68 | CRUD_FIND = 17; 69 | CRUD_INSERT = 18; 70 | CRUD_UPDATE = 19; 71 | CRUD_DELETE = 20; 72 | 73 | EXPECT_OPEN = 24; 74 | EXPECT_CLOSE = 25; 75 | 76 | CRUD_CREATE_VIEW = 30; 77 | CRUD_MODIFY_VIEW = 31; 78 | CRUD_DROP_VIEW = 32; 79 | } 80 | } 81 | 82 | // IDs of messages that can be sent from server to client 83 | // 84 | // .. note:: 85 | // this message is never sent on the wire. It is only used to let ``protoc`` 86 | // 87 | // * generate constants 88 | // * check for uniqueness 89 | message ServerMessages { 90 | enum Type { 91 | OK = 0; 92 | ERROR = 1; 93 | 94 | CONN_CAPABILITIES = 2; 95 | 96 | SESS_AUTHENTICATE_CONTINUE = 3; 97 | SESS_AUTHENTICATE_OK = 4; 98 | 99 | // NOTICE has to stay at 11 forever 100 | NOTICE = 11; 101 | 102 | RESULTSET_COLUMN_META_DATA = 12; 103 | RESULTSET_ROW = 13; 104 | RESULTSET_FETCH_DONE = 14; 105 | RESULTSET_FETCH_SUSPENDED = 15; 106 | RESULTSET_FETCH_DONE_MORE_RESULTSETS = 16; 107 | 108 | SQL_STMT_EXECUTE_OK = 17; 109 | RESULTSET_FETCH_DONE_MORE_OUT_PARAMS = 18; 110 | }; 111 | } 112 | // ifndef PROTOBUF_LITE 113 | extend google.protobuf.MessageOptions { 114 | optional ClientMessages.Type client_message_id = 100001; 115 | optional ServerMessages.Type server_message_id = 100002; 116 | } 117 | // endif 118 | 119 | // generic Ok message 120 | message Ok { 121 | optional string msg = 1; 122 | 123 | option (server_message_id) = OK; // comment_out_if PROTOBUF_LITE 124 | } 125 | 126 | 127 | // generic Error message 128 | // 129 | // A ``severity`` of ``ERROR`` indicates the current message sequence is 130 | // aborted for the given error and the session is ready for more. 131 | // 132 | // In case of a ``FATAL`` error message the client should not expect 133 | // the server to continue handling any further messages and should 134 | // close the connection. 135 | // 136 | // :param severity: severity of the error message 137 | // :param code: error-code 138 | // :param sql_state: SQL state 139 | // :param msg: human readable error message 140 | message Error { 141 | optional Severity severity = 1 [ default = ERROR ]; 142 | required uint32 code = 2; 143 | required string sql_state = 4; 144 | required string msg = 3; 145 | 146 | enum Severity { 147 | ERROR = 0; 148 | FATAL = 1; 149 | }; 150 | 151 | option (server_message_id) = ERROR; // comment_out_if PROTOBUF_LITE 152 | } 153 | -------------------------------------------------------------------------------- /internal/proto/mysqlx_connection.proto: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License, version 2.0, 6 | * as published by the Free Software Foundation. 7 | * 8 | * This program is also distributed with certain software (including 9 | * but not limited to OpenSSL) that is licensed under separate terms, 10 | * as designated in a particular file or component or in included license 11 | * documentation. The authors of MySQL hereby grant you an additional 12 | * permission to link the program and your derivative works with the 13 | * separately licensed software that they have included with MySQL. 14 | * 15 | * This program is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License, version 2.0, for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program; if not, write to the Free Software 22 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 23 | */ 24 | syntax = "proto2"; 25 | 26 | // ifdef PROTOBUF_LITE: option optimize_for = LITE_RUNTIME; 27 | 28 | import "mysqlx_datatypes.proto"; 29 | import "mysqlx.proto"; // comment_out_if PROTOBUF_LITE 30 | 31 | package Mysqlx.Connection; 32 | option java_package = "com.mysql.cj.x.protobuf"; 33 | 34 | // a Capability 35 | // 36 | // a tuple of a ``name`` and a :protobuf:msg:`Mysqlx.Datatypes::Any` 37 | message Capability { 38 | required string name = 1; 39 | required Mysqlx.Datatypes.Any value = 2; 40 | } 41 | 42 | // Capabilities 43 | message Capabilities { 44 | repeated Capability capabilities = 1; 45 | 46 | option (server_message_id) = CONN_CAPABILITIES; // comment_out_if PROTOBUF_LITE 47 | } 48 | 49 | // get supported connection capabilities and their current state 50 | // 51 | // :returns: :protobuf:msg:`Mysqlx.Connection::Capabilities` or :protobuf:msg:`Mysqlx::Error` 52 | // 53 | message CapabilitiesGet { 54 | option (client_message_id) = CON_CAPABILITIES_GET; // comment_out_if PROTOBUF_LITE 55 | }; 56 | 57 | // sets connection capabilities atomically 58 | // 59 | // only provided values are changed, other values are left unchanged. 60 | // If any of the changes fails, all changes are discarded. 61 | // 62 | // :precond: active sessions == 0 63 | // :returns: :protobuf:msg:`Mysqlx::Ok` or :protobuf:msg:`Mysqlx::Error` 64 | message CapabilitiesSet { 65 | required Capabilities capabilities = 1; 66 | 67 | option (client_message_id) = CON_CAPABILITIES_SET; // comment_out_if PROTOBUF_LITE 68 | }; 69 | 70 | // announce to the server that the client wants to close the connection 71 | // 72 | // it discards any session state of the server 73 | // 74 | // :Returns: :protobuf:msg:`Mysqlx::Ok` 75 | message Close { 76 | option (client_message_id) = CON_CLOSE; // comment_out_if PROTOBUF_LITE 77 | }; 78 | 79 | -------------------------------------------------------------------------------- /internal/proto/mysqlx_crud.proto: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License, version 2.0, 6 | * as published by the Free Software Foundation. 7 | * 8 | * This program is also distributed with certain software (including 9 | * but not limited to OpenSSL) that is licensed under separate terms, 10 | * as designated in a particular file or component or in included license 11 | * documentation. The authors of MySQL hereby grant you an additional 12 | * permission to link the program and your derivative works with the 13 | * separately licensed software that they have included with MySQL. 14 | * 15 | * This program is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License, version 2.0, for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program; if not, write to the Free Software 22 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 23 | */ 24 | syntax = "proto2"; 25 | 26 | 27 | // ifdef PROTOBUF_LITE: option optimize_for = LITE_RUNTIME; 28 | 29 | // Basic CRUD operations 30 | package Mysqlx.Crud; 31 | option java_package = "com.mysql.cj.x.protobuf"; 32 | 33 | import "mysqlx.proto"; // comment_out_if PROTOBUF_LITE 34 | import "mysqlx_expr.proto"; 35 | import "mysqlx_datatypes.proto"; 36 | 37 | // column definition 38 | message Column { 39 | optional string name = 1; 40 | optional string alias = 2; 41 | repeated Mysqlx.Expr.DocumentPathItem document_path = 3; 42 | } 43 | 44 | // a projection 45 | // 46 | // :param source: the expression identifying an element from the source data 47 | // which can include a column identifier or any expression 48 | // :param alias: optional alias. Required for DOCUMENTs (clients may use 49 | // the source string as default) 50 | message Projection { 51 | required Mysqlx.Expr.Expr source = 1; 52 | optional string alias = 2; 53 | } 54 | 55 | // DataModel to use for filters, names, ... 56 | enum DataModel { 57 | DOCUMENT = 1; 58 | TABLE = 2; 59 | }; 60 | 61 | // collection 62 | message Collection { 63 | required string name = 1; 64 | optional string schema = 2; 65 | } 66 | 67 | // limit 68 | // 69 | // :param row_count: maximum rows to filter 70 | // :param offset: maximum rows to skip before applying the row_count 71 | message Limit { 72 | required uint64 row_count = 1; 73 | optional uint64 offset = 2; 74 | } 75 | 76 | // sort order 77 | message Order { 78 | enum Direction { 79 | ASC = 1; 80 | DESC = 2; 81 | }; 82 | 83 | required Mysqlx.Expr.Expr expr = 1; 84 | optional Direction direction = 2 [ default=ASC ]; 85 | } 86 | 87 | // update operations 88 | // 89 | // :param source: specification of the value to be updated 90 | // if data_model is TABLE, a column name may be specified and also a document path, if the column has type JSON 91 | // if data_model is DOCUMENT, only document paths are allowed 92 | // in both cases, schema and table must be not set 93 | // :param operation: the type of operation to be performed 94 | // :param value: an expression to be computed as the new value for the operation 95 | message UpdateOperation { 96 | enum UpdateType { 97 | SET = 1; // only allowed for TABLE 98 | ITEM_REMOVE = 2; // no value (removes the identified path from a object or array) 99 | ITEM_SET = 3; // sets the new value on the identified path 100 | ITEM_REPLACE = 4; // replaces a value if the path exists 101 | ITEM_MERGE = 5; // source and value must be documents 102 | ARRAY_INSERT = 6; // insert the value in the array at the index identified in the source path 103 | ARRAY_APPEND = 7; // append the value on the array at the identified path 104 | MERGE_PATCH = 8; // merge JSON object value with the provided patch expression 105 | } 106 | required Mysqlx.Expr.ColumnIdentifier source = 1; 107 | required UpdateType operation = 2; 108 | optional Mysqlx.Expr.Expr value = 3; 109 | } 110 | 111 | // Find Documents/Rows in a Collection/Table 112 | // 113 | // .. uml:: 114 | // 115 | // client -> server: Find 116 | // ... one or more Resultset ... 117 | // 118 | // :param collection: collection to insert into 119 | // :param data_model: datamodel that the operations refer to 120 | // :param projection: list of column projections that shall be returned 121 | // :param args: values for parameters used in filter expression 122 | // :param criteria: filter criteria 123 | // :param limit: numbers of rows that shall be skipped and returned 124 | // :param order: sort-order in which the rows/document shall be returned in 125 | // :param grouping: column expression list for aggregation (GROUP BY) 126 | // :param grouping_criteria: filter criteria for aggregated groups 127 | // :param locking: perform row locking on matches 128 | // :Returns: :protobuf:msg:`Mysqlx.Resultset::` 129 | message Find { 130 | enum RowLock { 131 | SHARED_LOCK = 1; // Lock matching rows against updates 132 | EXCLUSIVE_LOCK = 2; // Lock matching rows so no other transaction can read or write to it 133 | }; 134 | 135 | required Collection collection = 2; 136 | 137 | optional DataModel data_model = 3; 138 | repeated Projection projection = 4; 139 | optional Mysqlx.Expr.Expr criteria = 5; 140 | repeated Mysqlx.Datatypes.Scalar args = 11; 141 | optional Limit limit = 6; 142 | repeated Order order = 7; 143 | repeated Mysqlx.Expr.Expr grouping = 8; 144 | optional Mysqlx.Expr.Expr grouping_criteria = 9; 145 | optional RowLock locking = 12; 146 | 147 | option (client_message_id) = CRUD_FIND; // comment_out_if PROTOBUF_LITE 148 | }; 149 | 150 | // Insert documents/rows into a collection/table 151 | // 152 | // :param collection: collection to insert into 153 | // :param data_model: datamodel that the operations refer to 154 | // :param projection: name of the columns to insert data into (empty if data_model is DOCUMENT) 155 | // :param row: set of rows to insert into the collection/table (a single expression with a JSON document literal or an OBJECT expression) 156 | // :param args: values for parameters used in row expressions 157 | // :param upsert: true if this should be treated as an Upsert (that is, update on duplicate key) 158 | // :Returns: :protobuf:msg:`Mysqlx.Resultset::` 159 | message Insert { 160 | required Collection collection = 1; 161 | 162 | optional DataModel data_model = 2; 163 | repeated Column projection = 3; 164 | 165 | message TypedRow { 166 | repeated Mysqlx.Expr.Expr field = 1; 167 | }; 168 | repeated TypedRow row = 4; 169 | repeated Mysqlx.Datatypes.Scalar args = 5; 170 | optional bool upsert = 6 [default = false]; 171 | 172 | option (client_message_id) = CRUD_INSERT; // comment_out_if PROTOBUF_LITE 173 | }; 174 | 175 | // Update documents/rows in a collection/table 176 | // 177 | // :param collection: collection to change 178 | // :param data_model: datamodel that the operations refer to 179 | // :param criteria: filter expression to match rows that the operations will apply on 180 | // :param args: values for parameters used in filter expression 181 | // :param limit: limits the number of rows to match 182 | // :param order: specifies order of matched rows 183 | // :param operation: list of operations to be applied. Valid operations will depend on the data_model. 184 | // :Returns: :protobuf:msg:`Mysqlx.Resultset::` 185 | message Update { 186 | required Collection collection = 2; 187 | 188 | optional DataModel data_model = 3; 189 | optional Mysqlx.Expr.Expr criteria = 4; 190 | repeated Mysqlx.Datatypes.Scalar args = 8; 191 | optional Limit limit = 5; 192 | repeated Order order = 6; 193 | 194 | repeated UpdateOperation operation = 7; 195 | 196 | option (client_message_id) = CRUD_UPDATE; // comment_out_if PROTOBUF_LITE 197 | }; 198 | 199 | // Delete documents/rows from a Collection/Table 200 | // 201 | // :param collection: collection to change 202 | // :param data_model: datamodel that the operations refer to 203 | // :param criteria: filter expression to match rows that the operations will apply on 204 | // :param args: values for parameters used in filter expression 205 | // :param limit: limits the number of rows to match 206 | // :param order: specifies order of matched rows 207 | // :Returns: :protobuf:msg:`Mysqlx.Resultset::` 208 | message Delete { 209 | required Collection collection = 1; 210 | 211 | optional DataModel data_model = 2; 212 | optional Mysqlx.Expr.Expr criteria = 3; 213 | repeated Mysqlx.Datatypes.Scalar args = 6; 214 | optional Limit limit = 4; 215 | repeated Order order = 5; 216 | 217 | option (client_message_id) = CRUD_DELETE; // comment_out_if PROTOBUF_LITE 218 | }; 219 | 220 | 221 | // ViewAlgorithm defines how MySQL Server processes the view 222 | enum ViewAlgorithm { 223 | UNDEFINED =1; // MySQL chooses which algorithm to use 224 | MERGE = 2; // the text of a statement that refers to the view and the view definition are merged 225 | TEMPTABLE = 3; // the view are retrieved into a temporary table 226 | } 227 | 228 | // ViewSqlSecurity defines the security context in which the view is going to be 229 | // executed, this means that VIEW can be executed with current user permissions or 230 | // with permissions of the uses who defined the VIEW 231 | enum ViewSqlSecurity { 232 | INVOKER = 1; 233 | DEFINER = 2; 234 | } 235 | 236 | 237 | // ViewCheckOption limits the write operations done on a `VIEW` 238 | // (`INSERT`, `UPDATE`, `DELETE`) to rows in which the `WHERE` clause is `TRUE` 239 | enum ViewCheckOption { 240 | LOCAL = 1; // the view WHERE clause is checked, but no underlying views are checked 241 | CASCADED = 2; // the view WHERE clause is checked, then checking recurses to underlying views 242 | } 243 | 244 | 245 | // CreateView create view based on indicated Mysqlx.Crud.Find message 246 | // 247 | // param collection: name of the VIEW object, which should be created 248 | // param definer: user name of the definer, if the value isn't set then the definer is current user 249 | // param algorithm: defines how MySQL Server processes the view 250 | // param security: defines the security context in which the view is going be executed 251 | // param check: limits the write operations done on a VIEW 252 | // param column: defines the list of aliases for column names specified in `stmt` 253 | // param stmt: Mysqlx.Crud.Find message from which the SELECT statement is going to be build 254 | // param replace_existing: if true then suppress error when created view already exists; just replace it 255 | 256 | message CreateView { 257 | required Collection collection = 1; 258 | 259 | optional string definer = 2; 260 | optional ViewAlgorithm algorithm = 3 [default = UNDEFINED]; 261 | optional ViewSqlSecurity security = 4 [default = DEFINER]; 262 | optional ViewCheckOption check = 5; 263 | 264 | repeated string column = 6; 265 | required Find stmt = 7; 266 | optional bool replace_existing = 8 [default = false]; 267 | 268 | option (client_message_id) = CRUD_CREATE_VIEW; // comment_out_if PROTOBUF_LITE 269 | } 270 | 271 | 272 | // ModifyView modify existing view based on indicated Mysqlx.Crud.Find message 273 | // 274 | // param collection: name of the VIEW object, which should be modified 275 | // param definer: user name of the definer, if the value isn't set then the definer is current user 276 | // param algorithm: defined how MySQL Server processes the view 277 | // param security: defines the security context in which the view is going be executed 278 | // param check: limits the write operations done on a VIEW 279 | // param column: defines the list of aliases for column names specified in `stmt` 280 | // param stmt: Mysqlx.Crud.Find message from which the SELECT statement is going to be build 281 | 282 | message ModifyView { 283 | required Collection collection = 1; 284 | 285 | optional string definer = 2; 286 | optional ViewAlgorithm algorithm = 3; 287 | optional ViewSqlSecurity security = 4; 288 | optional ViewCheckOption check = 5; 289 | 290 | repeated string column = 6; 291 | optional Find stmt = 7; 292 | 293 | option (client_message_id) = CRUD_MODIFY_VIEW; // comment_out_if PROTOBUF_LITE 294 | } 295 | 296 | 297 | // DropView removing existing view 298 | // 299 | // param collection: name of the VIEW object, which should be deleted 300 | // param if_exists: if true then suppress error when deleted view does not exists 301 | 302 | message DropView { 303 | required Collection collection = 1; 304 | optional bool if_exists = 2 [ default = false ]; 305 | 306 | option (client_message_id) = CRUD_DROP_VIEW; // comment_out_if PROTOBUF_LITE 307 | } 308 | -------------------------------------------------------------------------------- /internal/proto/mysqlx_datatypes.proto: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License, version 2.0, 6 | * as published by the Free Software Foundation. 7 | * 8 | * This program is also distributed with certain software (including 9 | * but not limited to OpenSSL) that is licensed under separate terms, 10 | * as designated in a particular file or component or in included license 11 | * documentation. The authors of MySQL hereby grant you an additional 12 | * permission to link the program and your derivative works with the 13 | * separately licensed software that they have included with MySQL. 14 | * 15 | * This program is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License, version 2.0, for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program; if not, write to the Free Software 22 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 23 | */ 24 | syntax = "proto2"; 25 | 26 | // ifdef PROTOBUF_LITE: option optimize_for = LITE_RUNTIME; 27 | 28 | package Mysqlx.Datatypes; 29 | option java_package = "com.mysql.cj.x.protobuf"; 30 | 31 | 32 | // a scalar 33 | message Scalar { 34 | // a string with a charset/collation 35 | message String { 36 | required bytes value = 1; 37 | optional uint64 collation = 2; 38 | }; 39 | 40 | // an opaque octet sequence, with an optional content_type 41 | // See ``Mysqlx.Resultset.ContentType_BYTES`` for list of known values. 42 | message Octets { 43 | required bytes value = 1; 44 | optional uint32 content_type = 2; 45 | }; 46 | 47 | enum Type { 48 | V_SINT = 1; 49 | V_UINT = 2; 50 | V_NULL = 3; 51 | V_OCTETS = 4; 52 | V_DOUBLE = 5; 53 | V_FLOAT = 6; 54 | V_BOOL = 7; 55 | V_STRING = 8; 56 | }; 57 | 58 | required Type type = 1; 59 | 60 | optional sint64 v_signed_int = 2; 61 | optional uint64 v_unsigned_int = 3; 62 | // 4 is unused, was Null which doesn't have a storage anymore 63 | optional Octets v_octets = 5; 64 | optional double v_double = 6; 65 | optional float v_float = 7; 66 | optional bool v_bool = 8; 67 | optional String v_string = 9; 68 | } 69 | 70 | // a object 71 | message Object { 72 | message ObjectField { 73 | required string key = 1; 74 | required Any value = 2; 75 | } 76 | 77 | repeated ObjectField fld = 1; 78 | } 79 | 80 | // a Array 81 | message Array { 82 | repeated Any value = 1; 83 | } 84 | 85 | // a helper to allow all field types 86 | message Any { 87 | enum Type { 88 | SCALAR = 1; 89 | OBJECT = 2; 90 | ARRAY = 3; 91 | }; 92 | 93 | required Type type = 1; 94 | 95 | optional Scalar scalar = 2; 96 | optional Object obj = 3; 97 | optional Array array = 4; 98 | } 99 | 100 | -------------------------------------------------------------------------------- /internal/proto/mysqlx_expect.proto: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License, version 2.0, 6 | * as published by the Free Software Foundation. 7 | * 8 | * This program is also distributed with certain software (including 9 | * but not limited to OpenSSL) that is licensed under separate terms, 10 | * as designated in a particular file or component or in included license 11 | * documentation. The authors of MySQL hereby grant you an additional 12 | * permission to link the program and your derivative works with the 13 | * separately licensed software that they have included with MySQL. 14 | * 15 | * This program is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License, version 2.0, for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program; if not, write to the Free Software 22 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 23 | */ 24 | syntax = "proto2"; 25 | 26 | import "mysqlx.proto"; // comment_out_if PROTOBUF_LITE 27 | 28 | // ifdef PROTOBUF_LITE: option optimize_for = LITE_RUNTIME; 29 | 30 | // Expect operations 31 | package Mysqlx.Expect; 32 | option java_package = "com.mysql.cj.x.protobuf"; 33 | 34 | // Pipelining messages is a core feature of the Mysqlx Protocol. It 35 | // sends messages to the server without waiting for a response to 36 | // save latency. 37 | // 38 | // * in case of success the time to wait and check the result as been saved 39 | // and the latency is reduced. 40 | // 41 | // * in the case of an error a mechanism is need to ensure that the following 42 | // messages are not executed, but skipped with an error instead. 43 | // 44 | // :: 45 | // 46 | // Mysqlx.Crud::PrepareFind(stmt_id=1,...) 47 | // Mysqlx.Expect::Open([no_error]) // if a execute fails 48 | // Mysqlx.PreparedStmt::Execute(stmt_id=1,...) 49 | // Mysqlx.PreparedStmt::Execute(stmt_id=1,...) 50 | // Mysqlx.Expect::Close() 51 | // Mysqlx.PreparedStmt::Close(stmt_id=1,...) 52 | // 53 | // This basic mechanism is extended to carry a arbitrary set of conditions that are 54 | // checked before executing message: 55 | // 56 | // :: 57 | // 58 | // Mysqlx.Expect::Open([+no_error, +gtid_executed_contains = "...", +max_stmt_exec_time_ms = 10]) 59 | // 60 | // Mysqlx.Expect::Close() 61 | // 62 | // Expect blocks can be nested to increase/change the restrictions for a subset of the 63 | // messages. At the end of the Expect block the previous restrictions are restored. 64 | // 65 | // :: 66 | // 67 | // Mysqlx.Expect::Open([+no_error]) // if preparing the Find fails, don't try to close it 68 | // Mysqlx.Crud::PrepareFind(stmt_id=1,...) 69 | // Mysqlx.Expect::Open([+no_error]) // if a Execute fails, skip the rest of them and continue with Close 70 | // Mysqlx.PreparedStmt::Execute(stmt_id=1,...) 71 | // Mysqlx.PreparedStmt::Execute(stmt_id=1,...) 72 | // Mysqlx.Expect::Close() 73 | // Mysqlx.PreparedStmt::Close(stmt_id=1,...) 74 | // Mysqlx.Expect::Close() 75 | 76 | // open an Expect block and set/unset the conditions that have to be fulfilled 77 | // 78 | // if any of the conditions fail, all enclosed messages will fail with 79 | // a Mysqlx.Error message. 80 | // 81 | // :returns: :protobuf:msg:`Mysqlx::Ok` on success, :protobuf:msg:`Mysqlx::Error` on error 82 | // 83 | message Open { 84 | message Condition { 85 | enum Key { 86 | // Change error propagation behaviour 87 | EXPECT_NO_ERROR = 1; 88 | // Check if X Protocol field exists 89 | EXPECT_FIELD_EXIST = 2; 90 | }; 91 | enum ConditionOperation { 92 | // set the condition 93 | // 94 | // set, if not set 95 | // overwrite, if set 96 | EXPECT_OP_SET = 0; 97 | // unset the condition 98 | EXPECT_OP_UNSET = 1; 99 | }; 100 | required uint32 condition_key = 1; 101 | optional bytes condition_value = 2; 102 | optional ConditionOperation op = 3 [ default = EXPECT_OP_SET ]; 103 | }; 104 | enum CtxOperation { 105 | // copy the operations from the parent Expect-block 106 | EXPECT_CTX_COPY_PREV = 0; 107 | // start with a empty set of operations 108 | EXPECT_CTX_EMPTY = 1; 109 | }; 110 | optional CtxOperation op = 1 [ default = EXPECT_CTX_COPY_PREV ]; 111 | repeated Condition cond = 2; 112 | 113 | option (client_message_id) = EXPECT_OPEN; // comment_out_if PROTOBUF_LITE 114 | } 115 | 116 | // close a Expect block 117 | // 118 | // closing a Expect block restores the state of the previous Expect block 119 | // for the following messages 120 | // 121 | // :returns: :protobuf:msg:`Mysqlx::Ok` on success, :protobuf:msg:`Mysqlx::Error` on error 122 | message Close { 123 | option (client_message_id) = EXPECT_CLOSE; // comment_out_if PROTOBUF_LITE 124 | } 125 | 126 | -------------------------------------------------------------------------------- /internal/proto/mysqlx_expr.proto: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License, version 2.0, 6 | * as published by the Free Software Foundation. 7 | * 8 | * This program is also distributed with certain software (including 9 | * but not limited to OpenSSL) that is licensed under separate terms, 10 | * as designated in a particular file or component or in included license 11 | * documentation. The authors of MySQL hereby grant you an additional 12 | * permission to link the program and your derivative works with the 13 | * separately licensed software that they have included with MySQL. 14 | * 15 | * This program is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License, version 2.0, for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program; if not, write to the Free Software 22 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 23 | */ 24 | syntax = "proto2"; 25 | 26 | // ifdef PROTOBUF_LITE: option optimize_for = LITE_RUNTIME; 27 | 28 | // Expression syntax 29 | // 30 | // expr is the fundamental structure in various places 31 | // of the SQL language: 32 | // 33 | // * ``SELECT AS ...`` 34 | // * ``WHERE `` 35 | // 36 | // The structures can be used to: 37 | // 38 | // * build an Item-tree in the MySQL Server 39 | // * generate SQL from it 40 | // * use as filter condition in CRUD's Find(), Update() and Delete() calls. 41 | package Mysqlx.Expr; 42 | option java_package = "com.mysql.cj.x.protobuf"; 43 | 44 | import "mysqlx_datatypes.proto"; 45 | 46 | // Expressions 47 | // 48 | // the "root" of the expression tree 49 | // 50 | // .. productionlist:: 51 | // expr: `operator` | 52 | // : `identifier` | 53 | // : `function_call` | 54 | // : variable | 55 | // : `literal` | 56 | // : placeholder 57 | // 58 | // If expression type is PLACEHOLDER then it refers to the value of a parameter 59 | // specified when executing a statement (see `args` field of `StmtExecute` command). 60 | // Field `position` (which must be present for such an expression) gives 0-based 61 | // position of the parameter in the parameter list. 62 | // 63 | message Expr { 64 | enum Type { 65 | IDENT = 1; 66 | LITERAL = 2; 67 | VARIABLE = 3; 68 | FUNC_CALL = 4; 69 | OPERATOR = 5; 70 | PLACEHOLDER = 6; 71 | OBJECT = 7; 72 | ARRAY = 8; 73 | }; 74 | 75 | required Type type = 1; 76 | 77 | optional ColumnIdentifier identifier = 2; 78 | optional string variable = 3; 79 | optional Mysqlx.Datatypes.Scalar literal = 4; 80 | optional FunctionCall function_call = 5; 81 | optional Operator operator = 6; 82 | optional uint32 position = 7; 83 | optional Object object = 8; 84 | optional Array array = 9; 85 | } 86 | 87 | // identifier: name, schame.name 88 | // 89 | // .. productionlist:: 90 | // identifier: string "." string | 91 | // : string 92 | message Identifier { 93 | required string name = 1; 94 | optional string schema_name = 2; 95 | } 96 | 97 | // DocumentPathItem 98 | // 99 | // .. productionlist:: 100 | // document_path: path_item | path_item document_path 101 | // path_item : member | array_index | "**" 102 | // member : "." string | "." "*" 103 | // array_index : "[" number "]" | "[" "*" "]" 104 | // 105 | message DocumentPathItem { 106 | enum Type { 107 | MEMBER = 1; // .member 108 | MEMBER_ASTERISK = 2; // .* 109 | ARRAY_INDEX = 3; // [index] 110 | ARRAY_INDEX_ASTERISK = 4; // [*] 111 | DOUBLE_ASTERISK = 5; // ** 112 | }; 113 | required Type type = 1; 114 | optional string value = 2; 115 | optional uint32 index = 3; 116 | } 117 | 118 | 119 | // col_identifier (table): col@doc_path, tbl.col@doc_path col, tbl.col, schema.tbl.col 120 | // col_identifier (document): doc_path 121 | // 122 | // .. productionlist:: 123 | // col_identifier: string "." string "." string | 124 | // : string "." string | 125 | // : string | 126 | // : string "." string "." string "@" document_path | 127 | // : string "." string "@" document_path | 128 | // : string "@" document_path | 129 | // : document_path 130 | // document_path: member | arrayLocation | doubleAsterisk 131 | // member = "." string | "." "*" 132 | // arrayLocation = "[" index "]" | "[" "*" "]" 133 | // doubleAsterisk = "**" 134 | // 135 | message ColumnIdentifier { 136 | repeated Mysqlx.Expr.DocumentPathItem document_path = 1; 137 | optional string name = 2; 138 | optional string table_name = 3; 139 | optional string schema_name = 4; 140 | } 141 | 142 | // function call: ``func(a, b, "1", 3)`` 143 | // 144 | // .. productionlist:: 145 | // function_call: `identifier` "(" [ `expr` ["," `expr` ]* ] ")" 146 | message FunctionCall { 147 | required Identifier name = 1; 148 | repeated Expr param = 2; 149 | } 150 | 151 | // operator: ``<<(a, b)`` 152 | // 153 | // .. note:: 154 | // 155 | // Non-authoritative list of operators implemented (case sensitive): 156 | // 157 | // Nullary 158 | // * ``*`` 159 | // * ``default`` 160 | // 161 | // Unary 162 | // * ``!`` 163 | // * ``sign_plus`` 164 | // * ``sign_minus`` 165 | // * ``~`` 166 | // 167 | // Binary 168 | // * ``&&`` 169 | // * ``||`` 170 | // * ``xor`` 171 | // * ``==`` 172 | // * ``!=`` 173 | // * ``>`` 174 | // * ``>=`` 175 | // * ``<`` 176 | // * ``<=`` 177 | // * ``&`` 178 | // * ``|`` 179 | // * ``^`` 180 | // * ``<<`` 181 | // * ``>>`` 182 | // * ``+`` 183 | // * ``-`` 184 | // * ``*`` 185 | // * ``/`` 186 | // * ``div`` 187 | // * ``%`` 188 | // * ``is`` 189 | // * ``is_not`` 190 | // * ``regexp`` 191 | // * ``not_regexp`` 192 | // * ``like`` 193 | // * ``not_like`` 194 | // * ``cast`` 195 | // * ``cont_in`` 196 | // * ``not_cont_in`` 197 | // 198 | // Using special representation, with more than 2 params 199 | // * ``in`` (param[0] IN (param[1], param[2], ...)) 200 | // * ``not_in`` (param[0] NOT IN (param[1], param[2], ...)) 201 | // 202 | // Ternary 203 | // * ``between`` 204 | // * ``between_not`` 205 | // * ``date_add`` 206 | // * ``date_sub`` 207 | // 208 | // Units for date_add/date_sub 209 | // * ``MICROSECOND`` 210 | // * ``SECOND`` 211 | // * ``MINUTE`` 212 | // * ``HOUR`` 213 | // * ``DAY`` 214 | // * ``WEEK`` 215 | // * ``MONTH`` 216 | // * ``QUARTER`` 217 | // * ``YEAR`` 218 | // * ``SECOND_MICROSECOND`` 219 | // * ``MINUTE_MICROSECOND`` 220 | // * ``MINUTE_SECOND`` 221 | // * ``HOUR_MICROSECOND`` 222 | // * ``HOUR_SECOND`` 223 | // * ``HOUR_MINUTE`` 224 | // * ``DAY_MICROSECOND`` 225 | // * ``DAY_SECOND`` 226 | // * ``DAY_MINUTE`` 227 | // * ``DAY_HOUR`` 228 | // 229 | // Types for cast 230 | // * ``BINARY[(N)]`` 231 | // * ``CHAR[(N)]`` 232 | // * ``DATE`` 233 | // * ``DATETIME`` 234 | // * ``DECIMAL[(M[,D])]`` 235 | // * ``JSON`` 236 | // * ``SIGNED [INTEGER]`` 237 | // * ``TIME`` 238 | // * ``UNSIGNED [INTEGER]`` 239 | // 240 | // .. productionlist:: 241 | // operator: `name` "(" [ `expr` ["," `expr` ]* ] ")" 242 | message Operator { 243 | required string name = 1; 244 | repeated Expr param = 2; 245 | } 246 | 247 | // an object (with expression values) 248 | message Object { 249 | message ObjectField { 250 | required string key = 1; 251 | required Expr value = 2; 252 | } 253 | 254 | repeated ObjectField fld = 1; 255 | } 256 | 257 | // a Array of expressions 258 | message Array { 259 | repeated Expr value = 1; 260 | } 261 | -------------------------------------------------------------------------------- /internal/proto/mysqlx_notice.proto: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License, version 2.0, 6 | * as published by the Free Software Foundation. 7 | * 8 | * This program is also distributed with certain software (including 9 | * but not limited to OpenSSL) that is licensed under separate terms, 10 | * as designated in a particular file or component or in included license 11 | * documentation. The authors of MySQL hereby grant you an additional 12 | * permission to link the program and your derivative works with the 13 | * separately licensed software that they have included with MySQL. 14 | * 15 | * This program is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License, version 2.0, for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program; if not, write to the Free Software 22 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 23 | */ 24 | 25 | // tell protobuf 3.0 to use protobuf 2.x rules 26 | syntax = "proto2"; 27 | 28 | import "mysqlx.proto"; // comment_out_if PROTOBUF_LITE 29 | 30 | // ifdef PROTOBUF_LITE: option optimize_for = LITE_RUNTIME; 31 | 32 | // Notices 33 | // 34 | // A notice 35 | // 36 | // * is sent from the server to the client 37 | // * may be global or relate to the current message sequence 38 | package Mysqlx.Notice; 39 | option java_package = "com.mysql.cj.x.protobuf"; 40 | 41 | import "mysqlx_datatypes.proto"; 42 | 43 | // Common Frame for all Notices 44 | // 45 | // ===================================================== ===== 46 | // .type value 47 | // ===================================================== ===== 48 | // :protobuf:msg:`Mysqlx.Notice::Warning` 1 49 | // :protobuf:msg:`Mysqlx.Notice::SessionVariableChanged` 2 50 | // :protobuf:msg:`Mysqlx.Notice::SessionStateChanged` 3 51 | // ===================================================== ===== 52 | // 53 | // :param type: the type of the payload 54 | // :param payload: the payload of the notification 55 | // :param scope: global or local notification 56 | // 57 | message Frame { 58 | enum Scope { 59 | GLOBAL = 1; 60 | LOCAL = 2; 61 | }; 62 | enum Type { 63 | WARNING = 1; 64 | SESSION_VARIABLE_CHANGED = 2; 65 | SESSION_STATE_CHANGED = 3; 66 | }; 67 | required uint32 type = 1; 68 | optional Scope scope = 2 [ default = GLOBAL ]; 69 | optional bytes payload = 3; 70 | 71 | option (server_message_id) = NOTICE; // comment_out_if PROTOBUF_LITE 72 | } 73 | 74 | // Server-side warnings and notes 75 | // 76 | // ``.scope`` == ``local`` 77 | // ``.level``, ``.code`` and ``.msg`` map the content of 78 | // 79 | // .. code-block:: sql 80 | // 81 | // SHOW WARNINGS 82 | // 83 | // ``.scope`` == ``global`` 84 | // (undefined) will be used for global, unstructured messages like: 85 | // 86 | // * server is shutting down 87 | // * a node disconnected from group 88 | // * schema or table dropped 89 | // 90 | // ========================================== ======================= 91 | // :protobuf:msg:`Mysqlx.Notice::Frame` field value 92 | // ========================================== ======================= 93 | // ``.type`` 1 94 | // ``.scope`` ``local`` or ``global`` 95 | // ========================================== ======================= 96 | // 97 | // :param level: warning level: Note or Warning 98 | // :param code: warning code 99 | // :param msg: warning message 100 | message Warning { 101 | enum Level { 102 | NOTE = 1; 103 | WARNING = 2; 104 | ERROR = 3; 105 | }; 106 | optional Level level = 1 [ default = WARNING ]; 107 | required uint32 code = 2; 108 | required string msg = 3; 109 | } 110 | 111 | // Notify clients about changes to the current session variables 112 | // 113 | // Every change to a variable that is accessible through: 114 | // 115 | // .. code-block:: sql 116 | // 117 | // SHOW SESSION VARIABLES 118 | // 119 | // ========================================== ========= 120 | // :protobuf:msg:`Mysqlx.Notice::Frame` field value 121 | // ========================================== ========= 122 | // ``.type`` 2 123 | // ``.scope`` ``local`` 124 | // ========================================== ========= 125 | // 126 | // :param namespace: namespace that param belongs to 127 | // :param param: name of the variable 128 | // :param value: the changed value of param 129 | message SessionVariableChanged { 130 | required string param = 1; 131 | optional Mysqlx.Datatypes.Scalar value = 2; 132 | } 133 | 134 | 135 | // Notify clients about changes to the internal session state 136 | // 137 | // ========================================== ========= 138 | // :protobuf:msg:`Mysqlx.Notice::Frame` field value 139 | // ========================================== ========= 140 | // ``.type`` 3 141 | // ``.scope`` ``local`` 142 | // ========================================== ========= 143 | // 144 | // :param param: parameter key 145 | // :param value: updated value 146 | message SessionStateChanged { 147 | enum Parameter { 148 | CURRENT_SCHEMA = 1; 149 | ACCOUNT_EXPIRED = 2; 150 | GENERATED_INSERT_ID = 3; 151 | ROWS_AFFECTED = 4; 152 | ROWS_FOUND = 5; 153 | ROWS_MATCHED = 6; 154 | TRX_COMMITTED = 7; 155 | TRX_ROLLEDBACK = 9; 156 | PRODUCED_MESSAGE = 10; 157 | CLIENT_ID_ASSIGNED = 11; 158 | // .. more to be added 159 | } 160 | required Parameter param = 1; 161 | optional Mysqlx.Datatypes.Scalar value = 2; 162 | } 163 | 164 | -------------------------------------------------------------------------------- /internal/proto/mysqlx_resultset.proto: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License, version 2.0, 6 | * as published by the Free Software Foundation. 7 | * 8 | * This program is also distributed with certain software (including 9 | * but not limited to OpenSSL) that is licensed under separate terms, 10 | * as designated in a particular file or component or in included license 11 | * documentation. The authors of MySQL hereby grant you an additional 12 | * permission to link the program and your derivative works with the 13 | * separately licensed software that they have included with MySQL. 14 | * 15 | * This program is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License, version 2.0, for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program; if not, write to the Free Software 22 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 23 | */ 24 | syntax = "proto2"; 25 | 26 | import "mysqlx.proto"; // comment_out_if PROTOBUF_LITE 27 | 28 | // ifdef PROTOBUF_LITE: option optimize_for = LITE_RUNTIME; 29 | 30 | // Resultsets 31 | // `````````` 32 | // 33 | // Executing a statement against the server may result in zero or more 34 | // Resultsets followed by zero or one Resultset of the ``OUT`` parameters. 35 | // 36 | // A Resultset consists of: 37 | // 38 | // * one or more :protobuf:msg:`Mysqlx.Resultset::ColumnMetaData` 39 | // * zero or more :protobuf:msg:`Mysqlx.Resultset::Row` 40 | // 41 | // It is followed by: 42 | // 43 | // * a :protobuf:msg:`Mysqlx.Resultset::FetchDoneMoreResultsets` if more 44 | // resultsets are following 45 | // * a :protobuf:msg:`Mysqlx.Resultset::FetchDoneMoreOutParams` if more 46 | // Resultset of ``OUT`` parameters is following 47 | // * a :protobuf:msg:`Mysqlx.Resultset::FetchDone` if the last resultset 48 | // was sent 49 | // 50 | // .. uml:: 51 | // 52 | // ... 53 | // loop has more resultsets 54 | // group resultset 55 | // loop has more columns 56 | // server --> client: ColumnMetaData 57 | // end 58 | // loop has more rows 59 | // server --> client: Row 60 | // end 61 | // end 62 | // alt has more resultsets 63 | // server --> client: FetchDoneMoreResultsets 64 | // end 65 | // end 66 | // loop has more OUT-paramsets 67 | // server --> client: FetchDoneMoreOutParams 68 | // group resultset 69 | // loop has more columns 70 | // server --> client: ColumnMetaData 71 | // end 72 | // loop has more rows 73 | // server --> client: Row 74 | // end 75 | // end 76 | // end 77 | // server --> client: FetchDone 78 | // ... 79 | // 80 | // Examples 81 | // ```````` 82 | // 83 | // .. rubric:: No Resultset 84 | // 85 | // A ``INSERT`` statement usually doesn't send any resultset which results in only 86 | // a ``FetchDone``. 87 | // 88 | // .. uml:: 89 | // 90 | // server --> client: FetchDone 91 | // 92 | // .. rubric:: Empty Resultset 93 | // 94 | // ``SELECT 1 LIMIT 0`` results in a empty resultset: 95 | // 96 | // .. uml:: 97 | // 98 | // server --> client: ColumnMetaData(.name = "1", .type = INT) 99 | // server --> client: FetchDone 100 | // 101 | // .. rubric:: Multi Resultset 102 | // 103 | // ``CALL`` may result in multiple resultsets. 104 | // 105 | // .. uml:: 106 | // 107 | // server --> client: ColumnMetaData(.name = "1", .type = INT) 108 | // server --> client: Row 109 | // server --> client: FetchDoneMoreResultsets 110 | // server --> client: ColumnMetaData(.name = "1", .type = INT) 111 | // server --> client: Row 112 | // server --> client: FetchDone 113 | // 114 | // .. rubric:: OUT params 115 | // 116 | // ``CALL`` may result OUT parameters only 117 | // 118 | // .. uml:: 119 | // 120 | // server --> client: FetchDoneMoreOutParams 121 | // server --> client: ColumnMetaData(.name = "1", .type = INT) 122 | // server --> client: Row 123 | // server --> client: FetchDone 124 | 125 | 126 | 127 | package Mysqlx.Resultset; 128 | option java_package = "com.mysql.cj.x.protobuf"; 129 | 130 | // resultsets are finished, OUT paramset is next 131 | message FetchDoneMoreOutParams { 132 | option (server_message_id) = RESULTSET_FETCH_DONE_MORE_OUT_PARAMS; // comment_out_if PROTOBUF_LITE 133 | } 134 | 135 | // resultset and out-params are finished, but more resultsets available 136 | message FetchDoneMoreResultsets { 137 | option (server_message_id) = RESULTSET_FETCH_DONE_MORE_RESULTSETS; // comment_out_if PROTOBUF_LITE 138 | } 139 | 140 | // all resultsets are finished 141 | message FetchDone { 142 | option (server_message_id) = RESULTSET_FETCH_DONE; // comment_out_if PROTOBUF_LITE 143 | } 144 | 145 | // meta data of a Column 146 | // 147 | // .. note:: the encoding used for the different ``bytes`` fields in the meta data is externally 148 | // controlled. 149 | // .. seealso:: https://dev.mysql.com/doc/refman/8.0/en/charset-connection.html 150 | // 151 | // .. note:: 152 | // The server may not set the ``original_{table|name}`` fields if they are equal to the plain 153 | // ``{table|name}`` field. 154 | // 155 | // A client has to reconstruct it like:: 156 | // 157 | // if .original_name is empty and .name is not empty: 158 | // .original_name = .name 159 | // 160 | // if .original_table is empty and .table is not empty: 161 | // .original_table = .table 162 | // 163 | // .. note:: 164 | // ``compact metadata format`` can be requested by the client. In that case only ``.type`` is set and 165 | // all other fields are empty. 166 | // 167 | // 168 | // :param type: 169 | // .. table:: Expected Datatype of Mysqlx.Resultset.Row per SQL Type for non NULL values 170 | // 171 | // ================= ============ ======= ========== ====== ======== 172 | // SQL Type .type .length .frac_dig .flags .charset 173 | // ================= ============ ======= ========== ====== ======== 174 | // TINY SINT x 175 | // TINY UNSIGNED UINT x x 176 | // SHORT SINT x 177 | // SHORT UNSIGNED UINT x x 178 | // INT24 SINT x 179 | // INT24 UNSIGNED UINT x x 180 | // INT SINT x 181 | // INT UNSIGNED UINT x x 182 | // LONGLONG SINT x 183 | // LONGLONG UNSIGNED UINT x x 184 | // DOUBLE DOUBLE x x x 185 | // FLOAT FLOAT x x x 186 | // DECIMAL DECIMAL x x x 187 | // VARCHAR,CHAR,... BYTES x x x 188 | // GEOMETRY BYTES 189 | // TIME TIME x 190 | // DATE DATETIME x 191 | // DATETIME DATETIME x 192 | // YEAR UINT x x 193 | // TIMESTAMP DATETIME x 194 | // SET SET x 195 | // ENUM ENUM x 196 | // NULL BYTES 197 | // BIT BIT x 198 | // ================= ============ ======= ========== ====== ======== 199 | // 200 | // .. note:: the SQL "NULL" value is sent as an empty field value in :protobuf:msg:`Mysqlx.Resultset::Row` 201 | // .. seealso:: protobuf encoding of primitive datatypes are decribed in https://developers.google.com/protocol-buffers/docs/encoding 202 | // 203 | // SINT 204 | // 205 | // ``.length`` 206 | // maximum number of displayable decimal digits (including minus sign) of the type 207 | // 208 | // .. note:: 209 | // valid range is 0-255, but usually you'll see 1-20 210 | // 211 | // =============== == 212 | // SQL Type max digits per type 213 | // =============== == 214 | // TINY SIGNED 4 215 | // SHORT SIGNED 6 216 | // INT24 SIGNED 8 217 | // INT SIGNED 11 218 | // LONGLONG SIGNED 20 219 | // =============== == 220 | // 221 | // .. seealso:: definition of ``M`` in https://dev.mysql.com/doc/refman/8.0/en/numeric-type-overview.html 222 | // 223 | // ``value`` 224 | // variable length encoded signed 64 integer 225 | // 226 | // UINT 227 | // 228 | // ``.flags & 1`` (zerofill) 229 | // the client has to left pad with 0's up to .length 230 | // 231 | // ``.length`` 232 | // maximum number of displayable decimal digits of the type 233 | // 234 | // .. note:: 235 | // valid range is 0-255, but usually you'll see 1-20 236 | // 237 | // ================= == 238 | // SQL Type max digits per type 239 | // ================= == 240 | // TINY UNSIGNED 3 241 | // SHORT UNSIGNED 5 242 | // INT24 UNSIGNED 8 243 | // INT UNSIGNED 10 244 | // LONGLONG UNSIGNED 20 245 | // ================= == 246 | // 247 | // .. seealso:: definition of ``M`` in https://dev.mysql.com/doc/refman/8.0/en/numeric-type-overview.html 248 | // 249 | // ``value`` 250 | // variable length encoded unsigned 64 integer 251 | // 252 | // BIT 253 | // 254 | // ``.length`` 255 | // maximum number of displayable binary digits 256 | // 257 | // .. note:: valid range for M of the ``BIT`` type is 1 - 64 258 | // .. seealso:: https://dev.mysql.com/doc/refman/8.0/en/numeric-type-overview.html 259 | // 260 | // ``value`` 261 | // variable length encoded unsigned 64 integer 262 | // 263 | // DOUBLE 264 | // 265 | // ``.length`` 266 | // maximum number of displayable decimal digits (including the decimal point and ``.fractional_digits``) 267 | // 268 | // ``.fractional_digits`` 269 | // maximum number of displayable decimal digits following the decimal point 270 | // 271 | // ``value`` 272 | // encoded as Protobuf's 'double' 273 | // 274 | // FLOAT 275 | // 276 | // ``.length`` 277 | // maximum number of displayable decimal digits (including the decimal point and ``.fractional_digits``) 278 | // 279 | // ``.fractional_digits`` 280 | // maximum number of displayable decimal digits following the decimal point 281 | // 282 | // ``value`` 283 | // encoded as Protobuf's 'float' 284 | // 285 | // BYTES, ENUM 286 | // BYTES is used for all opaque byte strings that may have a charset 287 | // 288 | // * TINYBLOB, BLOB, MEDIUMBLOB, LONGBLOB 289 | // * TINYTEXT, TEXT, MEDIUMTEXT, LONGTEXT 290 | // * VARCHAR, VARBINARY 291 | // * CHAR, BINARY 292 | // * ENUM 293 | // 294 | // ``.length`` 295 | // the maximum length of characters of the underlying type 296 | // 297 | // ``.flags & 1`` (rightpad) 298 | // if the length of the field is less than ``.length``, the receiver is 299 | // supposed to add padding characters to the right end of the string. 300 | // If the ``.charset`` is "binary", the padding character is ``0x00``, 301 | // otherwise it is a space character as defined by that character set. 302 | // 303 | // ============= ======= ======== ======= 304 | // SQL Type .length .charset .flags 305 | // ============= ======= ======== ======= 306 | // TINYBLOB 256 binary 307 | // BLOB 65535 binary 308 | // VARCHAR(32) 32 utf8 309 | // VARBINARY(32) 32 utf8_bin 310 | // BINARY(32) 32 binary rightpad 311 | // CHAR(32) 32 utf8 rightpad 312 | // ============= ======= ======== ======= 313 | // 314 | // ``value`` 315 | // sequence of bytes with added one extra '\0' byte at the end. To obtain the 316 | // original string, the extra '\0' should be removed. 317 | // .. note:: the length of the string can be acquired with protobuf's field length() method 318 | // length of sequence-of-bytes = length-of-field - 1 319 | // .. note:: the extra byte allows to distinguish between a NULL and empty byte sequence 320 | // 321 | // TIME 322 | // A time value. 323 | // 324 | // ``value`` 325 | // the following bytes sequence: 326 | // 327 | // ``| negate [ | hour | [ | minutes | [ | seconds | [ | useconds | ]]]]`` 328 | // 329 | // * negate - one byte, should be one of: 0x00 for "+", 0x01 for "-" 330 | // * hour - optional variable length encoded unsigned64 value for the hour 331 | // * minutes - optional variable length encoded unsigned64 value for the minutes 332 | // * seconds - optional variable length encoded unsigned64 value for the seconds 333 | // * useconds - optional variable length encoded unsigned64 value for the microseconds 334 | // 335 | // .. seealso:: protobuf encoding in https://developers.google.com/protocol-buffers/docs/encoding 336 | // .. note:: hour, minutes, seconds, useconds are optional if all the values to the right are 0 337 | // 338 | // Example: 0x00 -> +00:00:00.000000 339 | // 340 | // DATETIME 341 | // A date or date and time value. 342 | // 343 | // ``value`` 344 | // a sequence of variants, arranged as follows: 345 | // 346 | // ``| year | month | day | [ | hour | [ | minutes | [ | seconds | [ | useconds | ]]]]`` 347 | // 348 | // * year - variable length encoded unsigned64 value for the year 349 | // * month - variable length encoded unsigned64 value for the month 350 | // * day - variable length encoded unsigned64 value for the day 351 | // * hour - optional variable length encoded unsigned64 value for the hour 352 | // * minutes - optional variable length encoded unsigned64 value for the minutes 353 | // * seconds - optional variable length encoded unsigned64 value for the seconds 354 | // * useconds - optional variable length encoded unsigned64 value for the microseconds 355 | // 356 | // .. note:: hour, minutes, seconds, useconds are optional if all the values to the right are 0 357 | // 358 | // ``.flags & 1`` (timestamp) 359 | // 360 | // ============= ======= 361 | // SQL Type .flags 362 | // ============= ======= 363 | // DATETIME 364 | // TIMESTAMP 1 365 | // 366 | // DECIMAL 367 | // An arbitrary length number. The number is encoded as a single byte 368 | // indicating the position of the decimal point followed by the Packed BCD 369 | // encoded number. Packed BCD is used to simplify conversion to and 370 | // from strings and other native arbitrary precision math datatypes. 371 | // .. seealso:: packed BCD in https://en.wikipedia.org/wiki/Binary-coded_decimal 372 | // 373 | // ``.length`` 374 | // maximum number of displayable decimal digits (*excluding* the decimal point and sign, but including ``.fractional_digits``) 375 | // 376 | // .. note:: should be in the range of 1 - 65 377 | // 378 | // ``.fractional_digits`` 379 | // is the decimal digits to display out of length 380 | // 381 | // .. note:: should be in the range of 0 - 30 382 | // 383 | // ``value`` 384 | // the following bytes sequence: 385 | // 386 | // ``| scale | BCD | sign | [0x0] |`` 387 | // 388 | // * scale - 8bit scale value (number of decimal digit after the '.') 389 | // * BCD - BCD encoded digits (4 bits for each digit) 390 | // * sign - sign encoded on 4 bits (0xc = "+", 0xd = "-") 391 | // * 0x0 - last 4bits if length(digits) % 2 == 0 392 | // 393 | // Example: x04 0x12 0x34 0x01 0xd0 -> -12.3401 394 | // 395 | // SET 396 | // A list of strings representing a SET of values. 397 | // 398 | // ``value`` 399 | // A sequence of 0 or more of protobuf's bytes (length prepended octets) or one of 400 | // the special sequences with a predefined meaning listed below. 401 | // 402 | // Example (length of the bytes array shown in brackets): 403 | // * ``[0]`` - the NULL value 404 | // * ``[1] 0x00`` - a set containing a blank string '' 405 | // * ``[1] 0x01`` - this would be an invalid value, but is to be treated as the empty set 406 | // * ``[2] 0x01 0x00`` - a set with a single item, which is the '\0' character 407 | // * ``[8] 0x03 F O O 0x03 B A R`` - a set with 2 items: FOO,BAR 408 | // 409 | // 410 | // :param name: name of the column 411 | // :param original_name: name of the column before an alias was applied 412 | // :param table: name of the table the column orginates from 413 | // :param original_table: name of the table the column orginates from before an alias was applied 414 | // :param schema: schema the column originates from 415 | // :param catalog: 416 | // catalog the schema originates from 417 | // 418 | // .. note:: 419 | // as there is current no support for catalogs in MySQL, don't expect this field to be set. 420 | // In the MySQL C/S protocol the field had the value ``def`` all the time. 421 | // 422 | // :param fractional_digits: displayed factional decimal digits for floating point and fixed point numbers 423 | // :param length: maximum count of displayable characters of .type 424 | // :param flags: 425 | // ``.type`` specific flags 426 | // 427 | // ======= ====== =========== 428 | // type value description 429 | // ======= ====== =========== 430 | // UINT 0x0001 zerofill 431 | // DOUBLE 0x0001 unsigned 432 | // FLOAT 0x0001 unsigned 433 | // DECIMAL 0x0001 unsigned 434 | // BYTES 0x0001 rightpad 435 | // ======= ====== =========== 436 | // 437 | // ====== ================ 438 | // value description 439 | // ====== ================ 440 | // 0x0010 NOT_NULL 441 | // 0x0020 PRIMARY_KEY 442 | // 0x0040 UNIQUE_KEY 443 | // 0x0080 MULTIPLE_KEY 444 | // 0x0100 AUTO_INCREMENT 445 | // ====== ================ 446 | // 447 | // default: 0 448 | // :param content_type: 449 | // a hint about the higher-level encoding of a BYTES field, for more informations 450 | // please refer to Mysqlx.Resultset.ContentType_BYTES enum. 451 | // 452 | message ColumnMetaData { 453 | enum FieldType { 454 | SINT = 1; 455 | UINT = 2; 456 | 457 | DOUBLE = 5; 458 | FLOAT = 6; 459 | 460 | BYTES = 7; 461 | 462 | TIME = 10; 463 | DATETIME = 12; 464 | SET = 15; 465 | ENUM = 16; 466 | BIT = 17; 467 | 468 | DECIMAL = 18; 469 | } 470 | 471 | // datatype of the field in a row 472 | required FieldType type = 1; 473 | optional bytes name = 2; 474 | optional bytes original_name = 3; 475 | 476 | optional bytes table = 4; 477 | optional bytes original_table = 5; 478 | 479 | optional bytes schema = 6; 480 | optional bytes catalog = 7; 481 | 482 | optional uint64 collation = 8; 483 | 484 | optional uint32 fractional_digits = 9; 485 | 486 | optional uint32 length = 10; 487 | 488 | optional uint32 flags = 11; 489 | 490 | optional uint32 content_type = 12; 491 | 492 | option (server_message_id) = RESULTSET_COLUMN_META_DATA; // comment_out_if PROTOBUF_LITE 493 | } 494 | 495 | // Row in a Resultset 496 | // 497 | // a row is represented as a list of fields encoded as byte blobs. 498 | // Blob of size 0 represents the NULL value. Otherwise, if it contains at least 499 | // one byte, it encodes a non-null value of the field using encoding appropriate for the 500 | // type of the value given by ``ColumnMetadata``, as specified 501 | // in the :protobuf:msg:`Mysqlx.Resultset::ColumnMetaData` description. 502 | // 503 | message Row { 504 | repeated bytes field = 1; 505 | 506 | option (server_message_id) = RESULTSET_ROW; // comment_out_if PROTOBUF_LITE 507 | } 508 | 509 | 510 | // a hint about the higher-level encoding of a BYTES field 511 | // 512 | // ====== ====== =========== 513 | // type value description 514 | // ====== ====== =========== 515 | // BYTES 0x0001 GEOMETRY (WKB encoding) 516 | // BYTES 0x0002 JSON (text encoding) 517 | // BYTES 0x0003 XML (text encoding) 518 | // ====== ====== =========== 519 | // 520 | // .. note:: 521 | // this list isn't comprehensive. As guideline: the field's value is expected 522 | // to pass a validator check on client and server if this field is set. 523 | // If the server adds more internal datatypes that rely on BLOB storage 524 | // like image manipulation, seeking into complex types in BLOBs, ... more 525 | // types will be added. 526 | enum ContentType_BYTES { 527 | GEOMETRY = 1; 528 | JSON = 2; 529 | XML = 3; 530 | }; 531 | 532 | // a hint about the higher-level encoding of a DATETIME field 533 | // 534 | // ====== ====== =========== 535 | // type value description 536 | // ======== ====== =========== 537 | // DATE 0x0001 DATETIME contains only date part 538 | // DATETIME 0x0002 DATETIME contains both date and time parts 539 | // ====== ====== =========== 540 | enum ContentType_DATETIME { 541 | DATE = 1; 542 | DATETIME = 2; 543 | }; 544 | -------------------------------------------------------------------------------- /internal/proto/mysqlx_session.proto: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License, version 2.0, 6 | * as published by the Free Software Foundation. 7 | * 8 | * This program is also distributed with certain software (including 9 | * but not limited to OpenSSL) that is licensed under separate terms, 10 | * as designated in a particular file or component or in included license 11 | * documentation. The authors of MySQL hereby grant you an additional 12 | * permission to link the program and your derivative works with the 13 | * separately licensed software that they have included with MySQL. 14 | * 15 | * This program is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License, version 2.0, for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program; if not, write to the Free Software 22 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 23 | */ 24 | syntax = "proto2"; 25 | 26 | import "mysqlx.proto"; // comment_out_if PROTOBUF_LITE 27 | 28 | // ifdef PROTOBUF_LITE: option optimize_for = LITE_RUNTIME; 29 | 30 | // Messages to manage Sessions 31 | // 32 | // .. uml:: 33 | // 34 | // == session start == 35 | // Client -> Server: AuthenticateStart 36 | // opt 37 | // Server --> Client: AuthenticateContinue 38 | // Client --> Server: AuthenticateContinue 39 | // end 40 | // alt 41 | // Server --> Client: AuthenticateOk 42 | // else 43 | // Server --> Client: Error 44 | // end 45 | // ... 46 | // == session reset == 47 | // Client -> Server: Reset 48 | // Server --> Client: Ok 49 | // == session end == 50 | // Client -> Server: Close 51 | // Server --> Client: Ok 52 | // 53 | package Mysqlx.Session; 54 | option java_package = "com.mysql.cj.x.protobuf"; 55 | 56 | // the initial message send from the client to the server to start the 57 | // authentication proccess 58 | // 59 | // :param mech_name: authentication mechanism name 60 | // :param auth_data: authentication data 61 | // :param initial_response: initial response 62 | // :Returns: :protobuf:msg:`Mysqlx.Session::AuthenticateContinue` 63 | message AuthenticateStart { 64 | required string mech_name = 1; 65 | optional bytes auth_data = 2; 66 | optional bytes initial_response = 3; 67 | 68 | option (client_message_id) = SESS_AUTHENTICATE_START; // comment_out_if PROTOBUF_LITE 69 | } 70 | 71 | // send by client or server after a :protobuf:msg:`Mysqlx.Session::AuthenticateStart` to 72 | // exchange more auth data 73 | // 74 | // :param auth_data: authentication data 75 | // :Returns: :protobuf:msg:`Mysqlx.Session::AuthenticateContinue` 76 | message AuthenticateContinue { 77 | required bytes auth_data = 1; 78 | 79 | option (server_message_id) = SESS_AUTHENTICATE_CONTINUE; // comment_out_if PROTOBUF_LITE 80 | option (client_message_id) = SESS_AUTHENTICATE_CONTINUE; // comment_out_if PROTOBUF_LITE 81 | } 82 | 83 | // sent by the server after successful authentication 84 | // 85 | // :param auth_data: authentication data 86 | message AuthenticateOk { 87 | optional bytes auth_data = 1; 88 | 89 | option (server_message_id) = SESS_AUTHENTICATE_OK; // comment_out_if PROTOBUF_LITE 90 | } 91 | 92 | // reset the current session 93 | // 94 | // :Returns: :protobuf:msg:`Mysqlx::Ok` 95 | message Reset { 96 | option (client_message_id) = SESS_RESET; // comment_out_if PROTOBUF_LITE 97 | } 98 | 99 | // close the current session 100 | // 101 | // :Returns: :protobuf:msg:`Mysqlx::Ok` 102 | message Close { 103 | option (client_message_id) = SESS_CLOSE; // comment_out_if PROTOBUF_LITE 104 | } 105 | 106 | -------------------------------------------------------------------------------- /internal/proto/mysqlx_sql.proto: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License, version 2.0, 6 | * as published by the Free Software Foundation. 7 | * 8 | * This program is also distributed with certain software (including 9 | * but not limited to OpenSSL) that is licensed under separate terms, 10 | * as designated in a particular file or component or in included license 11 | * documentation. The authors of MySQL hereby grant you an additional 12 | * permission to link the program and your derivative works with the 13 | * separately licensed software that they have included with MySQL. 14 | * 15 | * This program is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License, version 2.0, for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program; if not, write to the Free Software 22 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 23 | */ 24 | syntax = "proto2"; 25 | 26 | import "mysqlx.proto"; // comment_out_if PROTOBUF_LITE 27 | 28 | // ifdef PROTOBUF_LITE: option optimize_for = LITE_RUNTIME; 29 | 30 | // Messages of the MySQL Package 31 | package Mysqlx.Sql; 32 | option java_package = "com.mysql.cj.x.protobuf"; 33 | 34 | import "mysqlx_datatypes.proto"; 35 | 36 | // execute a statement in the given namespace 37 | // 38 | // .. uml:: 39 | // 40 | // client -> server: StmtExecute 41 | // ... zero or more Resultsets ... 42 | // server --> client: StmtExecuteOk 43 | // 44 | // Notices: 45 | // This message may generate a notice containing WARNINGs generated by its execution. 46 | // This message may generate a notice containing INFO messages generated by its execution. 47 | // 48 | // :param namespace: namespace of the statement to be executed 49 | // :param stmt: statement that shall be executed. 50 | // :param args: values for wildcard replacements 51 | // :param compact_metadata: send only type information for :protobuf:msg:`Mysqlx.Resultset::ColumnMetadata`, skipping names and others 52 | // :returns: 53 | // * zero or one :protobuf:msg:`Mysqlx.Resultset::` followed by :protobuf:msg:`Mysqlx.Sql::StmtExecuteOk` 54 | message StmtExecute { 55 | optional string namespace = 3 [ default = "sql" ]; 56 | required bytes stmt = 1; 57 | repeated Mysqlx.Datatypes.Any args = 2; 58 | optional bool compact_metadata = 4 [ default = false ]; 59 | 60 | option (client_message_id) = SQL_STMT_EXECUTE; // comment_out_if PROTOBUF_LITE 61 | } 62 | 63 | // statement executed successful 64 | message StmtExecuteOk { 65 | option (server_message_id) = SQL_STMT_EXECUTE_OK; // comment_out_if PROTOBUF_LITE 66 | } 67 | 68 | -------------------------------------------------------------------------------- /internal/proto/mysqlx_sql/mysqlx_sql.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-gogo. DO NOT EDIT. 2 | // source: mysqlx_sql.proto 3 | 4 | package mysqlx_sql 5 | 6 | /* 7 | Messages of the MySQL Package 8 | */ 9 | 10 | import proto "github.com/golang/protobuf/proto" 11 | import fmt "fmt" 12 | import math "math" 13 | import _ "github.com/AlekSi/mysqlx/internal/proto/mysqlx" 14 | import mysqlx_datatypes "github.com/AlekSi/mysqlx/internal/proto/mysqlx_datatypes" 15 | 16 | import github_com_golang_protobuf_proto "github.com/golang/protobuf/proto" 17 | 18 | import io "io" 19 | 20 | // Reference imports to suppress errors if they are not otherwise used. 21 | var _ = proto.Marshal 22 | var _ = fmt.Errorf 23 | var _ = math.Inf 24 | 25 | // This is a compile-time assertion to ensure that this generated file 26 | // is compatible with the proto package it is being compiled against. 27 | // A compilation error at this line likely means your copy of the 28 | // proto package needs to be updated. 29 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 30 | 31 | // execute a statement in the given namespace 32 | // 33 | // .. uml:: 34 | // 35 | // client -> server: StmtExecute 36 | // ... zero or more Resultsets ... 37 | // server --> client: StmtExecuteOk 38 | // 39 | // Notices: 40 | // This message may generate a notice containing WARNINGs generated by its execution. 41 | // This message may generate a notice containing INFO messages generated by its execution. 42 | // 43 | // :param namespace: namespace of the statement to be executed 44 | // :param stmt: statement that shall be executed. 45 | // :param args: values for wildcard replacements 46 | // :param compact_metadata: send only type information for :protobuf:msg:`Mysqlx.Resultset::ColumnMetadata`, skipping names and others 47 | // :returns: 48 | // * zero or one :protobuf:msg:`Mysqlx.Resultset::` followed by :protobuf:msg:`Mysqlx.Sql::StmtExecuteOk` 49 | type StmtExecute struct { 50 | Namespace *string `protobuf:"bytes,3,opt,name=namespace,def=sql" json:"namespace,omitempty"` 51 | Stmt []byte `protobuf:"bytes,1,req,name=stmt" json:"stmt,omitempty"` 52 | Args []*mysqlx_datatypes.Any `protobuf:"bytes,2,rep,name=args" json:"args,omitempty"` 53 | CompactMetadata *bool `protobuf:"varint,4,opt,name=compact_metadata,json=compactMetadata,def=0" json:"compact_metadata,omitempty"` 54 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 55 | XXX_unrecognized []byte `json:"-"` 56 | XXX_sizecache int32 `json:"-"` 57 | } 58 | 59 | func (m *StmtExecute) Reset() { *m = StmtExecute{} } 60 | func (m *StmtExecute) String() string { return proto.CompactTextString(m) } 61 | func (*StmtExecute) ProtoMessage() {} 62 | func (*StmtExecute) Descriptor() ([]byte, []int) { 63 | return fileDescriptor_mysqlx_sql_c9df6f2ac9a597dc, []int{0} 64 | } 65 | func (m *StmtExecute) XXX_Unmarshal(b []byte) error { 66 | return m.Unmarshal(b) 67 | } 68 | func (m *StmtExecute) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 69 | if deterministic { 70 | return xxx_messageInfo_StmtExecute.Marshal(b, m, deterministic) 71 | } else { 72 | b = b[:cap(b)] 73 | n, err := m.MarshalTo(b) 74 | if err != nil { 75 | return nil, err 76 | } 77 | return b[:n], nil 78 | } 79 | } 80 | func (dst *StmtExecute) XXX_Merge(src proto.Message) { 81 | xxx_messageInfo_StmtExecute.Merge(dst, src) 82 | } 83 | func (m *StmtExecute) XXX_Size() int { 84 | return m.Size() 85 | } 86 | func (m *StmtExecute) XXX_DiscardUnknown() { 87 | xxx_messageInfo_StmtExecute.DiscardUnknown(m) 88 | } 89 | 90 | var xxx_messageInfo_StmtExecute proto.InternalMessageInfo 91 | 92 | const Default_StmtExecute_Namespace string = "sql" 93 | const Default_StmtExecute_CompactMetadata bool = false 94 | 95 | func (m *StmtExecute) GetNamespace() string { 96 | if m != nil && m.Namespace != nil { 97 | return *m.Namespace 98 | } 99 | return Default_StmtExecute_Namespace 100 | } 101 | 102 | func (m *StmtExecute) GetStmt() []byte { 103 | if m != nil { 104 | return m.Stmt 105 | } 106 | return nil 107 | } 108 | 109 | func (m *StmtExecute) GetArgs() []*mysqlx_datatypes.Any { 110 | if m != nil { 111 | return m.Args 112 | } 113 | return nil 114 | } 115 | 116 | func (m *StmtExecute) GetCompactMetadata() bool { 117 | if m != nil && m.CompactMetadata != nil { 118 | return *m.CompactMetadata 119 | } 120 | return Default_StmtExecute_CompactMetadata 121 | } 122 | 123 | // statement executed successful 124 | type StmtExecuteOk struct { 125 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 126 | XXX_unrecognized []byte `json:"-"` 127 | XXX_sizecache int32 `json:"-"` 128 | } 129 | 130 | func (m *StmtExecuteOk) Reset() { *m = StmtExecuteOk{} } 131 | func (m *StmtExecuteOk) String() string { return proto.CompactTextString(m) } 132 | func (*StmtExecuteOk) ProtoMessage() {} 133 | func (*StmtExecuteOk) Descriptor() ([]byte, []int) { 134 | return fileDescriptor_mysqlx_sql_c9df6f2ac9a597dc, []int{1} 135 | } 136 | func (m *StmtExecuteOk) XXX_Unmarshal(b []byte) error { 137 | return m.Unmarshal(b) 138 | } 139 | func (m *StmtExecuteOk) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 140 | if deterministic { 141 | return xxx_messageInfo_StmtExecuteOk.Marshal(b, m, deterministic) 142 | } else { 143 | b = b[:cap(b)] 144 | n, err := m.MarshalTo(b) 145 | if err != nil { 146 | return nil, err 147 | } 148 | return b[:n], nil 149 | } 150 | } 151 | func (dst *StmtExecuteOk) XXX_Merge(src proto.Message) { 152 | xxx_messageInfo_StmtExecuteOk.Merge(dst, src) 153 | } 154 | func (m *StmtExecuteOk) XXX_Size() int { 155 | return m.Size() 156 | } 157 | func (m *StmtExecuteOk) XXX_DiscardUnknown() { 158 | xxx_messageInfo_StmtExecuteOk.DiscardUnknown(m) 159 | } 160 | 161 | var xxx_messageInfo_StmtExecuteOk proto.InternalMessageInfo 162 | 163 | func init() { 164 | proto.RegisterType((*StmtExecute)(nil), "Mysqlx.Sql.StmtExecute") 165 | proto.RegisterType((*StmtExecuteOk)(nil), "Mysqlx.Sql.StmtExecuteOk") 166 | } 167 | func (m *StmtExecute) Marshal() (dAtA []byte, err error) { 168 | size := m.Size() 169 | dAtA = make([]byte, size) 170 | n, err := m.MarshalTo(dAtA) 171 | if err != nil { 172 | return nil, err 173 | } 174 | return dAtA[:n], nil 175 | } 176 | 177 | func (m *StmtExecute) MarshalTo(dAtA []byte) (int, error) { 178 | var i int 179 | _ = i 180 | var l int 181 | _ = l 182 | if m.Stmt == nil { 183 | return 0, new(github_com_golang_protobuf_proto.RequiredNotSetError) 184 | } else { 185 | dAtA[i] = 0xa 186 | i++ 187 | i = encodeVarintMysqlxSql(dAtA, i, uint64(len(m.Stmt))) 188 | i += copy(dAtA[i:], m.Stmt) 189 | } 190 | if len(m.Args) > 0 { 191 | for _, msg := range m.Args { 192 | dAtA[i] = 0x12 193 | i++ 194 | i = encodeVarintMysqlxSql(dAtA, i, uint64(msg.Size())) 195 | n, err := msg.MarshalTo(dAtA[i:]) 196 | if err != nil { 197 | return 0, err 198 | } 199 | i += n 200 | } 201 | } 202 | if m.Namespace != nil { 203 | dAtA[i] = 0x1a 204 | i++ 205 | i = encodeVarintMysqlxSql(dAtA, i, uint64(len(*m.Namespace))) 206 | i += copy(dAtA[i:], *m.Namespace) 207 | } 208 | if m.CompactMetadata != nil { 209 | dAtA[i] = 0x20 210 | i++ 211 | if *m.CompactMetadata { 212 | dAtA[i] = 1 213 | } else { 214 | dAtA[i] = 0 215 | } 216 | i++ 217 | } 218 | if m.XXX_unrecognized != nil { 219 | i += copy(dAtA[i:], m.XXX_unrecognized) 220 | } 221 | return i, nil 222 | } 223 | 224 | func (m *StmtExecuteOk) Marshal() (dAtA []byte, err error) { 225 | size := m.Size() 226 | dAtA = make([]byte, size) 227 | n, err := m.MarshalTo(dAtA) 228 | if err != nil { 229 | return nil, err 230 | } 231 | return dAtA[:n], nil 232 | } 233 | 234 | func (m *StmtExecuteOk) MarshalTo(dAtA []byte) (int, error) { 235 | var i int 236 | _ = i 237 | var l int 238 | _ = l 239 | if m.XXX_unrecognized != nil { 240 | i += copy(dAtA[i:], m.XXX_unrecognized) 241 | } 242 | return i, nil 243 | } 244 | 245 | func encodeVarintMysqlxSql(dAtA []byte, offset int, v uint64) int { 246 | for v >= 1<<7 { 247 | dAtA[offset] = uint8(v&0x7f | 0x80) 248 | v >>= 7 249 | offset++ 250 | } 251 | dAtA[offset] = uint8(v) 252 | return offset + 1 253 | } 254 | func (m *StmtExecute) Size() (n int) { 255 | var l int 256 | _ = l 257 | if m.Stmt != nil { 258 | l = len(m.Stmt) 259 | n += 1 + l + sovMysqlxSql(uint64(l)) 260 | } 261 | if len(m.Args) > 0 { 262 | for _, e := range m.Args { 263 | l = e.Size() 264 | n += 1 + l + sovMysqlxSql(uint64(l)) 265 | } 266 | } 267 | if m.Namespace != nil { 268 | l = len(*m.Namespace) 269 | n += 1 + l + sovMysqlxSql(uint64(l)) 270 | } 271 | if m.CompactMetadata != nil { 272 | n += 2 273 | } 274 | if m.XXX_unrecognized != nil { 275 | n += len(m.XXX_unrecognized) 276 | } 277 | return n 278 | } 279 | 280 | func (m *StmtExecuteOk) Size() (n int) { 281 | var l int 282 | _ = l 283 | if m.XXX_unrecognized != nil { 284 | n += len(m.XXX_unrecognized) 285 | } 286 | return n 287 | } 288 | 289 | func sovMysqlxSql(x uint64) (n int) { 290 | for { 291 | n++ 292 | x >>= 7 293 | if x == 0 { 294 | break 295 | } 296 | } 297 | return n 298 | } 299 | func sozMysqlxSql(x uint64) (n int) { 300 | return sovMysqlxSql(uint64((x << 1) ^ uint64((int64(x) >> 63)))) 301 | } 302 | func (m *StmtExecute) Unmarshal(dAtA []byte) error { 303 | var hasFields [1]uint64 304 | l := len(dAtA) 305 | iNdEx := 0 306 | for iNdEx < l { 307 | preIndex := iNdEx 308 | var wire uint64 309 | for shift := uint(0); ; shift += 7 { 310 | if shift >= 64 { 311 | return ErrIntOverflowMysqlxSql 312 | } 313 | if iNdEx >= l { 314 | return io.ErrUnexpectedEOF 315 | } 316 | b := dAtA[iNdEx] 317 | iNdEx++ 318 | wire |= (uint64(b) & 0x7F) << shift 319 | if b < 0x80 { 320 | break 321 | } 322 | } 323 | fieldNum := int32(wire >> 3) 324 | wireType := int(wire & 0x7) 325 | if wireType == 4 { 326 | return fmt.Errorf("proto: StmtExecute: wiretype end group for non-group") 327 | } 328 | if fieldNum <= 0 { 329 | return fmt.Errorf("proto: StmtExecute: illegal tag %d (wire type %d)", fieldNum, wire) 330 | } 331 | switch fieldNum { 332 | case 1: 333 | if wireType != 2 { 334 | return fmt.Errorf("proto: wrong wireType = %d for field Stmt", wireType) 335 | } 336 | var byteLen int 337 | for shift := uint(0); ; shift += 7 { 338 | if shift >= 64 { 339 | return ErrIntOverflowMysqlxSql 340 | } 341 | if iNdEx >= l { 342 | return io.ErrUnexpectedEOF 343 | } 344 | b := dAtA[iNdEx] 345 | iNdEx++ 346 | byteLen |= (int(b) & 0x7F) << shift 347 | if b < 0x80 { 348 | break 349 | } 350 | } 351 | if byteLen < 0 { 352 | return ErrInvalidLengthMysqlxSql 353 | } 354 | postIndex := iNdEx + byteLen 355 | if postIndex > l { 356 | return io.ErrUnexpectedEOF 357 | } 358 | m.Stmt = append(m.Stmt[:0], dAtA[iNdEx:postIndex]...) 359 | if m.Stmt == nil { 360 | m.Stmt = []byte{} 361 | } 362 | iNdEx = postIndex 363 | hasFields[0] |= uint64(0x00000001) 364 | case 2: 365 | if wireType != 2 { 366 | return fmt.Errorf("proto: wrong wireType = %d for field Args", wireType) 367 | } 368 | var msglen int 369 | for shift := uint(0); ; shift += 7 { 370 | if shift >= 64 { 371 | return ErrIntOverflowMysqlxSql 372 | } 373 | if iNdEx >= l { 374 | return io.ErrUnexpectedEOF 375 | } 376 | b := dAtA[iNdEx] 377 | iNdEx++ 378 | msglen |= (int(b) & 0x7F) << shift 379 | if b < 0x80 { 380 | break 381 | } 382 | } 383 | if msglen < 0 { 384 | return ErrInvalidLengthMysqlxSql 385 | } 386 | postIndex := iNdEx + msglen 387 | if postIndex > l { 388 | return io.ErrUnexpectedEOF 389 | } 390 | m.Args = append(m.Args, &mysqlx_datatypes.Any{}) 391 | if err := m.Args[len(m.Args)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { 392 | return err 393 | } 394 | iNdEx = postIndex 395 | case 3: 396 | if wireType != 2 { 397 | return fmt.Errorf("proto: wrong wireType = %d for field Namespace", wireType) 398 | } 399 | var stringLen uint64 400 | for shift := uint(0); ; shift += 7 { 401 | if shift >= 64 { 402 | return ErrIntOverflowMysqlxSql 403 | } 404 | if iNdEx >= l { 405 | return io.ErrUnexpectedEOF 406 | } 407 | b := dAtA[iNdEx] 408 | iNdEx++ 409 | stringLen |= (uint64(b) & 0x7F) << shift 410 | if b < 0x80 { 411 | break 412 | } 413 | } 414 | intStringLen := int(stringLen) 415 | if intStringLen < 0 { 416 | return ErrInvalidLengthMysqlxSql 417 | } 418 | postIndex := iNdEx + intStringLen 419 | if postIndex > l { 420 | return io.ErrUnexpectedEOF 421 | } 422 | s := string(dAtA[iNdEx:postIndex]) 423 | m.Namespace = &s 424 | iNdEx = postIndex 425 | case 4: 426 | if wireType != 0 { 427 | return fmt.Errorf("proto: wrong wireType = %d for field CompactMetadata", wireType) 428 | } 429 | var v int 430 | for shift := uint(0); ; shift += 7 { 431 | if shift >= 64 { 432 | return ErrIntOverflowMysqlxSql 433 | } 434 | if iNdEx >= l { 435 | return io.ErrUnexpectedEOF 436 | } 437 | b := dAtA[iNdEx] 438 | iNdEx++ 439 | v |= (int(b) & 0x7F) << shift 440 | if b < 0x80 { 441 | break 442 | } 443 | } 444 | b := bool(v != 0) 445 | m.CompactMetadata = &b 446 | default: 447 | iNdEx = preIndex 448 | skippy, err := skipMysqlxSql(dAtA[iNdEx:]) 449 | if err != nil { 450 | return err 451 | } 452 | if skippy < 0 { 453 | return ErrInvalidLengthMysqlxSql 454 | } 455 | if (iNdEx + skippy) > l { 456 | return io.ErrUnexpectedEOF 457 | } 458 | m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) 459 | iNdEx += skippy 460 | } 461 | } 462 | if hasFields[0]&uint64(0x00000001) == 0 { 463 | return new(github_com_golang_protobuf_proto.RequiredNotSetError) 464 | } 465 | 466 | if iNdEx > l { 467 | return io.ErrUnexpectedEOF 468 | } 469 | return nil 470 | } 471 | func (m *StmtExecuteOk) Unmarshal(dAtA []byte) error { 472 | l := len(dAtA) 473 | iNdEx := 0 474 | for iNdEx < l { 475 | preIndex := iNdEx 476 | var wire uint64 477 | for shift := uint(0); ; shift += 7 { 478 | if shift >= 64 { 479 | return ErrIntOverflowMysqlxSql 480 | } 481 | if iNdEx >= l { 482 | return io.ErrUnexpectedEOF 483 | } 484 | b := dAtA[iNdEx] 485 | iNdEx++ 486 | wire |= (uint64(b) & 0x7F) << shift 487 | if b < 0x80 { 488 | break 489 | } 490 | } 491 | fieldNum := int32(wire >> 3) 492 | wireType := int(wire & 0x7) 493 | if wireType == 4 { 494 | return fmt.Errorf("proto: StmtExecuteOk: wiretype end group for non-group") 495 | } 496 | if fieldNum <= 0 { 497 | return fmt.Errorf("proto: StmtExecuteOk: illegal tag %d (wire type %d)", fieldNum, wire) 498 | } 499 | switch fieldNum { 500 | default: 501 | iNdEx = preIndex 502 | skippy, err := skipMysqlxSql(dAtA[iNdEx:]) 503 | if err != nil { 504 | return err 505 | } 506 | if skippy < 0 { 507 | return ErrInvalidLengthMysqlxSql 508 | } 509 | if (iNdEx + skippy) > l { 510 | return io.ErrUnexpectedEOF 511 | } 512 | m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) 513 | iNdEx += skippy 514 | } 515 | } 516 | 517 | if iNdEx > l { 518 | return io.ErrUnexpectedEOF 519 | } 520 | return nil 521 | } 522 | func skipMysqlxSql(dAtA []byte) (n int, err error) { 523 | l := len(dAtA) 524 | iNdEx := 0 525 | for iNdEx < l { 526 | var wire uint64 527 | for shift := uint(0); ; shift += 7 { 528 | if shift >= 64 { 529 | return 0, ErrIntOverflowMysqlxSql 530 | } 531 | if iNdEx >= l { 532 | return 0, io.ErrUnexpectedEOF 533 | } 534 | b := dAtA[iNdEx] 535 | iNdEx++ 536 | wire |= (uint64(b) & 0x7F) << shift 537 | if b < 0x80 { 538 | break 539 | } 540 | } 541 | wireType := int(wire & 0x7) 542 | switch wireType { 543 | case 0: 544 | for shift := uint(0); ; shift += 7 { 545 | if shift >= 64 { 546 | return 0, ErrIntOverflowMysqlxSql 547 | } 548 | if iNdEx >= l { 549 | return 0, io.ErrUnexpectedEOF 550 | } 551 | iNdEx++ 552 | if dAtA[iNdEx-1] < 0x80 { 553 | break 554 | } 555 | } 556 | return iNdEx, nil 557 | case 1: 558 | iNdEx += 8 559 | return iNdEx, nil 560 | case 2: 561 | var length int 562 | for shift := uint(0); ; shift += 7 { 563 | if shift >= 64 { 564 | return 0, ErrIntOverflowMysqlxSql 565 | } 566 | if iNdEx >= l { 567 | return 0, io.ErrUnexpectedEOF 568 | } 569 | b := dAtA[iNdEx] 570 | iNdEx++ 571 | length |= (int(b) & 0x7F) << shift 572 | if b < 0x80 { 573 | break 574 | } 575 | } 576 | iNdEx += length 577 | if length < 0 { 578 | return 0, ErrInvalidLengthMysqlxSql 579 | } 580 | return iNdEx, nil 581 | case 3: 582 | for { 583 | var innerWire uint64 584 | var start int = iNdEx 585 | for shift := uint(0); ; shift += 7 { 586 | if shift >= 64 { 587 | return 0, ErrIntOverflowMysqlxSql 588 | } 589 | if iNdEx >= l { 590 | return 0, io.ErrUnexpectedEOF 591 | } 592 | b := dAtA[iNdEx] 593 | iNdEx++ 594 | innerWire |= (uint64(b) & 0x7F) << shift 595 | if b < 0x80 { 596 | break 597 | } 598 | } 599 | innerWireType := int(innerWire & 0x7) 600 | if innerWireType == 4 { 601 | break 602 | } 603 | next, err := skipMysqlxSql(dAtA[start:]) 604 | if err != nil { 605 | return 0, err 606 | } 607 | iNdEx = start + next 608 | } 609 | return iNdEx, nil 610 | case 4: 611 | return iNdEx, nil 612 | case 5: 613 | iNdEx += 4 614 | return iNdEx, nil 615 | default: 616 | return 0, fmt.Errorf("proto: illegal wireType %d", wireType) 617 | } 618 | } 619 | panic("unreachable") 620 | } 621 | 622 | var ( 623 | ErrInvalidLengthMysqlxSql = fmt.Errorf("proto: negative length found during unmarshaling") 624 | ErrIntOverflowMysqlxSql = fmt.Errorf("proto: integer overflow") 625 | ) 626 | 627 | func init() { proto.RegisterFile("mysqlx_sql.proto", fileDescriptor_mysqlx_sql_c9df6f2ac9a597dc) } 628 | 629 | var fileDescriptor_mysqlx_sql_c9df6f2ac9a597dc = []byte{ 630 | // 255 bytes of a gzipped FileDescriptorProto 631 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0xc8, 0xad, 0x2c, 0x2e, 632 | 0xcc, 0xa9, 0x88, 0x2f, 0x2e, 0xcc, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0xf2, 0x05, 633 | 0x8b, 0xe8, 0x05, 0x17, 0xe6, 0x48, 0xf1, 0x40, 0x64, 0x21, 0x32, 0x52, 0x62, 0x50, 0xb5, 0x29, 634 | 0x89, 0x25, 0x89, 0x25, 0x95, 0x05, 0xa9, 0xc5, 0x10, 0x71, 0xa5, 0xe5, 0x8c, 0x5c, 0xdc, 0xc1, 635 | 0x25, 0xb9, 0x25, 0xae, 0x15, 0xa9, 0xc9, 0xa5, 0x25, 0xa9, 0x42, 0x42, 0x5c, 0x2c, 0xc5, 0x25, 636 | 0xb9, 0x25, 0x12, 0x8c, 0x0a, 0x4c, 0x1a, 0x3c, 0x41, 0x60, 0xb6, 0x90, 0x26, 0x17, 0x4b, 0x62, 637 | 0x51, 0x7a, 0xb1, 0x04, 0x93, 0x02, 0xb3, 0x06, 0xb7, 0x91, 0xa8, 0x1e, 0xd4, 0x12, 0x17, 0xb8, 638 | 0x51, 0x8e, 0x79, 0x95, 0x41, 0x60, 0x25, 0x42, 0x8a, 0x5c, 0x9c, 0x79, 0x89, 0xb9, 0xa9, 0xc5, 639 | 0x05, 0x89, 0xc9, 0xa9, 0x12, 0xcc, 0x0a, 0x8c, 0x1a, 0x9c, 0x56, 0xcc, 0xc5, 0x85, 0x39, 0x41, 640 | 0x08, 0x51, 0x21, 0x03, 0x2e, 0x81, 0xe4, 0xfc, 0xdc, 0x82, 0xc4, 0xe4, 0x92, 0xf8, 0xdc, 0xd4, 641 | 0x92, 0x44, 0x90, 0x83, 0x24, 0x58, 0x14, 0x18, 0x35, 0x38, 0xac, 0x58, 0xd3, 0x12, 0x73, 0x8a, 642 | 0x53, 0x83, 0xf8, 0xa1, 0xd2, 0xbe, 0x50, 0x59, 0x2b, 0x96, 0x8e, 0x57, 0x06, 0x3c, 0x4a, 0xa2, 643 | 0x5c, 0xbc, 0x48, 0x0e, 0xf5, 0xcf, 0xb6, 0x62, 0x99, 0xf0, 0xca, 0x40, 0xd0, 0x49, 0xf3, 0xc4, 644 | 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf1, 0x58, 0x8e, 0x81, 645 | 0x4b, 0x3c, 0x39, 0x3f, 0x57, 0x0f, 0xec, 0x5d, 0xbd, 0xe4, 0x2c, 0x3d, 0x68, 0x00, 0x24, 0x95, 646 | 0xa6, 0x01, 0x02, 0x00, 0x00, 0xff, 0xff, 0xdf, 0x22, 0x63, 0x58, 0x30, 0x01, 0x00, 0x00, 647 | } 648 | -------------------------------------------------------------------------------- /internal/wait.go: -------------------------------------------------------------------------------- 1 | // mysqlx - MySQL driver for Go's database/​sql package and MySQL X Protocol. 2 | // Copyright (c) 2017-2018 Alexey Palazhchenko 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | 8 | // +build ignore 9 | 10 | package main 11 | 12 | import ( 13 | "database/sql" 14 | "flag" 15 | "fmt" 16 | "log" 17 | "os" 18 | "time" 19 | 20 | _ "github.com/AlekSi/mysqlx" 21 | ) 22 | 23 | const timeout = 60 * time.Second 24 | 25 | func main() { 26 | log.SetFlags(0) 27 | flag.Usage = func() { 28 | fmt.Fprintf(os.Stderr, "%s waits for MySQL to become available.\n", os.Args[0]) 29 | } 30 | flag.Parse() 31 | log.SetFlags(log.Lmicroseconds) 32 | 33 | dataSource := os.Getenv("MYSQLX_TEST_DATASOURCE") 34 | if dataSource == "" { 35 | log.Fatal("Please set environment variable MYSQLX_TEST_DATASOURCE.") 36 | } 37 | 38 | log.Printf("Connecting to %s ...", dataSource) 39 | start := time.Now() 40 | var prevErr error 41 | var attempts int 42 | for { 43 | attempts++ 44 | db, err := sql.Open("mysqlx", dataSource) 45 | if err == nil { 46 | err = db.Ping() 47 | } 48 | 49 | if err != nil { 50 | if prevErr != err || attempts%10 == 0 { 51 | log.Print(err) 52 | prevErr = err 53 | } 54 | if time.Since(start) > timeout { 55 | log.Fatalf("Failed! Last error: %s", err) 56 | } 57 | time.Sleep(time.Second) 58 | continue 59 | } 60 | 61 | log.Print("Connected!") 62 | db.Close() 63 | return 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /mysqlx_test.go: -------------------------------------------------------------------------------- 1 | // mysqlx - MySQL driver for Go's database/​sql package and MySQL X Protocol. 2 | // Copyright (c) 2017-2018 Alexey Palazhchenko 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | 8 | package mysqlx 9 | 10 | import ( 11 | "context" 12 | "database/sql" 13 | "database/sql/driver" 14 | "fmt" 15 | "math" 16 | "os" 17 | "reflect" 18 | "testing" 19 | "time" 20 | 21 | "github.com/stretchr/testify/assert" 22 | "github.com/stretchr/testify/require" 23 | ) 24 | 25 | type String string 26 | 27 | type City struct { 28 | ID int 29 | Name string 30 | District String 31 | Info string 32 | } 33 | 34 | func (c *City) Values() []interface{} { 35 | return []interface{}{ 36 | c.ID, 37 | c.Name, 38 | c.District, 39 | c.Info, 40 | } 41 | } 42 | 43 | func (c *City) Pointers() []interface{} { 44 | return []interface{}{ 45 | &c.ID, 46 | &c.Name, 47 | &c.District, 48 | &c.Info, 49 | } 50 | } 51 | 52 | type CountryLanguage struct { 53 | CountryCode string 54 | Language string 55 | IsOfficial bool 56 | Percentage float32 57 | } 58 | 59 | func (cl *CountryLanguage) Values() []interface{} { 60 | return []interface{}{ 61 | cl.CountryCode, 62 | cl.Language, 63 | cl.IsOfficial, 64 | cl.Percentage, 65 | } 66 | } 67 | 68 | func (cl *CountryLanguage) Pointers() []interface{} { 69 | return []interface{}{ 70 | &cl.CountryCode, 71 | &cl.Language, 72 | &cl.IsOfficial, 73 | &cl.Percentage, 74 | } 75 | } 76 | 77 | type ColumnType struct { 78 | Name string 79 | DatabaseTypeName string 80 | Length int64 81 | // ScanType reflect.Type 82 | // TODO more checks 83 | } 84 | 85 | func connector(tb testing.TB, database string) *Connector { 86 | tb.Helper() 87 | 88 | if testing.Short() { 89 | tb.Skip("skipping in short mode") 90 | } 91 | 92 | env := os.Getenv("MYSQLX_TEST_DATASOURCE") 93 | require.NotEmpty(tb, env, "Please set environment variable MYSQLX_TEST_DATASOURCE.") 94 | connector, err := ParseDataSource(env) 95 | require.NoError(tb, err) 96 | connector.Database = database 97 | connector.Trace = tb.Logf 98 | return connector 99 | } 100 | 101 | func openDB(tb testing.TB, database string) *sql.DB { 102 | tb.Helper() 103 | 104 | connector := connector(tb, database) 105 | db := sql.OpenDB(connector) 106 | require.NoError(tb, db.Ping()) 107 | return db 108 | } 109 | 110 | func closeDB(tb testing.TB, db *sql.DB) { 111 | tb.Helper() 112 | 113 | assert.NoError(tb, db.Close()) 114 | } 115 | 116 | var _, noLastInsertIdErr = driver.RowsAffected(0).LastInsertId() 117 | 118 | func TestQueryTableCity(t *testing.T) { 119 | t.Parallel() 120 | db := openDB(t, "world_x") 121 | defer closeDB(t, db) 122 | 123 | rows, err := db.Query("SELECT ID, Name, District, Info FROM city WHERE CountryCode = ? ORDER BY ID LIMIT 3", "RUS") 124 | require.NoError(t, err) 125 | 126 | columns, err := rows.Columns() 127 | assert.NoError(t, err) 128 | assert.Equal(t, []string{"ID", "Name", "District", "Info"}, columns) 129 | 130 | types, err := rows.ColumnTypes() 131 | assert.NoError(t, err) 132 | require.Len(t, types, 4) 133 | for i, expected := range []ColumnType{ 134 | // TODO convert internal X Protocol types to MySQL types (?) 135 | {"ID", "SINT", 11}, // FIXME should length of int(11) really be 11? 136 | {"Name", "BYTES", 35 * 3}, // CHAR(35) (inlike VARCHAR) stores 3 bytes per utf8 rune 137 | {"District", "BYTES", 20 * 3}, 138 | {"Info", "BYTES", math.MaxInt64}, 139 | } { 140 | assert.Equal(t, expected.Name, types[i].Name(), "type %+v", types[i]) 141 | assert.Equal(t, expected.DatabaseTypeName, types[i].DatabaseTypeName(), "type %+v", types[i]) 142 | l, ok := types[i].Length() 143 | if !ok { 144 | l = -1 145 | } 146 | assert.Equal(t, expected.Length, l, "type %+v", types[i]) 147 | // TODO more checks 148 | } 149 | 150 | for _, expected := range []City{ 151 | {3580, "Moscow", "Moscow (City)", `{"Population": 8389200}`}, 152 | {3581, "St Petersburg", "Pietari", `{"Population": 4694000}`}, 153 | {3582, "Novosibirsk", "Novosibirsk", `{"Population": 1398800}`}, 154 | } { 155 | assert.True(t, rows.Next()) 156 | var actual City 157 | assert.NoError(t, rows.Scan(actual.Pointers()...)) 158 | assert.Equal(t, expected, actual) 159 | } 160 | 161 | assert.False(t, rows.Next()) 162 | assert.NoError(t, rows.Err()) 163 | assert.NoError(t, rows.Close()) 164 | } 165 | 166 | func TestQueryTableCountryLanguage(t *testing.T) { 167 | t.Parallel() 168 | db := openDB(t, "world_x") 169 | defer closeDB(t, db) 170 | 171 | rows, err := db.Query("SELECT CountryCode, Language, IsOfficial, Percentage FROM countrylanguage WHERE CountryCode = ? ORDER BY Percentage DESC LIMIT 3", "RUS") 172 | require.NoError(t, err) 173 | 174 | columns, err := rows.Columns() 175 | assert.NoError(t, err) 176 | assert.Equal(t, []string{"CountryCode", "Language", "IsOfficial", "Percentage"}, columns) 177 | 178 | types, err := rows.ColumnTypes() 179 | assert.NoError(t, err) 180 | require.Len(t, types, 4) 181 | for i, expected := range []ColumnType{ 182 | // TODO convert internal X Protocol types to MySQL types (?) 183 | // https://dev.mysql.com/doc/internals/en/x-protocol-messages-messages.html 184 | {"CountryCode", "BYTES", 3 * 3}, // CHAR(3) (inlike VARCHAR) stores 3 bytes per utf8 rune 185 | {"Language", "BYTES", 30 * 3}, 186 | {"IsOfficial", "ENUM", -1}, // ENUM should not have length; in practice we have 3 for MySQL 5.7 and 0 for MySQL 8.0 187 | {"Percentage", "FLOAT", 4}, 188 | } { 189 | assert.Equal(t, expected.Name, types[i].Name(), "type %+v", types[i]) 190 | assert.Equal(t, expected.DatabaseTypeName, types[i].DatabaseTypeName(), "type %+v", types[i]) 191 | if expected.DatabaseTypeName != "ENUM" { 192 | l, ok := types[i].Length() 193 | if !ok { 194 | l = -1 195 | } 196 | assert.Equal(t, expected.Length, l, "type %+v", types[i]) 197 | } 198 | // TODO more checks 199 | } 200 | 201 | for _, expected := range []CountryLanguage{ 202 | {"RUS", "Russian", true, 86.6}, 203 | {"RUS", "Tatar", false, 3.2}, 204 | {"RUS", "Ukrainian", false, 1.3}, 205 | } { 206 | assert.True(t, rows.Next()) 207 | var actual CountryLanguage 208 | assert.NoError(t, rows.Scan(actual.Pointers()...)) 209 | assert.Equal(t, expected, actual) 210 | } 211 | 212 | assert.False(t, rows.Next()) 213 | assert.NoError(t, rows.Err()) 214 | assert.NoError(t, rows.Close()) 215 | } 216 | 217 | func TestGoTypes(t *testing.T) { 218 | t.Parallel() 219 | db := openDB(t, "") 220 | defer closeDB(t, db) 221 | 222 | for _, arg := range []interface{}{ 223 | byte(42), 224 | int(42), 225 | int8(42), 226 | int16(42), 227 | int32(42), 228 | int64(42), 229 | uint(42), 230 | uint8(42), 231 | uint16(42), 232 | uint32(42), 233 | uint64(42), 234 | float32(12.3401), 235 | float64(12.3401), 236 | bool(true), 237 | string("foo"), 238 | rune('f'), 239 | } { 240 | t.Run(fmt.Sprintf("%T %v", arg, arg), func(t *testing.T) { 241 | actual := reflect.New(reflect.TypeOf(arg)).Interface() 242 | err := db.QueryRow("SELECT ?", arg).Scan(actual) 243 | require.NoError(t, err) 244 | assert.Equal(t, arg, reflect.ValueOf(actual).Elem().Interface()) 245 | }) 246 | } 247 | 248 | for _, arg := range []interface{}{ 249 | uintptr(0xDEADBEEF), 250 | time.Date(2017, 2, 5, 19, 40, 42, 123456789, time.UTC), 251 | } { 252 | t.Run(fmt.Sprintf("%T %v", arg, arg), func(t *testing.T) { 253 | actual := reflect.New(reflect.TypeOf(arg)).Interface() 254 | err := db.QueryRow("SELECT ?", arg).Scan(actual) 255 | require.Error(t, err) 256 | assert.Zero(t, reflect.ValueOf(actual).Elem().Interface()) 257 | }) 258 | } 259 | } 260 | 261 | func TestQueryData(t *testing.T) { 262 | t.Parallel() 263 | db := openDB(t, "world_x") 264 | defer closeDB(t, db) 265 | 266 | fullDate := time.Date(2017, 7, 1, 12, 34, 56, 123456789, time.UTC) 267 | for _, q := range []struct { 268 | query string 269 | arg []interface{} 270 | expected interface{} 271 | }{ 272 | // untyped NULL 273 | {`SELECT NULL`, nil, nil}, 274 | {`SELECT ?`, []interface{}{nil}, nil}, 275 | 276 | // CHAR 277 | {`SELECT 'foo'`, nil, "foo"}, 278 | {`SELECT ?`, []interface{}{"foo"}, "foo"}, 279 | {`SELECT ''`, nil, ""}, 280 | {`SELECT ?`, []interface{}{""}, ""}, 281 | {`SELECT CAST(NULL AS CHAR)`, nil, nil}, 282 | {`SELECT CAST(? AS CHAR)`, []interface{}{nil}, nil}, 283 | 284 | // SIGNED 285 | {`SELECT -42`, nil, int64(-42)}, 286 | {`SELECT ?`, []interface{}{int64(-42)}, int64(-42)}, 287 | {`SELECT -0`, nil, int64(0)}, 288 | {`SELECT ?`, []interface{}{int64(-0)}, int64(0)}, 289 | {`SELECT CAST(NULL AS SIGNED)`, nil, nil}, 290 | {`SELECT CAST(? AS SIGNED)`, []interface{}{nil}, nil}, 291 | 292 | // UNSIGNED 293 | {`SELECT CAST(42 AS UNSIGNED)`, nil, uint64(42)}, 294 | {`SELECT CAST(? AS UNSIGNED)`, []interface{}{uint64(42)}, uint64(42)}, 295 | {`SELECT CAST(0 AS UNSIGNED)`, nil, uint64(0)}, 296 | {`SELECT CAST(? AS UNSIGNED)`, []interface{}{uint64(0)}, uint64(0)}, 297 | {`SELECT CAST(NULL AS UNSIGNED)`, nil, nil}, 298 | {`SELECT CAST(? AS UNSIGNED)`, []interface{}{nil}, nil}, 299 | 300 | // floats are returned as DECIMAL 301 | {`SELECT 12.3401`, nil, "12.3401"}, 302 | {`SELECT ?`, []interface{}{12.3401}, "12.3401"}, 303 | 304 | // DECIMAL 305 | // valid values from datatypes_test.go 306 | {`SELECT CAST(12.3401 AS DECIMAL(1))`, nil, "9"}, // Warning (code 1264): Out of range value 307 | {`SELECT CAST(-12.3401 AS DECIMAL(1))`, nil, "-9"}, // Warning (code 1264): Out of range value 308 | {`SELECT CAST(12.3401 AS DECIMAL(6))`, nil, "12"}, 309 | {`SELECT CAST(-12.3401 AS DECIMAL(6))`, nil, "-12"}, 310 | {`SELECT CAST(12.3401 AS DECIMAL(6,4))`, nil, "12.3401"}, 311 | {`SELECT CAST(-12.3401 AS DECIMAL(6,4))`, nil, "-12.3401"}, 312 | {`SELECT CAST(12.3401 AS DECIMAL(6,3))`, nil, "12.340"}, 313 | {`SELECT CAST(-12.3401 AS DECIMAL(6,3))`, nil, "-12.340"}, 314 | {`SELECT CAST(12.3401 AS DECIMAL(1,1))`, nil, "0.9"}, // Warning (code 1264): Out of range value 315 | {`SELECT CAST(-12.3401 AS DECIMAL(1,1))`, nil, "-0.9"}, // Warning (code 1264): Out of range value 316 | 317 | {`SELECT CAST(? AS DECIMAL(6,4))`, []interface{}{"-12.3401"}, "-12.3401"}, 318 | {`SELECT CAST(0 AS DECIMAL(6,4))`, nil, "0.0000"}, 319 | {`SELECT CAST(? AS DECIMAL(6,4))`, []interface{}{"0"}, "0.0000"}, 320 | {`SELECT CAST(NULL AS DECIMAL(6,4))`, nil, nil}, 321 | {`SELECT CAST(? AS DECIMAL(6,4))`, []interface{}{nil}, nil}, 322 | 323 | // DATE 324 | {`SELECT CAST('2017-07-01 12:34:56.123456789' AS DATE)`, nil, time.Date(2017, 7, 1, 0, 0, 0, 0, time.UTC)}, 325 | {`SELECT CAST(? AS DATE)`, []interface{}{fullDate}, time.Date(2017, 7, 1, 0, 0, 0, 0, time.UTC)}, 326 | {`SELECT CAST(NULL AS DATE)`, nil, nil}, 327 | {`SELECT CAST(? AS DATE)`, []interface{}{nil}, nil}, 328 | 329 | // DATETIME 330 | {`SELECT CAST('2017-07-01 12:34:56.123456789' AS DATETIME)`, nil, time.Date(2017, 7, 1, 12, 34, 56, 0, time.UTC)}, 331 | {`SELECT CAST(? AS DATETIME)`, []interface{}{fullDate}, time.Date(2017, 7, 1, 12, 34, 56, 0, time.UTC)}, 332 | {`SELECT CAST(NULL AS DATETIME)`, nil, nil}, 333 | {`SELECT CAST(? AS DATETIME)`, []interface{}{nil}, nil}, 334 | 335 | // DATETIME(6) 336 | {`SELECT CAST('2017-07-01 12:34:56.123456789' AS DATETIME(6))`, nil, time.Date(2017, 7, 1, 12, 34, 56, 123457000, time.UTC)}, 337 | {`SELECT CAST(? AS DATETIME(6))`, []interface{}{fullDate}, time.Date(2017, 7, 1, 12, 34, 56, 123457000, time.UTC)}, 338 | {`SELECT CAST(NULL AS DATETIME(6))`, nil, nil}, 339 | {`SELECT CAST(? AS DATETIME(6))`, []interface{}{nil}, nil}, 340 | } { 341 | t.Run(q.query, func(t *testing.T) { 342 | // test QueryRow 343 | var actual interface{} = "NOT SET" 344 | require.NoError(t, db.QueryRow(q.query, q.arg...).Scan(&actual)) 345 | assert.Equal(t, q.expected, actual) 346 | 347 | // test Query, read all rows 348 | rows, err := db.Query(q.query, q.arg...) 349 | require.NoError(t, err) 350 | types, err := rows.ColumnTypes() 351 | assert.NoError(t, err) 352 | require.Len(t, types, 1) 353 | 354 | assert.True(t, rows.Next()) 355 | actual = "NOT SET" 356 | assert.NoError(t, rows.Scan(&actual)) 357 | assert.Equal(t, q.expected, actual) 358 | assert.False(t, rows.Next()) 359 | assert.NoError(t, rows.Err()) 360 | assert.NoError(t, rows.Close()) 361 | 362 | stmt, err := db.Prepare(q.query) 363 | require.NoError(t, err) 364 | 365 | // test Prepare + QueryRow 366 | actual = "NOT SET" 367 | require.NoError(t, stmt.QueryRow(q.arg...).Scan(&actual)) 368 | assert.Equal(t, q.expected, actual) 369 | 370 | // test Prepare + Query, read all rows 371 | rows, err = stmt.Query(q.arg...) 372 | require.NoError(t, err) 373 | types, err = rows.ColumnTypes() 374 | assert.NoError(t, err) 375 | require.Len(t, types, 1) 376 | 377 | assert.True(t, rows.Next()) 378 | actual = "NOT SET" 379 | assert.NoError(t, rows.Scan(&actual)) 380 | assert.Equal(t, q.expected, actual) 381 | assert.False(t, rows.Next()) 382 | assert.NoError(t, rows.Err()) 383 | assert.NoError(t, rows.Close()) 384 | 385 | assert.NoError(t, stmt.Close()) 386 | }) 387 | } 388 | } 389 | 390 | func TestQueryEmpty(t *testing.T) { 391 | t.Parallel() 392 | db := openDB(t, "world_x") 393 | defer closeDB(t, db) 394 | 395 | _, err := db.Exec("CREATE TEMPORARY TABLE TestQueryEmpty (id int AUTO_INCREMENT, PRIMARY KEY (id))") 396 | require.NoError(t, err) 397 | 398 | var actual interface{} = "NOT SET" 399 | assert.Equal(t, sql.ErrNoRows, db.QueryRow("SELECT * FROM TestQueryEmpty").Scan(&actual)) 400 | assert.Equal(t, "NOT SET", actual) 401 | } 402 | 403 | func TestQueryExec(t *testing.T) { 404 | t.Parallel() 405 | db := openDB(t, "world_x") 406 | defer closeDB(t, db) 407 | 408 | // test QueryRow 409 | var actual interface{} = "NOT SET" 410 | assert.Equal(t, sql.ErrNoRows, db.QueryRow(`CREATE TEMPORARY TABLE TestQueryExec1 (id int)`).Scan(&actual)) 411 | assert.Equal(t, "NOT SET", actual) 412 | 413 | // test Query, read all rows 414 | rows, err := db.Query(`CREATE TEMPORARY TABLE TestQueryExec2 (id int)`) 415 | require.NoError(t, err) 416 | types, err := rows.ColumnTypes() 417 | assert.NoError(t, err) 418 | assert.Len(t, types, 0) 419 | assert.False(t, rows.Next()) 420 | assert.NoError(t, rows.Err()) 421 | assert.NoError(t, rows.Close()) 422 | } 423 | 424 | func TestQueryCloseEarly(t *testing.T) { 425 | t.Parallel() 426 | db := openDB(t, "world_x") 427 | defer closeDB(t, db) 428 | 429 | // read 0 rows 430 | rows, err := db.Query("SELECT ID, Name, District, Info FROM city WHERE CountryCode = ? ORDER BY ID LIMIT 3", "RUS") 431 | require.NoError(t, err) 432 | assert.NoError(t, rows.Close()) 433 | 434 | // read 1 row 435 | var city City 436 | rows, err = db.Query("SELECT ID, Name, District, Info FROM city WHERE CountryCode = ? ORDER BY ID LIMIT 3", "USA") 437 | require.NoError(t, err) 438 | assert.True(t, rows.Next()) 439 | assert.NoError(t, rows.Scan(city.Pointers()...)) 440 | assert.Equal(t, City{3793, "New York", "New York", `{"Population": 8008278}`}, city) 441 | assert.NoError(t, rows.Close()) 442 | 443 | // read 2 rows 444 | rows, err = db.Query("SELECT ID, Name, District, Info FROM city WHERE CountryCode = ? ORDER BY ID LIMIT 3", "FRA") 445 | require.NoError(t, err) 446 | assert.True(t, rows.Next()) 447 | assert.NoError(t, rows.Scan(city.Pointers()...)) 448 | assert.Equal(t, City{2974, "Paris", "Île-de-France", `{"Population": 2125246}`}, city) 449 | assert.True(t, rows.Next()) 450 | assert.NoError(t, rows.Scan(city.Pointers()...)) 451 | assert.Equal(t, City{2975, "Marseille", "Provence-Alpes-Côte", `{"Population": 798430}`}, city) 452 | assert.NoError(t, rows.Close()) 453 | } 454 | 455 | func TestExec(t *testing.T) { 456 | t.Parallel() 457 | db := openDB(t, "world_x") 458 | defer closeDB(t, db) 459 | 460 | res, err := db.Exec("CREATE TEMPORARY TABLE TestExec (id int AUTO_INCREMENT, PRIMARY KEY (id))") 461 | require.NoError(t, err) 462 | id, err := res.LastInsertId() 463 | assert.Equal(t, noLastInsertIdErr, err) 464 | assert.Equal(t, int64(0), id) 465 | ra, err := res.RowsAffected() 466 | assert.NoError(t, err) 467 | assert.Equal(t, int64(0), ra) 468 | 469 | res, err = db.Exec("INSERT INTO TestExec VALUES (1), (2)") 470 | require.NoError(t, err) 471 | id, err = res.LastInsertId() 472 | assert.NoError(t, err) 473 | assert.Equal(t, int64(2), id) 474 | ra, err = res.RowsAffected() 475 | assert.NoError(t, err) 476 | assert.Equal(t, int64(2), ra) 477 | 478 | res, err = db.Exec("UPDATE TestExec SET id = ? WHERE id = ?", 3, 1) 479 | require.NoError(t, err) 480 | id, err = res.LastInsertId() 481 | assert.Equal(t, noLastInsertIdErr, err) 482 | assert.Equal(t, int64(0), id) 483 | ra, err = res.RowsAffected() 484 | assert.NoError(t, err) 485 | assert.Equal(t, int64(1), ra) 486 | 487 | stmt, err := db.Prepare("UPDATE TestExec SET id = ? WHERE id = ?") 488 | require.NoError(t, err) 489 | res, err = stmt.Exec(4, 2) 490 | require.NoError(t, err) 491 | id, err = res.LastInsertId() 492 | assert.Equal(t, noLastInsertIdErr, err) 493 | assert.Equal(t, int64(0), id) 494 | ra, err = res.RowsAffected() 495 | assert.NoError(t, err) 496 | assert.Equal(t, int64(1), ra) 497 | assert.NoError(t, stmt.Close()) 498 | } 499 | 500 | func TestExecQuery(t *testing.T) { 501 | t.Parallel() 502 | db := openDB(t, "world_x") 503 | defer closeDB(t, db) 504 | 505 | res, err := db.Exec("SELECT 1") 506 | assert.NoError(t, err) 507 | id, err := res.LastInsertId() 508 | assert.Equal(t, noLastInsertIdErr, err) 509 | assert.Equal(t, int64(0), id) 510 | ra, err := res.RowsAffected() 511 | assert.NoError(t, err) 512 | assert.Equal(t, int64(0), ra) 513 | } 514 | 515 | func TestBeginCommit(t *testing.T) { 516 | t.Parallel() 517 | db := openDB(t, "world_x") 518 | defer closeDB(t, db) 519 | 520 | _, err := db.Exec("CREATE TEMPORARY TABLE TestBeginCommit (id int AUTO_INCREMENT, PRIMARY KEY (id))") 521 | require.NoError(t, err) 522 | 523 | tx, err := db.Begin() 524 | require.NoError(t, err) 525 | 526 | _, err = tx.Exec("INSERT INTO TestBeginCommit VALUES (1)") 527 | assert.NoError(t, err) 528 | 529 | assert.NoError(t, tx.Commit()) 530 | assert.Equal(t, sql.ErrTxDone, tx.Commit()) 531 | assert.Equal(t, sql.ErrTxDone, tx.Rollback()) 532 | 533 | var count int 534 | assert.NoError(t, db.QueryRow("SELECT COUNT(*) FROM TestBeginCommit").Scan(&count)) 535 | assert.Equal(t, 1, count) 536 | } 537 | 538 | func TestBeginRollback(t *testing.T) { 539 | t.Parallel() 540 | db := openDB(t, "world_x") 541 | defer closeDB(t, db) 542 | 543 | _, err := db.Exec("CREATE TEMPORARY TABLE TestBeginRollback (id int AUTO_INCREMENT, PRIMARY KEY (id))") 544 | require.NoError(t, err) 545 | 546 | tx, err := db.Begin() 547 | require.NoError(t, err) 548 | 549 | _, err = tx.Exec("INSERT INTO TestBeginRollback VALUES (1)") 550 | assert.NoError(t, err) 551 | 552 | assert.NoError(t, tx.Rollback()) 553 | assert.Equal(t, sql.ErrTxDone, tx.Rollback()) 554 | assert.Equal(t, sql.ErrTxDone, tx.Commit()) 555 | 556 | var count int 557 | assert.NoError(t, db.QueryRow("SELECT COUNT(*) FROM TestBeginRollback").Scan(&count)) 558 | assert.Equal(t, 0, count) 559 | } 560 | 561 | func TestBeginRollbackOptions(t *testing.T) { 562 | t.Parallel() 563 | db := openDB(t, "world_x") 564 | defer closeDB(t, db) 565 | 566 | _, err := db.Exec("CREATE TEMPORARY TABLE TestBeginRollback (id int AUTO_INCREMENT, PRIMARY KEY (id))") 567 | require.NoError(t, err) 568 | 569 | tx, err := db.BeginTx(context.Background(), &sql.TxOptions{ 570 | Isolation: sql.LevelSerializable, 571 | ReadOnly: true, 572 | }) 573 | require.NoError(t, err) 574 | 575 | _, err = tx.Exec("INSERT INTO TestBeginRollback VALUES (1)") 576 | assert.NoError(t, err) 577 | 578 | assert.NoError(t, tx.Rollback()) 579 | assert.Equal(t, sql.ErrTxDone, tx.Rollback()) 580 | assert.Equal(t, sql.ErrTxDone, tx.Commit()) 581 | 582 | var count int 583 | assert.NoError(t, db.QueryRow("SELECT COUNT(*) FROM TestBeginRollback").Scan(&count)) 584 | assert.Equal(t, 0, count) 585 | } 586 | 587 | func TestNoDatabase(t *testing.T) { 588 | t.Parallel() 589 | db := openDB(t, "") 590 | defer closeDB(t, db) 591 | 592 | var s string 593 | require.NoError(t, db.QueryRow("SELECT VERSION()").Scan(&s)) 594 | t.Log(s) 595 | 596 | err := db.QueryRow("SELECT Name FROM city LIMIT 1").Scan(&s) 597 | assert.Equal(t, &Error{Severity: SeverityError, Code: 1046, SQLState: "3D000", Msg: "No database selected"}, err) 598 | assert.EqualError(t, err, "ERROR 1046 (3D000): No database selected") 599 | 600 | res, err := db.Exec("UPDATE city SET Name = ?", "Moscow") 601 | assert.Nil(t, res) 602 | assert.Equal(t, &Error{Severity: SeverityError, Code: 1046, SQLState: "3D000", Msg: "No database selected"}, err) 603 | assert.EqualError(t, err, "ERROR 1046 (3D000): No database selected") 604 | } 605 | 606 | func TestInvalidPassword(t *testing.T) { 607 | connector := connector(t, "") 608 | connector.Password = "invalid password" 609 | 610 | db := sql.OpenDB(connector) 611 | assert.Equal(t, 0, db.Stats().OpenConnections) 612 | defer db.Close() 613 | 614 | err := db.Ping() 615 | require.Equal(t, &Error{Severity: SeverityFatal, Code: 1045, SQLState: "HY000", Msg: "Invalid user or password"}, err) 616 | assert.Equal(t, 0, db.Stats().OpenConnections) 617 | } 618 | 619 | func TestConnectionClose(t *testing.T) { 620 | db := openDB(t, "") 621 | db.SetMaxOpenConns(1) 622 | defer db.Close() 623 | 624 | conn, err := db.Conn(context.Background()) 625 | require.NoError(t, err) 626 | 627 | var i int 628 | err = conn.QueryRowContext(context.Background(), "SELECT 1").Scan(&i) 629 | assert.NoError(t, err) 630 | 631 | testConnections.get().transport.Close() 632 | 633 | err = conn.QueryRowContext(context.Background(), "SELECT 1").Scan(&i) 634 | assert.Equal(t, driver.ErrBadConn, err) 635 | 636 | err = conn.Close() 637 | assert.Equal(t, sql.ErrConnDone, err) 638 | 639 | err = db.QueryRow("SELECT 1").Scan(&i) 640 | assert.NoError(t, err) 641 | } 642 | 643 | func TestDriverMethods(t *testing.T) { 644 | checkConn := func(conn driver.Conn) { 645 | err := conn.(driver.Pinger).Ping(context.Background()) 646 | assert.NoError(t, err) 647 | err = conn.Close() 648 | assert.NoError(t, err) 649 | } 650 | 651 | connector := connector(t, "") 652 | conn, err := Driver.Open(connector.URL().String()) 653 | require.NoError(t, err) 654 | checkConn(conn) 655 | 656 | connector2, err := Driver.OpenConnector(connector.URL().String()) 657 | require.NoError(t, err) 658 | assert.Equal(t, Driver, connector2.Driver()) 659 | conn, err = connector2.Connect(context.Background()) 660 | require.NoError(t, err) 661 | checkConn(conn) 662 | } 663 | 664 | func BenchmarkTableCity(b *testing.B) { 665 | connector := connector(b, "world_x") 666 | connector.Trace = nil 667 | db := sql.OpenDB(connector) 668 | require.NoError(b, db.Ping()) 669 | defer closeDB(b, db) 670 | 671 | var rows *sql.Rows 672 | var err error 673 | b.ResetTimer() 674 | for i := 0; i < b.N; i++ { 675 | rows, err = db.Query("SELECT ID, Name, District, Info FROM city WHERE CountryCode = ?", "RUS") 676 | if err != nil { 677 | b.Fatal(err) 678 | } 679 | if err = rows.Close(); err != nil { 680 | b.Fatal(err) 681 | } 682 | } 683 | b.StopTimer() 684 | } 685 | -------------------------------------------------------------------------------- /rows.go: -------------------------------------------------------------------------------- 1 | // mysqlx - MySQL driver for Go's database/​sql package and MySQL X Protocol. 2 | // Copyright (c) 2017-2018 Alexey Palazhchenko 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | 8 | package mysqlx 9 | 10 | import ( 11 | "context" 12 | "database/sql/driver" 13 | "io" 14 | "math" 15 | 16 | "github.com/AlekSi/mysqlx/internal/proto/mysqlx_notice" 17 | "github.com/AlekSi/mysqlx/internal/proto/mysqlx_resultset" 18 | "github.com/AlekSi/mysqlx/internal/proto/mysqlx_sql" 19 | ) 20 | 21 | // rows is an iterator over an executed query's results. 22 | type rows struct { 23 | c *conn 24 | columns []mysqlx_resultset.ColumnMetaData 25 | rows chan *mysqlx_resultset.Row 26 | readErr error 27 | } 28 | 29 | // runReader reads rows and sends then to channel until all rows are read or error is encountered. 30 | func (r *rows) runReader(ctx context.Context) { 31 | defer close(r.rows) 32 | 33 | for { 34 | m, err := r.c.readMessage(ctx) 35 | if err != nil { 36 | r.readErr = err 37 | return 38 | } 39 | 40 | switch m := m.(type) { 41 | case *mysqlx_resultset.Row: 42 | r.rows <- m 43 | case *mysqlx_resultset.FetchDone: 44 | continue 45 | case *mysqlx_notice.Warning: 46 | // TODO expose warnings? 47 | continue 48 | case *mysqlx_notice.SessionStateChanged: 49 | switch m.GetParam() { 50 | case mysqlx_notice.SessionStateChanged_ROWS_AFFECTED: 51 | continue 52 | default: 53 | r.readErr = bugf("rows.runReader: unhandled session state change %v", m) 54 | return 55 | } 56 | case *mysqlx_sql.StmtExecuteOk: 57 | return 58 | default: 59 | r.readErr = bugf("rows.runReader: unhandled message %T", m) 60 | return 61 | } 62 | } 63 | } 64 | 65 | // Columns returns the names of the columns. 66 | // The number of columns of the result is inferred from the length of the slice. 67 | // If a particular column name isn't known, an empty string should be returned for that entry. 68 | func (r *rows) Columns() []string { 69 | res := make([]string, len(r.columns)) 70 | for i, c := range r.columns { 71 | res[i] = string(c.Name) 72 | } 73 | return res 74 | } 75 | 76 | // Close closes the rows iterator. 77 | func (r *rows) Close() error { 78 | // TODO limit a number of messages to drain there? and close connection? and return driver.ErrBadConn? 79 | 80 | // drain messages until r.rows is closed in runReader 81 | for range r.rows { 82 | } 83 | 84 | // FIXME should we return r.readErr instead of nil? 85 | return nil 86 | } 87 | 88 | // Next is called to populate the next row of data into the provided slice. 89 | // The provided slice will be the same size as the Columns() are wide. 90 | // Next should return io.EOF when there are no more rows. 91 | func (r *rows) Next(dest []driver.Value) error { 92 | row, ok := <-r.rows 93 | if !ok { 94 | if r.readErr != nil { 95 | return r.readErr 96 | } 97 | return io.EOF 98 | } 99 | 100 | // unmarshal all values, return first encountered error 101 | var err error 102 | for i, value := range row.Field { 103 | d, e := unmarshalValue(value, &r.columns[i]) 104 | dest[i] = d 105 | if err == nil { 106 | err = e 107 | } 108 | } 109 | return err 110 | } 111 | 112 | // RowsColumnTypeDatabaseTypeName returns the database system type name without the length. 113 | // Type names should be uppercase. Returned types: 114 | // SINT, UINT, DOUBLE, FLOAT, BYTES, TIME, DATETIME, SET, ENUM, BIT, DECIMAL. 115 | func (r *rows) ColumnTypeDatabaseTypeName(index int) string { 116 | return r.columns[index].Type.String() 117 | } 118 | 119 | // RowsColumnTypeLength return the length of the column type if the column is a variable length type. 120 | // If the column is not a variable length type ok should return false. 121 | // If length is not limited other than system limits, it should return math.MaxInt64. 122 | // The following are examples of returned values for various types: 123 | // TODO add examples 124 | func (r *rows) ColumnTypeLength(index int) (length int64, ok bool) { 125 | c := r.columns[index] 126 | length = int64(c.GetLength()) 127 | if c.Type.String() == "BYTES" && length == math.MaxUint32 { 128 | length = math.MaxInt64 129 | } 130 | ok = true 131 | return 132 | } 133 | 134 | // RowsColumnTypeNullable returns true if it is known the column may be null, 135 | // or false if the column is known to be not nullable. 136 | // If the column nullability is unknown, ok should be false. 137 | func (r *rows) ColumnTypeNullable(index int) (nullable, ok bool) { 138 | nullable = (r.columns[index].GetFlags() & 0x0010) != 0 139 | ok = true 140 | return 141 | } 142 | 143 | // check interfaces 144 | var ( 145 | _ driver.Rows = (*rows)(nil) 146 | _ driver.RowsColumnTypeDatabaseTypeName = (*rows)(nil) 147 | _ driver.RowsColumnTypeLength = (*rows)(nil) 148 | _ driver.RowsColumnTypeNullable = (*rows)(nil) 149 | 150 | // TODO 151 | // _ driver.RowsColumnTypePrecisionScale = (*rows)(nil) 152 | // _ driver.RowsColumnTypeScanType = (*rows)(nil) 153 | // _ driver.RowsNextResultSet = (*rows)(nil) 154 | ) 155 | -------------------------------------------------------------------------------- /stmt.go: -------------------------------------------------------------------------------- 1 | // mysqlx - MySQL driver for Go's database/​sql package and MySQL X Protocol. 2 | // Copyright (c) 2017-2018 Alexey Palazhchenko 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | 8 | package mysqlx 9 | 10 | import ( 11 | "context" 12 | "database/sql/driver" 13 | ) 14 | 15 | // stmt is a prepared statement. It is bound to a Conn and not used by multiple goroutines concurrently. 16 | type stmt struct { 17 | c *conn 18 | query string 19 | } 20 | 21 | // Close closes the statement. 22 | func (s *stmt) Close() error { 23 | return nil 24 | } 25 | 26 | // NumInput returns the number of placeholder parameters. 27 | // 28 | // If NumInput returns >= 0, the sql package will sanity check 29 | // argument counts from callers and return errors to the caller 30 | // before the statement's Exec or Query methods are called. 31 | // 32 | // NumInput may also return -1, if the driver doesn't know 33 | // its number of placeholders. In that case, the sql package 34 | // will not sanity check Exec or Query argument counts. 35 | func (s *stmt) NumInput() int { 36 | return -1 37 | } 38 | 39 | // Exec executes a query that doesn't return rows, such as an INSERT or UPDATE. 40 | func (s *stmt) Exec(args []driver.Value) (driver.Result, error) { 41 | return s.c.Exec(s.query, args) 42 | } 43 | 44 | // ExecContext executes a query that doesn't return rows, such as an INSERT or UPDATE. 45 | // It honors the context timeout and return when it is canceled. 46 | func (s *stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) { 47 | return s.c.ExecContext(ctx, s.query, args) 48 | } 49 | 50 | // Query executes a query that may return rows, such as a SELECT. 51 | func (s *stmt) Query(args []driver.Value) (driver.Rows, error) { 52 | return s.c.Query(s.query, args) 53 | } 54 | 55 | // Query executes a query that may return rows, such as a SELECT. 56 | // It honors the context timeout and return when it is canceled. 57 | func (s *stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) { 58 | return s.c.QueryContext(ctx, s.query, args) 59 | } 60 | 61 | // TODO? 62 | // func (s *stmt) CheckNamedValue(arg *driver.NamedValue) error { 63 | // return s.c.CheckNamedValue(arg) 64 | // } 65 | 66 | // check interfaces 67 | var ( 68 | _ driver.Stmt = (*stmt)(nil) 69 | _ driver.StmtExecContext = (*stmt)(nil) 70 | _ driver.StmtQueryContext = (*stmt)(nil) 71 | // TODO? _ driver.NamedValueChecker = (*stmt)(nil) 72 | // ColumnConverter is not implemented: NamedValueChecker is enough 73 | ) 74 | -------------------------------------------------------------------------------- /tracking.go: -------------------------------------------------------------------------------- 1 | // mysqlx - MySQL driver for Go's database/​sql package and MySQL X Protocol. 2 | // Copyright (c) 2017-2018 Alexey Palazhchenko 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | 8 | package mysqlx 9 | 10 | import ( 11 | "fmt" 12 | "runtime/pprof" 13 | "sync" 14 | ) 15 | 16 | type trackedConnections struct { 17 | l sync.Mutex 18 | m map[*conn]struct{} 19 | } 20 | 21 | func (tc *trackedConnections) get() *conn { 22 | tc.l.Lock() 23 | defer tc.l.Unlock() 24 | 25 | if len(tc.m) != 1 { 26 | panic(fmt.Errorf("expected 1 connection, got %d", len(tc.m))) 27 | } 28 | for c := range tc.m { 29 | return c 30 | } 31 | panic("not reached") 32 | } 33 | 34 | var ( 35 | connectionsProfile = pprof.NewProfile("github.com/AlekSi/mysqlx.connections") 36 | 37 | // initialized in tracking_test.go for testing only 38 | testConnections *trackedConnections 39 | ) 40 | 41 | func connectionOpened(c *conn) { 42 | connectionsProfile.Add(c, 1) 43 | 44 | if testConnections != nil { 45 | testConnections.l.Lock() 46 | testConnections.m[c] = struct{}{} 47 | testConnections.l.Unlock() 48 | } 49 | } 50 | 51 | func connectionClosed(c *conn) { 52 | connectionsProfile.Remove(c) 53 | 54 | if testConnections != nil { 55 | testConnections.l.Lock() 56 | delete(testConnections.m, c) 57 | testConnections.l.Unlock() 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tracking_test.go: -------------------------------------------------------------------------------- 1 | // mysqlx - MySQL driver for Go's database/​sql package and MySQL X Protocol. 2 | // Copyright (c) 2017-2018 Alexey Palazhchenko 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | 8 | package mysqlx 9 | 10 | func init() { 11 | testConnections = &trackedConnections{ 12 | m: make(map[*conn]struct{}), 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tx.go: -------------------------------------------------------------------------------- 1 | // mysqlx - MySQL driver for Go's database/​sql package and MySQL X Protocol. 2 | // Copyright (c) 2017-2018 Alexey Palazhchenko 3 | // 4 | // This Source Code Form is subject to the terms of the Mozilla Public 5 | // License, v. 2.0. If a copy of the MPL was not distributed with this 6 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | 8 | package mysqlx 9 | 10 | import ( 11 | "context" 12 | "database/sql/driver" 13 | ) 14 | 15 | // tx represents transaction. 16 | // 17 | // It implements driver.Tx. 18 | type tx struct { 19 | c *conn 20 | } 21 | 22 | // Commit commits the transaction. 23 | func (t *tx) Commit() error { 24 | _, err := t.c.ExecContext(context.Background(), "COMMIT", nil) 25 | return err 26 | } 27 | 28 | // Rollback aborts the transaction. 29 | func (t *tx) Rollback() error { 30 | _, err := t.c.ExecContext(context.Background(), "ROLLBACK", nil) 31 | return err 32 | } 33 | 34 | // check interfaces 35 | var ( 36 | _ driver.Tx = (*tx)(nil) 37 | ) 38 | --------------------------------------------------------------------------------