From aa2915b62ae26d543e42ca9b536233ef9b1bda8e Mon Sep 17 00:00:00 2001 From: Mario Castro Date: Tue, 5 May 2020 15:43:27 +0200 Subject: [PATCH] Cherry-pick #17862 to 7.x: [Metricbeat] Update MSSQL module to use latest library version and fix release tags (#18237) --- CHANGELOG.next.asciidoc | 1 + NOTICE.txt | 11 +- go.mod | 2 +- go.sum | 7 +- metricbeat/docs/modules/mssql.asciidoc | 5 +- .../docs/modules/mssql/performance.asciidoc | 2 - .../modules/mssql/transaction_log.asciidoc | 2 - metricbeat/docs/modules_list.asciidoc | 4 +- .../denisenkom/go-mssqldb/README.md | 102 ++- .../go-mssqldb/accesstokenconnector.go | 51 ++ .../denisenkom/go-mssqldb/appveyor.yml | 6 +- .../github.com/denisenkom/go-mssqldb/buf.go | 24 +- .../denisenkom/go-mssqldb/bulkcopy.go | 117 +-- .../denisenkom/go-mssqldb/conn_str.go | 471 ++++++++++++ .../denisenkom/go-mssqldb/decimal.go | 131 ---- .../github.com/denisenkom/go-mssqldb/go.mod | 8 + .../github.com/denisenkom/go-mssqldb/go.sum | 5 + .../go-mssqldb/internal/decimal/decimal.go | 252 ++++++ .../{ => internal/querytext}/parser.go | 12 +- .../github.com/denisenkom/go-mssqldb/mssql.go | 90 +-- .../denisenkom/go-mssqldb/mssql_go110.go | 10 +- .../denisenkom/go-mssqldb/mssql_go110pre.go | 31 + .../denisenkom/go-mssqldb/mssql_go19.go | 27 +- .../github.com/denisenkom/go-mssqldb/net.go | 145 +++- .../github.com/denisenkom/go-mssqldb/ntlm.go | 178 ++++- .../github.com/denisenkom/go-mssqldb/tds.go | 726 +++++------------- .../github.com/denisenkom/go-mssqldb/token.go | 47 +- .../denisenkom/go-mssqldb/tvp_go19.go | 231 ++++++ .../github.com/denisenkom/go-mssqldb/types.go | 32 +- .../denisenkom/go-mssqldb/uniqueidentifier.go | 6 + .../golang-sql/civil/CONTRIBUTING.md | 73 ++ vendor/github.com/golang-sql/civil/LICENSE | 202 +++++ vendor/github.com/golang-sql/civil/README.md | 15 + .../golang-sql}/civil/civil.go | 0 vendor/modules.txt | 7 +- .../module/mssql/_meta/docs.asciidoc | 6 +- x-pack/metricbeat/module/mssql/fields.go | 2 +- .../module/mssql/performance/_meta/data.json | 37 +- .../module/mssql/performance/_meta/fields.yml | 2 +- .../performance/data_integration_test.go | 23 +- .../module/mssql/performance/performance.go | 71 +- .../performance_integration_test.go | 2 +- .../mssql/transaction_log/_meta/data.json | 21 +- .../mssql/transaction_log/_meta/fields.yml | 2 +- .../transaction_log/data_integration_test.go | 8 +- .../mssql/transaction_log/transaction_log.go | 7 +- 46 files changed, 2242 insertions(+), 972 deletions(-) create mode 100644 vendor/github.com/denisenkom/go-mssqldb/accesstokenconnector.go create mode 100644 vendor/github.com/denisenkom/go-mssqldb/conn_str.go delete mode 100644 vendor/github.com/denisenkom/go-mssqldb/decimal.go create mode 100644 vendor/github.com/denisenkom/go-mssqldb/go.mod create mode 100644 vendor/github.com/denisenkom/go-mssqldb/go.sum create mode 100644 vendor/github.com/denisenkom/go-mssqldb/internal/decimal/decimal.go rename vendor/github.com/denisenkom/go-mssqldb/{ => internal/querytext}/parser.go (89%) create mode 100644 vendor/github.com/denisenkom/go-mssqldb/mssql_go110pre.go create mode 100644 vendor/github.com/denisenkom/go-mssqldb/tvp_go19.go create mode 100644 vendor/github.com/golang-sql/civil/CONTRIBUTING.md create mode 100644 vendor/github.com/golang-sql/civil/LICENSE create mode 100644 vendor/github.com/golang-sql/civil/README.md rename vendor/{cloud.google.com/go => github.com/golang-sql}/civil/civil.go (100%) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 18954a9a08d..3cafc632a59 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -448,6 +448,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Add static mapping for metricsets under aws module. {pull}17614[17614] {pull}17650[17650] - Collect new `bulk` indexing metrics from Elasticsearch when `xpack.enabled:true` is set. {issue} {pull}17992[17992] - Remove requirement to connect as sysdba in Oracle module {issue}15846[15846] {pull}18182[18182] +- Update MSSQL module to fix some SSPI authentication and add brackets to USE statements {pull}17862[17862]] *Packetbeat* diff --git a/NOTICE.txt b/NOTICE.txt index a8f70e8c42d..95b9ae8a8c6 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1158,7 +1158,7 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -------------------------------------------------------------------- Dependency: github.com/denisenkom/go-mssqldb -Revision: 4e0d7dc8888f +Revision: bbfc9a55622e License type (autodetected): BSD-3-Clause ./vendor/github.com/denisenkom/go-mssqldb/LICENSE.txt: -------------------------------------------------------------------- @@ -2829,6 +2829,15 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-------------------------------------------------------------------- +Dependency: github.com/golang-sql/civil +Revision: cb61b32ac6fe +License type (autodetected): Apache-2.0 +./vendor/github.com/golang-sql/civil/LICENSE: +-------------------------------------------------------------------- +Apache License 2.0 + + -------------------------------------------------------------------- Dependency: github.com/golang/groupcache Revision: 215e87163ea7 diff --git a/go.mod b/go.mod index c1e20b023ce..9ed97411f35 100644 --- a/go.mod +++ b/go.mod @@ -42,7 +42,7 @@ require ( github.com/coreos/go-systemd/v22 v22.0.0 github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea github.com/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892 // indirect - github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f + github.com/denisenkom/go-mssqldb v0.0.0-20200206145737-bbfc9a55622e github.com/devigned/tab v0.1.2-0.20190607222403-0c15cf42f9a2 // indirect github.com/dgrijalva/jwt-go v3.2.1-0.20190620180102-5e25c22bd5d6+incompatible // indirect github.com/digitalocean/go-libvirt v0.0.0-20180301200012-6075ea3c39a1 diff --git a/go.sum b/go.sum index 420419809b5..1a4a0510371 100644 --- a/go.sum +++ b/go.sum @@ -183,8 +183,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892 h1:qg9VbHo1TlL0KDM0vYvBG9EY0X0Yku5WYIPoFWt8f6o= github.com/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892/go.mod h1:CTDl0pzVzE5DEzZhPfvhY/9sPFMQIxaJ9VAMs9AagrE= -github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f h1:WH0w/R4Yoey+04HhFxqZ6VX6I0d7RMyw5aXQ9UTvQPs= -github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc= +github.com/denisenkom/go-mssqldb v0.0.0-20200206145737-bbfc9a55622e h1:LzwWXEScfcTu7vUZNlDDWDARoSGEtvlDKK2BYHowNeE= +github.com/denisenkom/go-mssqldb v0.0.0-20200206145737-bbfc9a55622e/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY= github.com/devigned/tab v0.1.2-0.20190607222403-0c15cf42f9a2 h1:6+hM8KeYKV0Z9EIINNqIEDyyIRAcNc2FW+/TUYNmWyw= github.com/devigned/tab v0.1.2-0.20190607222403-0c15cf42f9a2/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY= @@ -323,6 +323,8 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903 h1:LbsanbbD6LieFkXbj9YNNBupiGHJgFeLpO0j0Fza1h8= @@ -700,6 +702,7 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= diff --git a/metricbeat/docs/modules/mssql.asciidoc b/metricbeat/docs/modules/mssql.asciidoc index 15ad0f80113..a8a5aa2ba28 100644 --- a/metricbeat/docs/modules/mssql.asciidoc +++ b/metricbeat/docs/modules/mssql.asciidoc @@ -8,7 +8,7 @@ This file is generated! See scripts/mage/docs_collector.go beta[] -This is the https://www.microsoft.com/en-us/sql-server/sql-server-2017[Microsoft SQL 2017] Metricbeat module. It is still in beta and under active development to add new Metricsets and introduce enhancements. +This is the https://www.microsoft.com/en-us/sql-server/sql-server-2017[Microsoft SQL 2017] Metricbeat module. It is still under active development to add new Metricsets and introduce enhancements. [float] === Compatibility @@ -33,7 +33,7 @@ The following Metricsets are already included: [float] === Module-specific configuration notes -When configuring the `hosts` option, you can specify native user credentials +When configuring the `hosts` option, you can specify native user credentials as part of the host string with the following format: ---- @@ -57,6 +57,7 @@ metricbeat.modules: Store sensitive values like passwords in the <>. + [float] === Example configuration diff --git a/metricbeat/docs/modules/mssql/performance.asciidoc b/metricbeat/docs/modules/mssql/performance.asciidoc index 214ae0455fa..b21411b5a60 100644 --- a/metricbeat/docs/modules/mssql/performance.asciidoc +++ b/metricbeat/docs/modules/mssql/performance.asciidoc @@ -5,8 +5,6 @@ This file is generated! See scripts/mage/docs_collector.go [[metricbeat-metricset-mssql-performance]] === MSSQL performance metricset -beta[] - include::../../../../x-pack/metricbeat/module/mssql/performance/_meta/docs.asciidoc[] This is a default metricset. If the host module is unconfigured, this metricset is enabled by default. diff --git a/metricbeat/docs/modules/mssql/transaction_log.asciidoc b/metricbeat/docs/modules/mssql/transaction_log.asciidoc index cf0bb4f35eb..63bf00583c4 100644 --- a/metricbeat/docs/modules/mssql/transaction_log.asciidoc +++ b/metricbeat/docs/modules/mssql/transaction_log.asciidoc @@ -5,8 +5,6 @@ This file is generated! See scripts/mage/docs_collector.go [[metricbeat-metricset-mssql-transaction_log]] === MSSQL transaction_log metricset -beta[] - include::../../../../x-pack/metricbeat/module/mssql/transaction_log/_meta/docs.asciidoc[] This is a default metricset. If the host module is unconfigured, this metricset is enabled by default. diff --git a/metricbeat/docs/modules_list.asciidoc b/metricbeat/docs/modules_list.asciidoc index 2215dd901eb..77b12f41c6e 100644 --- a/metricbeat/docs/modules_list.asciidoc +++ b/metricbeat/docs/modules_list.asciidoc @@ -182,8 +182,8 @@ This file is generated! See scripts/mage/docs_collector.go |<> |<> |<> beta[] |image:./images/icon-yes.png[Prebuilt dashboards are available] | -.2+| .2+| |<> beta[] -|<> beta[] +.2+| .2+| |<> +|<> |<> |image:./images/icon-no.png[No prebuilt dashboards] | .1+| .1+| |<> |<> |image:./images/icon-yes.png[Prebuilt dashboards are available] | diff --git a/vendor/github.com/denisenkom/go-mssqldb/README.md b/vendor/github.com/denisenkom/go-mssqldb/README.md index ea06b900a8f..46e4eeabb5a 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/README.md +++ b/vendor/github.com/denisenkom/go-mssqldb/README.md @@ -56,7 +56,7 @@ Other supported formats are listed below. * `hostNameInCertificate` - Specifies the Common Name (CN) in the server certificate. Default value is the server host. * `ServerSPN` - The kerberos SPN (Service Principal Name) for the server. Default is MSSQLSvc/host:port. * `Workstation ID` - The workstation name (default is the host name) -* `ApplicationIntent` - Can be given the value `ReadOnly` to initiate a read-only connection to an Availability Group listener. +* `ApplicationIntent` - Can be given the value `ReadOnly` to initiate a read-only connection to an Availability Group listener. The `database` must be specified when connecting with `Application Intent` set to `ReadOnly`. ### The connection string can be specified in one of three formats: @@ -106,6 +106,26 @@ Other supported formats are listed below. * `odbc:server=localhost;user id=sa;password={foo{bar}` // Literal `{`, password is "foo{bar" * `odbc:server=localhost;user id=sa;password={foo}}bar}` // Escaped `} with `}}`, password is "foo}bar" +### Azure Active Directory authentication - preview + +The configuration of functionality might change in the future. + +Azure Active Directory (AAD) access tokens are relatively short lived and need to be +valid when a new connection is made. Authentication is supported using a callback func that +provides a fresh and valid token using a connector: +``` golang +conn, err := mssql.NewAccessTokenConnector( + "Server=test.database.windows.net;Database=testdb", + tokenProvider) +if err != nil { + // handle errors in DSN +} +db := sql.OpenDB(conn) +``` +Where `tokenProvider` is a function that returns a fresh access token or an error. None of these statements +actually trigger the retrieval of a token, this happens when the first statment is issued and a connection +is created. + ## Executing Stored Procedures To run a stored procedure, set the query text to the procedure name: @@ -117,6 +137,66 @@ _, err := db.ExecContext(ctx, "sp_RunMe", ) ``` +## Reading Output Parameters from a Stored Procedure with Resultset + +To read output parameters from a stored procedure with resultset, make sure you read all the rows before reading the output parameters: +```go +sqltextcreate := ` +CREATE PROCEDURE spwithoutputandrows + @bitparam BIT OUTPUT +AS BEGIN + SET @bitparam = 1 + SELECT 'Row 1' +END +` +var bitout int64 +rows, err := db.QueryContext(ctx, "spwithoutputandrows", sql.Named("bitparam", sql.Out{Dest: &bitout})) +var strrow string +for rows.Next() { + err = rows.Scan(&strrow) +} +fmt.Printf("bitparam is %d", bitout) +``` + +## Caveat for local temporary tables + +Due to protocol limitations, temporary tables will only be allocated on the connection +as a result of executing a query with zero parameters. The following query +will, due to the use of a parameter, execute in its own session, +and `#mytemp` will be de-allocated right away: + +```go +conn, err := pool.Conn(ctx) +defer conn.Close() +_, err := conn.ExecContext(ctx, "select @p1 as x into #mytemp", 1) +// at this point #mytemp is already dropped again as the session of the ExecContext is over +``` + +To work around this, always explicitly create the local temporary +table in a query without any parameters. As a special case, the driver +will then be able to execute the query directly on the +connection-scoped session. The following example works: + +```go +conn, err := pool.Conn(ctx) + +// Set us up so that temp table is always cleaned up, since conn.Close() +// merely returns conn to pool, rather than actually closing the connection. +defer func() { + _, _ = conn.ExecContext(ctx, "drop table #mytemp") // always clean up + conn.Close() // merely returns conn to pool +}() + + +// Since we not pass any parameters below, the query will execute on the scope of +// the connection and succeed in creating the table. +_, err := conn.ExecContext(ctx, "create table #mytemp ( x int )") + +// #mytemp is now available even if you pass parameters +_, err := conn.ExecContext(ctx, "insert into #mytemp (x) values (@p1)", 1) + +``` + ## Return Status To get the procedure return status, pass into the parameters a @@ -127,6 +207,19 @@ _, err := db.ExecContext(ctx, "theproc", &rs) log.Printf("status=%d", rs) ``` +or + +``` +var rs mssql.ReturnStatus +_, err := db.QueryContext(ctx, "theproc", &rs) +for rows.Next() { + err = rows.Scan(&val) +} +log.Printf("status=%d", rs) +``` + +Limitation: ReturnStatus cannot be retrieved using `QueryRow`. + ## Parameters The `sqlserver` driver uses normal MS SQL Server syntax and expects parameters in @@ -147,9 +240,10 @@ are supported: * time.Time -> datetimeoffset or datetime (TDS version dependent) * mssql.DateTime1 -> datetime * mssql.DateTimeOffset -> datetimeoffset - * "cloud.google.com/go/civil".Date -> date - * "cloud.google.com/go/civil".DateTime -> datetime2 - * "cloud.google.com/go/civil".Time -> time + * "github.com/golang-sql/civil".Date -> date + * "github.com/golang-sql/civil".DateTime -> datetime2 + * "github.com/golang-sql/civil".Time -> time + * mssql.TVP -> Table Value Parameter (TDS version dependent) ## Important Notes diff --git a/vendor/github.com/denisenkom/go-mssqldb/accesstokenconnector.go b/vendor/github.com/denisenkom/go-mssqldb/accesstokenconnector.go new file mode 100644 index 00000000000..8dbe5099e49 --- /dev/null +++ b/vendor/github.com/denisenkom/go-mssqldb/accesstokenconnector.go @@ -0,0 +1,51 @@ +// +build go1.10 + +package mssql + +import ( + "context" + "database/sql/driver" + "errors" + "fmt" +) + +var _ driver.Connector = &accessTokenConnector{} + +// accessTokenConnector wraps Connector and injects a +// fresh access token when connecting to the database +type accessTokenConnector struct { + Connector + + accessTokenProvider func() (string, error) +} + +// NewAccessTokenConnector creates a new connector from a DSN and a token provider. +// The token provider func will be called when a new connection is requested and should return a valid access token. +// The returned connector may be used with sql.OpenDB. +func NewAccessTokenConnector(dsn string, tokenProvider func() (string, error)) (driver.Connector, error) { + if tokenProvider == nil { + return nil, errors.New("mssql: tokenProvider cannot be nil") + } + + conn, err := NewConnector(dsn) + if err != nil { + return nil, err + } + + c := &accessTokenConnector{ + Connector: *conn, + accessTokenProvider: tokenProvider, + } + return c, nil +} + +// Connect returns a new database connection +func (c *accessTokenConnector) Connect(ctx context.Context) (driver.Conn, error) { + var err error + c.Connector.params.fedAuthAccessToken, err = c.accessTokenProvider() + if err != nil { + return nil, fmt.Errorf("mssql: error retrieving access token: %+v", err) + } + + return c.Connector.Connect(ctx) +} diff --git a/vendor/github.com/denisenkom/go-mssqldb/appveyor.yml b/vendor/github.com/denisenkom/go-mssqldb/appveyor.yml index 2ae5456d5cb..c4d2bb060ee 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/appveyor.yml +++ b/vendor/github.com/denisenkom/go-mssqldb/appveyor.yml @@ -10,7 +10,7 @@ environment: SQLUSER: sa SQLPASSWORD: Password12! DATABASE: test - GOVERSION: 110 + GOVERSION: 111 matrix: - GOVERSION: 18 SQLINSTANCE: SQL2016 @@ -18,6 +18,8 @@ environment: SQLINSTANCE: SQL2016 - GOVERSION: 110 SQLINSTANCE: SQL2016 + - GOVERSION: 111 + SQLINSTANCE: SQL2016 - SQLINSTANCE: SQL2014 - SQLINSTANCE: SQL2012SP1 - SQLINSTANCE: SQL2008R2SP2 @@ -27,7 +29,7 @@ install: - set PATH=%GOPATH%\bin;%GOROOT%\bin;%PATH% - go version - go env - - go get -u cloud.google.com/go/civil + - go get -u github.com/golang-sql/civil build_script: - go build diff --git a/vendor/github.com/denisenkom/go-mssqldb/buf.go b/vendor/github.com/denisenkom/go-mssqldb/buf.go index 927d75d1b78..ba39b40f173 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/buf.go +++ b/vendor/github.com/denisenkom/go-mssqldb/buf.go @@ -221,23 +221,27 @@ func (r *tdsBuffer) uint16() uint16 { } func (r *tdsBuffer) BVarChar() string { - l := int(r.byte()) - return r.readUcs2(l) + return readBVarCharOrPanic(r) } -func (r *tdsBuffer) UsVarChar() string { - l := int(r.uint16()) - return r.readUcs2(l) +func readBVarCharOrPanic(r io.Reader) string { + s, err := readBVarChar(r) + if err != nil { + badStreamPanic(err) + } + return s } -func (r *tdsBuffer) readUcs2(numchars int) string { - b := make([]byte, numchars*2) - r.ReadFull(b) - res, err := ucs22str(b) +func readUsVarCharOrPanic(r io.Reader) string { + s, err := readUsVarChar(r) if err != nil { badStreamPanic(err) } - return res + return s +} + +func (r *tdsBuffer) UsVarChar() string { + return readUsVarCharOrPanic(r) } func (r *tdsBuffer) Read(buf []byte) (copied int, err error) { diff --git a/vendor/github.com/denisenkom/go-mssqldb/bulkcopy.go b/vendor/github.com/denisenkom/go-mssqldb/bulkcopy.go index 3b319af893f..1d5eacb3818 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/bulkcopy.go +++ b/vendor/github.com/denisenkom/go-mssqldb/bulkcopy.go @@ -7,9 +7,10 @@ import ( "fmt" "math" "reflect" - "strconv" "strings" "time" + + "github.com/denisenkom/go-mssqldb/internal/decimal" ) type Bulk struct { @@ -42,6 +43,11 @@ type BulkOptions struct { type DataValue interface{} +const ( + sqlDateFormat = "2006-01-02" + sqlTimeFormat = "2006-01-02 15:04:05.999999999Z07:00" +) + func (cn *Conn) CreateBulk(table string, columns []string) (_ *Bulk) { b := Bulk{ctx: context.Background(), cn: cn, tablename: table, headerSent: false, columnsName: columns} b.Debug = false @@ -334,7 +340,7 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error) case int64: intvalue = val default: - err = fmt.Errorf("mssql: invalid type for int column") + err = fmt.Errorf("mssql: invalid type for int column: %T", val) return } @@ -361,7 +367,7 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error) case int64: floatvalue = float64(val) default: - err = fmt.Errorf("mssql: invalid type for float column: %s", val) + err = fmt.Errorf("mssql: invalid type for float column: %T %s", val, val) return } @@ -380,7 +386,7 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error) case []byte: res.buffer = val default: - err = fmt.Errorf("mssql: invalid type for nvarchar column: %s", val) + err = fmt.Errorf("mssql: invalid type for nvarchar column: %T %s", val, val) return } res.ti.Size = len(res.buffer) @@ -392,14 +398,14 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error) case []byte: res.buffer = val default: - err = fmt.Errorf("mssql: invalid type for varchar column: %s", val) + err = fmt.Errorf("mssql: invalid type for varchar column: %T %s", val, val) return } res.ti.Size = len(res.buffer) case typeBit, typeBitN: if reflect.TypeOf(val).Kind() != reflect.Bool { - err = fmt.Errorf("mssql: invalid type for bit column: %s", val) + err = fmt.Errorf("mssql: invalid type for bit column: %T %s", val, val) return } res.ti.TypeId = typeBitN @@ -413,18 +419,31 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error) case time.Time: res.buffer = encodeDateTime2(val, int(col.ti.Scale)) res.ti.Size = len(res.buffer) + case string: + var t time.Time + if t, err = time.Parse(sqlTimeFormat, val); err != nil { + return res, fmt.Errorf("bulk: unable to convert string to date: %v", err) + } + res.buffer = encodeDateTime2(t, int(col.ti.Scale)) + res.ti.Size = len(res.buffer) default: - err = fmt.Errorf("mssql: invalid type for datetime2 column: %s", val) + err = fmt.Errorf("mssql: invalid type for datetime2 column: %T %s", val, val) return } case typeDateTimeOffsetN: switch val := val.(type) { case time.Time: - res.buffer = encodeDateTimeOffset(val, int(res.ti.Scale)) + res.buffer = encodeDateTimeOffset(val, int(col.ti.Scale)) + res.ti.Size = len(res.buffer) + case string: + var t time.Time + if t, err = time.Parse(sqlTimeFormat, val); err != nil { + return res, fmt.Errorf("bulk: unable to convert string to date: %v", err) + } + res.buffer = encodeDateTimeOffset(t, int(col.ti.Scale)) res.ti.Size = len(res.buffer) - default: - err = fmt.Errorf("mssql: invalid type for datetimeoffset column: %s", val) + err = fmt.Errorf("mssql: invalid type for datetimeoffset column: %T %s", val, val) return } case typeDateN: @@ -432,69 +451,79 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error) case time.Time: res.buffer = encodeDate(val) res.ti.Size = len(res.buffer) + case string: + var t time.Time + if t, err = time.ParseInLocation(sqlDateFormat, val, time.UTC); err != nil { + return res, fmt.Errorf("bulk: unable to convert string to date: %v", err) + } + res.buffer = encodeDate(t) + res.ti.Size = len(res.buffer) default: - err = fmt.Errorf("mssql: invalid type for date column: %s", val) + err = fmt.Errorf("mssql: invalid type for date column: %T %s", val, val) return } case typeDateTime, typeDateTimeN, typeDateTim4: + var t time.Time switch val := val.(type) { case time.Time: - if col.ti.Size == 4 { - res.buffer = encodeDateTim4(val) - res.ti.Size = len(res.buffer) - } else if col.ti.Size == 8 { - res.buffer = encodeDateTime(val) - res.ti.Size = len(res.buffer) - } else { - err = fmt.Errorf("mssql: invalid size of column") + t = val + case string: + if t, err = time.Parse(sqlTimeFormat, val); err != nil { + return res, fmt.Errorf("bulk: unable to convert string to date: %v", err) } - default: - err = fmt.Errorf("mssql: invalid type for datetime column: %s", val) + err = fmt.Errorf("mssql: invalid type for datetime column: %T %s", val, val) + return + } + + if col.ti.Size == 4 { + res.buffer = encodeDateTim4(t) + res.ti.Size = len(res.buffer) + } else if col.ti.Size == 8 { + res.buffer = encodeDateTime(t) + res.ti.Size = len(res.buffer) + } else { + err = fmt.Errorf("mssql: invalid size of column %d", col.ti.Size) } // case typeMoney, typeMoney4, typeMoneyN: case typeDecimal, typeDecimalN, typeNumeric, typeNumericN: - var value float64 + prec := col.ti.Prec + scale := col.ti.Scale + var dec decimal.Decimal switch v := val.(type) { case int: - value = float64(v) + dec = decimal.Int64ToDecimalScale(int64(v), 0) case int8: - value = float64(v) + dec = decimal.Int64ToDecimalScale(int64(v), 0) case int16: - value = float64(v) + dec = decimal.Int64ToDecimalScale(int64(v), 0) case int32: - value = float64(v) + dec = decimal.Int64ToDecimalScale(int64(v), 0) case int64: - value = float64(v) + dec = decimal.Int64ToDecimalScale(int64(v), 0) case float32: - value = float64(v) + dec, err = decimal.Float64ToDecimalScale(float64(v), scale) case float64: - value = v + dec, err = decimal.Float64ToDecimalScale(float64(v), scale) case string: - if value, err = strconv.ParseFloat(v, 64); err != nil { - return res, fmt.Errorf("bulk: unable to convert string to float: %v", err) - } + dec, err = decimal.StringToDecimalScale(v, scale) default: - return res, fmt.Errorf("unknown value for decimal: %#v", v) + return res, fmt.Errorf("unknown value for decimal: %T %#v", v, v) } - perc := col.ti.Prec - scale := col.ti.Scale - var dec Decimal - dec, err = Float64ToDecimalScale(value, scale) if err != nil { return res, err } - dec.prec = perc + dec.SetPrec(prec) var length byte switch { - case perc <= 9: + case prec <= 9: length = 4 - case perc <= 19: + case prec <= 19: length = 8 - case perc <= 28: + case prec <= 28: length = 12 default: length = 16 @@ -504,7 +533,7 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error) // first byte length written by typeInfo.writer res.ti.Size = int(length) + 1 // second byte sign - if value < 0 { + if !dec.IsPositive() { buf[0] = 0 } else { buf[0] = 1 @@ -527,7 +556,7 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error) res.ti.Size = len(val) res.buffer = val default: - err = fmt.Errorf("mssql: invalid type for Binary column: %s", val) + err = fmt.Errorf("mssql: invalid type for Binary column: %T %s", val, val) return } case typeGuid: @@ -536,7 +565,7 @@ func (b *Bulk) makeParam(val DataValue, col columnStruct) (res param, err error) res.ti.Size = len(val) res.buffer = val default: - err = fmt.Errorf("mssql: invalid type for Guid column: %s", val) + err = fmt.Errorf("mssql: invalid type for Guid column: %T %s", val, val) return } diff --git a/vendor/github.com/denisenkom/go-mssqldb/conn_str.go b/vendor/github.com/denisenkom/go-mssqldb/conn_str.go new file mode 100644 index 00000000000..26ac50f38df --- /dev/null +++ b/vendor/github.com/denisenkom/go-mssqldb/conn_str.go @@ -0,0 +1,471 @@ +package mssql + +import ( + "fmt" + "net" + "net/url" + "os" + "strconv" + "strings" + "time" + "unicode" +) + +const defaultServerPort = 1433 + +type connectParams struct { + logFlags uint64 + port uint64 + host string + instance string + database string + user string + password string + dial_timeout time.Duration + conn_timeout time.Duration + keepAlive time.Duration + encrypt bool + disableEncryption bool + trustServerCertificate bool + certificate string + hostInCertificate string + hostInCertificateProvided bool + serverSPN string + workstation string + appname string + typeFlags uint8 + failOverPartner string + failOverPort uint64 + packetSize uint16 + fedAuthAccessToken string +} + +func parseConnectParams(dsn string) (connectParams, error) { + var p connectParams + + var params map[string]string + if strings.HasPrefix(dsn, "odbc:") { + parameters, err := splitConnectionStringOdbc(dsn[len("odbc:"):]) + if err != nil { + return p, err + } + params = parameters + } else if strings.HasPrefix(dsn, "sqlserver://") { + parameters, err := splitConnectionStringURL(dsn) + if err != nil { + return p, err + } + params = parameters + } else { + params = splitConnectionString(dsn) + } + + strlog, ok := params["log"] + if ok { + var err error + p.logFlags, err = strconv.ParseUint(strlog, 10, 64) + if err != nil { + return p, fmt.Errorf("Invalid log parameter '%s': %s", strlog, err.Error()) + } + } + server := params["server"] + parts := strings.SplitN(server, `\`, 2) + p.host = parts[0] + if p.host == "." || strings.ToUpper(p.host) == "(LOCAL)" || p.host == "" { + p.host = "localhost" + } + if len(parts) > 1 { + p.instance = parts[1] + } + p.database = params["database"] + p.user = params["user id"] + p.password = params["password"] + + p.port = 0 + strport, ok := params["port"] + if ok { + var err error + p.port, err = strconv.ParseUint(strport, 10, 16) + if err != nil { + f := "Invalid tcp port '%v': %v" + return p, fmt.Errorf(f, strport, err.Error()) + } + } + + // https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/configure-the-network-packet-size-server-configuration-option + // Default packet size remains at 4096 bytes + p.packetSize = 4096 + strpsize, ok := params["packet size"] + if ok { + var err error + psize, err := strconv.ParseUint(strpsize, 0, 16) + if err != nil { + f := "Invalid packet size '%v': %v" + return p, fmt.Errorf(f, strpsize, err.Error()) + } + + // Ensure packet size falls within the TDS protocol range of 512 to 32767 bytes + // NOTE: Encrypted connections have a maximum size of 16383 bytes. If you request + // a higher packet size, the server will respond with an ENVCHANGE request to + // alter the packet size to 16383 bytes. + p.packetSize = uint16(psize) + if p.packetSize < 512 { + p.packetSize = 512 + } else if p.packetSize > 32767 { + p.packetSize = 32767 + } + } + + // https://msdn.microsoft.com/en-us/library/dd341108.aspx + // + // Do not set a connection timeout. Use Context to manage such things. + // Default to zero, but still allow it to be set. + if strconntimeout, ok := params["connection timeout"]; ok { + timeout, err := strconv.ParseUint(strconntimeout, 10, 64) + if err != nil { + f := "Invalid connection timeout '%v': %v" + return p, fmt.Errorf(f, strconntimeout, err.Error()) + } + p.conn_timeout = time.Duration(timeout) * time.Second + } + p.dial_timeout = 15 * time.Second + if strdialtimeout, ok := params["dial timeout"]; ok { + timeout, err := strconv.ParseUint(strdialtimeout, 10, 64) + if err != nil { + f := "Invalid dial timeout '%v': %v" + return p, fmt.Errorf(f, strdialtimeout, err.Error()) + } + p.dial_timeout = time.Duration(timeout) * time.Second + } + + // default keep alive should be 30 seconds according to spec: + // https://msdn.microsoft.com/en-us/library/dd341108.aspx + p.keepAlive = 30 * time.Second + if keepAlive, ok := params["keepalive"]; ok { + timeout, err := strconv.ParseUint(keepAlive, 10, 64) + if err != nil { + f := "Invalid keepAlive value '%s': %s" + return p, fmt.Errorf(f, keepAlive, err.Error()) + } + p.keepAlive = time.Duration(timeout) * time.Second + } + encrypt, ok := params["encrypt"] + if ok { + if strings.EqualFold(encrypt, "DISABLE") { + p.disableEncryption = true + } else { + var err error + p.encrypt, err = strconv.ParseBool(encrypt) + if err != nil { + f := "Invalid encrypt '%s': %s" + return p, fmt.Errorf(f, encrypt, err.Error()) + } + } + } else { + p.trustServerCertificate = true + } + trust, ok := params["trustservercertificate"] + if ok { + var err error + p.trustServerCertificate, err = strconv.ParseBool(trust) + if err != nil { + f := "Invalid trust server certificate '%s': %s" + return p, fmt.Errorf(f, trust, err.Error()) + } + } + p.certificate = params["certificate"] + p.hostInCertificate, ok = params["hostnameincertificate"] + if ok { + p.hostInCertificateProvided = true + } else { + p.hostInCertificate = p.host + p.hostInCertificateProvided = false + } + + serverSPN, ok := params["serverspn"] + if ok { + p.serverSPN = serverSPN + } else { + p.serverSPN = generateSpn(p.host, resolveServerPort(p.port)) + } + + workstation, ok := params["workstation id"] + if ok { + p.workstation = workstation + } else { + workstation, err := os.Hostname() + if err == nil { + p.workstation = workstation + } + } + + appname, ok := params["app name"] + if !ok { + appname = "go-mssqldb" + } + p.appname = appname + + appintent, ok := params["applicationintent"] + if ok { + if appintent == "ReadOnly" { + if p.database == "" { + return p, fmt.Errorf("Database must be specified when ApplicationIntent is ReadOnly") + } + p.typeFlags |= fReadOnlyIntent + } + } + + failOverPartner, ok := params["failoverpartner"] + if ok { + p.failOverPartner = failOverPartner + } + + failOverPort, ok := params["failoverport"] + if ok { + var err error + p.failOverPort, err = strconv.ParseUint(failOverPort, 0, 16) + if err != nil { + f := "Invalid tcp port '%v': %v" + return p, fmt.Errorf(f, failOverPort, err.Error()) + } + } + + return p, nil +} + +func splitConnectionString(dsn string) (res map[string]string) { + res = map[string]string{} + parts := strings.Split(dsn, ";") + for _, part := range parts { + if len(part) == 0 { + continue + } + lst := strings.SplitN(part, "=", 2) + name := strings.TrimSpace(strings.ToLower(lst[0])) + if len(name) == 0 { + continue + } + var value string = "" + if len(lst) > 1 { + value = strings.TrimSpace(lst[1]) + } + res[name] = value + } + return res +} + +// Splits a URL of the form sqlserver://username:password@host/instance?param1=value¶m2=value +func splitConnectionStringURL(dsn string) (map[string]string, error) { + res := map[string]string{} + + u, err := url.Parse(dsn) + if err != nil { + return res, err + } + + if u.Scheme != "sqlserver" { + return res, fmt.Errorf("scheme %s is not recognized", u.Scheme) + } + + if u.User != nil { + res["user id"] = u.User.Username() + p, exists := u.User.Password() + if exists { + res["password"] = p + } + } + + host, port, err := net.SplitHostPort(u.Host) + if err != nil { + host = u.Host + } + + if len(u.Path) > 0 { + res["server"] = host + "\\" + u.Path[1:] + } else { + res["server"] = host + } + + if len(port) > 0 { + res["port"] = port + } + + query := u.Query() + for k, v := range query { + if len(v) > 1 { + return res, fmt.Errorf("key %s provided more than once", k) + } + res[strings.ToLower(k)] = v[0] + } + + return res, nil +} + +// Splits a URL in the ODBC format +func splitConnectionStringOdbc(dsn string) (map[string]string, error) { + res := map[string]string{} + + type parserState int + const ( + // Before the start of a key + parserStateBeforeKey parserState = iota + + // Inside a key + parserStateKey + + // Beginning of a value. May be bare or braced + parserStateBeginValue + + // Inside a bare value + parserStateBareValue + + // Inside a braced value + parserStateBracedValue + + // A closing brace inside a braced value. + // May be the end of the value or an escaped closing brace, depending on the next character + parserStateBracedValueClosingBrace + + // After a value. Next character should be a semicolon or whitespace. + parserStateEndValue + ) + + var state = parserStateBeforeKey + + var key string + var value string + + for i, c := range dsn { + switch state { + case parserStateBeforeKey: + switch { + case c == '=': + return res, fmt.Errorf("Unexpected character = at index %d. Expected start of key or semi-colon or whitespace.", i) + case !unicode.IsSpace(c) && c != ';': + state = parserStateKey + key += string(c) + } + + case parserStateKey: + switch c { + case '=': + key = normalizeOdbcKey(key) + state = parserStateBeginValue + + case ';': + // Key without value + key = normalizeOdbcKey(key) + res[key] = value + key = "" + value = "" + state = parserStateBeforeKey + + default: + key += string(c) + } + + case parserStateBeginValue: + switch { + case c == '{': + state = parserStateBracedValue + case c == ';': + // Empty value + res[key] = value + key = "" + state = parserStateBeforeKey + case unicode.IsSpace(c): + // Ignore whitespace + default: + state = parserStateBareValue + value += string(c) + } + + case parserStateBareValue: + if c == ';' { + res[key] = strings.TrimRightFunc(value, unicode.IsSpace) + key = "" + value = "" + state = parserStateBeforeKey + } else { + value += string(c) + } + + case parserStateBracedValue: + if c == '}' { + state = parserStateBracedValueClosingBrace + } else { + value += string(c) + } + + case parserStateBracedValueClosingBrace: + if c == '}' { + // Escaped closing brace + value += string(c) + state = parserStateBracedValue + continue + } + + // End of braced value + res[key] = value + key = "" + value = "" + + // This character is the first character past the end, + // so it needs to be parsed like the parserStateEndValue state. + state = parserStateEndValue + switch { + case c == ';': + state = parserStateBeforeKey + case unicode.IsSpace(c): + // Ignore whitespace + default: + return res, fmt.Errorf("Unexpected character %c at index %d. Expected semi-colon or whitespace.", c, i) + } + + case parserStateEndValue: + switch { + case c == ';': + state = parserStateBeforeKey + case unicode.IsSpace(c): + // Ignore whitespace + default: + return res, fmt.Errorf("Unexpected character %c at index %d. Expected semi-colon or whitespace.", c, i) + } + } + } + + switch state { + case parserStateBeforeKey: // Okay + case parserStateKey: // Unfinished key. Treat as key without value. + key = normalizeOdbcKey(key) + res[key] = value + case parserStateBeginValue: // Empty value + res[key] = value + case parserStateBareValue: + res[key] = strings.TrimRightFunc(value, unicode.IsSpace) + case parserStateBracedValue: + return res, fmt.Errorf("Unexpected end of braced value at index %d.", len(dsn)) + case parserStateBracedValueClosingBrace: // End of braced value + res[key] = value + case parserStateEndValue: // Okay + } + + return res, nil +} + +// Normalizes the given string as an ODBC-format key +func normalizeOdbcKey(s string) string { + return strings.ToLower(strings.TrimRightFunc(s, unicode.IsSpace)) +} + +func resolveServerPort(port uint64) uint64 { + if port == 0 { + return defaultServerPort + } + + return port +} + +func generateSpn(host string, port uint64) string { + return fmt.Sprintf("MSSQLSvc/%s:%d", host, port) +} diff --git a/vendor/github.com/denisenkom/go-mssqldb/decimal.go b/vendor/github.com/denisenkom/go-mssqldb/decimal.go deleted file mode 100644 index 372f64b4eb1..00000000000 --- a/vendor/github.com/denisenkom/go-mssqldb/decimal.go +++ /dev/null @@ -1,131 +0,0 @@ -package mssql - -import ( - "encoding/binary" - "errors" - "math" - "math/big" -) - -// http://msdn.microsoft.com/en-us/library/ee780893.aspx -type Decimal struct { - integer [4]uint32 - positive bool - prec uint8 - scale uint8 -} - -var scaletblflt64 [39]float64 - -func (d Decimal) ToFloat64() float64 { - val := float64(0) - for i := 3; i >= 0; i-- { - val *= 0x100000000 - val += float64(d.integer[i]) - } - if !d.positive { - val = -val - } - if d.scale != 0 { - val /= scaletblflt64[d.scale] - } - return val -} - -const autoScale = 100 - -func Float64ToDecimal(f float64) (Decimal, error) { - return Float64ToDecimalScale(f, autoScale) -} - -func Float64ToDecimalScale(f float64, scale uint8) (Decimal, error) { - var dec Decimal - if math.IsNaN(f) { - return dec, errors.New("NaN") - } - if math.IsInf(f, 0) { - return dec, errors.New("Infinity can't be converted to decimal") - } - dec.positive = f >= 0 - if !dec.positive { - f = math.Abs(f) - } - if f > 3.402823669209385e+38 { - return dec, errors.New("Float value is out of range") - } - dec.prec = 20 - var integer float64 - for dec.scale = 0; dec.scale <= scale; dec.scale++ { - integer = f * scaletblflt64[dec.scale] - _, frac := math.Modf(integer) - if frac == 0 && scale == autoScale { - break - } - } - for i := 0; i < 4; i++ { - mod := math.Mod(integer, 0x100000000) - integer -= mod - integer /= 0x100000000 - dec.integer[i] = uint32(mod) - } - return dec, nil -} - -func init() { - var acc float64 = 1 - for i := 0; i <= 38; i++ { - scaletblflt64[i] = acc - acc *= 10 - } -} - -func (d Decimal) BigInt() big.Int { - bytes := make([]byte, 16) - binary.BigEndian.PutUint32(bytes[0:4], d.integer[3]) - binary.BigEndian.PutUint32(bytes[4:8], d.integer[2]) - binary.BigEndian.PutUint32(bytes[8:12], d.integer[1]) - binary.BigEndian.PutUint32(bytes[12:16], d.integer[0]) - var x big.Int - x.SetBytes(bytes) - if !d.positive { - x.Neg(&x) - } - return x -} - -func (d Decimal) Bytes() []byte { - x := d.BigInt() - return scaleBytes(x.String(), d.scale) -} - -func (d Decimal) UnscaledBytes() []byte { - x := d.BigInt() - return x.Bytes() -} - -func scaleBytes(s string, scale uint8) []byte { - z := make([]byte, 0, len(s)+1) - if s[0] == '-' || s[0] == '+' { - z = append(z, byte(s[0])) - s = s[1:] - } - pos := len(s) - int(scale) - if pos <= 0 { - z = append(z, byte('0')) - } else if pos > 0 { - z = append(z, []byte(s[:pos])...) - } - if scale > 0 { - z = append(z, byte('.')) - for pos < 0 { - z = append(z, byte('0')) - pos++ - } - z = append(z, []byte(s[pos:])...) - } - return z -} - -func (d Decimal) String() string { - return string(d.Bytes()) -} diff --git a/vendor/github.com/denisenkom/go-mssqldb/go.mod b/vendor/github.com/denisenkom/go-mssqldb/go.mod new file mode 100644 index 00000000000..ebc02ab88f9 --- /dev/null +++ b/vendor/github.com/denisenkom/go-mssqldb/go.mod @@ -0,0 +1,8 @@ +module github.com/denisenkom/go-mssqldb + +go 1.11 + +require ( + github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe + golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c +) diff --git a/vendor/github.com/denisenkom/go-mssqldb/go.sum b/vendor/github.com/denisenkom/go-mssqldb/go.sum new file mode 100644 index 00000000000..1887801bbf4 --- /dev/null +++ b/vendor/github.com/denisenkom/go-mssqldb/go.sum @@ -0,0 +1,5 @@ +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/vendor/github.com/denisenkom/go-mssqldb/internal/decimal/decimal.go b/vendor/github.com/denisenkom/go-mssqldb/internal/decimal/decimal.go new file mode 100644 index 00000000000..7da0375d91d --- /dev/null +++ b/vendor/github.com/denisenkom/go-mssqldb/internal/decimal/decimal.go @@ -0,0 +1,252 @@ +package decimal + +import ( + "encoding/binary" + "errors" + "fmt" + "math" + "math/big" + "strings" +) + +// Decimal represents decimal type in the Microsoft Open Specifications: http://msdn.microsoft.com/en-us/library/ee780893.aspx +type Decimal struct { + integer [4]uint32 // Little-endian + positive bool + prec uint8 + scale uint8 +} + +var ( + scaletblflt64 [39]float64 + int10 big.Int + int1e5 big.Int +) + +func init() { + var acc float64 = 1 + for i := 0; i <= 38; i++ { + scaletblflt64[i] = acc + acc *= 10 + } + + int10.SetInt64(10) + int1e5.SetInt64(1e5) +} + +const autoScale = 100 + +// SetInteger sets the ind'th element in the integer array +func (d *Decimal) SetInteger(integer uint32, ind uint8) { + d.integer[ind] = integer +} + +// SetPositive sets the positive member +func (d *Decimal) SetPositive(positive bool) { + d.positive = positive +} + +// SetPrec sets the prec member +func (d *Decimal) SetPrec(prec uint8) { + d.prec = prec +} + +// SetScale sets the scale member +func (d *Decimal) SetScale(scale uint8) { + d.scale = scale +} + +// IsPositive returns true if the Decimal is positive +func (d *Decimal) IsPositive() bool { + return d.positive +} + +// ToFloat64 converts decimal to a float64 +func (d Decimal) ToFloat64() float64 { + val := float64(0) + for i := 3; i >= 0; i-- { + val *= 0x100000000 + val += float64(d.integer[i]) + } + if !d.positive { + val = -val + } + if d.scale != 0 { + val /= scaletblflt64[d.scale] + } + return val +} + +// BigInt converts decimal to a bigint +func (d Decimal) BigInt() big.Int { + bytes := make([]byte, 16) + binary.BigEndian.PutUint32(bytes[0:4], d.integer[3]) + binary.BigEndian.PutUint32(bytes[4:8], d.integer[2]) + binary.BigEndian.PutUint32(bytes[8:12], d.integer[1]) + binary.BigEndian.PutUint32(bytes[12:16], d.integer[0]) + var x big.Int + x.SetBytes(bytes) + if !d.positive { + x.Neg(&x) + } + return x +} + +// Bytes converts decimal to a scaled byte slice +func (d Decimal) Bytes() []byte { + x := d.BigInt() + return ScaleBytes(x.String(), d.scale) +} + +// UnscaledBytes converts decimal to a unscaled byte slice +func (d Decimal) UnscaledBytes() []byte { + x := d.BigInt() + return x.Bytes() +} + +// String converts decimal to a string +func (d Decimal) String() string { + return string(d.Bytes()) +} + +// Float64ToDecimal converts float64 to decimal +func Float64ToDecimal(f float64) (Decimal, error) { + return Float64ToDecimalScale(f, autoScale) +} + +// Float64ToDecimalScale converts float64 to decimal; user can specify the scale +func Float64ToDecimalScale(f float64, scale uint8) (Decimal, error) { + var dec Decimal + if math.IsNaN(f) { + return dec, errors.New("NaN") + } + if math.IsInf(f, 0) { + return dec, errors.New("Infinity can't be converted to decimal") + } + dec.positive = f >= 0 + if !dec.positive { + f = math.Abs(f) + } + if f > 3.402823669209385e+38 { + return dec, errors.New("Float value is out of range") + } + dec.prec = 20 + var integer float64 + for dec.scale = 0; dec.scale <= scale; dec.scale++ { + integer = f * scaletblflt64[dec.scale] + _, frac := math.Modf(integer) + if frac == 0 && scale == autoScale { + break + } + } + for i := 0; i < 4; i++ { + mod := math.Mod(integer, 0x100000000) + integer -= mod + integer /= 0x100000000 + dec.integer[i] = uint32(mod) + if mod-math.Trunc(mod) >= 0.5 { + dec.integer[i] = uint32(mod) + 1 + } + } + return dec, nil +} + +// Int64ToDecimalScale converts float64 to decimal; user can specify the scale +func Int64ToDecimalScale(v int64, scale uint8) Decimal { + positive := v >= 0 + if !positive { + if v == math.MinInt64 { + // Special case - can't negate + return Decimal{ + integer: [4]uint32{0, 0x80000000, 0, 0}, + positive: false, + prec: 20, + scale: 0, + } + } + v = -v + } + return Decimal{ + integer: [4]uint32{uint32(v), uint32(v >> 32), 0, 0}, + positive: positive, + prec: 20, + scale: scale, + } +} + +// StringToDecimalScale converts string to decimal +func StringToDecimalScale(v string, outScale uint8) (Decimal, error) { + var r big.Int + var unscaled string + var inScale int + + point := strings.LastIndexByte(v, '.') + if point == -1 { + inScale = 0 + unscaled = v + } else { + inScale = len(v) - point - 1 + unscaled = v[:point] + v[point+1:] + } + if inScale > math.MaxUint8 { + return Decimal{}, fmt.Errorf("can't parse %q as a decimal number: scale too large", v) + } + + _, ok := r.SetString(unscaled, 10) + if !ok { + return Decimal{}, fmt.Errorf("can't parse %q as a decimal number", v) + } + + if inScale > int(outScale) { + return Decimal{}, fmt.Errorf("can't parse %q as a decimal number: scale %d is larger than the scale %d of the target column", v, inScale, outScale) + } + for inScale < int(outScale) { + if int(outScale)-inScale >= 5 { + r.Mul(&r, &int1e5) + inScale += 5 + } else { + r.Mul(&r, &int10) + inScale++ + } + } + + bytes := r.Bytes() + if len(bytes) > 16 { + return Decimal{}, fmt.Errorf("can't parse %q as a decimal number: precision too large", v) + } + var out [4]uint32 + for i, b := range bytes { + pos := len(bytes) - i - 1 + out[pos/4] += uint32(b) << uint(pos%4*8) + } + return Decimal{ + integer: out, + positive: r.Sign() >= 0, + prec: 20, + scale: uint8(inScale), + }, nil +} + +// ScaleBytes converts a stringified decimal to a scaled byte slice +func ScaleBytes(s string, scale uint8) []byte { + z := make([]byte, 0, len(s)+1) + if s[0] == '-' || s[0] == '+' { + z = append(z, byte(s[0])) + s = s[1:] + } + pos := len(s) - int(scale) + if pos <= 0 { + z = append(z, byte('0')) + } else if pos > 0 { + z = append(z, []byte(s[:pos])...) + } + if scale > 0 { + z = append(z, byte('.')) + for pos < 0 { + z = append(z, byte('0')) + pos++ + } + z = append(z, []byte(s[pos:])...) + } + return z +} diff --git a/vendor/github.com/denisenkom/go-mssqldb/parser.go b/vendor/github.com/denisenkom/go-mssqldb/internal/querytext/parser.go similarity index 89% rename from vendor/github.com/denisenkom/go-mssqldb/parser.go rename to vendor/github.com/denisenkom/go-mssqldb/internal/querytext/parser.go index 8021ca603c9..14650e38944 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/parser.go +++ b/vendor/github.com/denisenkom/go-mssqldb/internal/querytext/parser.go @@ -1,4 +1,8 @@ -package mssql +// Package querytext is the old query parser and parameter substitute process. +// Do not use on new code. +// +// This package is not subject to any API compatibility guarantee. +package querytext import ( "bytes" @@ -40,7 +44,11 @@ func (p *parser) write(ch rune) { type stateFunc func(*parser) stateFunc -func parseParams(query string) (string, int) { +// ParseParams rewrites the query from using "?" placeholders +// to using "@pN" parameter names that SQL Server will accept. +// +// This function and package is not subject to any API compatibility guarantee. +func ParseParams(query string) (string, int) { p := &parser{ r: bytes.NewReader([]byte(query)), namedParams: map[string]bool{}, diff --git a/vendor/github.com/denisenkom/go-mssqldb/mssql.go b/vendor/github.com/denisenkom/go-mssqldb/mssql.go index aa173b3ed51..a74bc7e3fc4 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/mssql.go +++ b/vendor/github.com/denisenkom/go-mssqldb/mssql.go @@ -14,6 +14,8 @@ import ( "strings" "time" "unicode" + + "github.com/denisenkom/go-mssqldb/internal/querytext" ) // ReturnStatus may be used to return the return value from a proc. @@ -29,24 +31,19 @@ var driverInstanceNoProcess = &Driver{processQueryText: false} func init() { sql.Register("mssql", driverInstance) sql.Register("sqlserver", driverInstanceNoProcess) - createDialer = func(p *connectParams) dialer { - return tcpDialer{&net.Dialer{KeepAlive: p.keepAlive}} + createDialer = func(p *connectParams) Dialer { + return netDialer{&net.Dialer{KeepAlive: p.keepAlive}} } } -// Abstract the dialer for testing and for non-TCP based connections. -type dialer interface { - Dial(ctx context.Context, addr string) (net.Conn, error) -} - -var createDialer func(p *connectParams) dialer +var createDialer func(p *connectParams) Dialer -type tcpDialer struct { +type netDialer struct { nd *net.Dialer } -func (d tcpDialer) Dial(ctx context.Context, addr string) (net.Conn, error) { - return d.nd.DialContext(ctx, "tcp", addr) +func (d netDialer) DialContext(ctx context.Context, network string, addr string) (net.Conn, error) { + return d.nd.DialContext(ctx, network, addr) } type Driver struct { @@ -125,6 +122,21 @@ type Connector struct { // SessionInitSQL is optional. The session will be reset even if // SessionInitSQL is empty. SessionInitSQL string + + // Dialer sets a custom dialer for all network operations. + // If Dialer is not set, normal net dialers are used. + Dialer Dialer +} + +type Dialer interface { + DialContext(ctx context.Context, network string, addr string) (net.Conn, error) +} + +func (c *Connector) getDialer(p *connectParams) Dialer { + if c != nil && c.Dialer != nil { + return c.Dialer + } + return createDialer(p) } type Conn struct { @@ -310,12 +322,12 @@ func (d *Driver) open(ctx context.Context, dsn string) (*Conn, error) { if err != nil { return nil, err } - return d.connect(ctx, params) + return d.connect(ctx, nil, params) } // connect to the server, using the provided context for dialing only. -func (d *Driver) connect(ctx context.Context, params connectParams) (*Conn, error) { - sess, err := connect(ctx, d.log, params) +func (d *Driver) connect(ctx context.Context, c *Connector, params connectParams) (*Conn, error) { + sess, err := connect(ctx, c, d.log, params) if err != nil { // main server failed, try fail-over partner if params.failOverPartner == "" { @@ -327,7 +339,7 @@ func (d *Driver) connect(ctx context.Context, params connectParams) (*Conn, erro params.port = params.failOverPort } - sess, err = connect(ctx, d.log, params) + sess, err = connect(ctx, c, d.log, params) if err != nil { // fail-over partner also failed, now fail return nil, err @@ -335,12 +347,12 @@ func (d *Driver) connect(ctx context.Context, params connectParams) (*Conn, erro } conn := &Conn{ + connector: c, sess: sess, transactionCtx: context.Background(), processQueryText: d.processQueryText, connectionGood: true, } - conn.sess.log = d.log return conn, nil } @@ -375,7 +387,7 @@ func (c *Conn) Prepare(query string) (driver.Stmt, error) { func (c *Conn) prepareContext(ctx context.Context, query string) (*Stmt, error) { paramCount := -1 if c.processQueryText { - query, paramCount = parseParams(query) + query, paramCount = querytext.ParseParams(query) } return &Stmt{c, query, paramCount, nil}, nil } @@ -385,7 +397,10 @@ func (s *Stmt) Close() error { } func (s *Stmt) SetQueryNotification(id, options string, timeout time.Duration) { - to := uint32(timeout / time.Second) + // 2.2.5.3.1 Query Notifications Header + // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/e168d373-a7b7-41aa-b6ca-25985466a7e0 + // Timeout in milliseconds in TDS protocol. + to := uint32(timeout / time.Millisecond) if to < 1 { to = 1 } @@ -445,13 +460,13 @@ func (s *Stmt) sendQuery(args []namedValue) (err error) { var params []param if isProc(s.query) { proc.name = s.query - params, _, err = s.makeRPCParams(args, 0) + params, _, err = s.makeRPCParams(args, true) if err != nil { return } } else { var decls []string - params, decls, err = s.makeRPCParams(args, 2) + params, decls, err = s.makeRPCParams(args, false) if err != nil { return } @@ -523,8 +538,12 @@ func isProc(s string) bool { return true } -func (s *Stmt) makeRPCParams(args []namedValue, offset int) ([]param, []string, error) { +func (s *Stmt) makeRPCParams(args []namedValue, isProc bool) ([]param, []string, error) { var err error + var offset int + if !isProc { + offset = 2 + } params := make([]param, len(args)+offset) decls := make([]string, len(args)) for i, val := range args { @@ -535,7 +554,7 @@ func (s *Stmt) makeRPCParams(args []namedValue, offset int) ([]param, []string, var name string if len(val.Name) > 0 { name = "@" + val.Name - } else { + } else if !isProc { name = fmt.Sprintf("@p%d", val.Ordinal) } params[i+offset].Name = name @@ -597,11 +616,13 @@ loop: break loop case doneStruct: if token.isError() { + cancel() return nil, s.c.checkBadConn(token.getError()) } case ReturnStatus: s.c.setReturnStatus(token) case error: + cancel() return nil, s.c.checkBadConn(token) } } @@ -700,6 +721,8 @@ func (rc *Rows) Next(dest []driver.Value) error { if tokdata.isError() { return rc.stmt.c.checkBadConn(tokdata.getError()) } + case ReturnStatus: + rc.stmt.c.setReturnStatus(tokdata) case error: return rc.stmt.c.checkBadConn(tokdata) } @@ -858,29 +881,6 @@ func (r *Result) RowsAffected() (int64, error) { return r.rowsAffected, nil } -func (r *Result) LastInsertId() (int64, error) { - s, err := r.c.Prepare("select cast(@@identity as bigint)") - if err != nil { - return 0, err - } - defer s.Close() - rows, err := s.Query(nil) - if err != nil { - return 0, err - } - defer rows.Close() - dest := make([]driver.Value, 1) - err = rows.Next(dest) - if err != nil { - return 0, err - } - if dest[0] == nil { - return -1, errors.New("There is no generated identity value") - } - lastInsertId := dest[0].(int64) - return lastInsertId, nil -} - var _ driver.Pinger = &Conn{} // Ping is used to check if the remote server is available and satisfies the Pinger interface. diff --git a/vendor/github.com/denisenkom/go-mssqldb/mssql_go110.go b/vendor/github.com/denisenkom/go-mssqldb/mssql_go110.go index 3d5ab57a526..6d76fbad08f 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/mssql_go110.go +++ b/vendor/github.com/denisenkom/go-mssqldb/mssql_go110.go @@ -5,6 +5,7 @@ package mssql import ( "context" "database/sql/driver" + "errors" ) var _ driver.Connector = &Connector{} @@ -34,10 +35,7 @@ func (c *Conn) ResetSession(ctx context.Context) error { // Connect to the server and return a TDS connection. func (c *Connector) Connect(ctx context.Context) (driver.Conn, error) { - conn, err := c.driver.connect(ctx, c.params) - if conn != nil { - conn.connector = c - } + conn, err := c.driver.connect(ctx, c, c.params) if err == nil { err = conn.ResetSession(ctx) } @@ -48,3 +46,7 @@ func (c *Connector) Connect(ctx context.Context) (driver.Conn, error) { func (c *Connector) Driver() driver.Driver { return c.driver } + +func (r *Result) LastInsertId() (int64, error) { + return -1, errors.New("LastInsertId is not supported. Please use the OUTPUT clause or add `select ID = convert(bigint, SCOPE_IDENTITY())` to the end of your query.") +} diff --git a/vendor/github.com/denisenkom/go-mssqldb/mssql_go110pre.go b/vendor/github.com/denisenkom/go-mssqldb/mssql_go110pre.go new file mode 100644 index 00000000000..3baae0c4048 --- /dev/null +++ b/vendor/github.com/denisenkom/go-mssqldb/mssql_go110pre.go @@ -0,0 +1,31 @@ +// +build !go1.10 + +package mssql + +import ( + "database/sql/driver" + "errors" +) + +func (r *Result) LastInsertId() (int64, error) { + s, err := r.c.Prepare("select cast(@@identity as bigint)") + if err != nil { + return 0, err + } + defer s.Close() + rows, err := s.Query(nil) + if err != nil { + return 0, err + } + defer rows.Close() + dest := make([]driver.Value, 1) + err = rows.Next(dest) + if err != nil { + return 0, err + } + if dest[0] == nil { + return -1, errors.New("There is no generated identity value") + } + lastInsertId := dest[0].(int64) + return lastInsertId, nil +} diff --git a/vendor/github.com/denisenkom/go-mssqldb/mssql_go19.go b/vendor/github.com/denisenkom/go-mssqldb/mssql_go19.go index 65a11720da2..a2bd1167ba3 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/mssql_go19.go +++ b/vendor/github.com/denisenkom/go-mssqldb/mssql_go19.go @@ -11,7 +11,7 @@ import ( "time" // "github.com/cockroachdb/apd" - "cloud.google.com/go/civil" + "github.com/golang-sql/civil" ) // Type alias provided for compatibility. @@ -112,6 +112,8 @@ func (c *Conn) CheckNamedValue(nv *driver.NamedValue) error { *v = 0 // By default the return value should be zero. c.returnStatus = v return driver.ErrRemoveArgument + case TVP: + return nil default: var err error nv.Value, err = convertInputParameter(nv.Value) @@ -160,6 +162,29 @@ func (s *Stmt) makeParamExtra(val driver.Value) (res param, err error) { case sql.Out: res, err = s.makeParam(val.Dest) res.Flags = fByRevValue + case TVP: + err = val.check() + if err != nil { + return + } + schema, name, errGetName := getSchemeAndName(val.TypeName) + if errGetName != nil { + return + } + res.ti.UdtInfo.TypeName = name + res.ti.UdtInfo.SchemaName = schema + res.ti.TypeId = typeTvp + columnStr, tvpFieldIndexes, errCalTypes := val.columnTypes() + if errCalTypes != nil { + err = errCalTypes + return + } + res.buffer, err = val.encode(schema, name, columnStr, tvpFieldIndexes) + if err != nil { + return + } + res.ti.Size = len(res.buffer) + default: err = fmt.Errorf("mssql: unknown type for %T", val) } diff --git a/vendor/github.com/denisenkom/go-mssqldb/net.go b/vendor/github.com/denisenkom/go-mssqldb/net.go index e3864d1a222..94858cc74fe 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/net.go +++ b/vendor/github.com/denisenkom/go-mssqldb/net.go @@ -9,9 +9,6 @@ import ( type timeoutConn struct { c net.Conn timeout time.Duration - buf *tdsBuffer - packetPending bool - continueRead bool } func newTimeoutConn(conn net.Conn, timeout time.Duration) *timeoutConn { @@ -22,32 +19,6 @@ func newTimeoutConn(conn net.Conn, timeout time.Duration) *timeoutConn { } func (c *timeoutConn) Read(b []byte) (n int, err error) { - if c.buf != nil { - if c.packetPending { - c.packetPending = false - err = c.buf.FinishPacket() - if err != nil { - err = fmt.Errorf("Cannot send handshake packet: %s", err.Error()) - return - } - c.continueRead = false - } - if !c.continueRead { - var packet packetType - packet, err = c.buf.BeginRead() - if err != nil { - err = fmt.Errorf("Cannot read handshake packet: %s", err.Error()) - return - } - if packet != packPrelogin { - err = fmt.Errorf("unexpected packet %d, expecting prelogin", packet) - return - } - c.continueRead = true - } - n, err = c.buf.Read(b) - return - } if c.timeout > 0 { err = c.c.SetDeadline(time.Now().Add(c.timeout)) if err != nil { @@ -58,17 +29,6 @@ func (c *timeoutConn) Read(b []byte) (n int, err error) { } func (c *timeoutConn) Write(b []byte) (n int, err error) { - if c.buf != nil { - if !c.packetPending { - c.buf.BeginPacket(packPrelogin, false) - c.packetPending = true - } - n, err = c.buf.Write(b) - if err != nil { - return - } - return - } if c.timeout > 0 { err = c.c.SetDeadline(time.Now().Add(c.timeout)) if err != nil { @@ -101,3 +61,108 @@ func (c timeoutConn) SetReadDeadline(t time.Time) error { func (c timeoutConn) SetWriteDeadline(t time.Time) error { panic("Not implemented") } + +// this connection is used during TLS Handshake +// TDS protocol requires TLS handshake messages to be sent inside TDS packets +type tlsHandshakeConn struct { + buf *tdsBuffer + packetPending bool + continueRead bool +} + +func (c *tlsHandshakeConn) Read(b []byte) (n int, err error) { + if c.packetPending { + c.packetPending = false + err = c.buf.FinishPacket() + if err != nil { + err = fmt.Errorf("Cannot send handshake packet: %s", err.Error()) + return + } + c.continueRead = false + } + if !c.continueRead { + var packet packetType + packet, err = c.buf.BeginRead() + if err != nil { + err = fmt.Errorf("Cannot read handshake packet: %s", err.Error()) + return + } + if packet != packPrelogin { + err = fmt.Errorf("unexpected packet %d, expecting prelogin", packet) + return + } + c.continueRead = true + } + return c.buf.Read(b) +} + +func (c *tlsHandshakeConn) Write(b []byte) (n int, err error) { + if !c.packetPending { + c.buf.BeginPacket(packPrelogin, false) + c.packetPending = true + } + return c.buf.Write(b) +} + +func (c *tlsHandshakeConn) Close() error { + panic("Not implemented") +} + +func (c *tlsHandshakeConn) LocalAddr() net.Addr { + panic("Not implemented") +} + +func (c *tlsHandshakeConn) RemoteAddr() net.Addr { + panic("Not implemented") +} + +func (c *tlsHandshakeConn) SetDeadline(t time.Time) error { + panic("Not implemented") +} + +func (c *tlsHandshakeConn) SetReadDeadline(t time.Time) error { + panic("Not implemented") +} + +func (c *tlsHandshakeConn) SetWriteDeadline(t time.Time) error { + panic("Not implemented") +} + +// this connection just delegates all methods to it's wrapped connection +// it also allows switching underlying connection on the fly +// it is needed because tls.Conn does not allow switching underlying connection +type passthroughConn struct { + c net.Conn +} + +func (c passthroughConn) Read(b []byte) (n int, err error) { + return c.c.Read(b) +} + +func (c passthroughConn) Write(b []byte) (n int, err error) { + return c.c.Write(b) +} + +func (c passthroughConn) Close() error { + return c.c.Close() +} + +func (c passthroughConn) LocalAddr() net.Addr { + panic("Not implemented") +} + +func (c passthroughConn) RemoteAddr() net.Addr { + panic("Not implemented") +} + +func (c passthroughConn) SetDeadline(t time.Time) error { + panic("Not implemented") +} + +func (c passthroughConn) SetReadDeadline(t time.Time) error { + panic("Not implemented") +} + +func (c passthroughConn) SetWriteDeadline(t time.Time) error { + panic("Not implemented") +} diff --git a/vendor/github.com/denisenkom/go-mssqldb/ntlm.go b/vendor/github.com/denisenkom/go-mssqldb/ntlm.go index 7c0cc4f785c..3ba48ff8d2c 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/ntlm.go +++ b/vendor/github.com/denisenkom/go-mssqldb/ntlm.go @@ -4,11 +4,14 @@ package mssql import ( "crypto/des" + "crypto/hmac" "crypto/md5" "crypto/rand" "encoding/binary" "errors" + "fmt" "strings" + "time" "unicode/utf16" "golang.org/x/crypto/md4" @@ -198,86 +201,209 @@ func ntlmSessionResponse(clientNonce [8]byte, serverChallenge [8]byte, password return response(hash, passwordHash) } -func (auth *ntlmAuth) NextBytes(bytes []byte) ([]byte, error) { - if string(bytes[0:8]) != "NTLMSSP\x00" { - return nil, errorNTLM +func ntlmHashNoPadding(val string) []byte { + hash := make([]byte, 16) + h := md4.New() + h.Write(utf16le(val)) + h.Sum(hash[:0]) + + return hash +} + +func hmacMD5(passwordHash, data []byte) []byte { + hmacEntity := hmac.New(md5.New, passwordHash) + hmacEntity.Write(data) + + return hmacEntity.Sum(nil) +} + +func getNTLMv2AndLMv2ResponsePayloads(target, username, password string, challenge, nonce [8]byte, targetInfoFields []byte, timestamp time.Time) (ntlmV2Payload, lmV2Payload []byte) { + // NTLMv2 response payload: http://davenport.sourceforge.net/ntlm.html#theNtlmv2Response + + ntlmHash := ntlmHashNoPadding(password) + usernameAndTargetBytes := utf16le(strings.ToUpper(username) + target) + ntlmV2Hash := hmacMD5(ntlmHash, usernameAndTargetBytes) + targetInfoLength := len(targetInfoFields) + blob := make([]byte, 32+targetInfoLength) + binary.BigEndian.PutUint32(blob[:4], 0x01010000) + binary.BigEndian.PutUint32(blob[4:8], 0x00000000) + binary.BigEndian.PutUint64(blob[8:16], uint64(timestamp.UnixNano())) + copy(blob[16:24], nonce[:]) + binary.BigEndian.PutUint32(blob[24:28], 0x00000000) + copy(blob[28:], targetInfoFields) + binary.BigEndian.PutUint32(blob[28+targetInfoLength:], 0x00000000) + challengeLength := len(challenge) + blobLength := len(blob) + challengeAndBlob := make([]byte, challengeLength + blobLength) + copy(challengeAndBlob[:challengeLength], challenge[:]) + copy(challengeAndBlob[challengeLength:], blob) + hashedChallenge := hmacMD5(ntlmV2Hash, challengeAndBlob) + ntlmV2Payload = append(hashedChallenge, blob...) + + // LMv2 response payload: http://davenport.sourceforge.net/ntlm.html#theLmv2Response + ntlmV2hash := hmacMD5(ntlmHash, usernameAndTargetBytes) + challengeAndNonce := make([]byte, 16) + copy(challengeAndNonce[:8], challenge[:]) + copy(challengeAndNonce[8:], nonce[:]) + hashedChallenge = hmacMD5(ntlmV2hash, challengeAndNonce) + lmV2Payload = append(hashedChallenge, nonce[:]...) + + return +} + +func negotiateExtendedSessionSecurity(flags uint32, message []byte, challenge [8]byte, username, password string) (lm, nt []byte, err error) { + nonce := clientChallenge() + + // Official specification: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/b38c36ed-2804-4868-a9ff-8dd3182128e4 + // Unofficial walk through referenced by https://www.freetds.org/userguide/domains.htm: http://davenport.sourceforge.net/ntlm.html + if (flags & _NEGOTIATE_TARGET_INFO) != 0 { + targetInfoFields, target, err := getNTLMv2TargetInfoFields(message) + if err != nil { + return lm, nt, err + } + + nt, lm = getNTLMv2AndLMv2ResponsePayloads(target, username, password, challenge, nonce, targetInfoFields, time.Now()) + + return lm, nt, nil } - if binary.LittleEndian.Uint32(bytes[8:12]) != _CHALLENGE_MESSAGE { - return nil, errorNTLM + + var lm_bytes [24]byte + copy(lm_bytes[:8], nonce[:]) + lm = lm_bytes[:] + nt_bytes := ntlmSessionResponse(nonce, challenge, password) + nt = nt_bytes[:] + + return lm, nt, nil +} + +func getNTLMv2TargetInfoFields(type2Message []byte) (info []byte, target string, err error) { + type2MessageError := "mssql: while parsing NTLMv2 type 2 message, length %d too small for offset %d" + type2MessageLength := len(type2Message) + if type2MessageLength < 20 { + return nil, target, fmt.Errorf(type2MessageError, type2MessageLength, 20) } - flags := binary.LittleEndian.Uint32(bytes[20:24]) - var challenge [8]byte - copy(challenge[:], bytes[24:32]) - var lm, nt []byte - if (flags & _NEGOTIATE_EXTENDED_SESSIONSECURITY) != 0 { - nonce := clientChallenge() - var lm_bytes [24]byte - copy(lm_bytes[:8], nonce[:]) - lm = lm_bytes[:] - nt_bytes := ntlmSessionResponse(nonce, challenge, auth.Password) - nt = nt_bytes[:] - } else { - lm_bytes := lmResponse(challenge, auth.Password) - lm = lm_bytes[:] - nt_bytes := ntResponse(challenge, auth.Password) - nt = nt_bytes[:] + targetNameAllocated := binary.LittleEndian.Uint16(type2Message[14:16]) + targetNameOffset := binary.LittleEndian.Uint32(type2Message[16:20]) + endOfOffset := int(targetNameOffset + uint32(targetNameAllocated)) + if type2MessageLength < endOfOffset { + return nil, target, fmt.Errorf(type2MessageError, type2MessageLength, endOfOffset) + } + + target, err = ucs22str(type2Message[targetNameOffset:targetNameOffset+uint32(targetNameAllocated)]) + if err != nil { + return nil, target, err + } + + targetInformationAllocated := binary.LittleEndian.Uint16(type2Message[42:44]) + targetInformationDataOffset := binary.LittleEndian.Uint32(type2Message[44:48]) + endOfOffset = int(targetInformationDataOffset + uint32(targetInformationAllocated)) + if type2MessageLength < endOfOffset { + return nil, target, fmt.Errorf(type2MessageError, type2MessageLength, endOfOffset) } + + targetInformationBytes := make([]byte, targetInformationAllocated) + copy(targetInformationBytes, type2Message[targetInformationDataOffset:targetInformationDataOffset+uint32(targetInformationAllocated)]) + + return targetInformationBytes, target, nil +} + +func buildNTLMResponsePayload(lm, nt []byte, flags uint32, domain, workstation, username string) ([]byte, error) { lm_len := len(lm) nt_len := len(nt) - - domain16 := utf16le(auth.Domain) + domain16 := utf16le(domain) domain_len := len(domain16) - user16 := utf16le(auth.UserName) + user16 := utf16le(username) user_len := len(user16) - workstation16 := utf16le(auth.Workstation) + workstation16 := utf16le(workstation) workstation_len := len(workstation16) - msg := make([]byte, 88+lm_len+nt_len+domain_len+user_len+workstation_len) copy(msg, []byte("NTLMSSP\x00")) binary.LittleEndian.PutUint32(msg[8:], _AUTHENTICATE_MESSAGE) + // Lm Challenge Response Fields binary.LittleEndian.PutUint16(msg[12:], uint16(lm_len)) binary.LittleEndian.PutUint16(msg[14:], uint16(lm_len)) binary.LittleEndian.PutUint32(msg[16:], 88) + // Nt Challenge Response Fields binary.LittleEndian.PutUint16(msg[20:], uint16(nt_len)) binary.LittleEndian.PutUint16(msg[22:], uint16(nt_len)) binary.LittleEndian.PutUint32(msg[24:], uint32(88+lm_len)) + // Domain Name Fields binary.LittleEndian.PutUint16(msg[28:], uint16(domain_len)) binary.LittleEndian.PutUint16(msg[30:], uint16(domain_len)) binary.LittleEndian.PutUint32(msg[32:], uint32(88+lm_len+nt_len)) + // User Name Fields binary.LittleEndian.PutUint16(msg[36:], uint16(user_len)) binary.LittleEndian.PutUint16(msg[38:], uint16(user_len)) binary.LittleEndian.PutUint32(msg[40:], uint32(88+lm_len+nt_len+domain_len)) + // Workstation Fields binary.LittleEndian.PutUint16(msg[44:], uint16(workstation_len)) binary.LittleEndian.PutUint16(msg[46:], uint16(workstation_len)) binary.LittleEndian.PutUint32(msg[48:], uint32(88+lm_len+nt_len+domain_len+user_len)) + // Encrypted Random Session Key Fields binary.LittleEndian.PutUint16(msg[52:], 0) binary.LittleEndian.PutUint16(msg[54:], 0) binary.LittleEndian.PutUint32(msg[56:], uint32(88+lm_len+nt_len+domain_len+user_len+workstation_len)) + // Negotiate Flags binary.LittleEndian.PutUint32(msg[60:], flags) + // Version binary.LittleEndian.PutUint32(msg[64:], 0) binary.LittleEndian.PutUint32(msg[68:], 0) + // MIC binary.LittleEndian.PutUint32(msg[72:], 0) binary.LittleEndian.PutUint32(msg[76:], 0) binary.LittleEndian.PutUint32(msg[88:], 0) binary.LittleEndian.PutUint32(msg[84:], 0) + // Payload copy(msg[88:], lm) copy(msg[88+lm_len:], nt) copy(msg[88+lm_len+nt_len:], domain16) copy(msg[88+lm_len+nt_len+domain_len:], user16) copy(msg[88+lm_len+nt_len+domain_len+user_len:], workstation16) + return msg, nil } +func (auth *ntlmAuth) NextBytes(bytes []byte) ([]byte, error) { + signature := string(bytes[0:8]) + if signature != "NTLMSSP\x00" { + return nil, errorNTLM + } + + messageTypeIndicator := binary.LittleEndian.Uint32(bytes[8:12]) + if messageTypeIndicator != _CHALLENGE_MESSAGE { + return nil, errorNTLM + } + + var challenge [8]byte + copy(challenge[:], bytes[24:32]) + flags := binary.LittleEndian.Uint32(bytes[20:24]) + if (flags & _NEGOTIATE_EXTENDED_SESSIONSECURITY) != 0 { + lm, nt, err := negotiateExtendedSessionSecurity(flags, bytes, challenge, auth.UserName, auth.Password) + if err != nil { + return nil, err + } + + return buildNTLMResponsePayload(lm, nt, flags, auth.Domain, auth.Workstation, auth.UserName) + } + + lm_bytes := lmResponse(challenge, auth.Password) + lm := lm_bytes[:] + nt_bytes := ntResponse(challenge, auth.Password) + nt := nt_bytes[:] + + return buildNTLMResponsePayload(lm, nt, flags, auth.Domain, auth.Workstation, auth.UserName) +} + func (auth *ntlmAuth) Free() { } diff --git a/vendor/github.com/denisenkom/go-mssqldb/tds.go b/vendor/github.com/denisenkom/go-mssqldb/tds.go index a45711d5517..832c4fd23a0 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/tds.go +++ b/vendor/github.com/denisenkom/go-mssqldb/tds.go @@ -10,13 +10,9 @@ import ( "io" "io/ioutil" "net" - "net/url" - "os" "sort" "strconv" "strings" - "time" - "unicode" "unicode/utf16" "unicode/utf8" ) @@ -50,17 +46,14 @@ func parseInstances(msg []byte) map[string]map[string]string { return results } -func getInstances(ctx context.Context, address string) (map[string]map[string]string, error) { - maxTime := 5 * time.Second - dialer := &net.Dialer{ - Timeout: maxTime, - } - conn, err := dialer.DialContext(ctx, "udp", address+":1434") +func getInstances(ctx context.Context, d Dialer, address string) (map[string]map[string]string, error) { + conn, err := d.DialContext(ctx, "udp", address+":1434") if err != nil { return nil, err } defer conn.Close() - conn.SetDeadline(time.Now().Add(maxTime)) + deadline, _ := ctx.Deadline() + conn.SetDeadline(deadline) _, err = conn.Write([]byte{3}) if err != nil { return nil, err @@ -107,13 +100,15 @@ const ( // prelogin fields // http://msdn.microsoft.com/en-us/library/dd357559.aspx const ( - preloginVERSION = 0 - preloginENCRYPTION = 1 - preloginINSTOPT = 2 - preloginTHREADID = 3 - preloginMARS = 4 - preloginTRACEID = 5 - preloginTERMINATOR = 0xff + preloginVERSION = 0 + preloginENCRYPTION = 1 + preloginINSTOPT = 2 + preloginTHREADID = 3 + preloginMARS = 4 + preloginTRACEID = 5 + preloginFEDAUTHREQUIRED = 6 + preloginNONCEOPT = 7 + preloginTERMINATOR = 0xff ) const ( @@ -252,6 +247,12 @@ const ( fReadOnlyIntent = 32 ) +// OptionFlags3 +// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/773a62b6-ee89-4c02-9e5e-344882630aac +const ( + fExtension = 0x10 +) + type login struct { TDSVersion uint32 PacketSize uint32 @@ -276,6 +277,89 @@ type login struct { SSPI []byte AtchDBFile string ChangePassword string + FeatureExt featureExts +} + +type featureExts struct { + features map[byte]featureExt +} + +type featureExt interface { + featureID() byte + toBytes() []byte +} + +func (e *featureExts) Add(f featureExt) error { + if f == nil { + return nil + } + id := f.featureID() + if _, exists := e.features[id]; exists { + f := "Login error: Feature with ID '%v' is already present in FeatureExt block." + return fmt.Errorf(f, id) + } + if e.features == nil { + e.features = make(map[byte]featureExt) + } + e.features[id] = f + return nil +} + +func (e featureExts) toBytes() []byte { + if len(e.features) == 0 { + return nil + } + var d []byte + for featureID, f := range e.features { + featureData := f.toBytes() + + hdr := make([]byte, 5) + hdr[0] = featureID // FedAuth feature extension BYTE + binary.LittleEndian.PutUint32(hdr[1:], uint32(len(featureData))) // FeatureDataLen DWORD + d = append(d, hdr...) + + d = append(d, featureData...) // FeatureData *BYTE + } + if d != nil { + d = append(d, 0xff) // Terminator + } + return d +} + +type featureExtFedAuthSTS struct { + FedAuthEcho bool + FedAuthToken string + Nonce []byte +} + +func (e *featureExtFedAuthSTS) featureID() byte { + return 0x02 +} + +func (e *featureExtFedAuthSTS) toBytes() []byte { + if e == nil { + return nil + } + + options := byte(0x01) << 1 // 0x01 => STS bFedAuthLibrary 7BIT + if e.FedAuthEcho { + options |= 1 // fFedAuthEcho + } + + d := make([]byte, 5) + d[0] = options + + // looks like string in + // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/f88b63bb-b479-49e1-a87b-deda521da508 + tokenBytes := str2ucs2(e.FedAuthToken) + binary.LittleEndian.PutUint32(d[1:], uint32(len(tokenBytes))) // Should be a signed int32, but since the length is relatively small, this should work + d = append(d, tokenBytes...) + + if len(e.Nonce) == 32 { + d = append(d, e.Nonce...) + } + + return d } type loginHeader struct { @@ -302,7 +386,7 @@ type loginHeader struct { ServerNameOffset uint16 ServerNameLength uint16 ExtensionOffset uint16 - ExtensionLenght uint16 + ExtensionLength uint16 CtlIntNameOffset uint16 CtlIntNameLength uint16 LanguageOffset uint16 @@ -364,6 +448,8 @@ func sendLogin(w *tdsBuffer, login login) error { database := str2ucs2(login.Database) atchdbfile := str2ucs2(login.AtchDBFile) changepassword := str2ucs2(login.ChangePassword) + featureExt := login.FeatureExt.toBytes() + hdr := loginHeader{ TDSVersion: login.TDSVersion, PacketSize: login.PacketSize, @@ -412,7 +498,18 @@ func sendLogin(w *tdsBuffer, login login) error { offset += uint16(len(atchdbfile)) hdr.ChangePasswordOffset = offset offset += uint16(len(changepassword)) - hdr.Length = uint32(offset) + + featureExtOffset := uint32(0) + featureExtLen := len(featureExt) + if featureExtLen > 0 { + hdr.OptionFlags3 |= fExtension + hdr.ExtensionOffset = offset + hdr.ExtensionLength = 4 + offset += hdr.ExtensionLength // DWORD + featureExtOffset = uint32(offset) + } + hdr.Length = uint32(offset) + uint32(featureExtLen) + var err error err = binary.Write(w, binary.LittleEndian, &hdr) if err != nil { @@ -462,6 +559,16 @@ func sendLogin(w *tdsBuffer, login login) error { if err != nil { return err } + if featureExtOffset > 0 { + err = binary.Write(w, binary.LittleEndian, featureExtOffset) + if err != nil { + return err + } + _, err = w.Write(featureExt) + if err != nil { + return err + } + } return w.FinishPacket() } @@ -475,10 +582,9 @@ func readUcs2(r io.Reader, numchars int) (res string, err error) { } func readUsVarChar(r io.Reader) (res string, err error) { - var numchars uint16 - err = binary.Read(r, binary.LittleEndian, &numchars) + numchars, err := readUshort(r) if err != nil { - return "", err + return } return readUcs2(r, int(numchars)) } @@ -498,8 +604,7 @@ func writeUsVarChar(w io.Writer, s string) (err error) { } func readBVarChar(r io.Reader) (res string, err error) { - var numchars uint8 - err = binary.Read(r, binary.LittleEndian, &numchars) + numchars, err := readByte(r) if err != nil { return "", err } @@ -526,8 +631,7 @@ func writeBVarChar(w io.Writer, s string) (err error) { } func readBVarByte(r io.Reader) (res []byte, err error) { - var length uint8 - err = binary.Read(r, binary.LittleEndian, &length) + length, err := readByte(r) if err != nil { return } @@ -655,454 +759,6 @@ func sendAttention(buf *tdsBuffer) error { return buf.FinishPacket() } -type connectParams struct { - logFlags uint64 - port uint64 - host string - instance string - database string - user string - password string - dial_timeout time.Duration - conn_timeout time.Duration - keepAlive time.Duration - encrypt bool - disableEncryption bool - trustServerCertificate bool - certificate string - hostInCertificate string - serverSPN string - workstation string - appname string - typeFlags uint8 - failOverPartner string - failOverPort uint64 - packetSize uint16 -} - -func splitConnectionString(dsn string) (res map[string]string) { - res = map[string]string{} - parts := strings.Split(dsn, ";") - for _, part := range parts { - if len(part) == 0 { - continue - } - lst := strings.SplitN(part, "=", 2) - name := strings.TrimSpace(strings.ToLower(lst[0])) - if len(name) == 0 { - continue - } - var value string = "" - if len(lst) > 1 { - value = strings.TrimSpace(lst[1]) - } - res[name] = value - } - return res -} - -// Splits a URL in the ODBC format -func splitConnectionStringOdbc(dsn string) (map[string]string, error) { - res := map[string]string{} - - type parserState int - const ( - // Before the start of a key - parserStateBeforeKey parserState = iota - - // Inside a key - parserStateKey - - // Beginning of a value. May be bare or braced - parserStateBeginValue - - // Inside a bare value - parserStateBareValue - - // Inside a braced value - parserStateBracedValue - - // A closing brace inside a braced value. - // May be the end of the value or an escaped closing brace, depending on the next character - parserStateBracedValueClosingBrace - - // After a value. Next character should be a semicolon or whitespace. - parserStateEndValue - ) - - var state = parserStateBeforeKey - - var key string - var value string - - for i, c := range dsn { - switch state { - case parserStateBeforeKey: - switch { - case c == '=': - return res, fmt.Errorf("Unexpected character = at index %d. Expected start of key or semi-colon or whitespace.", i) - case !unicode.IsSpace(c) && c != ';': - state = parserStateKey - key += string(c) - } - - case parserStateKey: - switch c { - case '=': - key = normalizeOdbcKey(key) - if len(key) == 0 { - return res, fmt.Errorf("Unexpected end of key at index %d.", i) - } - - state = parserStateBeginValue - - case ';': - // Key without value - key = normalizeOdbcKey(key) - if len(key) == 0 { - return res, fmt.Errorf("Unexpected end of key at index %d.", i) - } - - res[key] = value - key = "" - value = "" - state = parserStateBeforeKey - - default: - key += string(c) - } - - case parserStateBeginValue: - switch { - case c == '{': - state = parserStateBracedValue - case c == ';': - // Empty value - res[key] = value - key = "" - state = parserStateBeforeKey - case unicode.IsSpace(c): - // Ignore whitespace - default: - state = parserStateBareValue - value += string(c) - } - - case parserStateBareValue: - if c == ';' { - res[key] = strings.TrimRightFunc(value, unicode.IsSpace) - key = "" - value = "" - state = parserStateBeforeKey - } else { - value += string(c) - } - - case parserStateBracedValue: - if c == '}' { - state = parserStateBracedValueClosingBrace - } else { - value += string(c) - } - - case parserStateBracedValueClosingBrace: - if c == '}' { - // Escaped closing brace - value += string(c) - state = parserStateBracedValue - continue - } - - // End of braced value - res[key] = value - key = "" - value = "" - - // This character is the first character past the end, - // so it needs to be parsed like the parserStateEndValue state. - state = parserStateEndValue - switch { - case c == ';': - state = parserStateBeforeKey - case unicode.IsSpace(c): - // Ignore whitespace - default: - return res, fmt.Errorf("Unexpected character %c at index %d. Expected semi-colon or whitespace.", c, i) - } - - case parserStateEndValue: - switch { - case c == ';': - state = parserStateBeforeKey - case unicode.IsSpace(c): - // Ignore whitespace - default: - return res, fmt.Errorf("Unexpected character %c at index %d. Expected semi-colon or whitespace.", c, i) - } - } - } - - switch state { - case parserStateBeforeKey: // Okay - case parserStateKey: // Unfinished key. Treat as key without value. - key = normalizeOdbcKey(key) - if len(key) == 0 { - return res, fmt.Errorf("Unexpected end of key at index %d.", len(dsn)) - } - res[key] = value - case parserStateBeginValue: // Empty value - res[key] = value - case parserStateBareValue: - res[key] = strings.TrimRightFunc(value, unicode.IsSpace) - case parserStateBracedValue: - return res, fmt.Errorf("Unexpected end of braced value at index %d.", len(dsn)) - case parserStateBracedValueClosingBrace: // End of braced value - res[key] = value - case parserStateEndValue: // Okay - } - - return res, nil -} - -// Normalizes the given string as an ODBC-format key -func normalizeOdbcKey(s string) string { - return strings.ToLower(strings.TrimRightFunc(s, unicode.IsSpace)) -} - -// Splits a URL of the form sqlserver://username:password@host/instance?param1=value¶m2=value -func splitConnectionStringURL(dsn string) (map[string]string, error) { - res := map[string]string{} - - u, err := url.Parse(dsn) - if err != nil { - return res, err - } - - if u.Scheme != "sqlserver" { - return res, fmt.Errorf("scheme %s is not recognized", u.Scheme) - } - - if u.User != nil { - res["user id"] = u.User.Username() - p, exists := u.User.Password() - if exists { - res["password"] = p - } - } - - host, port, err := net.SplitHostPort(u.Host) - if err != nil { - host = u.Host - } - - if len(u.Path) > 0 { - res["server"] = host + "\\" + u.Path[1:] - } else { - res["server"] = host - } - - if len(port) > 0 { - res["port"] = port - } - - query := u.Query() - for k, v := range query { - if len(v) > 1 { - return res, fmt.Errorf("key %s provided more than once", k) - } - res[strings.ToLower(k)] = v[0] - } - - return res, nil -} - -func parseConnectParams(dsn string) (connectParams, error) { - var p connectParams - - var params map[string]string - if strings.HasPrefix(dsn, "odbc:") { - parameters, err := splitConnectionStringOdbc(dsn[len("odbc:"):]) - if err != nil { - return p, err - } - params = parameters - } else if strings.HasPrefix(dsn, "sqlserver://") { - parameters, err := splitConnectionStringURL(dsn) - if err != nil { - return p, err - } - params = parameters - } else { - params = splitConnectionString(dsn) - } - - strlog, ok := params["log"] - if ok { - var err error - p.logFlags, err = strconv.ParseUint(strlog, 10, 64) - if err != nil { - return p, fmt.Errorf("Invalid log parameter '%s': %s", strlog, err.Error()) - } - } - server := params["server"] - parts := strings.SplitN(server, `\`, 2) - p.host = parts[0] - if p.host == "." || strings.ToUpper(p.host) == "(LOCAL)" || p.host == "" { - p.host = "localhost" - } - if len(parts) > 1 { - p.instance = parts[1] - } - p.database = params["database"] - p.user = params["user id"] - p.password = params["password"] - - p.port = 1433 - strport, ok := params["port"] - if ok { - var err error - p.port, err = strconv.ParseUint(strport, 10, 16) - if err != nil { - f := "Invalid tcp port '%v': %v" - return p, fmt.Errorf(f, strport, err.Error()) - } - } - - // https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/configure-the-network-packet-size-server-configuration-option - // Default packet size remains at 4096 bytes - p.packetSize = 4096 - strpsize, ok := params["packet size"] - if ok { - var err error - psize, err := strconv.ParseUint(strpsize, 0, 16) - if err != nil { - f := "Invalid packet size '%v': %v" - return p, fmt.Errorf(f, strpsize, err.Error()) - } - - // Ensure packet size falls within the TDS protocol range of 512 to 32767 bytes - // NOTE: Encrypted connections have a maximum size of 16383 bytes. If you request - // a higher packet size, the server will respond with an ENVCHANGE request to - // alter the packet size to 16383 bytes. - p.packetSize = uint16(psize) - if p.packetSize < 512 { - p.packetSize = 512 - } else if p.packetSize > 32767 { - p.packetSize = 32767 - } - } - - // https://msdn.microsoft.com/en-us/library/dd341108.aspx - // - // Do not set a connection timeout. Use Context to manage such things. - // Default to zero, but still allow it to be set. - if strconntimeout, ok := params["connection timeout"]; ok { - timeout, err := strconv.ParseUint(strconntimeout, 10, 64) - if err != nil { - f := "Invalid connection timeout '%v': %v" - return p, fmt.Errorf(f, strconntimeout, err.Error()) - } - p.conn_timeout = time.Duration(timeout) * time.Second - } - p.dial_timeout = 15 * time.Second - if strdialtimeout, ok := params["dial timeout"]; ok { - timeout, err := strconv.ParseUint(strdialtimeout, 10, 64) - if err != nil { - f := "Invalid dial timeout '%v': %v" - return p, fmt.Errorf(f, strdialtimeout, err.Error()) - } - p.dial_timeout = time.Duration(timeout) * time.Second - } - - // default keep alive should be 30 seconds according to spec: - // https://msdn.microsoft.com/en-us/library/dd341108.aspx - p.keepAlive = 30 * time.Second - if keepAlive, ok := params["keepalive"]; ok { - timeout, err := strconv.ParseUint(keepAlive, 10, 64) - if err != nil { - f := "Invalid keepAlive value '%s': %s" - return p, fmt.Errorf(f, keepAlive, err.Error()) - } - p.keepAlive = time.Duration(timeout) * time.Second - } - encrypt, ok := params["encrypt"] - if ok { - if strings.EqualFold(encrypt, "DISABLE") { - p.disableEncryption = true - } else { - var err error - p.encrypt, err = strconv.ParseBool(encrypt) - if err != nil { - f := "Invalid encrypt '%s': %s" - return p, fmt.Errorf(f, encrypt, err.Error()) - } - } - } else { - p.trustServerCertificate = true - } - trust, ok := params["trustservercertificate"] - if ok { - var err error - p.trustServerCertificate, err = strconv.ParseBool(trust) - if err != nil { - f := "Invalid trust server certificate '%s': %s" - return p, fmt.Errorf(f, trust, err.Error()) - } - } - p.certificate = params["certificate"] - p.hostInCertificate, ok = params["hostnameincertificate"] - if !ok { - p.hostInCertificate = p.host - } - - serverSPN, ok := params["serverspn"] - if ok { - p.serverSPN = serverSPN - } else { - p.serverSPN = fmt.Sprintf("MSSQLSvc/%s:%d", p.host, p.port) - } - - workstation, ok := params["workstation id"] - if ok { - p.workstation = workstation - } else { - workstation, err := os.Hostname() - if err == nil { - p.workstation = workstation - } - } - - appname, ok := params["app name"] - if !ok { - appname = "go-mssqldb" - } - p.appname = appname - - appintent, ok := params["applicationintent"] - if ok { - if appintent == "ReadOnly" { - p.typeFlags |= fReadOnlyIntent - } - } - - failOverPartner, ok := params["failoverpartner"] - if ok { - p.failOverPartner = failOverPartner - } - - failOverPort, ok := params["failoverport"] - if ok { - var err error - p.failOverPort, err = strconv.ParseUint(failOverPort, 0, 16) - if err != nil { - f := "Invalid tcp port '%v': %v" - return p, fmt.Errorf(f, failOverPort, err.Error()) - } - } - - return p, nil -} - type auth interface { InitialBytes() ([]byte, error) NextBytes([]byte) ([]byte, error) @@ -1112,7 +768,7 @@ type auth interface { // SQL Server AlwaysOn Availability Group Listeners are bound by DNS to a // list of IP addresses. So if there is more than one, try them all and // use the first one that allows a connection. -func dialConnection(ctx context.Context, p connectParams) (conn net.Conn, err error) { +func dialConnection(ctx context.Context, c *Connector, p connectParams) (conn net.Conn, err error) { var ips []net.IP ips, err = net.LookupIP(p.host) if err != nil { @@ -1123,20 +779,20 @@ func dialConnection(ctx context.Context, p connectParams) (conn net.Conn, err er ips = []net.IP{ip} } if len(ips) == 1 { - d := createDialer(&p) - addr := net.JoinHostPort(ips[0].String(), strconv.Itoa(int(p.port))) - conn, err = d.Dial(ctx, addr) + d := c.getDialer(&p) + addr := net.JoinHostPort(ips[0].String(), strconv.Itoa(int(resolveServerPort(p.port)))) + conn, err = d.DialContext(ctx, "tcp", addr) } else { //Try Dials in parallel to avoid waiting for timeouts. connChan := make(chan net.Conn, len(ips)) errChan := make(chan error, len(ips)) - portStr := strconv.Itoa(int(p.port)) + portStr := strconv.Itoa(int(resolveServerPort(p.port))) for _, ip := range ips { go func(ip net.IP) { - d := createDialer(&p) + d := c.getDialer(&p) addr := net.JoinHostPort(ip.String(), portStr) - conn, err := d.Dial(ctx, addr) + conn, err := d.DialContext(ctx, "tcp", addr) if err == nil { connChan <- conn } else { @@ -1169,12 +825,12 @@ func dialConnection(ctx context.Context, p connectParams) (conn net.Conn, err er // Can't do the usual err != nil check, as it is possible to have gotten an error before a successful connection if conn == nil { f := "Unable to open tcp connection with host '%v:%v': %v" - return nil, fmt.Errorf(f, p.host, p.port, err.Error()) + return nil, fmt.Errorf(f, p.host, resolveServerPort(p.port), err.Error()) } return conn, err } -func connect(ctx context.Context, log optionalLogger, p connectParams) (res *tdsSession, err error) { +func connect(ctx context.Context, c *Connector, log optionalLogger, p connectParams) (res *tdsSession, err error) { dialCtx := ctx if p.dial_timeout > 0 { var cancel func() @@ -1182,9 +838,10 @@ func connect(ctx context.Context, log optionalLogger, p connectParams) (res *tds defer cancel() } // if instance is specified use instance resolution service - if p.instance != "" { + if p.instance != "" && p.port == 0 { p.instance = strings.ToUpper(p.instance) - instances, err := getInstances(dialCtx, p.host) + d := c.getDialer(&p) + instances, err := getInstances(dialCtx, d, p.host) if err != nil { f := "Unable to get instances from Sql Server Browser on host %v: %v" return nil, fmt.Errorf(f, p.host, err.Error()) @@ -1194,15 +851,16 @@ func connect(ctx context.Context, log optionalLogger, p connectParams) (res *tds f := "No instance matching '%v' returned from host '%v'" return nil, fmt.Errorf(f, p.instance, p.host) } - p.port, err = strconv.ParseUint(strport, 0, 16) + port, err := strconv.ParseUint(strport, 0, 16) if err != nil { f := "Invalid tcp port returned from Sql Server Browser '%v': %v" return nil, fmt.Errorf(f, strport, err.Error()) } + p.port = port } initiate_connection: - conn, err := dialConnection(dialCtx, p) + conn, err := dialConnection(dialCtx, c, p) if err != nil { return nil, err } @@ -1273,12 +931,12 @@ initiate_connection: // while SQL Server seems to expect one TCP segment per encrypted TDS package. // Setting DynamicRecordSizingDisabled to true disables that algorithm and uses 16384 bytes per TLS package config.DynamicRecordSizingDisabled = true - outbuf.transport = conn - toconn.buf = outbuf - tlsConn := tls.Client(toconn, &config) + // setting up connection handler which will allow wrapping of TLS handshake packets inside TDS stream + handshakeConn := tlsHandshakeConn{buf: outbuf} + passthrough := passthroughConn{c: &handshakeConn} + tlsConn := tls.Client(&passthrough, &config) err = tlsConn.Handshake() - - toconn.buf = nil + passthrough.c = toconn outbuf.transport = tlsConn if err != nil { return nil, fmt.Errorf("TLS Handshake failed: %v", err) @@ -1300,15 +958,23 @@ initiate_connection: AppName: p.appname, TypeFlags: p.typeFlags, } - auth, auth_ok := getAuth(p.user, p.password, p.serverSPN, p.workstation) - if auth_ok { + auth, authOk := getAuth(p.user, p.password, p.serverSPN, p.workstation) + switch { + case p.fedAuthAccessToken != "": // accesstoken ignores user/password + featurext := &featureExtFedAuthSTS{ + FedAuthEcho: len(fields[preloginFEDAUTHREQUIRED]) > 0 && fields[preloginFEDAUTHREQUIRED][0] == 1, + FedAuthToken: p.fedAuthAccessToken, + Nonce: fields[preloginNONCEOPT], + } + login.FeatureExt.Add(featurext) + case authOk: login.SSPI, err = auth.InitialBytes() if err != nil { return nil, err } login.OptionFlags2 |= fIntSecurity defer auth.Free() - } else { + default: login.UserName = p.user login.Password = p.password } @@ -1318,42 +984,43 @@ initiate_connection: } // processing login response - var sspi_msg []byte -continue_login: - tokchan := make(chan tokenStruct, 5) - go processResponse(context.Background(), &sess, tokchan, nil) success := false - for tok := range tokchan { - switch token := tok.(type) { - case sspiMsg: - sspi_msg, err = auth.NextBytes(token) - if err != nil { - return nil, err - } - case loginAckStruct: - success = true - sess.loginAck = token - case error: - return nil, fmt.Errorf("Login error: %s", token.Error()) - case doneStruct: - if token.isError() { - return nil, fmt.Errorf("Login error: %s", token.getError()) + for { + tokchan := make(chan tokenStruct, 5) + go processResponse(context.Background(), &sess, tokchan, nil) + for tok := range tokchan { + switch token := tok.(type) { + case sspiMsg: + sspi_msg, err := auth.NextBytes(token) + if err != nil { + return nil, err + } + if sspi_msg != nil && len(sspi_msg) > 0 { + outbuf.BeginPacket(packSSPIMessage, false) + _, err = outbuf.Write(sspi_msg) + if err != nil { + return nil, err + } + err = outbuf.FinishPacket() + if err != nil { + return nil, err + } + sspi_msg = nil + } + case loginAckStruct: + success = true + sess.loginAck = token + case error: + return nil, fmt.Errorf("Login error: %s", token.Error()) + case doneStruct: + if token.isError() { + return nil, fmt.Errorf("Login error: %s", token.getError()) + } + goto loginEnd } } } - if sspi_msg != nil { - outbuf.BeginPacket(packSSPIMessage, false) - _, err = outbuf.Write(sspi_msg) - if err != nil { - return nil, err - } - err = outbuf.FinishPacket() - if err != nil { - return nil, err - } - sspi_msg = nil - goto continue_login - } +loginEnd: if !success { return nil, fmt.Errorf("Login failed") } @@ -1361,6 +1028,9 @@ continue_login: toconn.Close() p.host = sess.routedServer p.port = uint64(sess.routedPort) + if !p.hostInCertificateProvided { + p.hostInCertificate = sess.routedServer + } goto initiate_connection } return &sess, nil diff --git a/vendor/github.com/denisenkom/go-mssqldb/token.go b/vendor/github.com/denisenkom/go-mssqldb/token.go index 1acac8a5d2b..25385e89dcb 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/token.go +++ b/vendor/github.com/denisenkom/go-mssqldb/token.go @@ -17,20 +17,21 @@ type token byte // token ids const ( - tokenReturnStatus token = 121 // 0x79 - tokenColMetadata token = 129 // 0x81 - tokenOrder token = 169 // 0xA9 - tokenError token = 170 // 0xAA - tokenInfo token = 171 // 0xAB - tokenReturnValue token = 0xAC - tokenLoginAck token = 173 // 0xad - tokenRow token = 209 // 0xd1 - tokenNbcRow token = 210 // 0xd2 - tokenEnvChange token = 227 // 0xE3 - tokenSSPI token = 237 // 0xED - tokenDone token = 253 // 0xFD - tokenDoneProc token = 254 - tokenDoneInProc token = 255 + tokenReturnStatus token = 121 // 0x79 + tokenColMetadata token = 129 // 0x81 + tokenOrder token = 169 // 0xA9 + tokenError token = 170 // 0xAA + tokenInfo token = 171 // 0xAB + tokenReturnValue token = 0xAC + tokenLoginAck token = 173 // 0xad + tokenFeatureExtAck token = 174 // 0xae + tokenRow token = 209 // 0xd1 + tokenNbcRow token = 210 // 0xd2 + tokenEnvChange token = 227 // 0xE3 + tokenSSPI token = 237 // 0xED + tokenDone token = 253 // 0xFD + tokenDoneProc token = 254 + tokenDoneInProc token = 255 ) // done flags @@ -447,6 +448,22 @@ func parseLoginAck(r *tdsBuffer) loginAckStruct { return res } +// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/2eb82f8e-11f0-46dc-b42d-27302fa4701a +func parseFeatureExtAck(r *tdsBuffer) { + // at most 1 featureAck per feature in featureExt + // go-mssqldb will add at most 1 feature, the spec defines 7 different features + for i := 0; i < 8; i++ { + featureID := r.byte() // FeatureID + if featureID == 0xff { + return + } + size := r.uint32() // FeatureAckDataLen + d := make([]byte, size) + r.ReadFull(d) + } + panic("parsed more than 7 featureAck's, protocol implementation error?") +} + // http://msdn.microsoft.com/en-us/library/dd357363.aspx func parseColMetadata72(r *tdsBuffer) (columns []columnStruct) { count := r.uint16() @@ -577,6 +594,8 @@ func processSingleResponse(sess *tdsSession, ch chan tokenStruct, outs map[strin case tokenLoginAck: loginAck := parseLoginAck(sess.buf) ch <- loginAck + case tokenFeatureExtAck: + parseFeatureExtAck(sess.buf) case tokenOrder: order := parseOrder(sess.buf) ch <- order diff --git a/vendor/github.com/denisenkom/go-mssqldb/tvp_go19.go b/vendor/github.com/denisenkom/go-mssqldb/tvp_go19.go new file mode 100644 index 00000000000..64e5e21fbd8 --- /dev/null +++ b/vendor/github.com/denisenkom/go-mssqldb/tvp_go19.go @@ -0,0 +1,231 @@ +// +build go1.9 + +package mssql + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "reflect" + "strings" + "time" +) + +const ( + jsonTag = "json" + tvpTag = "tvp" + skipTagValue = "-" + sqlSeparator = "." +) + +var ( + ErrorEmptyTVPTypeName = errors.New("TypeName must not be empty") + ErrorTypeSlice = errors.New("TVP must be slice type") + ErrorTypeSliceIsEmpty = errors.New("TVP mustn't be null value") + ErrorSkip = errors.New("all fields mustn't skip") + ErrorObjectName = errors.New("wrong tvp name") + ErrorWrongTyping = errors.New("the number of elements in columnStr and tvpFieldIndexes do not align") +) + +//TVP is driver type, which allows supporting Table Valued Parameters (TVP) in SQL Server +type TVP struct { + //TypeName mustn't be default value + TypeName string + //Value must be the slice, mustn't be nil + Value interface{} +} + +func (tvp TVP) check() error { + if len(tvp.TypeName) == 0 { + return ErrorEmptyTVPTypeName + } + if !isProc(tvp.TypeName) { + return ErrorEmptyTVPTypeName + } + if sepCount := getCountSQLSeparators(tvp.TypeName); sepCount > 1 { + return ErrorObjectName + } + valueOf := reflect.ValueOf(tvp.Value) + if valueOf.Kind() != reflect.Slice { + return ErrorTypeSlice + } + if valueOf.IsNil() { + return ErrorTypeSliceIsEmpty + } + if reflect.TypeOf(tvp.Value).Elem().Kind() != reflect.Struct { + return ErrorTypeSlice + } + return nil +} + +func (tvp TVP) encode(schema, name string, columnStr []columnStruct, tvpFieldIndexes []int) ([]byte, error) { + if len(columnStr) != len(tvpFieldIndexes) { + return nil, ErrorWrongTyping + } + preparedBuffer := make([]byte, 0, 20+(10*len(columnStr))) + buf := bytes.NewBuffer(preparedBuffer) + err := writeBVarChar(buf, "") + if err != nil { + return nil, err + } + + writeBVarChar(buf, schema) + writeBVarChar(buf, name) + binary.Write(buf, binary.LittleEndian, uint16(len(columnStr))) + + for i, column := range columnStr { + binary.Write(buf, binary.LittleEndian, uint32(column.UserType)) + binary.Write(buf, binary.LittleEndian, uint16(column.Flags)) + writeTypeInfo(buf, &columnStr[i].ti) + writeBVarChar(buf, "") + } + // The returned error is always nil + buf.WriteByte(_TVP_END_TOKEN) + + conn := new(Conn) + conn.sess = new(tdsSession) + conn.sess.loginAck = loginAckStruct{TDSVersion: verTDS73} + stmt := &Stmt{ + c: conn, + } + + val := reflect.ValueOf(tvp.Value) + for i := 0; i < val.Len(); i++ { + refStr := reflect.ValueOf(val.Index(i).Interface()) + buf.WriteByte(_TVP_ROW_TOKEN) + for columnStrIdx, fieldIdx := range tvpFieldIndexes { + field := refStr.Field(fieldIdx) + tvpVal := field.Interface() + valOf := reflect.ValueOf(tvpVal) + elemKind := field.Kind() + if elemKind == reflect.Ptr && valOf.IsNil() { + switch tvpVal.(type) { + case *bool, *time.Time, *int8, *int16, *int32, *int64, *float32, *float64, *int: + binary.Write(buf, binary.LittleEndian, uint8(0)) + continue + default: + binary.Write(buf, binary.LittleEndian, uint64(_PLP_NULL)) + continue + } + } + if elemKind == reflect.Slice && valOf.IsNil() { + binary.Write(buf, binary.LittleEndian, uint64(_PLP_NULL)) + continue + } + + cval, err := convertInputParameter(tvpVal) + if err != nil { + return nil, fmt.Errorf("failed to convert tvp parameter row col: %s", err) + } + param, err := stmt.makeParam(cval) + if err != nil { + return nil, fmt.Errorf("failed to make tvp parameter row col: %s", err) + } + columnStr[columnStrIdx].ti.Writer(buf, param.ti, param.buffer) + } + } + buf.WriteByte(_TVP_END_TOKEN) + return buf.Bytes(), nil +} + +func (tvp TVP) columnTypes() ([]columnStruct, []int, error) { + val := reflect.ValueOf(tvp.Value) + var firstRow interface{} + if val.Len() != 0 { + firstRow = val.Index(0).Interface() + } else { + firstRow = reflect.New(reflect.TypeOf(tvp.Value).Elem()).Elem().Interface() + } + + tvpRow := reflect.TypeOf(firstRow) + columnCount := tvpRow.NumField() + defaultValues := make([]interface{}, 0, columnCount) + tvpFieldIndexes := make([]int, 0, columnCount) + for i := 0; i < columnCount; i++ { + field := tvpRow.Field(i) + tvpTagValue, isTvpTag := field.Tag.Lookup(tvpTag) + jsonTagValue, isJsonTag := field.Tag.Lookup(jsonTag) + if IsSkipField(tvpTagValue, isTvpTag, jsonTagValue, isJsonTag) { + continue + } + tvpFieldIndexes = append(tvpFieldIndexes, i) + if field.Type.Kind() == reflect.Ptr { + v := reflect.New(field.Type.Elem()) + defaultValues = append(defaultValues, v.Interface()) + continue + } + defaultValues = append(defaultValues, reflect.Zero(field.Type).Interface()) + } + + if columnCount-len(tvpFieldIndexes) == columnCount { + return nil, nil, ErrorSkip + } + + conn := new(Conn) + conn.sess = new(tdsSession) + conn.sess.loginAck = loginAckStruct{TDSVersion: verTDS73} + stmt := &Stmt{ + c: conn, + } + + columnConfiguration := make([]columnStruct, 0, columnCount) + for index, val := range defaultValues { + cval, err := convertInputParameter(val) + if err != nil { + return nil, nil, fmt.Errorf("failed to convert tvp parameter row %d col %d: %s", index, val, err) + } + param, err := stmt.makeParam(cval) + if err != nil { + return nil, nil, err + } + column := columnStruct{ + ti: param.ti, + } + switch param.ti.TypeId { + case typeNVarChar, typeBigVarBin: + column.ti.Size = 0 + } + columnConfiguration = append(columnConfiguration, column) + } + + return columnConfiguration, tvpFieldIndexes, nil +} + +func IsSkipField(tvpTagValue string, isTvpValue bool, jsonTagValue string, isJsonTagValue bool) bool { + if !isTvpValue && !isJsonTagValue { + return false + } else if isTvpValue && tvpTagValue != skipTagValue { + return false + } else if !isTvpValue && isJsonTagValue && jsonTagValue != skipTagValue { + return false + } + return true +} + +func getSchemeAndName(tvpName string) (string, string, error) { + if len(tvpName) == 0 { + return "", "", ErrorEmptyTVPTypeName + } + splitVal := strings.Split(tvpName, ".") + if len(splitVal) > 2 { + return "", "", errors.New("wrong tvp name") + } + if len(splitVal) == 2 { + res := make([]string, 2) + for key, value := range splitVal { + tmp := strings.Replace(value, "[", "", -1) + tmp = strings.Replace(tmp, "]", "", -1) + res[key] = tmp + } + return res[0], res[1], nil + } + tmp := strings.Replace(splitVal[0], "[", "", -1) + tmp = strings.Replace(tmp, "]", "", -1) + + return "", tmp, nil +} + +func getCountSQLSeparators(str string) int { + return strings.Count(str, sqlSeparator) +} diff --git a/vendor/github.com/denisenkom/go-mssqldb/types.go b/vendor/github.com/denisenkom/go-mssqldb/types.go index 3bad788b923..b6e7fb2b526 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/types.go +++ b/vendor/github.com/denisenkom/go-mssqldb/types.go @@ -11,6 +11,7 @@ import ( "time" "github.com/denisenkom/go-mssqldb/internal/cp" + "github.com/denisenkom/go-mssqldb/internal/decimal" ) // fixed-length data types @@ -62,6 +63,7 @@ const ( typeNChar = 0xef typeXml = 0xf1 typeUdt = 0xf0 + typeTvp = 0xf3 // long length types typeText = 0x23 @@ -73,6 +75,10 @@ const _PLP_NULL = 0xFFFFFFFFFFFFFFFF const _UNKNOWN_PLP_LEN = 0xFFFFFFFFFFFFFFFE const _PLP_TERMINATOR = 0x00000000 +// TVP COLUMN FLAGS +const _TVP_END_TOKEN = 0x00 +const _TVP_ROW_TOKEN = 0x01 + // TYPE_INFO rule // http://msdn.microsoft.com/en-us/library/dd358284.aspx type typeInfo struct { @@ -145,6 +151,8 @@ func writeTypeInfo(w io.Writer, ti *typeInfo) (err error) { // those are fixed length // https://msdn.microsoft.com/en-us/library/dd341171.aspx ti.Writer = writeFixedType + case typeTvp: + ti.Writer = writeFixedType default: // all others are VARLENTYPE err = writeVarLen(w, ti) if err != nil { @@ -162,6 +170,7 @@ func writeFixedType(w io.Writer, ti typeInfo, buf []byte) (err error) { // https://msdn.microsoft.com/en-us/library/dd358341.aspx func writeVarLen(w io.Writer, ti *typeInfo) (err error) { switch ti.TypeId { + case typeDateN: ti.Writer = writeByteLenType case typeTimeN, typeDateTime2N, typeDateTimeOffsetN: @@ -203,6 +212,7 @@ func writeVarLen(w io.Writer, ti *typeInfo) (err error) { ti.Writer = writeByteLenType case typeBigVarBin, typeBigVarChar, typeBigBinary, typeBigChar, typeNVarChar, typeNChar, typeXml, typeUdt: + // short len types if ti.Size > 8000 || ti.Size == 0 { if err = binary.Write(w, binary.LittleEndian, uint16(0xffff)); err != nil { @@ -809,12 +819,12 @@ func decodeMoney(buf []byte) []byte { uint64(buf[1])<<40 | uint64(buf[2])<<48 | uint64(buf[3])<<56) - return scaleBytes(strconv.FormatInt(money, 10), 4) + return decimal.ScaleBytes(strconv.FormatInt(money, 10), 4) } func decodeMoney4(buf []byte) []byte { money := int32(binary.LittleEndian.Uint32(buf[0:4])) - return scaleBytes(strconv.FormatInt(int64(money), 10), 4) + return decimal.ScaleBytes(strconv.FormatInt(int64(money), 10), 4) } func decodeGuid(buf []byte) []byte { @@ -826,15 +836,14 @@ func decodeGuid(buf []byte) []byte { func decodeDecimal(prec uint8, scale uint8, buf []byte) []byte { var sign uint8 sign = buf[0] - dec := Decimal{ - positive: sign != 0, - prec: prec, - scale: scale, - } + var dec decimal.Decimal + dec.SetPositive(sign != 0) + dec.SetPrec(prec) + dec.SetScale(scale) buf = buf[1:] l := len(buf) / 4 for i := 0; i < l; i++ { - dec.integer[i] = binary.LittleEndian.Uint32(buf[0:4]) + dec.SetInteger(binary.LittleEndian.Uint32(buf[0:4]), uint8(i)) buf = buf[4:] } return dec.Bytes() @@ -1177,7 +1186,7 @@ func makeDecl(ti typeInfo) string { case typeBigChar, typeChar: return fmt.Sprintf("char(%d)", ti.Size) case typeBigVarChar, typeVarChar: - if ti.Size > 4000 || ti.Size == 0 { + if ti.Size > 8000 || ti.Size == 0 { return fmt.Sprintf("varchar(max)") } else { return fmt.Sprintf("varchar(%d)", ti.Size) @@ -1219,6 +1228,11 @@ func makeDecl(ti typeInfo) string { return ti.UdtInfo.TypeName case typeGuid: return "uniqueidentifier" + case typeTvp: + if ti.UdtInfo.SchemaName != "" { + return fmt.Sprintf("%s.%s READONLY", ti.UdtInfo.SchemaName, ti.UdtInfo.TypeName) + } + return fmt.Sprintf("%s READONLY", ti.UdtInfo.TypeName) default: panic(fmt.Sprintf("not implemented makeDecl for type %#x", ti.TypeId)) } diff --git a/vendor/github.com/denisenkom/go-mssqldb/uniqueidentifier.go b/vendor/github.com/denisenkom/go-mssqldb/uniqueidentifier.go index c8ef3149b19..3ad6f8634a2 100644 --- a/vendor/github.com/denisenkom/go-mssqldb/uniqueidentifier.go +++ b/vendor/github.com/denisenkom/go-mssqldb/uniqueidentifier.go @@ -72,3 +72,9 @@ func (u UniqueIdentifier) Value() (driver.Value, error) { func (u UniqueIdentifier) String() string { return fmt.Sprintf("%X-%X-%X-%X-%X", u[0:4], u[4:6], u[6:8], u[8:10], u[10:]) } + +// MarshalText converts Uniqueidentifier to bytes corresponding to the stringified hexadecimal representation of the Uniqueidentifier +// e.g., "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA" -> [65 65 65 65 65 65 65 65 45 65 65 65 65 45 65 65 65 65 45 65 65 65 65 65 65 65 65 65 65 65 65] +func (u UniqueIdentifier) MarshalText() []byte { + return []byte(u.String()) +} diff --git a/vendor/github.com/golang-sql/civil/CONTRIBUTING.md b/vendor/github.com/golang-sql/civil/CONTRIBUTING.md new file mode 100644 index 00000000000..d0635c3ab56 --- /dev/null +++ b/vendor/github.com/golang-sql/civil/CONTRIBUTING.md @@ -0,0 +1,73 @@ +# Contributing + +1. Sign one of the contributor license agreements below. + +#### Running + +Once you've done the necessary setup, you can run the integration tests by +running: + +``` sh +$ go test -v github.com/golang-sql/civil +``` + +## Contributor License Agreements + +Before we can accept your pull requests you'll need to sign a Contributor +License Agreement (CLA): + +- **If you are an individual writing original source code** and **you own the +intellectual property**, then you'll need to sign an [individual CLA][indvcla]. +- **If you work for a company that wants to allow you to contribute your +work**, then you'll need to sign a [corporate CLA][corpcla]. + +You can sign these electronically (just scroll to the bottom). After that, +we'll be able to accept your pull requests. + +## Contributor Code of Conduct + +As contributors and maintainers of this project, +and in the interest of fostering an open and welcoming community, +we pledge to respect all people who contribute through reporting issues, +posting feature requests, updating documentation, +submitting pull requests or patches, and other activities. + +We are committed to making participation in this project +a harassment-free experience for everyone, +regardless of level of experience, gender, gender identity and expression, +sexual orientation, disability, personal appearance, +body size, race, ethnicity, age, religion, or nationality. + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery +* Personal attacks +* Trolling or insulting/derogatory comments +* Public or private harassment +* Publishing other's private information, +such as physical or electronic +addresses, without explicit permission +* Other unethical or unprofessional conduct. + +Project maintainers have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct. +By adopting this Code of Conduct, +project maintainers commit themselves to fairly and consistently +applying these principles to every aspect of managing this project. +Project maintainers who do not follow or enforce the Code of Conduct +may be permanently removed from the project team. + +This code of conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. + +Instances of abusive, harassing, or otherwise unacceptable behavior +may be reported by opening an issue +or contacting one or more of the project maintainers. + +This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, +available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/) + +[gcloudcli]: https://developers.google.com/cloud/sdk/gcloud/ +[indvcla]: https://developers.google.com/open-source/cla/individual +[corpcla]: https://developers.google.com/open-source/cla/corporate \ No newline at end of file diff --git a/vendor/github.com/golang-sql/civil/LICENSE b/vendor/github.com/golang-sql/civil/LICENSE new file mode 100644 index 00000000000..7a4a3ea2424 --- /dev/null +++ b/vendor/github.com/golang-sql/civil/LICENSE @@ -0,0 +1,202 @@ + + 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 [yyyy] [name of copyright owner] + + 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/golang-sql/civil/README.md b/vendor/github.com/golang-sql/civil/README.md new file mode 100644 index 00000000000..3a7956ecda4 --- /dev/null +++ b/vendor/github.com/golang-sql/civil/README.md @@ -0,0 +1,15 @@ +# Civil Date and Time + +[![GoDoc](https://godoc.org/github.com/golang-sql/civil?status.svg)](https://godoc.org/github.com/golang-sql/civil) + +Civil provides Date, Time of Day, and DateTime data types. + +While there are many uses, using specific types when working +with databases make is conceptually eaiser to understand what value +is set in the remote system. + +## Source + +This civil package was extracted and forked from `cloud.google.com/go/civil`. +As such the license and contributing requirements remain the same as that +module. diff --git a/vendor/cloud.google.com/go/civil/civil.go b/vendor/github.com/golang-sql/civil/civil.go similarity index 100% rename from vendor/cloud.google.com/go/civil/civil.go rename to vendor/github.com/golang-sql/civil/civil.go diff --git a/vendor/modules.txt b/vendor/modules.txt index 20ac6753395..b130bce53d7 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -4,7 +4,6 @@ 4d63.com/tz # cloud.google.com/go v0.51.0 cloud.google.com/go -cloud.google.com/go/civil cloud.google.com/go/compute/metadata cloud.google.com/go/functions/metadata cloud.google.com/go/iam @@ -320,9 +319,11 @@ github.com/coreos/pkg/dlopen github.com/davecgh/go-spew/spew # github.com/davecgh/go-xdr v0.0.0-20161123171359-e6a2ba005892 github.com/davecgh/go-xdr/xdr2 -# github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f +# github.com/denisenkom/go-mssqldb v0.0.0-20200206145737-bbfc9a55622e github.com/denisenkom/go-mssqldb github.com/denisenkom/go-mssqldb/internal/cp +github.com/denisenkom/go-mssqldb/internal/decimal +github.com/denisenkom/go-mssqldb/internal/querytext # github.com/devigned/tab v0.1.2-0.20190607222403-0c15cf42f9a2 github.com/devigned/tab # github.com/dgrijalva/jwt-go v3.2.1-0.20190620180102-5e25c22bd5d6+incompatible @@ -523,6 +524,8 @@ github.com/gogo/protobuf/proto github.com/gogo/protobuf/protoc-gen-gogo/descriptor github.com/gogo/protobuf/sortkeys github.com/gogo/protobuf/types +# github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe +github.com/golang-sql/civil # github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 github.com/golang/groupcache/lru # github.com/golang/protobuf v1.3.2 diff --git a/x-pack/metricbeat/module/mssql/_meta/docs.asciidoc b/x-pack/metricbeat/module/mssql/_meta/docs.asciidoc index 155e643b4a2..ad32544a465 100644 --- a/x-pack/metricbeat/module/mssql/_meta/docs.asciidoc +++ b/x-pack/metricbeat/module/mssql/_meta/docs.asciidoc @@ -1,4 +1,4 @@ -This is the https://www.microsoft.com/en-us/sql-server/sql-server-2017[Microsoft SQL 2017] Metricbeat module. It is still in beta and under active development to add new Metricsets and introduce enhancements. +This is the https://www.microsoft.com/en-us/sql-server/sql-server-2017[Microsoft SQL 2017] Metricbeat module. It is still under active development to add new Metricsets and introduce enhancements. [float] === Compatibility @@ -23,7 +23,7 @@ The following Metricsets are already included: [float] === Module-specific configuration notes -When configuring the `hosts` option, you can specify native user credentials +When configuring the `hosts` option, you can specify native user credentials as part of the host string with the following format: ---- @@ -45,4 +45,4 @@ metricbeat.modules: period: 10 ---- -Store sensitive values like passwords in the <>. \ No newline at end of file +Store sensitive values like passwords in the <>. diff --git a/x-pack/metricbeat/module/mssql/fields.go b/x-pack/metricbeat/module/mssql/fields.go index f3d9bcc13e2..55c8f0c787a 100644 --- a/x-pack/metricbeat/module/mssql/fields.go +++ b/x-pack/metricbeat/module/mssql/fields.go @@ -19,5 +19,5 @@ func init() { // AssetMssql returns asset data. // This is the base64 encoded gzipped contents of module/mssql. func AssetMssql() string { - return "eJzMWU9v3LgPvedTED21QDK959bfb4HdAml3u0nPhkambSGy5Ij0TGc//YKSZ8bjsed/i+2hQWyLfHykHinlAV5x9Qg10Zu9A2DDFh/h3Zfn529P7+4AciQdTMPGu0f48gzP356g9nlr8Q4goEVF+AhzZHUHUBi0OT3eAQA8gFM1bi3LP141+Ahl8G3TPdkx/1IhBO852QHtHSvjjCtBWQsRUueiW9331/eZK1ZzRbh5MeZ61P16JXClGLhCqJGD0QSGYI6CJWCBIWAO7HuWhlD6cEy+83gNxnpXDl7s4PnuzFuL8Pk38EWEsgFnHJkcEyOjHuX/UZ+vuFr6MMSz4/arqnHocY/hBkPhQ62cPpPk3sI1tchQIOsKCYyLL+VTUHPfphT81Vvzf986xkA9+8MqPCUljSoxo8YapqzBkBHqi3L0ta3nGIQusQjJogQJhNq7PNWR17oNoChGE5Bay7LELzAU1i+lqozL8Uc0QrONw1Ho1uvXbKluh1wMQsC3FmkEu7wwsdwrBK2sxQDsQQAcAdoShkx751CLS7oI5otnZcFtwIpRGDM6CoGDcqRG3R/0cmDdCOxR17J6gRlj3WSs5hYPAthmQxb4oMIK0qqP8QcsVDDxdzBOSJhdCKtHXRaQkK8qoiFv1pfGERCrwJhDEXyd6mbjFRrv7dEKFysTwM7xv63lGbxUhiD3SOA8g3HatjlGNJj3WbmUWOtL307uyWOoRep8gyFK32gAF8IKqH3dGJsM30gxiBVjjY435pF2yI4qneSut6lMjTS+VgUEDqYsMWA+g9/RYVDWru5h5VtYKseddG5WsIc5gvXLI7V0QvQTQcq00V+9E+BnlxutGMdjTCUfkYL2UmaKK5kgUHoXxvWx/gg2AWnVEuYwX20ZerC4QDtIoex/gfaMYYFhBp8KxtB7khQyqo/hlTilqCD3wLIDFsq2QqSK/VYBMaq883lpjc0V6ypbd5DzWH7plPaho7tWLk8GkSRyNIuxjSx4DbHREqAqCtScyJNJUXtHHJSREnxPra6k9X7++Of9oI/QPWjhAcj8g/cxFRZ/CGu+2DTEexBE5MG7DzP4w5RVgrftmDUqB6X30iKDb8uqafliKtuiwDC6eDhWwcSIs1P9El5WGd774pDVvXTtVnuDQaNjmXe6uYeg8K3LpTTlgxREx+3ScCXqVqmFzDnspfi61pAbeh0ydSisfmiNHgtqD3o8U8jOkUIRcDwQ34SykrEtNwuTpzKa/tJ6/9o2FIe3+JlVJAPsEiROkmKJo6DSGomQ1jtUxewLecbna6m4T7IW8dV+gQQLDCuwhtniDP6HURUiY0Letp9GKIagluq2SAT4o0FHZhGPLm53ifC8I6WGOqdJRitTVhNkpirJfTu3OF66MByrrSkwEzialdOrW5beNhlJDUTBIttLY62IwmpQg9LfNyUYT27oNO41+VOLbl/WfiFoeG/c2sSHsQjgBKHpR6Mr1K+NN46zuIsnlPvk8DopsC1V8Xgc665/oBB97nkFH8BzJYvXo8/OmSOKeW4CrzrTqVo7B2MMnBP9+mybYr8u5on8bU/s2jtGt9cW4GDdnRMNq1AinxhLjjvCNhXD1WhHjmGZ9eV5NwaDxb1bg7h/4tXBxMXBtqxElnuGZOSWyGUM2iYpaqXqLpqMIx5cb1xy0UCN0pi1pMrxC5kxEdwJ/1kMQDSwE2XhU/sZRHXmgEDGacykhWVzpV+P67H0U1XLeB8FrQOHebK0bYhC8KTFw/p/ihDPVzxa5zfAKxvhkPnJw1Kv3mVyuK7vvXRj6fombj+1P4WqgcsbcCFkX0+F17ptTEzbL+dkyvcV5MD5g+ynwdzNh5CpOGOk79fP0zgrb+Xrg5gL6xWPyxkrHr8ROypkfyO3wRFQW9dKhtx4uFXMwcxbaawqniC2CifTwECyi3hU9sVGs2kG3+NtvaE9day9M+xD/PuByyE3qnRezow0uOKLpitUlqthyzumn90t3wSjI7c+acGedzEwPdD8GqU8hu8GYpAUNpOjz2SwueKjXD6JZA8xdvItxvep7N+HyRlrKmVn6NLTmpfUSgTC2nriDN9aGdzXk9b7p+evo3P7TdP4NMzW1fDgtNz2RontiH9bhiXrvePDf5DkaxCeyHPU8RuUb9rvpwrRT5CagxpzAkH/BgAA//9lwsfR" + return "eJzMWU9v3LgPvedTED21QDK959bfb4HdAml3u0nPhkambSGy5Ij0TGc//YKSZ8bjsed/i+2hQWyLfHykHinlAV5x9Qg10Zu9A2DDFh/h3Zfn529P7+4AciQdTMPGu0f48gzP356g9nlr8Q4goEVF+AhzZHUHUBi0OT3eAQA8gFM1bi3LP141+Ahl8G3TPdkx/1IhBO852QHtHSvjjCtBWQsRUueiW9331/eZK1ZzRbh5MeZ61P16JXClGLhCqJGD0QSGYI6CJWCBIWAO7HuWhlD6cEy+83gNxnpXDl7s4PnuzFuL8Pk38EWEsgFnHJkcEyOjHuX/UZ+vuFr6MMSz4/arqnHocY/hBkPhQ62cPpPk3sI1tchQIOsKCYyLL+VTUHPfphT81Vvzf986xkA9+5sqLNWJCWlUiRk11jBlDYaMUF+Uoa9tPccgZIlFSBYlRCDU3uWpirzWbQBFMZaA1FqWJX6BobB+KTVlXI4/ohGabRyOQrdev2ZLdTvkYhACvrVII9jlhYnFXiFoZS0GYA8C4AjQljBk2juHWlzSRTBfPCsLbgNWjMKY0VEIHJQjNer+oJcD60Zgj7qW1QvMGOsmYzW3eBDANhuywAcVVpBWfYw/YKGCib+DcULC7EJYPeqygIR8VRENebO+NI6AWAXGHIrg61Q3G6/QeG+PVrhYmQB2jv9tLc/gpTIEuUcC5xmM07bNMaLBvM/KpcRaX/p2ck8eQy1C5xsMUfhGA7gQVkDt68bYZPhGikGsGGt0vDGPtEN21Ogkd71NZWqk8bUqIHAwZYkB8xn8jg6DsnZ1DyvfwlI57qRzs4I9zBGsXx6ppROinwhSZo3+6p0AP7vcaMU4HmMq+YgUtJcyU1zJ/IDSuTCuj/VHsAlIq5Ywh/lqy9CDxQXaQQpl/wu0ZwwLDDP4VDCG3pOkkFF9DK/EKUUFuQeWHbBQthUiVey2CohR5Z3PS2tsrlhX2bqDnMfyS6e0Dx3dtXJ5MogkkaNZjG1kwWuIjZYAVVGg5kSezInaO+KgjJTge2p1Ja3388c/7wd9hO5BCw9A5h+8j6mw+ENY88WmId6DICIP3n2YwR+mrBK8bcesUTkovZcWGXxbVk3LF1PZFgWG0cXDoQomRpyd6pfwssrw3heHrO6la7faGwwaHcu80809BIVvXS6lKR+kIDpul4YrUbdKLWTOYS/F17WG3NDrkKlDYfVDa/RYUHvQ44lCdo4UioDjgfgmlJWMbblZmDyV0fSX1vvXtqE4vMXPrCIZX5cgcZIUSxwFldZIhLTeoSpmX8gzPl9LxX2StYiv9gskWGBYgTXMFmfwP4yqEBkT8rb9NEIxBLVUt0UiwB8NOjKLeHBxu0uE5x0pNdQ5TTJambKaIDNVSe7bucXx0oXhWG1NgZnA0aycXt2y9LbJSGogChbZXhprRRRWgxqU/r4pwXhuQ6dxr8mfWnT7svYLQcN749YmPoxFACcITT8aXaF+bbxxnMVdPKHcJ4fXSYFtqYqH41h3/QOF6HPPK/gAnitZvB59ds4cUcxzE3jVmU7V2jkYY+Cc6Ncn2xT7dTFP5G97XtfeMbq9tgAH6+6caFiFEvnEWHLcEbapGK5GO3IMy6wvz7svGCzu3RnE/RMvDiauDbZlJbLcMyQjt0QuY9A2SVErVXfNZBzx4HLj/GsGapTGrCVVjl/GjEngTvDPYgCigZ0YC5+azyCmM8cDMk5jJg0smyv9elyNpZuqWob7KGcdOMyTpW07FHonLR5W/1NkeL7i0Sq/AV7ZBofMTx6VetUuc8N1Xe+lG0rXt3D7qf0pVA1c3oALIft6KrzWbWNi2n45J1O+ryAHzh9jPw2mbj6ETMUJI32/fp6GWXkrXx/EXFiveFzOWPH4fdhRIfsbuQ2OgNq6VjLixqOtYg5m3kpbVfH8sFU4mQUGgl3Eg7IvNopNM/geb+oN7alj7Z1hH+LfDlwOuVGl83JipMEFXzRdobJcDRveMf3s7vgmGB2580kL9ryLgelx5tco5TF8NxCDpLCZHHwmg80VH+XySSR7iLGTbzG+T2X/NkxOWFMpO0OXnta8pFYiENbWE2f41srYvp6z3j89fx2d2m+axqdhtq6GB6fltjdKbAf82zIsWe8dHv6DJF+D8ESeo47foHzTfj9ViH6C1BzUmBMI+jcAAP//oRLGKQ==" } diff --git a/x-pack/metricbeat/module/mssql/performance/_meta/data.json b/x-pack/metricbeat/module/mssql/performance/_meta/data.json index 7f409619ea2..656c0b525fd 100644 --- a/x-pack/metricbeat/module/mssql/performance/_meta/data.json +++ b/x-pack/metricbeat/module/mssql/performance/_meta/data.json @@ -1,45 +1,42 @@ { "@timestamp": "2017-10-12T08:05:34.853Z", - "agent": { - "hostname": "host.example.com", - "name": "host.example.com" - }, "event": { "dataset": "mssql.performance", "duration": 115000, "module": "mssql" }, "metricset": { - "name": "performance" + "name": "performance", + "period": 10000 }, "mssql": { "performance": { "active_temp_tables": 0, - "batch_requests_per_sec": 24249, + "batch_requests_per_sec": 3355, "buffer": { "cache_hit": { - "pct": 0.14 + "pct": 0.22 }, - "checkpoint_pages_per_sec": 182, - "database_pages": 1892, + "checkpoint_pages_per_sec": 295, + "database_pages": 2152, "page_life_expectancy": { - "sec": 8201 + "sec": 1400 }, - "target_pages": 3194880 + "target_pages": 3178496 }, - "compilations_per_sec": 7379, - "connections_reset_per_sec": 2179, - "lock_waits_per_sec": 3, - "logins_per_sec": 7346, - "logouts_per_sec": 7339, - "page_splits_per_sec": 45, - "recompilations_per_sec": 0, + "compilations_per_sec": 1151, + "connections_reset_per_sec": 88, + "lock_waits_per_sec": 6, + "logins_per_sec": 1105, + "logouts_per_sec": 1103, + "page_splits_per_sec": 14, + "recompilations_per_sec": 2, "transactions": 0, - "user_connections": 7 + "user_connections": 2 } }, "service": { - "address": "172.26.0.2", + "address": "172.23.0.2:1433", "type": "mssql" } } \ No newline at end of file diff --git a/x-pack/metricbeat/module/mssql/performance/_meta/fields.yml b/x-pack/metricbeat/module/mssql/performance/_meta/fields.yml index 199be4830fd..4352c237015 100644 --- a/x-pack/metricbeat/module/mssql/performance/_meta/fields.yml +++ b/x-pack/metricbeat/module/mssql/performance/_meta/fields.yml @@ -1,7 +1,7 @@ - name: performance type: group description: performance metricset fetches information about the Performance Counters - release: beta + release: ga fields: - name: page_splits_per_sec type: long diff --git a/x-pack/metricbeat/module/mssql/performance/data_integration_test.go b/x-pack/metricbeat/module/mssql/performance/data_integration_test.go index f7753b98364..f0520a843bd 100644 --- a/x-pack/metricbeat/module/mssql/performance/data_integration_test.go +++ b/x-pack/metricbeat/module/mssql/performance/data_integration_test.go @@ -8,6 +8,9 @@ import ( "net/url" "testing" + "github.com/elastic/beats/v7/libbeat/logp" + "github.com/elastic/beats/v7/libbeat/tests/compose" + _ "github.com/denisenkom/go-mssqldb" "github.com/pkg/errors" "github.com/stretchr/testify/assert" @@ -17,23 +20,13 @@ import ( ) func TestData(t *testing.T) { - t.Skip("Skipping `data.json` generation test") + logp.TestingSetup() + service := compose.EnsureUp(t, "mssql") - _, config, err := getHostURI() - if err != nil { - t.Fatal("error getting config information", err.Error()) - } + f := mbtest.NewReportingMetricSetV2(t, mtest.GetConfig(service.Host(), "performance")) - f := mbtest.NewReportingMetricSetV2(t, config) - events, errs := mbtest.ReportingFetchV2(f) - if len(errs) > 0 { - t.Fatalf("Expected 0 error, had %d. %v\n", len(errs), errs) - } - assert.NotEmpty(t, events) - - if err = mbtest.WriteEventsReporterV2(f, t, ""); err != nil { - t.Fatal("write", err) - } + err := mbtest.WriteEventsReporterV2(f, t, "") + assert.NoError(t, err) } func getHostURI() (string, map[string]interface{}, error) { diff --git a/x-pack/metricbeat/module/mssql/performance/performance.go b/x-pack/metricbeat/module/mssql/performance/performance.go index faea7f09c2b..fb9c4b4c888 100644 --- a/x-pack/metricbeat/module/mssql/performance/performance.go +++ b/x-pack/metricbeat/module/mssql/performance/performance.go @@ -13,7 +13,6 @@ import ( "github.com/pkg/errors" - "github.com/elastic/beats/v7/libbeat/common/cfgwarn" "github.com/elastic/beats/v7/libbeat/logp" "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/beats/v7/x-pack/metricbeat/module/mssql" @@ -49,8 +48,6 @@ type MetricSet struct { // New creates a new instance of the MetricSet. New is responsible for unpacking // any MetricSet specific configuration options if there are any. func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - cfgwarn.Beta("The mssql performance metricset is beta.") - logger := logp.NewLogger("mssql.performance").With("host", base.HostData().SanitizedURI) db, err := mssql.NewConnection(base.HostData().URI) @@ -71,40 +68,40 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) { func (m *MetricSet) Fetch(reporter mb.ReporterV2) { var err error var rows *sql.Rows - rows, err = m.db.Query(`SELECT object_name, - counter_name, - instance_name, - cntr_value -FROM sys.dm_os_performance_counters -WHERE counter_name = 'SQL Compilations/sec' - OR counter_name = 'SQL Re-Compilations/sec' - OR counter_name = 'User Connections' - OR counter_name = 'Page splits/sec' - OR ( counter_name = 'Lock Waits/sec' - AND instance_name = '_Total' ) - OR counter_name = 'Page splits/sec' - OR ( object_name = 'SQLServer:Buffer Manager' - AND counter_name = 'Page life expectancy' ) - OR counter_name = 'Batch Requests/sec' - OR ( counter_name = 'Buffer cache hit ratio' - AND object_name = 'SQLServer:Buffer Manager' ) - OR ( counter_name = 'Target pages' - AND object_name = 'SQLServer:Buffer Manager' ) - OR ( counter_name = 'Database pages' - AND object_name = 'SQLServer:Buffer Manager' ) - OR ( counter_name = 'Checkpoint pages/sec' - AND object_name = 'SQLServer:Buffer Manager' ) - OR ( counter_name = 'Lock Waits/sec' - AND instance_name = '_Total' ) - OR ( counter_name = 'Transactions' - AND object_name = 'SQLServer:General Statistics' ) - OR ( counter_name = 'Logins/sec' - AND object_name = 'SQLServer:General Statistics' ) - OR ( counter_name = 'Logouts/sec' - AND object_name = 'SQLServer:General Statistics' ) - OR ( counter_name = 'Connection Reset/sec' - AND object_name = 'SQLServer:General Statistics' ) - OR ( counter_name = 'Active Temp Tables' + rows, err = m.db.Query(`SELECT object_name, + counter_name, + instance_name, + cntr_value +FROM sys.dm_os_performance_counters +WHERE counter_name = 'SQL Compilations/sec' + OR counter_name = 'SQL Re-Compilations/sec' + OR counter_name = 'User Connections' + OR counter_name = 'Page splits/sec' + OR ( counter_name = 'Lock Waits/sec' + AND instance_name = '_Total' ) + OR counter_name = 'Page splits/sec' + OR ( object_name = 'SQLServer:Buffer Manager' + AND counter_name = 'Page life expectancy' ) + OR counter_name = 'Batch Requests/sec' + OR ( counter_name = 'Buffer cache hit ratio' + AND object_name = 'SQLServer:Buffer Manager' ) + OR ( counter_name = 'Target pages' + AND object_name = 'SQLServer:Buffer Manager' ) + OR ( counter_name = 'Database pages' + AND object_name = 'SQLServer:Buffer Manager' ) + OR ( counter_name = 'Checkpoint pages/sec' + AND object_name = 'SQLServer:Buffer Manager' ) + OR ( counter_name = 'Lock Waits/sec' + AND instance_name = '_Total' ) + OR ( counter_name = 'Transactions' + AND object_name = 'SQLServer:General Statistics' ) + OR ( counter_name = 'Logins/sec' + AND object_name = 'SQLServer:General Statistics' ) + OR ( counter_name = 'Logouts/sec' + AND object_name = 'SQLServer:General Statistics' ) + OR ( counter_name = 'Connection Reset/sec' + AND object_name = 'SQLServer:General Statistics' ) + OR ( counter_name = 'Active Temp Tables' AND object_name = 'SQLServer:General Statistics' )`) if err != nil { reporter.Error(errors.Wrapf(err, "error closing rows")) diff --git a/x-pack/metricbeat/module/mssql/performance/performance_integration_test.go b/x-pack/metricbeat/module/mssql/performance/performance_integration_test.go index 884bfe72763..5e039265d2b 100644 --- a/x-pack/metricbeat/module/mssql/performance/performance_integration_test.go +++ b/x-pack/metricbeat/module/mssql/performance/performance_integration_test.go @@ -81,7 +81,7 @@ func TestFetch(t *testing.T) { {key: "connections_reset_per_sec", assertion: int64Assertion(int64HigherThanZero)}, {key: "logouts_per_sec", assertion: int64Assertion(int64HigherThanZero)}, {key: "logins_per_sec", assertion: int64Assertion(int64HigherThanZero)}, - {key: "recompilations_per_sec", assertion: int64Assertion(int64EqualZero)}, + {key: "recompilations_per_sec", assertion: int64Assertion(int64EqualOrHigherThanZero)}, {key: "compilations_per_sec", assertion: int64Assertion(int64HigherThanZero)}, {key: "batch_requests_per_sec", assertion: int64Assertion(int64HigherThanZero)}, {key: "buffer.cache_hit.pct", assertion: float64Assertion(float64HigherThanZero)}, diff --git a/x-pack/metricbeat/module/mssql/transaction_log/_meta/data.json b/x-pack/metricbeat/module/mssql/transaction_log/_meta/data.json index ce46579d561..186c154ee98 100644 --- a/x-pack/metricbeat/module/mssql/transaction_log/_meta/data.json +++ b/x-pack/metricbeat/module/mssql/transaction_log/_meta/data.json @@ -1,16 +1,13 @@ { "@timestamp": "2017-10-12T08:05:34.853Z", - "agent": { - "hostname": "host.example.com", - "name": "host.example.com" - }, "event": { "dataset": "mssql.transaction_log", "duration": 115000, "module": "mssql" }, "metricset": { - "name": "transaction_log" + "name": "transaction_log", + "period": 10000 }, "mssql": { "database": { @@ -20,26 +17,26 @@ "transaction_log": { "space_usage": { "since_last_backup": { - "bytes": 135168 + "bytes": 937984 }, "total": { "bytes": 2088960 }, "used": { - "bytes": 622592, - "pct": 29.80392074584961 + "bytes": 1318912, + "pct": 63.13725662231445 } }, "stats": { "active_size": { - "bytes": 135167.737856 + "bytes": 937983.737856 }, "backup_time": "1900-01-01T00:00:00Z", "recovery_size": { - "bytes": 0.128906 + "bytes": 0.894531 }, "since_last_checkpoint": { - "bytes": 135167.737856 + "bytes": 937983.737856 }, "total_size": { "bytes": 2088959.475712 @@ -48,7 +45,7 @@ } }, "service": { - "address": "172.26.0.2", + "address": "172.23.0.2:1433", "type": "mssql" } } \ No newline at end of file diff --git a/x-pack/metricbeat/module/mssql/transaction_log/_meta/fields.yml b/x-pack/metricbeat/module/mssql/transaction_log/_meta/fields.yml index 5ba421ba079..c03df4084d0 100644 --- a/x-pack/metricbeat/module/mssql/transaction_log/_meta/fields.yml +++ b/x-pack/metricbeat/module/mssql/transaction_log/_meta/fields.yml @@ -1,7 +1,7 @@ - name: transaction_log type: group description: transaction_log metricset will fetch information about the operation and transaction log of each database from a MSSQL instance - release: beta + release: ga fields: - name: space_usage type: group diff --git a/x-pack/metricbeat/module/mssql/transaction_log/data_integration_test.go b/x-pack/metricbeat/module/mssql/transaction_log/data_integration_test.go index fcdad6ccb44..16f5c16eac6 100644 --- a/x-pack/metricbeat/module/mssql/transaction_log/data_integration_test.go +++ b/x-pack/metricbeat/module/mssql/transaction_log/data_integration_test.go @@ -7,14 +7,18 @@ package transaction_log import ( "testing" + "github.com/elastic/beats/v7/libbeat/logp" + "github.com/elastic/beats/v7/libbeat/tests/compose" + mbtest "github.com/elastic/beats/v7/metricbeat/mb/testing" mtest "github.com/elastic/beats/v7/x-pack/metricbeat/module/mssql/testing" ) func TestData(t *testing.T) { - t.Skip("Skipping `data.json` generation test") + logp.TestingSetup() + service := compose.EnsureUp(t, "mssql") - f := mbtest.NewReportingMetricSetV2(t, mtest.GetConfig("transaction_log")) + f := mbtest.NewReportingMetricSetV2(t, mtest.GetConfig(service.Host(), "transaction_log")) err := mbtest.WriteEventsReporterV2(f, t, "") if err != nil { diff --git a/x-pack/metricbeat/module/mssql/transaction_log/transaction_log.go b/x-pack/metricbeat/module/mssql/transaction_log/transaction_log.go index 6047d0161b4..a974ffdab9b 100644 --- a/x-pack/metricbeat/module/mssql/transaction_log/transaction_log.go +++ b/x-pack/metricbeat/module/mssql/transaction_log/transaction_log.go @@ -11,7 +11,6 @@ import ( "github.com/pkg/errors" "github.com/elastic/beats/v7/libbeat/common" - "github.com/elastic/beats/v7/libbeat/common/cfgwarn" "github.com/elastic/beats/v7/libbeat/logp" "github.com/elastic/beats/v7/metricbeat/mb" "github.com/elastic/beats/v7/x-pack/metricbeat/module/mssql" @@ -55,8 +54,6 @@ type MetricSet struct { // New create a new instance of the MetricSet func New(base mb.BaseMetricSet) (mb.MetricSet, error) { - cfgwarn.Beta("The mssql transaction_log metricset is beta.") - logger := logp.NewLogger("mssql.transaction_log").With("host", base.HostData().SanitizedURI) db, err := mssql.NewConnection(base.HostData().URI) @@ -127,7 +124,7 @@ func (m *MetricSet) Close() error { func (m *MetricSet) getLogSpaceUsageForDb(dbName string) (common.MapStr, error) { // According to MS docs a single result is always returned for this query - row := m.db.QueryRow(fmt.Sprintf(`USE %s; SELECT * FROM sys.dm_db_log_space_usage;`, dbName)) + row := m.db.QueryRow(fmt.Sprintf(`USE [%s]; SELECT * FROM sys.dm_db_log_space_usage;`, dbName)) var res logSpace if err := row.Scan(&res.id, &res.totalLogSizeInBytes, &res.usedLogSpaceInBytes, &res.usedLogSpaceInPercent, @@ -153,7 +150,7 @@ func (m *MetricSet) getLogSpaceUsageForDb(dbName string) (common.MapStr, error) } func (m *MetricSet) getLogStats(db dbInfo) (common.MapStr, error) { // According to MS docs a single result is always returned for this query - row := m.db.QueryRow(fmt.Sprintf(`USE %s; SELECT database_id,total_log_size_mb,active_log_size_mb,log_backup_time,log_since_last_log_backup_mb,log_since_last_checkpoint_mb,log_recovery_size_mb FROM sys.dm_db_log_stats(%d);`, db.name, db.id)) + row := m.db.QueryRow(fmt.Sprintf(`USE [%s]; SELECT database_id,total_log_size_mb,active_log_size_mb,log_backup_time,log_since_last_log_backup_mb,log_since_last_checkpoint_mb,log_recovery_size_mb FROM sys.dm_db_log_stats(%d);`, db.name, db.id)) var res logStats if err := row.Scan(&res.databaseID, &res.sizeMB, &res.activeSizeMB, &res.backupTime, &res.sinceLastBackupMB, &res.sinceLastCheckpointMB, &res.recoverySizeMB); err != nil {