From 79bac02448355067ff58abd32633533533e5fa46 Mon Sep 17 00:00:00 2001 From: Lukas Mikelionis Date: Wed, 22 Jul 2020 10:40:17 +0300 Subject: [PATCH] add support for Vertica --- README.md | 7 +- sql.go | 1 + .../vertica/vertica-sql-go/CONTRIBUTING.md | 198 ++++++ .../github.com/vertica/vertica-sql-go/LICENSE | 201 ++++++ .../vertica/vertica-sql-go/README.md | 407 ++++++++++++ .../vertica/vertica-sql-go/connection.go | 618 ++++++++++++++++++ .../vertica/vertica-sql-go/context.go | 124 ++++ .../vertica/vertica-sql-go/driver.go | 106 +++ .../github.com/vertica/vertica-sql-go/go.mod | 3 + .../vertica/vertica-sql-go/result.go | 46 ++ .../github.com/vertica/vertica-sql-go/rows.go | 183 ++++++ .../github.com/vertica/vertica-sql-go/stmt.go | 549 ++++++++++++++++ .../github.com/vertica/vertica-sql-go/tx.go | 123 ++++ vendor/vendor.json | 6 + 14 files changed, 2569 insertions(+), 3 deletions(-) create mode 100644 vendor/github.com/vertica/vertica-sql-go/CONTRIBUTING.md create mode 100644 vendor/github.com/vertica/vertica-sql-go/LICENSE create mode 100644 vendor/github.com/vertica/vertica-sql-go/README.md create mode 100644 vendor/github.com/vertica/vertica-sql-go/connection.go create mode 100644 vendor/github.com/vertica/vertica-sql-go/context.go create mode 100644 vendor/github.com/vertica/vertica-sql-go/driver.go create mode 100644 vendor/github.com/vertica/vertica-sql-go/go.mod create mode 100644 vendor/github.com/vertica/vertica-sql-go/result.go create mode 100644 vendor/github.com/vertica/vertica-sql-go/rows.go create mode 100644 vendor/github.com/vertica/vertica-sql-go/stmt.go create mode 100644 vendor/github.com/vertica/vertica-sql-go/tx.go diff --git a/README.md b/README.md index 0cb55462..abb275e2 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Database agnostic SQL exporter for [Prometheus](https://prometheus.io). ## Overview SQL Exporter is a configuration driven exporter that exposes metrics gathered from DBMSs, for use by the Prometheus -monitoring system. Out of the box, it provides support for MySQL, PostgreSQL, Microsoft SQL Server and Clickhouse, but +monitoring system. Out of the box, it provides support for MySQL, PostgreSQL, Microsoft SQL Server, Clickhouse and Vertica, but any DBMS for which a Go driver is available may be monitored after rebuilding the binary with the DBMS driver included. The collected metrics and the queries that produce them are entirely configuration defined. SQL queries are grouped into @@ -131,8 +131,8 @@ To keep things simple and yet allow fully configurable database connections to b Go `sql` library does not allow for automatic driver selection based on the DSN (i.e. an explicit driver name must be specified) SQL Exporter uses the schema part of the DSN (the part before the `://`) to determine which driver to use. -Unfortunately, while this works out of the box with the [MS SQL Server](https://github.com/denisenkom/go-mssqldb) and -[PostgreSQL](github.com/lib/pq) drivers, the [MySQL driver](github.com/go-sql-driver/mysql) DSNs format does not include +Unfortunately, while this works out of the box with the [MS SQL Server](https://github.com/denisenkom/go-mssqldb), +[PostgreSQL](github.com/lib/pq) and [Vertica](github.com/vertica/vertica-sql-go) drivers, the [MySQL driver](github.com/go-sql-driver/mysql) DSNs format does not include a schema and the [Clickhouse](github.com/kshvakov/clickhouse) one uses `tcp://`. So SQL Exporter does a bit of massaging of DSNs for the latter two drivers in order for this to work: @@ -141,6 +141,7 @@ DB | SQL Exporter expected DSN | Driver sees MySQL | `mysql://user:passw@protocol(host:port)/dbname` | `user:passw@protocol(host:port)/dbname` PostgreSQL | `postgres://user:passw@host:port/dbname` | *unchanged* SQL Server | `sqlserver://user:passw@host:port/instance` | *unchanged* +Vertica | `vertica://user:passw@host:port/dbname` | *unchanged* Clickhouse | `clickhouse://host:port?username=user&password=passw&database=dbname` | `tcp://host:port?username=user&password=passw&database=dbname` ## Why It Exists diff --git a/sql.go b/sql.go index f4f5c980..d15901f9 100644 --- a/sql.go +++ b/sql.go @@ -11,6 +11,7 @@ import ( _ "github.com/go-sql-driver/mysql" // register the MySQL driver log "github.com/golang/glog" _ "github.com/lib/pq" // register the PostgreSQL driver + _ "github.com/vertica/vertica-sql-go" // register the Vertica driver ) // OpenConnection extracts the driver name from the DSN (expected as the URI scheme), adjusts it where necessary (e.g. diff --git a/vendor/github.com/vertica/vertica-sql-go/CONTRIBUTING.md b/vendor/github.com/vertica/vertica-sql-go/CONTRIBUTING.md new file mode 100644 index 00000000..6c2e72ec --- /dev/null +++ b/vendor/github.com/vertica/vertica-sql-go/CONTRIBUTING.md @@ -0,0 +1,198 @@ +First off, thank you for considering contributing to *vertica-sql-go* and helping make it even better than it is today! + +This document will guide you through the contribution process. There are a number of ways you can help: + + - [Bug Reports](#bug-reports) + - [Feature Requests](#feature-requests) + - [Code Contributions](#code-contributions) + +# Bug Reports + +If you find a bug, submit an [issue](https://github.com/vertica/vertica-sql-go/issues) with a complete and reproducible bug report. If the issue can't be reproduced, it will be closed. If you opened an issue, but figured out the answer later on your own, comment on the issue to let people know, then close the issue. + +For issues (e.g. security related issues) that are **not suitable** to be reported publicly on the GitHub issue system, report your issues to [Vertica team](mailto:vertica-opensrc@microfocus.com) directly or file a case with Vertica support if you have a support account. + +# Feature Requests + +Feel free to share your ideas for how to improve *vertica-sql-go*. We’re always open to suggestions. +You can open an [issue](https://github.com/vertica/vertica-sql-go/issues) +with details describing what feature(s) you'd like added or changed. + +If you would like to implement the feature yourself, open an issue to ask before working on it. Once approved, please refer to the [Code Contributions](#code-contributions) section. + +# Code Contributions + +## Configure Git for the first time + +Make sure git knows your [name](https://help.github.com/articles/setting-your-username-in-git/ "Set commit username in Git") and [email address](https://help.github.com/articles/setting-your-commit-email-address-in-git/ "Set commit email address in Git"): + +```shell +git config --global user.name "John Smith" +git config --global user.email "email@example.com" +``` + +## Step 1: Fork + +Fork the project [on Github](https://github.com/vertica/vertica-sql-go) and check out your copy locally. + +```shell +git clone git@github.com:YOURUSERNAME/vertica-sql-go.git +cd vertica-sql-go +``` + +Your GitHub repository **YOURUSERNAME/vertica-sql-go** will be called "origin" in +Git. You should also setup **vertica/vertica-sql-go** as an "upstream" remote. + +```shell +git remote add upstream git@github.com:vertica/vertica-sql-go.git +git fetch upstream +``` + +**NOTE:** If you are new to Go development and forking, here is an excellent blog post to read on managing import paths when forking: +http://code.openark.org/blog/development/forking-golang-repositories-on-github-and-managing-the-import-path + + +## Step 2: Branch + +Create a new branch for the work with a descriptive name: + +```shell +git checkout -b my-fix-branch +``` + +## Step 3: Get the driver test running + +*vertica-sql-go* comes with a driver test of its own, in the root directory of the code base. It’s our policy to make sure all tests pass at all times. + +We appreciate any and all [contributions to the tests](#tests)! These tests are built within the standard Go testing framework. You might want to check out the Go documentation for more details. + +The test is built around the standard Go test framework. From the source directory simply run: + +```sh +go test +``` +were args are one of the following: + +| Query Argument | Description | Values | +|----------------|-------------|--------| +| use_prepared_statements | whether to use client-side query interpolation or server-side argument binding | true = (default) use server-side bindings | +| | | false = user client side interpolation | +| tlsmode | the ssl policy for this connection | 'none' (default) = don't use SSL for this connection | +| | | 'server' = server must support SSL, but skip verification (INSECURE!) | +| | | 'server-strict' = server must support SSL | +| locator | host and port of the Vertica connection | (default) localhost:5433 +| user | Vertica user ID | (default) the userid of the running user | +| password | Vertica password for the connecting user | (default) (empty) +| + +**NOTE:** Since it's often a bad idea to put your password on the command line, you can set the VERTICA_TEST_PASSWORD environment variable. Even if environment variable is set, the "--password" flag will supercede it. + +For example: + +```shell script +export VERTICA_TEST_PASSWORD=mypassword +go test --locator hostname:5433 --user rhuebner --tlsmode server +``` +You should run the test suite under multiple configurations (local interpolation vs. prepared statements, SSL vs plain, etc.) + +For example: + +```shell script +export VERTICA_TEST_PASSWORD=mypassword + +# Against a plain connection. +go test --locator hostname:5433 --user rhuebner --tlsmode none --use_prepared_statements true +go test --locator hostname:5433 --user rhuebner --tlsmode none --use_prepared_statements false + +# Against an SSL-enabled server. +go test --locator hostname:5433 --user rhuebner --tlsmode server --use_prepared_statements true +go test --locator hostname:5433 --user rhuebner --tlsmode server --use_prepared_statements false +``` + +The Travis CI configuration committed as part of the project will automatically run through several combinations of parameters. +These CI tests must pass before any PR will be considered. + +## Step 4: Implement your fix or feature + +At this point, you're ready to make your changes! Feel free to ask for help; everyone is a beginner at first. + +### License Headers + +Every file in this project must use the following Apache 2.0 header (with the appropriate year or years in the "[yyyy]" box; if a copyright statement from another party is already present in the code, you may add the statement on top of the existing copyright statement): + +``` +Copyright (c) [yyyy] Micro Focus or one of its affiliates. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` + +### Commits + +Make some changes on your branch, then stage and commit as often as necessary: + +```shell +git add . +git commit -m 'Added two more tests for #166' +``` + +When writing the commit message, try to describe precisely what the commit does. The commit message should be in lines of 72 chars maximum. Include the issue number `#N`, if the commit is related to an issue. + +### Tests + +Add appropriate tests for the bug’s or feature's behavior, run the test suite again and ensure that all tests pass. Here is the guideline for writing test: + - Tests should be easy for any contributor to run. Contributors may not get complete access to their Vertica database, for example, they may only have a non-admin user with write privileges to a single schema, and the database may not be the latest version. We encourage tests to use only what they need and nothing more. + - If there are requirements to the database for running a test, the test should adapt to different situations and never report a failure. For example, if a test depends on a multi-node database, it should check the number of DB nodes first, and skip itself when it connects to a single-node database. + +## Step 5: Push and Rebase + +You can publish your work on GitHub just by doing: + +```shell +git push origin my-fix-branch +``` + +When you go to your GitHub page, you will notice commits made on your local branch is pushed to the remote repository. + +When upstream (vertica/vertica-sql-go) has changed, you should rebase your work. The **rebase** command creates a linear history by moving your local commits onto the tip of the upstream commits. + +You can rebase your branch locally and force-push to your GitHub repository by doing: + +```shell +git checkout my-fix-branch +git fetch upstream +git rebase upstream/master +git push -f origin my-fix-branch +``` + + +## Step 6: Make a Pull Request + +When you think your work is ready to be pulled into *vertica-sql-go*, you should create a pull request(PR) at GitHub. + +A good pull request means: + - commits with one logical change in each + - well-formed messages for each commit + - documentation and tests, if needed + +Go to https://github.com/YOURUSERNAME/verticasql-go and [make a Pull Request](https://help.github.com/articles/creating-a-pull-request/) to `vertica:master`. + +### Sign the CLA +Before we can accept a pull request, we first ask people to sign a Contributor License Agreement (or CLA). We ask this so that we know that contributors have the right to donate the code. You should notice a comment from **CLAassistant** on your pull request page, follow this comment to sign the CLA electronically. + +### Review +Pull requests are usually reviewed within a few days. If there are comments to address, apply your changes in new commits, rebase your branch and force-push to the same branch, re-run the test suite to ensure tests are still passing. We care about quality, Vertica has internal test suites to run as well, so your pull request won't be merged until all internal tests pass. In order to produce a clean commit history, our maintainers would do squash merging once your PR is approved, which means combining all commits of your PR into a single commit in the master branch. + +That's it! Thank you for your code contribution! + +After your pull request is merged, you can safely delete your branch and pull the changes from the upstream repository. + diff --git a/vendor/github.com/vertica/vertica-sql-go/LICENSE b/vendor/github.com/vertica/vertica-sql-go/LICENSE new file mode 100644 index 00000000..35d74641 --- /dev/null +++ b/vendor/github.com/vertica/vertica-sql-go/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2019 Micro Focus + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/vendor/github.com/vertica/vertica-sql-go/README.md b/vendor/github.com/vertica/vertica-sql-go/README.md new file mode 100644 index 00000000..203da122 --- /dev/null +++ b/vendor/github.com/vertica/vertica-sql-go/README.md @@ -0,0 +1,407 @@ +# vertica-sql-go + +[![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg)](https://opensource.org/licenses/Apache-2.0) +[![GoDoc](https://godoc.org/github.com/vertica/vertica-sql-go?status.svg)](https://godoc.org/github.com/vertica/vertica-sql-go) +[![Go Report Card](https://goreportcard.com/badge/github.com/vertica/vertica-sql-go)](https://goreportcard.com/report/github.com/vertica/vertica-sql-go) + +vertica-sql-go is a native Go adapter for the Vertica (http://www.vertica.com) database. + +Please check out [release notes](https://github.com/vertica/vertica-sql-go/releases) to learn about the latest improvements. + +vertica-sql-go has been tested with Vertica 9.2.0+ and Go 1.11.2. + +## Installation + +Source code for vertica-sql-go can be found at: + +https://github.com/vertica/vertica-sql-go + +Alternatively you can use the 'go get' variant to install the package into your local Go environment. + +```sh +go get github.com/vertica/vertica-sql-go +``` + +## Usage + +As this library is written to Go's SQL standard [database/sql](https://golang.org/pkg/database/sql/), usage is compliant with its methods and behavioral expectations. + +### Importing + +First ensure that you have the library checked out in your standard Go hierarchy and import it. + +```Go +import ( + "context" + "database/sql" + "github.com/vertica/vertica-sql-go" +) +``` + +### Setting the Log Level + +The vertica-sql-go driver supports multiple log levels, as defined in the following table + +| Log Level (int) | Log Level Name | Description | +|-----------------|----------------|-------------| +| 0 | TRACE | Show function calls, plus all below | +| 1 | DEBUG | Show low-level functional operations, plus all below | +| 2 | INFO | Show important state information, plus all below | +| 3 | WARN | (default) Show non-breaking abnormalities, plus all below | +| 4 | ERROR | Show breaking errors, plus all below | +| 5 | FATAL | Show process-breaking errors | +| 6 | NONE | Disable all log messages | + +and they can be set programmatically by calling the logger global level itself + +```Go +logger.SetLogLevel(logger.DEBUG) +``` + +or by setting the environment variable VERTICA_SQL_GO_LOG_LEVEL to one of the integer values in the table above. This must be done before the process using the driver has started as the global log level will be read from here on start-up. + +Example: + +```bash +export VERTICA_SQL_GO_LOG_LEVEL=3 +``` + +### Setting the Log File + +By default, log messages are sent to stdout, but the vertica-sql-go driver can also output to a file in cases where stdout is not available. +Simply set the environment variable VERTICA_SQL_GO_LOG_FILE to your desired output location. + +Example: + +```bash +export VERTICA_SQL_GO_LOG_FILE=/var/log/vertica-sql-go.log +``` + +### Creating a connection + +```Go +connDB, err := sql.Open("vertica", myDBConnectString) +``` + +where *myDBConnectString* is of the form: + +```Go +vertica://(user):(password)@(host):(port)/(database)?(queryArgs) +``` + +Currently supported query arguments are: + +| Query Argument | Description | Values | +|----------------|-------------|--------| +| use_prepared_statements | whether to use client-side query interpolation or server-side argument binding | 1 = (default) use server-side bindings | +| | | 0 = user client side interpolation **(LESS SECURE)** | +| connection_load_balance | whether to enable connection load balancing on the client side | 0 = (default) disable load balancing | +| | | 1 = enable load balancing | +| tlsmode | the ssl/tls policy for this connection | 'none' (default) = don't use SSL/TLS for this connection | +| | | 'server' = server must support SSL/TLS, but skip verification **(INSECURE!)** | +| | | 'server-strict' = server must support SSL/TLS | + +To ping the server and validate a connection (as the connection isn't necessarily created at that moment), simply call the *PingContext()* method. + +```Go +ctx := context.Background() + +err = connDB.PingContext(ctx) +``` + +If there is an error in connection, the error result will be non-nil and contain a description of whatever problem occurred. + +### Performing a simple query + +Performing a simple query is merely a matter of using that connection to create a query and iterate its results. +Here is an example of a query that should always work. + +```Go +rows, err := connDB.QueryContext(ctx, "SELECT * FROM v_monitor.cpu_usage LIMIT 5") + +defer rows.Close() +``` + +**IMPORTANT** : Just as with connections, you should always Close() the results cursor once you are done with it. It's often easier to just defer the closure, for convenience. + +### Performing a query with arguments + +This is done in a similar manner on the client side. + +```Go +rows, err := connDB.QueryContext(ctx, "SELECT name FROM MyTable WHERE id=?", 21) +``` + +Behind the scenes, this will be handled in one of two ways, based on whether or not you requested client interpolation in the connection string. + +With client interpolation enabled, the client library will create a new query string with the arguments already in place, and submit it as a simple query. + +With client interpolation disabled (default), the client library will use the full server-side parse(), describe(), bind(), execute() cycle. + +#### Named Arguments + +```Go +rows, err := connDB.QueryContext(ctx, "SELECT name FROM MyTable WHERE id=@id and something=@example", sql.Named("id", 21), sql.Named("example", "hello")) +``` + +Named arguments are emulated by the driver. They will be converted to positional arguments by the driver and the named arguments given later will be slotted +into the required positions. This still allows server side prepared statements as `@id` and `@example` above will be replaced by `?` before being sent. If +you use named arguments, all the arguments must be named. Do not mix positional and named together. All named arguments are normalized to upper case which means +`@param`, `@PaRaM`, and `@PARAM` are treated as equivalent. + +### Reading query result rows + +As outlined in the GoLang specs, reading the results of a query is done via a loop, bounded by a .next() iterator. + +```Go +for rows.Next() { + var nodeName string + var startTime string + var endTime string + var avgCPU float64 + + rows.Scan(&nodeName, &startTime, &endTime, &avgCPU) + + // Use these values for something here. +} +``` + +If you need to examine the names of the columns, simply access the Columns() operator of the rows object. + +```Go +columnNames, _ := rows.Columns() + +for _, columnName := range columnNames { + // use the column name here. +} +``` + +### Paging in Data + +By default, the query results are cached in memory allowing for rapid iteration of result row content. +This generally works well, but in the case of exceptionally large result sets, you could run out of memory. + +If such a query needs to be performed, it is recommended that you tell the driver that you wish to cache +that data in a temporary file, so its results can be "paged in" as you iterate the results. The data is +stored in a process-read-only file in the OS's temp directory. + +To enable result paging, simply create a VerticaContext and use it to perform your query. + +```go +vCtx := NewVerticaContext(context.Background()) + +// Only keep 50000 rows in memory at once. +vCtx.SetInMemoryResultRowLimit(50000) + +rows, _ := connDB.QueryContext( + vCtx, + "SELECT a, b, c, d, e FROM result_cache_test ORDER BY a") + +defer rows.Close() + +// Use rows result as normal. +``` + +If you want to disable paging on the same context all together, you can simply set the row +limit to 0 (the default). + +### Performing a simple execute call + +This is very similar to a simple query, but has a slightly different result type. A simple execute() might look like this: + +```Go +res, err = connDB.ExecContext(ctx, "DROP TABLE IF EXISTS MyTable") +``` + +In this instance, *res* will contain information (such as 'rows affected') about the result of this execution. + +### Performing an execute with arguments + +This, again, looks very similar to the query-with-arguments use case and is subject to the same effects of client-side interpolation. + +```Go +res, err := connDB.ExecContext( + ctx, + "INSERT INTO MyTable VALUES (?)", 21) +``` + +### Server-side prepared statements + +**IMPORTANT** : Vertica does not support executing a command string containing multiple statements using server-side prepared statements. + +If you wish to reuse queries or executions, you can prepare them once and supply arguments only. + +```Go +// Prepare the query. +stmt, err := connDB.PrepareContext(ctx, "SELECT id FROM MyTable WHERE name=?") + +// Execute it with this argument. +rows, err = stmt.Query("Joe Perry") +``` + +**NOTE** : Please note that this method is subject to modification by the 'interpolate' setting. If the client side interpolation is requested, the statement will simply be stored on the client and interpolated with arguments each time it's used. If not using client side interpolation (default), the statement will be parsed and described on the server as expected. + +### Transactions + +The vertica-sql-go driver supports basic transactions as defined by the GoLang standard. + +```Go +// Define the options for this transaction state +opts := &sql.TxOptions{ + Isolation: sql.LevelDefault, + ReadOnly: false, +} + +// Begin the transaction. +tx, err := connDB.BeginTx(ctx, opts) +``` + +```Go +// You can either commit it. +err = tx.Commit() +``` + +```Go +// Or roll it back. +err = tx.Rollback() +``` + +The following transaction isolation levels are supported: + +* sql.LevelReadUncommitted +* sql.LevelReadCommitted +* sql.LevelSerializable +* sql.LevelRepeatableRead +* sql.LevelDefault + + The following transaction isolation levels are unsupported: + +* sql.LevelSnapshot +* sql.LevelLinearizable + + Although Vertica supports the grammars for these transaction isolation levels, they are internally promoted to stronger isolation levels. + +## COPY modes Supported + +### COPY FROM STDIN + +vertica-sql-go supports copying from stdin. This allows you to write a command-line tool that accepts stdin as an +input and passes it to Vertica for processing. An example: + +```go +_, err = connDB.ExecContext(ctx, "COPY stdin_data FROM STDIN DELIMITER ','") +``` + +This will process input from stdin until an EOF is reached. + +### COPY FROM STDIN with alternate stream + +In your code, you may also supply a different io.Reader object (such as *File) from which to supply your data. +Simply create a new VerticaContext, set the copy input stream, and provide this context to the execute call. +An example: + +```go +fp, err := os.OpenFile("./resources/csv/sample_data.csv", os.O_RDONLY, 0600) +... +vCtx := NewVerticaContext(ctx) +vCtx.SetCopyInputStream(fp) + +_, err = connDB.ExecContext(vCtx, "COPY stdin_data FROM STDIN DELIMITER ','") +``` + +If you provide a VerticaContext but don't set a copy input stream, the driver will fall back to os.stdin. + +## Full Example + +By following the above instructions, you should be able to successfully create a connection to your Vertica instance and perform the operations you require. A complete example program is listed below: + +```Go +package main + +import ( + "context" + "database/sql" + "os" + + _ "github.com/vertica/vertica-sql-go" + "github.com/vertica/vertica-sql-go/logger" +) + +func main() { + // Have our logger output INFO and above. + logger.SetLogLevel(logger.INFO) + + var testLogger = logger.New("samplecode") + + ctx := context.Background() + + // Create a connection to our database. Connection is lazy and won't + // happen until it's used. + connDB, err := sql.Open("vertica", "vertica://dbadmin:@localhost:5433/dbadmin") + + if err != nil { + testLogger.Fatal(err.Error()) + os.Exit(1) + } + + defer connDB.Close() + + // Ping the database connnection to force it to attempt to connect. + if err = connDB.PingContext(ctx); err != nil { + testLogger.Fatal(err.Error()) + os.Exit(1) + } + + // Query a standard metric table in Vertica. + rows, err := connDB.QueryContext(ctx, "SELECT * FROM v_monitor.cpu_usage LIMIT 5") + + if err != nil { + testLogger.Fatal(err.Error()) + os.Exit(1) + } + + defer rows.Close() + + // Iterate over the results and print them out. + for rows.Next() { + var nodeName string + var startTime string + var endTime string + var avgCPU float64 + + if err = rows.Scan(&nodeName, &startTime, &endTime, &avgCPU); err != nil { + testLogger.Fatal(err.Error()) + os.Exit(1) + } + + testLogger.Info("%s\t%s\t%s\t%f", nodeName, startTime, endTime, avgCPU) + } + + testLogger.Info("Test complete") + + os.Exit(0) +} +``` + +## License + +Apache 2.0 License, please see `LICENSE` for details. + +## Contributing guidelines + +Have a bug or an idea? Please see `CONTRIBUTING.md` for details. + +### Benchmarks + +You can run a benchmark and profile it with a command like: +`go test -bench '^BenchmarkRowsWithLimit$' -benchmem -memprofile memprofile.out -cpuprofile profile.out -run=none` + +and then explore it with `go tool pprof`. The `-run` part excludes the tests for brevity. + +## Acknowledgements +* @grzm (Github) +* @watercraft (Github) +* @fbernier (Github) +* @mlh758 (Github) for the awesome work filling in and enhancing the driver in many important ways. +* Tom Wall (Vertica) for the infinite patience and deep knowledge. +* The creators and contributors of the vertica-python library, and members of the Vertica team, for their help in understanding the wire protocol. diff --git a/vendor/github.com/vertica/vertica-sql-go/connection.go b/vendor/github.com/vertica/vertica-sql-go/connection.go new file mode 100644 index 00000000..fb767af6 --- /dev/null +++ b/vendor/github.com/vertica/vertica-sql-go/connection.go @@ -0,0 +1,618 @@ +package vertigo + +// Copyright (c) 2019 Micro Focus or one of its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import ( + "context" + "crypto/md5" + "crypto/sha512" + "crypto/tls" + "database/sql/driver" + "encoding/binary" + "fmt" + "net" + "net/url" + "os" + "strings" + "sync" + "time" + + "github.com/vertica/vertica-sql-go/common" + "github.com/vertica/vertica-sql-go/logger" + "github.com/vertica/vertica-sql-go/msgs" +) + +var ( + connectionLogger = logger.New("connection") +) + +// Connection represents a connection to Vertica +type connection struct { + driver.Conn + + conn net.Conn + connURL *url.URL + parameters map[string]string + clientPID int + backendPID uint32 + cancelKey uint32 + transactionState byte + usePreparedStmts bool + scratch [512]byte + sessionID string + serverTZOffset string + dead bool // used if a ROLLBACK severity error is encountered + sessMutex sync.Mutex +} + +// Begin - Begin starts and returns a new transaction. (DEPRECATED) +// From interface: sql.driver.Conn +func (v *connection) Begin() (driver.Tx, error) { + return nil, nil +} + +// BeginTx - Begin starts and returns a new transaction. +// From interface: sql.driver.ConnBeginTx +func (v *connection) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) { + connectionLogger.Trace("connection.BeginTx()") + return newTransaction(ctx, v, opts) +} + +// Close closes a connection to the Vertica DB. After calling close, you shouldn't use this connection anymore. +// From interface: sql.driver.Conn +func (v *connection) Close() error { + connectionLogger.Trace("connection.Close()") + + var result error = nil + + if v.conn != nil { + result = v.conn.Close() + v.conn = nil + } + + return result +} + +// PrepareContext returns a prepared statement, bound to this connection. +// context is for the preparation of the statement, +// it must not store the context within the statement itself. +// From interface: sql.driver.ConnPrepareContext +func (v *connection) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) { + + s, err := newStmt(v, query) + + if err != nil { + return nil, err + } + + if v.usePreparedStmts { + if err = s.prepareAndDescribe(); err != nil { + return nil, err + } + } + + return s, nil +} + +// Prepare returns a prepared statement, bound to this connection. +// From interface: sql.driver.Conn +func (v *connection) Prepare(query string) (driver.Stmt, error) { + return v.PrepareContext(context.Background(), query) +} + +// Ping implements the Pinger interface for connection. Use this to check for a valid connection state. +// This has to prepare AND execute the query in case prepared statements are disabled. +func (v *connection) Ping(ctx context.Context) error { + stmt, err := v.PrepareContext(ctx, "select 1 as test") + if err != nil { + return driver.ErrBadConn + } + defer stmt.Close() + // If we are preparing statements server side, successfully preparing verifies the connection + if v.usePreparedStmts { + return nil + } + queryContext := stmt.(driver.StmtQueryContext) + rows, err := queryContext.QueryContext(ctx, nil) + if err != nil { + return driver.ErrBadConn + } + rows.Close() + return nil +} + +// ResetSession implements the SessionResetter interface for connection. This allows the sql +// package to evaluate the connection state when managing the connection pool. +func (v *connection) ResetSession(ctx context.Context) error { + if v.dead { + return driver.ErrBadConn + } + return v.Ping(ctx) +} + +// newConnection constructs a new Vertica Connection object based on the connection string. +func newConnection(connString string) (*connection, error) { + + result := &connection{parameters: make(map[string]string), usePreparedStmts: true} + + var err error + result.connURL, err = url.Parse(connString) + + if err != nil { + return nil, err + } + + result.clientPID = os.Getpid() + result.sessionID = fmt.Sprintf("%s-%s-%d-%d", driverName, driverVersion, result.clientPID, time.Now().Unix()) + + // Read the interpolate flag. + if iFlag := result.connURL.Query().Get("use_prepared_statements"); iFlag != "" { + result.usePreparedStmts = iFlag == "1" + } + + // Read connection load balance flag. + loadBalanceFlag := result.connURL.Query().Get("connection_load_balance") + + sslFlag := strings.ToLower(result.connURL.Query().Get("tlsmode")) + if sslFlag == "" { + sslFlag = "none" + } + + result.conn, err = net.Dial("tcp", result.connURL.Host) + + if err != nil { + return nil, fmt.Errorf("cannot connect to %s (%s)", result.connURL.Host, err.Error()) + } + + // Load Balancing + if loadBalanceFlag == "1" { + if err = result.balanceLoad(); err != nil { + return nil, err + } + } + + if sslFlag != "none" { + if err = result.initializeSSL(sslFlag); err != nil { + return nil, err + } + } + + if err = result.handshake(); err != nil { + return nil, err + } + + if err = result.initializeSession(); err != nil { + return nil, err + } + + return result, nil +} + +func (v *connection) recvMessage() (msgs.BackEndMsg, error) { + msgHeader := v.scratch[:5] + + var err error + + if err = v.readAll(msgHeader); err != nil { + return nil, err + } + + msgSize := int(binary.BigEndian.Uint32(msgHeader[1:]) - 4) + + msgBytes := v.scratch[5:] + + var y []byte + if msgSize > 0 { + if msgSize <= len(msgBytes) { + y = msgBytes[:msgSize] + } else { + y = make([]byte, msgSize) + } + if err = v.readAll(y); err != nil { + return nil, err + } + } + + bem, err := msgs.CreateBackEndMsg(msgHeader[0], y) + + if err != nil { + return nil, err + } + + // Print the message to stdout (for debugging purposes) + if _, drm := bem.(*msgs.BEDataRowMsg); !drm { + connectionLogger.Debug("<- " + bem.String()) + } else { + connectionLogger.Trace("<- " + bem.String()) + } + + return bem, nil +} + +func (v *connection) sendMessage(msg msgs.FrontEndMsg) error { + return v.sendMessageTo(msg, v.conn) +} + +func (v *connection) sendMessageTo(msg msgs.FrontEndMsg, conn net.Conn) error { + var result error = nil + + msgBytes, msgTag := msg.Flatten() + + if msgTag != 0 { + _, result = conn.Write([]byte{msgTag}) + } + + if result == nil { + sizeBytes := v.scratch[:4] + binary.BigEndian.PutUint32(sizeBytes, uint32(len(msgBytes)+4)) + + _, result = conn.Write(sizeBytes) + + if result == nil { + if len(msgBytes) > 0 { + _, result = conn.Write(msgBytes) + } + } + } + + if result != nil { + connectionLogger.Error("-> FAILED SENDING "+msg.String()+": %v", result.Error()) + } else { + connectionLogger.Debug("-> " + msg.String()) + } + + return result +} + +func (v *connection) handshake() error { + + if v.connURL.User == nil { + return fmt.Errorf("connection string must include a user name") + } + + userName := v.connURL.User.Username() + + if len(userName) == 0 { + return fmt.Errorf("connection string must have a non-empty user name") + } + + if len(v.connURL.Path) <= 1 { + return fmt.Errorf("connection string must include a database name") + } + + path := v.connURL.Path[1:] + + msg := &msgs.FEStartupMsg{ + ProtocolVersion: protocolVersion, + DriverName: driverName, + DriverVersion: driverVersion, + Username: userName, + Database: path, + SessionID: v.sessionID, + ClientPID: v.clientPID, + } + + if err := v.sendMessage(msg); err != nil { + return err + } + + for { + bMsg, err := v.recvMessage() + + if err != nil { + return err + } + + switch msg := bMsg.(type) { + case *msgs.BEErrorMsg: + return msg.ToErrorType() + case *msgs.BEReadyForQueryMsg: + v.transactionState = msg.TransactionState + return nil + case *msgs.BEParamStatusMsg: + v.parameters[msg.ParamName] = msg.ParamValue + case *msgs.BEKeyDataMsg: + v.backendPID = msg.BackendPID + v.cancelKey = msg.CancelKey + default: + _, err = v.defaultMessageHandler(msg) + if err != nil { + return err + } + } + } +} + +// We have to be tricky here since we're inside of a connection, but trying to use interfaces of the +// driver class. +func (v *connection) initializeSession() error { + + stmt, err := newStmt(v, "select now()::timestamptz") + + if err != nil { + return err + } + + result, err := stmt.QueryContextRaw(context.Background(), []driver.NamedValue{}) + + if err != nil { + return err + } + + firstRow := result.resultData.Peek() + + if len(result.Columns()) != 1 && result.Columns()[1] != "now" || firstRow == nil { + return fmt.Errorf("unable to initialize session; functionality may be unreliable") + } + + // Peek into the results manually. + colData := firstRow.Columns() + str := string(colData.Chunk()) + + if len(str) < 23 { + return fmt.Errorf("can't get server timezone: %s", str) + } + + v.serverTZOffset = str[len(str)-3:] + + connectionLogger.Debug("Setting server timezone offset to %s", str[len(str)-3:]) + + return nil +} + +func (v *connection) defaultMessageHandler(bMsg msgs.BackEndMsg) (bool, error) { + + handled := true + + var err error = nil + + switch msg := bMsg.(type) { + case *msgs.BEAuthenticationMsg: + switch msg.Response { + case common.AuthenticationOK: + break + case common.AuthenticationCleartextPassword: + err = v.authSendPlainTextPassword() + case common.AuthenticationMD5Password: + err = v.authSendMD5Password(msg.ExtraAuthData) + case common.AuthenticationSHA512Password: + err = v.authSendSHA512Password(msg.ExtraAuthData) + default: + handled = false + err = fmt.Errorf("unsupported authentication scheme: %d", msg.Response) + } + case *msgs.BENoticeMsg: + break + case *msgs.BEParamStatusMsg: + connectionLogger.Debug("%v", msg) + default: + handled = false + err = fmt.Errorf("unhandled message: %v", msg) + connectionLogger.Warn("%v", err) + } + + return handled, err +} + +func (v *connection) readAll(buf []byte) error { + readIndex := 0 + + for { + bytesRead, err := v.conn.Read(buf[readIndex:]) + + if err != nil { + return err + } + + readIndex += bytesRead + + if readIndex == len(buf) { + return nil + } + } +} + +func (v *connection) balanceLoad() error { + v.sendMessage(&msgs.FELoadBalanceMsg{}) + response := v.scratch[:1] + + var err error + if err = v.readAll(response); err != nil { + return err + } + + if response[0] == 'N' { + // keep existing connection + connectionLogger.Debug("<- LoadBalanceResponse: N") + connectionLogger.Warn("Load balancing requested but not supported by server") + return nil + } + + if response[0] != 'Y' { + connectionLogger.Debug("<- LoadBalanceResponse: %c", response[0]) + return fmt.Errorf("Load balancing request gave unknown response: %c", response[0]) + } + + header := v.scratch[1:5] + if err = v.readAll(header); err != nil { + return err + } + msgSize := int(binary.BigEndian.Uint32(header) - 4) + msgBytes := v.scratch[5:] + + var y []byte + if msgSize > 0 { + if msgSize <= len(msgBytes) { + y = msgBytes[:msgSize] + } else { + y = make([]byte, msgSize) + } + if err = v.readAll(y); err != nil { + return err + } + } + + bem, err := msgs.CreateBackEndMsg(response[0], y) + if err != nil { + return err + } + connectionLogger.Debug("<- " + bem.String()) + msg := bem.(*msgs.BELoadBalanceMsg) + + // v.connURL.Hostname() is used by initializeSSL(), so load balancing info should not write into v.connURL + loadBalanceAddr := fmt.Sprintf("%s:%d", msg.Host, msg.Port) + + // Connect to new host + v.conn.Close() + v.conn, err = net.Dial("tcp", loadBalanceAddr) + + if err != nil { + return fmt.Errorf("cannot redirect to %s (%s)", loadBalanceAddr, err.Error()) + } + + return nil +} + +func (v *connection) initializeSSL(sslFlag string) error { + v.sendMessage(&msgs.FESSLMsg{}) + + buf := v.scratch[:1] + + err := v.readAll(buf) + + if err != nil { + return err + } + + if buf[0] == 'N' { + return fmt.Errorf("SSL/TLS is not enabled on this server") + } + + if buf[0] != 'S' { + return fmt.Errorf("SSL/TLS probe gave unknown response: %c", buf[0]) + } + + switch sslFlag { + case "server": + connectionLogger.Info("enabling SSL/TLS server mode") + v.conn = tls.Client(v.conn, &tls.Config{InsecureSkipVerify: true}) + case "server-strict": + connectionLogger.Info("enabling SSL/TLS server strict mode") + v.conn = tls.Client(v.conn, &tls.Config{ServerName: v.connURL.Hostname()}) + default: + err := fmt.Errorf("unsupported tlsmode flag: %s - should be 'server', 'server-strict' or 'none'", sslFlag) + connectionLogger.Error(err.Error()) + return err + } + // case "mutual": + // err = fmt.Errorf("mutual ssl mode not currently supported") + // default: + // err = fmt.Errorf("unsupported ssl value in connect string: %s", sslFlag) + + return nil +} + +func (v *connection) authSendPlainTextPassword() error { + passwd, isSet := v.connURL.User.Password() + + if !isSet { + passwd = "" + } + + msg := &msgs.FEPasswordMsg{PasswordData: passwd} + + return v.sendMessage(msg) +} + +func (v *connection) authSendMD5Password(extraAuthData []byte) error { + passwd, isSet := v.connURL.User.Password() + + if !isSet { + passwd = "" + } + + hash1 := fmt.Sprintf("%x", md5.Sum([]byte(passwd+v.connURL.User.Username()))) + hash2 := fmt.Sprintf("md5%x", md5.Sum(append([]byte(hash1), extraAuthData[0:4]...))) + + msg := &msgs.FEPasswordMsg{PasswordData: hash2} + + return v.sendMessage(msg) +} + +func (v *connection) authSendSHA512Password(extraAuthData []byte) error { + passwd, isSet := v.connURL.User.Password() + + if !isSet { + passwd = "" + } + + hash1 := fmt.Sprintf("%x", sha512.Sum512(append([]byte(passwd), extraAuthData[8:]...))) + hash2 := fmt.Sprintf("sha512%x", sha512.Sum512(append([]byte(hash1), extraAuthData[0:4]...))) + + msg := &msgs.FEPasswordMsg{PasswordData: hash2} + + return v.sendMessage(msg) +} + +func (v *connection) sync() error { + err := v.sendMessage(&msgs.FESyncMsg{}) + + if err != nil { + return err + } + + for true { + bem, err := v.recvMessage() + if err != nil { + return err + } + + _, ok := bem.(*msgs.BEReadyForQueryMsg) + + if ok { + break + } + + _, _ = v.defaultMessageHandler(bem) + } + + return nil +} + +func (v *connection) lockSessionMutex() { + v.sessMutex.Lock() +} + +func (v *connection) unlockSessionMutex() { + v.sessMutex.Unlock() +} diff --git a/vendor/github.com/vertica/vertica-sql-go/context.go b/vendor/github.com/vertica/vertica-sql-go/context.go new file mode 100644 index 00000000..c0283176 --- /dev/null +++ b/vendor/github.com/vertica/vertica-sql-go/context.go @@ -0,0 +1,124 @@ +package vertigo + +import ( + "context" + "fmt" + "io" + "os" +) + +// Copyright (c) 2019 Micro Focus or one of its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +const ( + minCopyBlockSize = 16384 + stdInDefaultCopyBlockSize = 65536 +) + +type VerticaContext interface { + context.Context + + SetCopyInputStream(inputStream io.Reader) error + GetCopyInputStream() io.Reader + + SetCopyBlockSizeBytes(blockSize int) error + GetCopyBlockSizeBytes() int + + SetInMemoryResultRowLimit(rowLimit int) error + GetInMemoryResultRowLimit() int +} + +type verticaContext struct { + context.Context + + inputStream io.Reader + blockSize int + rowLimit int +} + +// NewVerticaContext creates a new context that inherits the values and behavior of the provided parent context. +func NewVerticaContext(parentCtx context.Context) VerticaContext { + return &verticaContext{ + Context: parentCtx, + inputStream: os.Stdin, + blockSize: stdInDefaultCopyBlockSize, + rowLimit: 0, + } +} + +// SetCopyInputStream sets the input stream to be used when copying from stdin. If not set, copying from stdin will +// read from os.stdin. +func (c *verticaContext) SetCopyInputStream(inputStream io.Reader) error { + if inputStream == nil { + return fmt.Errorf("cannot SetInputStream to a nil value") + } + + c.inputStream = inputStream + + return nil +} + +// GetCopyInputStream returns the currently active input stream to be used when copying from stdin. +func (c *verticaContext) GetCopyInputStream() io.Reader { + return c.inputStream +} + +// SetCopyBlockSizeBytes sets the size of the buffer used to transfer from the input stream to Vertica. By +// default, it's 65536 (64k). It must be at least 16384 (16k) bytes. +func (c *verticaContext) SetCopyBlockSizeBytes(blockSize int) error { + if blockSize < minCopyBlockSize { + return fmt.Errorf("cannot set copy block size to less than %d", minCopyBlockSize) + } + + c.blockSize = blockSize + + return nil +} + +// GetCopyBlockSizeBytes gets the size of the buffer used to transfer from the input stream to Vertica. +func (c *verticaContext) GetCopyBlockSizeBytes() int { + return c.blockSize +} + +func (c *verticaContext) SetInMemoryResultRowLimit(rowLimit int) error { + if rowLimit < 0 { + return fmt.Errorf("cannot set result limit to a negative number") + } + + c.rowLimit = rowLimit + + return nil +} + +func (c *verticaContext) GetInMemoryResultRowLimit() int { + return c.rowLimit +} diff --git a/vendor/github.com/vertica/vertica-sql-go/driver.go b/vendor/github.com/vertica/vertica-sql-go/driver.go new file mode 100644 index 00000000..07798b1d --- /dev/null +++ b/vendor/github.com/vertica/vertica-sql-go/driver.go @@ -0,0 +1,106 @@ +package vertigo + +// Copyright (c) 2019-2020 Micro Focus or one of its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import ( + "database/sql" + "database/sql/driver" + "os" + "strconv" + + "github.com/vertica/vertica-sql-go/logger" +) + +// Driver as defined by the Go language Driver interface +type Driver struct{} + +const ( + driverName string = "vertica-sql-go" + driverVersion string = "1.1.0" + protocolVersion uint32 = 0x00030008 +) + +var driverLogger = logger.New("driver") + +// Open takes a connection string in this format: +// user:pass@host:port/database +func (d *Driver) Open(connString string) (driver.Conn, error) { + conn, err := newConnection(connString) + if err != nil { + driverLogger.Error(err.Error()) + } + return conn, err +} + +// Register ourselves with the sql package. +func init() { + logger.SetLogLevel(logger.WARN) + + if logLevel := os.Getenv("VERTICA_SQL_GO_LOG_LEVEL"); logLevel != "" { + logVal, err := strconv.ParseUint(logLevel, 10, 32) + if err != nil { + driverLogger.Error(err.Error()) + } else { + logFlag := logger.WARN + switch logVal { + case 0: + logFlag = logger.TRACE + case 1: + logFlag = logger.DEBUG + case 2: + logFlag = logger.INFO + case 3: + logFlag = logger.WARN + case 4: + logFlag = logger.ERROR + case 5: + logFlag = logger.FATAL + case 6: + logFlag = logger.NONE + default: + driverLogger.Error("invalid VERTICA_SQL_GO_LOG_LEVEL value; should be 0-6") + } + logger.SetLogLevel(logFlag) + } + } + + if logFile := os.Getenv("VERTICA_SQL_GO_LOG_FILE"); logFile != "" { + if loggerBackend, err := logger.NewFileLogger(logFile); err == nil { + logger.SetLogger(loggerBackend) + } else { + driverLogger.Error("unable to create file logger: %v", err) + } + } + + sql.Register("vertica", &Driver{}) +} diff --git a/vendor/github.com/vertica/vertica-sql-go/go.mod b/vendor/github.com/vertica/vertica-sql-go/go.mod new file mode 100644 index 00000000..78506617 --- /dev/null +++ b/vendor/github.com/vertica/vertica-sql-go/go.mod @@ -0,0 +1,3 @@ +module github.com/vertica/vertica-sql-go + +go 1.13 diff --git a/vendor/github.com/vertica/vertica-sql-go/result.go b/vendor/github.com/vertica/vertica-sql-go/result.go new file mode 100644 index 00000000..2a5f79b5 --- /dev/null +++ b/vendor/github.com/vertica/vertica-sql-go/result.go @@ -0,0 +1,46 @@ +package vertigo + +// Copyright (c) 2019-2020 Micro Focus or one of its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +type result struct { + lastInsertID int64 + rowsAffected int64 +} + +func (r *result) LastInsertId() (int64, error) { + return r.lastInsertID, nil +} + +func (r *result) RowsAffected() (int64, error) { + return r.rowsAffected, nil +} diff --git a/vendor/github.com/vertica/vertica-sql-go/rows.go b/vendor/github.com/vertica/vertica-sql-go/rows.go new file mode 100644 index 00000000..46b88127 --- /dev/null +++ b/vendor/github.com/vertica/vertica-sql-go/rows.go @@ -0,0 +1,183 @@ +package vertigo + +// Copyright (c) 2019-2020 Micro Focus or one of its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import ( + "context" + "database/sql/driver" + "encoding/hex" + "io" + "strconv" + "strings" + "time" + + "github.com/vertica/vertica-sql-go/common" + "github.com/vertica/vertica-sql-go/msgs" + "github.com/vertica/vertica-sql-go/rowcache" +) + +type rowStore interface { + AddRow(msg *msgs.BEDataRowMsg) + GetRow() *msgs.BEDataRowMsg + Peek() *msgs.BEDataRowMsg + Close() error + Finalize() error +} + +type rows struct { + columnDefs *msgs.BERowDescMsg + resultData rowStore + + tzOffset string + inMemRowLimit int +} + +var ( + paddingString = "000000" + defaultRowBufferSize = 256 +) + +// Columns returns the names of all of the columns +// Interface: driver.Rows +func (r *rows) Columns() []string { + columnLabels := make([]string, len(r.columnDefs.Columns)) + for idx, cd := range r.columnDefs.Columns { + columnLabels[idx] = cd.FieldName + } + return columnLabels +} + +// Close closes the read cursor +// Interface: driver.Rows +func (r *rows) Close() error { + return r.resultData.Close() +} + +func (r *rows) Next(dest []driver.Value) error { + nextRow := r.resultData.GetRow() + if nextRow == nil { + return io.EOF + } + + rowCols := nextRow.Columns() + + for idx := uint16(0); idx < rowCols.NumCols; idx++ { + colVal := rowCols.Chunk() + if colVal == nil { + dest[idx] = nil + continue + } + + switch r.columnDefs.Columns[idx].DataTypeOID { + case common.ColTypeBoolean: // to boolean + dest[idx] = colVal[0] == 't' + case common.ColTypeInt64: // to integer + dest[idx], _ = strconv.Atoi(string(colVal)) + case common.ColTypeVarChar, common.ColTypeLongVarChar, common.ColTypeChar, common.ColTypeUUID: // stays string, convert char to string + dest[idx] = string(colVal) + case common.ColTypeFloat64, common.ColTypeNumeric: // to float64 + dest[idx], _ = strconv.ParseFloat(string(colVal), 64) + case common.ColTypeTimestamp: // to time.Time from YYYY-MM-DD hh:mm:ss + dest[idx], _ = parseTimestampTZColumn(string(colVal) + r.tzOffset) + case common.ColTypeTimestampTZ: + dest[idx], _ = parseTimestampTZColumn(string(colVal)) + case common.ColTypeVarBinary, common.ColTypeLongVarBinary, common.ColTypeBinary: // to []byte - this one's easy + dest[idx] = hex.EncodeToString(colVal) + default: + dest[idx] = string(colVal) + } + } + + return nil +} + +func parseTimestampTZColumn(fullString string) (driver.Value, error) { + var result driver.Value + var err error + + if strings.IndexByte(fullString, '.') != -1 { + neededPadding := 29 - len(fullString) + if neededPadding > 0 { + fullString = fullString[0:26-neededPadding] + paddingString[0:neededPadding] + fullString[len(fullString)-3:] + } + result, err = time.Parse("2006-01-02 15:04:05.000000-07", fullString) + } else { + result, err = time.Parse("2006-01-02 15:04:05-07", fullString) + } + + return result, err +} + +func (r *rows) finalize() { + r.resultData.Finalize() +} + +func (r *rows) addRow(rowData *msgs.BEDataRowMsg) { + r.resultData.AddRow(rowData) +} + +func newRows(ctx context.Context, columnsDefsMsg *msgs.BERowDescMsg, tzOffset string) *rows { + + rowBufferSize := defaultRowBufferSize + inMemRowLimit := 0 + var resultData rowStore + var err error + + if vCtx, ok := ctx.(VerticaContext); ok { + rowBufferSize = vCtx.GetInMemoryResultRowLimit() + inMemRowLimit = rowBufferSize + } + if inMemRowLimit != 0 { + resultData, err = rowcache.NewFileCache(inMemRowLimit) + if err != nil { + resultData = rowcache.NewMemoryCache(rowBufferSize) + } + } else { + resultData = rowcache.NewMemoryCache(rowBufferSize) + } + + res := &rows{ + columnDefs: columnsDefsMsg, + resultData: resultData, + tzOffset: tzOffset, + inMemRowLimit: inMemRowLimit, + } + + return res +} + +func newEmptyRows() *rows { + cdf := make([]*msgs.BERowDescColumnDef, 0) + be := &msgs.BERowDescMsg{Columns: cdf} + return newRows(context.Background(), be, "") +} diff --git a/vendor/github.com/vertica/vertica-sql-go/stmt.go b/vendor/github.com/vertica/vertica-sql-go/stmt.go new file mode 100644 index 00000000..7677c0d1 --- /dev/null +++ b/vendor/github.com/vertica/vertica-sql-go/stmt.go @@ -0,0 +1,549 @@ +package vertigo + +// Copyright (c) 2019-2020 Micro Focus or one of its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import ( + "context" + "database/sql/driver" + "errors" + "fmt" + "io" + "math/rand" + "net" + "os" + "reflect" + "regexp" + "strings" + "time" + + "github.com/vertica/vertica-sql-go/common" + "github.com/vertica/vertica-sql-go/logger" + "github.com/vertica/vertica-sql-go/msgs" + "github.com/vertica/vertica-sql-go/parse" +) + +var ( + stmtLogger = logger.New("stmt") +) + +type parseState int + +const ( + parseStateUnparsed parseState = iota + parseStateParseError + parseStateParsed +) + +type stmt struct { + conn *connection + command string + preparedName string + parseState parseState + namedArgPos []string + posArgCnt int + paramTypes []common.ParameterType + lastRowDesc *msgs.BERowDescMsg + // set if Vertica issues an error of ROLLBACK severity + rolledBack bool +} + +func newStmt(connection *connection, command string) (*stmt, error) { + + if len(command) == 0 { + return nil, fmt.Errorf("cannot create an empty statement") + } + + s := &stmt{ + conn: connection, + preparedName: fmt.Sprintf("S%d%d%d", os.Getpid(), time.Now().Unix(), rand.Int31()), + parseState: parseStateUnparsed, + } + argCounter := func() string { + s.posArgCnt++ + return "?" + } + s.command = parse.Lex(command, parse.WithNamedCallback(s.pushNamed), parse.WithPositionalSubstitution(argCounter)) + return s, nil +} + +func (s *stmt) pushNamed(name string) { + s.namedArgPos = append(s.namedArgPos, name) +} + +// Close closes this statement. +func (s *stmt) Close() error { + if s.parseState != parseStateParsed { + return nil + } + if s.rolledBack { + s.parseState = parseStateUnparsed + s.conn.dead = true + return nil + } + closeMsg := &msgs.FECloseMsg{TargetType: msgs.CmdTargetTypeStatement, TargetName: s.preparedName} + + if err := s.conn.sendMessage(closeMsg); err != nil { + return err + } + + if err := s.conn.sendMessage(&msgs.FEFlushMsg{}); err != nil { + return err + } + + for { + bMsg, err := s.conn.recvMessage() + + if err != nil { + return err + } + + switch bMsg.(type) { + case *msgs.BECloseCompleteMsg: + s.parseState = parseStateUnparsed + return nil + case *msgs.BECmdDescriptionMsg: + continue + default: + s.conn.defaultMessageHandler(bMsg) + } + } +} + +// NumInput is used by database/sql to sanity check the number of arguments given +// before calling into the driver's query/exec functions. If named arguments are used +// this will return the number of unique named parameters, otherwise it is the number +// if ? placeholders. +func (s *stmt) NumInput() int { + if len(s.namedArgPos) > 0 { + uniqueArgs := make(map[string]bool) + for _, arg := range s.namedArgPos { + uniqueArgs[arg] = true + } + return len(uniqueArgs) + } + return s.posArgCnt +} + +// convertToNamed takes an argument list of Value that come from the older Exec/Query functions +// and converts them to NamedValue to be forwarded to their Context equivalents. +func (s *stmt) convertToNamed(args []driver.Value) []driver.NamedValue { + namedArgs := make([]driver.NamedValue, len(args)) + for idx, arg := range args { + namedArgs[idx] = driver.NamedValue{ + Ordinal: idx, + Value: arg, + } + } + return namedArgs +} + +// injectNamedArgs takes a list of arguments, builds a symbol table of name => arg and then +// fills a list of positional arguments based on the names from the args parameter +// This will return an error if any of the given args lack a name +func (s *stmt) injectNamedArgs(args []driver.NamedValue) ([]driver.NamedValue, error) { + if len(s.namedArgPos) == 0 { + return args, nil + } + symbols := make(map[string]driver.NamedValue, len(args)) + for _, arg := range args { + if len(arg.Name) > 0 { + symbols[strings.ToUpper(arg.Name)] = arg + continue + } + namedVal, ok := arg.Value.(driver.NamedValue) + if !ok || len(namedVal.Name) == 0 { + return nil, errors.New("all parameters must have names when using named parameters") + } + symbols[strings.ToUpper(namedVal.Name)] = namedVal + } + realArgs := make([]driver.NamedValue, len(s.namedArgPos)) + for pos, name := range s.namedArgPos { + realArgs[pos] = symbols[name] + realArgs[pos].Ordinal = pos + } + return realArgs, nil +} + +// Exec docs +func (s *stmt) Exec(args []driver.Value) (driver.Result, error) { + return s.ExecContext(context.Background(), s.convertToNamed(args)) +} + +// Query docs +func (s *stmt) Query(args []driver.Value) (driver.Rows, error) { + stmtLogger.Debug("stmt.Query(): %s\n", s.command) + return s.QueryContext(context.Background(), s.convertToNamed(args)) +} + +// ExecContext docs +func (s *stmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) { + stmtLogger.Trace("stmt.ExecContext()") + + rows, err := s.QueryContext(ctx, args) + + if err != nil { + return driver.ResultNoRows, err + } + + numCols := len(rows.Columns()) + vals := make([]driver.Value, numCols) + + if rows.Next(vals) == io.EOF { + return driver.ResultNoRows, nil + } + + rv := reflect.ValueOf(vals[0]) + + return &result{lastInsertID: 0, rowsAffected: rv.Int()}, nil +} + +func (s *stmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) { + return s.QueryContextRaw(ctx, args) +} + +// QueryContext docs +func (s *stmt) QueryContextRaw(ctx context.Context, baseArgs []driver.NamedValue) (*rows, error) { + stmtLogger.Debug("stmt.QueryContextRaw(): %s", s.command) + + var cmd string + var err error + var portalName string + + args, err := s.injectNamedArgs(baseArgs) + if err != nil { + return newEmptyRows(), err + } + + doneChan := make(chan bool, 1) + go func(pid, key uint32) { + select { + case <-doneChan: + return + case <-ctx.Done(): + stmtLogger.Info("Context cancelled, cancelling %s", s.preparedName) + cancelMsg := msgs.FECancelMsg{PID: pid, Key: key} + conn, err := net.Dial("tcp", s.conn.connURL.Host) + if err != nil { + stmtLogger.Warn("unable to establish connection for cancellation") + return + } + conn.SetDeadline(time.Now().Add(10 * time.Second)) + if err := s.conn.sendMessageTo(&cancelMsg, conn); err != nil { + stmtLogger.Warn("unable to send cancel message: %v", err) + } + if err := conn.Close(); err != nil { + stmtLogger.Warn("error closing cancel connection: %v", err) + } + stmtLogger.Info("Cancelled %s", s.preparedName) + } + }(s.conn.backendPID, s.conn.cancelKey) + + s.conn.lockSessionMutex() + defer s.conn.unlockSessionMutex() + defer func() { + doneChan <- true + }() + + // If we have a prepared statement, go through bind/execute() phases instead. + if s.parseState == parseStateParsed { + if err = s.bindAndExecute(portalName, args); err != nil { + return newEmptyRows(), err + } + + return s.collectResults(ctx) + } + + rows := newEmptyRows() + + // We aren't a prepared statement, manually interpolate and do as a simple query. + cmd, err = s.interpolate(args) + + if err != nil { + return rows, err + } + + if err = s.conn.sendMessage(&msgs.FEQueryMsg{Query: cmd}); err != nil { + return rows, err + } + + for { + bMsg, err := s.conn.recvMessage() + + if err != nil { + return newEmptyRows(), err + } + + switch msg := bMsg.(type) { + case *msgs.BEDataRowMsg: + rows.addRow(msg) + case *msgs.BERowDescMsg: + rows = newRows(ctx, msg, s.conn.serverTZOffset) + case *msgs.BECmdCompleteMsg: + break + case *msgs.BEErrorMsg: + return newEmptyRows(), s.evaluateErrorMsg(msg) + case *msgs.BEEmptyQueryResponseMsg: + return newEmptyRows(), nil + case *msgs.BEReadyForQueryMsg, *msgs.BEPortalSuspendedMsg: + rows.finalize() + return rows, ctx.Err() + case *msgs.BEInitSTDINLoadMsg: + s.copySTDIN(ctx) + default: + s.conn.defaultMessageHandler(bMsg) + } + } +} + +func (s *stmt) copySTDIN(ctx context.Context) { + + var streamToUse io.Reader + streamToUse = os.Stdin + + var copyBlockSize = stdInDefaultCopyBlockSize + + if vCtx, ok := ctx.(VerticaContext); ok { + streamToUse = vCtx.GetCopyInputStream() + copyBlockSize = vCtx.GetCopyBlockSizeBytes() + } + + block := make([]byte, copyBlockSize) + for { + bytesRead, err := streamToUse.Read(block) + if err == io.EOF { + s.conn.sendMessage(&msgs.FELoadDoneMsg{}) + break + } + if err != nil { + s.conn.sendMessage(&msgs.FELoadFailMsg{Message: err.Error()}) + break + } + s.conn.sendMessage(&msgs.FELoadDataMsg{Data: block, UsedBytes: bytesRead}) + } + s.conn.sendMessage(&msgs.FEFlushMsg{}) +} + +func (s *stmt) cleanQuotes(val string) string { + re := regexp.MustCompile(`'+`) + pairs := re.FindAllStringIndex(val, -1) + if pairs == nil { + return val + } + cleaned := strings.Builder{} + cleaned.Grow(len(val)) + cleanedTo := 0 + for _, matchPair := range pairs { + if (matchPair[1]-matchPair[0])%2 != 0 { + cleaned.WriteString(val[cleanedTo:matchPair[1]]) + cleaned.WriteRune('\'') + cleanedTo = matchPair[1] + } + } + cleaned.WriteString(val[cleanedTo:]) + return cleaned.String() +} + +func (s *stmt) formatArg(arg driver.NamedValue) string { + var replaceStr string + switch v := arg.Value.(type) { + case int64, float64: + replaceStr = fmt.Sprintf("%v", v) + case string: + replaceStr = fmt.Sprintf("'%s'", s.cleanQuotes(v)) + case bool: + if v { + replaceStr = "true" + } else { + replaceStr = "false" + } + case time.Time: + replaceStr = fmt.Sprintf("%02d-%02d-%02d %02d:%02d:%02d", + v.Year(), + v.Month(), + v.Day(), + v.Hour(), + v.Minute(), + v.Second()) + default: + replaceStr = "?unknown_type?" + } + return replaceStr +} + +func (s *stmt) interpolate(args []driver.NamedValue) (string, error) { + + numArgs := s.NumInput() + + if numArgs == 0 { + return s.command, nil + } + + curArg := 0 + argSwapper := func() string { + arg := s.formatArg(args[curArg]) + curArg++ + return arg + } + + result := parse.Lex(s.command, parse.WithPositionalSubstitution(argSwapper)) + return result, nil +} + +func (s *stmt) evaluateErrorMsg(msg *msgs.BEErrorMsg) error { + if msg.Severity == "ROLLBACK" { + s.rolledBack = true + } + return msg.ToErrorType() +} + +func (s *stmt) prepareAndDescribe() error { + + parseMsg := &msgs.FEParseMsg{ + PreparedName: s.preparedName, + Command: s.command, + NumArgs: 0, + } + + // If we've already been parsed, no reason to do it again. + if s.parseState == parseStateParsed { + return nil + } + + s.parseState = parseStateParseError + + s.conn.lockSessionMutex() + defer s.conn.unlockSessionMutex() + + if err := s.conn.sendMessage(parseMsg); err != nil { + return err + } + + describeMsg := &msgs.FEDescribeMsg{TargetType: msgs.CmdTargetTypeStatement, TargetName: s.preparedName} + + if err := s.conn.sendMessage(describeMsg); err != nil { + return err + } + + if err := s.conn.sendMessage(&msgs.FEFlushMsg{}); err != nil { + return err + } + + for { + bMsg, err := s.conn.recvMessage() + + if err != nil { + return err + } + + switch msg := bMsg.(type) { + case *msgs.BEErrorMsg: + s.conn.sync() + return msg.ToErrorType() + case *msgs.BEParseCompleteMsg: + s.parseState = parseStateParsed + case *msgs.BERowDescMsg: + s.lastRowDesc = msg + return nil + case *msgs.BENoDataMsg: + s.lastRowDesc = nil + return nil + case *msgs.BEParameterDescMsg: + s.paramTypes = msg.ParameterTypes + case *msgs.BECmdDescriptionMsg: + continue + default: + s.conn.defaultMessageHandler(msg) + } + } +} + +func (s *stmt) bindAndExecute(portalName string, args []driver.NamedValue) error { + + // We only need to send the OID types + paramOIDs := make([]int32, len(s.paramTypes)) + for i, p := range s.paramTypes { + paramOIDs[i] = int32(p.TypeOID) + } + + if err := s.conn.sendMessage(&msgs.FEBindMsg{Portal: portalName, Statement: s.preparedName, NamedArgs: args, OIDTypes: paramOIDs}); err != nil { + return err + } + + if err := s.conn.sendMessage(&msgs.FEExecuteMsg{Portal: portalName}); err != nil { + return err + } + + if err := s.conn.sendMessage(&msgs.FEFlushMsg{}); err != nil { + return err + } + + return nil +} + +func (s *stmt) collectResults(ctx context.Context) (*rows, error) { + rows := newEmptyRows() + + if s.lastRowDesc != nil { + rows = newRows(ctx, s.lastRowDesc, s.conn.serverTZOffset) + } + + for { + bMsg, err := s.conn.recvMessage() + + if err != nil { + return newEmptyRows(), err + } + + switch msg := bMsg.(type) { + case *msgs.BEDataRowMsg: + rows.addRow(msg) + case *msgs.BERowDescMsg: + s.lastRowDesc = msg + rows = newRows(ctx, s.lastRowDesc, s.conn.serverTZOffset) + case *msgs.BEErrorMsg: + s.conn.sync() + return newEmptyRows(), s.evaluateErrorMsg(msg) + case *msgs.BEEmptyQueryResponseMsg: + return newEmptyRows(), nil + case *msgs.BEBindCompleteMsg, *msgs.BECmdDescriptionMsg: + continue + case *msgs.BEReadyForQueryMsg, *msgs.BEPortalSuspendedMsg, *msgs.BECmdCompleteMsg: + rows.finalize() + return rows, ctx.Err() + case *msgs.BEInitSTDINLoadMsg: + s.copySTDIN(ctx) + default: + _, _ = s.conn.defaultMessageHandler(msg) + } + } +} diff --git a/vendor/github.com/vertica/vertica-sql-go/tx.go b/vendor/github.com/vertica/vertica-sql-go/tx.go new file mode 100644 index 00000000..de8b0be0 --- /dev/null +++ b/vendor/github.com/vertica/vertica-sql-go/tx.go @@ -0,0 +1,123 @@ +package vertigo + +// Copyright (c) 2019 Micro Focus or one of its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import ( + "context" + "database/sql" + "database/sql/driver" + "fmt" +) + +type tx struct { + conn *connection + context context.Context +} + +func (t *tx) Commit() error { + stmt, err := t.conn.PrepareContext(t.context, "COMMIT") + + if err != nil { + return err + } + + _, err = stmt.Exec([]driver.Value{}) + + if err != nil { + return err + } + + return nil +} + +func (t *tx) Rollback() error { + stmt, err := t.conn.PrepareContext(t.context, "ROLLBACK") + + if err != nil { + return err + } + + _, err = stmt.Exec([]driver.Value{}) + + if err != nil { + return err + } + + return nil + +} + +func newTransaction(ctx context.Context, c *connection, opts driver.TxOptions) (*tx, error) { + + queryStr := "START TRANSACTION" + + switch sql.IsolationLevel(opts.Isolation) { + case sql.LevelReadUncommitted: + queryStr += " ISOLATION LEVEL READ UNCOMMITTED" + case sql.LevelReadCommitted: + queryStr += " ISOLATION LEVEL READ COMMITTED" + case sql.LevelSerializable: + queryStr += " ISOLATION LEVEL SERIALIZABLE" + case sql.LevelRepeatableRead: + queryStr += " ISOLATION LEVEL REPEATABLE READ" + case sql.LevelDefault: + break + default: + return nil, fmt.Errorf("unsupported transaction isolation level: %s", sql.IsolationLevel(opts.Isolation)) + } + + if opts.ReadOnly { + queryStr += " READ ONLY" + } else { + queryStr += " READ WRITE" + } + + stmt, err := c.PrepareContext(ctx, queryStr) + + if err != nil { + return nil, err + } + + _, err = stmt.Exec([]driver.Value{}) + + if err != nil { + return nil, err + } + + res := &tx{ + conn: c, + context: ctx, + } + + return res, nil +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 6b62f63e..f7649475 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -218,6 +218,12 @@ "revision": "3a8122e6a950185a58d8d6a909450a575602becd", "revisionTime": "2019-11-14T08:59:18Z" }, + { + "checksumSHA1": "UT39wTV1Wdmhnv2kbGEiz53uRF8=", + "path": "github.com/vertica/vertica-sql-go", + "revision": "e3abe879e920984fe910ae9b884d1c064ee19f6f", + "revisionTime": "2020-05-02T01:25:01Z" + }, { "checksumSHA1": "UDvj5huw3BaGehfVRCB1UGQAtP4=", "path": "golang.org/x/crypto/md4",