From 18bdfe315a1c0b339580a374c6dae83ee66afe06 Mon Sep 17 00:00:00 2001 From: Geoff Greer Date: Wed, 1 Oct 2025 10:21:19 -0700 Subject: [PATCH 1/4] Add support for SAP HANA. --- go.mod | 5 +- go.sum | 18 +- pkg/database/database.go | 10 + pkg/database/hdb/hdb.go | 28 + vendor/github.com/SAP/go-hdb/LICENSE.md | 201 ++++ vendor/github.com/SAP/go-hdb/driver/bytes.go | 27 + .../SAP/go-hdb/driver/calldriver.go | 52 + vendor/github.com/SAP/go-hdb/driver/conn.go | 425 ++++++++ .../github.com/SAP/go-hdb/driver/connector.go | 966 ++++++++++++++++++ .../github.com/SAP/go-hdb/driver/convert.go | 308 ++++++ vendor/github.com/SAP/go-hdb/driver/dbconn.go | 116 +++ .../SAP/go-hdb/driver/dbconnectinfo.go | 17 + .../github.com/SAP/go-hdb/driver/decimal.go | 62 ++ .../SAP/go-hdb/driver/deprecated.go | 13 + .../SAP/go-hdb/driver/dial/dialer.go | 31 + vendor/github.com/SAP/go-hdb/driver/doc.go | 4 + vendor/github.com/SAP/go-hdb/driver/driver.go | 130 +++ vendor/github.com/SAP/go-hdb/driver/dsn.go | 232 +++++ vendor/github.com/SAP/go-hdb/driver/error.go | 39 + .../SAP/go-hdb/driver/identifier.go | 35 + .../go-hdb/driver/internal/protocol/auth.go | 146 +++ .../driver/internal/protocol/auth/auth.go | 237 +++++ .../driver/internal/protocol/auth/certkey.go | 174 ++++ .../driver/internal/protocol/auth/jwt.go | 60 ++ .../driver/internal/protocol/auth/list.go | 59 ++ .../driver/internal/protocol/auth/scram.go | 65 ++ .../protocol/auth/scrampbkdf2sha256.go | 122 +++ .../internal/protocol/auth/scramsha256.go | 107 ++ .../internal/protocol/auth/sessioncookie.go | 60 ++ .../driver/internal/protocol/auth/x509.go | 106 ++ .../driver/internal/protocol/convert.go | 649 ++++++++++++ .../driver/internal/protocol/datatype.go | 63 ++ .../go-hdb/driver/internal/protocol/decode.go | 148 +++ .../driver/internal/protocol/decodeerror.go | 47 + .../go-hdb/driver/internal/protocol/dfv.go | 34 + .../go-hdb/driver/internal/protocol/doc.go | 4 + .../internal/protocol/encoding/datetime.go | 51 + .../internal/protocol/encoding/decimal.go | 233 +++++ .../internal/protocol/encoding/decode.go | 581 +++++++++++ .../driver/internal/protocol/encoding/doc.go | 2 + .../internal/protocol/encoding/encode.go | 535 ++++++++++ .../internal/protocol/encoding/field.go | 114 +++ .../go-hdb/driver/internal/protocol/error.go | 215 ++++ .../driver/internal/protocol/fieldnames.go | 74 ++ .../driver/internal/protocol/functioncode.go | 37 + .../driver/internal/protocol/headers.go | 294 ++++++ .../go-hdb/driver/internal/protocol/init.go | 113 ++ .../driver/internal/protocol/julian/julian.go | 48 + .../internal/protocol/keyvaluesparts.go | 52 + .../protocol/levenshtein/levenshtein.go | 57 ++ .../go-hdb/driver/internal/protocol/lob.go | 448 ++++++++ .../driver/internal/protocol/messagetype.go | 55 + .../driver/internal/protocol/optionsparts.go | 400 ++++++++ .../driver/internal/protocol/optiontype.go | 145 +++ .../driver/internal/protocol/parameter.go | 455 +++++++++ .../driver/internal/protocol/partkind.go | 63 ++ .../go-hdb/driver/internal/protocol/parts.go | 177 ++++ .../driver/internal/protocol/parts1.24.go | 27 + .../driver/internal/protocol/parts1.25.go | 27 + .../driver/internal/protocol/protocol.go | 479 +++++++++ .../driver/internal/protocol/resizeslice.go | 11 + .../go-hdb/driver/internal/protocol/result.go | 180 ++++ .../driver/internal/protocol/rowsaffected.go | 42 + .../driver/internal/protocol/simpleparts.go | 60 ++ .../driver/internal/protocol/typecode.go | 176 ++++ .../driver/internal/protocol/x_generator.go | 3 + .../driver/internal/protocol/x_stringer.go | 735 +++++++++++++ .../driver/internal/rand/alphanum/rand.go | 28 + .../go-hdb/driver/internal/unsafe/unsafe.go | 20 + vendor/github.com/SAP/go-hdb/driver/lob.go | 178 ++++ .../github.com/SAP/go-hdb/driver/metadata.go | 49 + .../github.com/SAP/go-hdb/driver/metrics.go | 234 +++++ vendor/github.com/SAP/go-hdb/driver/result.go | 234 +++++ .../github.com/SAP/go-hdb/driver/scanner.go | 301 ++++++ .../github.com/SAP/go-hdb/driver/session.go | 794 ++++++++++++++ .../github.com/SAP/go-hdb/driver/sniffer.go | 102 ++ vendor/github.com/SAP/go-hdb/driver/stats.go | 30 + .../github.com/SAP/go-hdb/driver/stats.tmpl | 19 + .../github.com/SAP/go-hdb/driver/statscfg.go | 54 + .../SAP/go-hdb/driver/statscfg.json | 5 + vendor/github.com/SAP/go-hdb/driver/stmt.go | 398 ++++++++ vendor/github.com/SAP/go-hdb/driver/trace.go | 89 ++ .../SAP/go-hdb/driver/unicode/cesu8/cesu8.go | 142 +++ .../go-hdb/driver/unicode/cesu8/encoding.go | 197 ++++ .../github.com/SAP/go-hdb/driver/version.go | 173 ++++ .../SAP/go-hdb/driver/wgroup/wgroup1.24.go | 15 + .../SAP/go-hdb/driver/wgroup/wgroup1.25.go | 11 + .../SAP/go-hdb/driver/x_bstring_test.py | 30 + vendor/modules.txt | 21 +- 89 files changed, 13799 insertions(+), 14 deletions(-) create mode 100644 pkg/database/hdb/hdb.go create mode 100644 vendor/github.com/SAP/go-hdb/LICENSE.md create mode 100644 vendor/github.com/SAP/go-hdb/driver/bytes.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/calldriver.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/conn.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/connector.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/convert.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/dbconn.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/dbconnectinfo.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/decimal.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/deprecated.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/dial/dialer.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/doc.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/driver.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/dsn.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/error.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/identifier.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/auth.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/certkey.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/jwt.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/list.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/scram.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/scrampbkdf2sha256.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/scramsha256.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/sessioncookie.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/x509.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/convert.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/datatype.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/decode.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/decodeerror.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/dfv.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/doc.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/encoding/datetime.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/encoding/decimal.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/encoding/decode.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/encoding/doc.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/encoding/encode.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/encoding/field.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/error.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/fieldnames.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/functioncode.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/headers.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/init.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/julian/julian.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/keyvaluesparts.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/levenshtein/levenshtein.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/lob.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/messagetype.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/optionsparts.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/optiontype.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/parameter.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/partkind.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/parts.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/parts1.24.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/parts1.25.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/protocol.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/resizeslice.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/result.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/rowsaffected.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/simpleparts.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/typecode.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/x_generator.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/protocol/x_stringer.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/rand/alphanum/rand.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/internal/unsafe/unsafe.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/lob.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/metadata.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/metrics.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/result.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/scanner.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/session.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/sniffer.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/stats.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/stats.tmpl create mode 100644 vendor/github.com/SAP/go-hdb/driver/statscfg.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/statscfg.json create mode 100644 vendor/github.com/SAP/go-hdb/driver/stmt.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/trace.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/unicode/cesu8/cesu8.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/unicode/cesu8/encoding.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/version.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/wgroup/wgroup1.24.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/wgroup/wgroup1.25.go create mode 100644 vendor/github.com/SAP/go-hdb/driver/x_bstring_test.py diff --git a/go.mod b/go.mod index bcb44891..496beaa2 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/conductorone/baton-sql go 1.25 require ( + github.com/SAP/go-hdb v1.14.5 github.com/conductorone/baton-sdk v0.4.6 github.com/elliotchance/phpserialize v1.4.0 github.com/ennyjfrick/ruleguard-logfatal v0.0.2 @@ -16,7 +17,7 @@ require ( github.com/spf13/viper v1.20.1 github.com/stretchr/testify v1.11.1 go.uber.org/zap v1.27.0 - golang.org/x/text v0.24.0 + golang.org/x/text v0.29.0 google.golang.org/grpc v1.71.1 google.golang.org/protobuf v1.36.6 gopkg.in/yaml.v3 v3.0.1 @@ -125,7 +126,7 @@ require ( golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect golang.org/x/net v0.39.0 // indirect golang.org/x/oauth2 v0.29.0 // indirect - golang.org/x/sync v0.13.0 // indirect + golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.35.0 // indirect golang.org/x/term v0.31.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e // indirect diff --git a/go.sum b/go.sum index d18ce0b3..c91ae94d 100644 --- a/go.sum +++ b/go.sum @@ -24,6 +24,8 @@ github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20O github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/SAP/go-hdb v1.14.5 h1:SJcaEyMw4t6UPasxNTAZENXMguK2HKorpwiY5KnnAZk= +github.com/SAP/go-hdb v1.14.5/go.mod h1:n2822T2EW6WVy7+M6p+YrDe9qG/U/lY5RCeQzd+YwWA= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/aws/aws-lambda-go v1.48.0 h1:1aZUYsrJu0yo5fC4z+Rba1KhNImXcJcvHu763BxoyIo= @@ -355,8 +357,8 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= -golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -375,8 +377,8 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= -golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -393,8 +395,8 @@ golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -404,8 +406,8 @@ golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= -golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/database/database.go b/pkg/database/database.go index 8d33c4d2..7e1751f3 100644 --- a/pkg/database/database.go +++ b/pkg/database/database.go @@ -9,6 +9,7 @@ import ( "os" "regexp" + "github.com/conductorone/baton-sql/pkg/database/hdb" "github.com/conductorone/baton-sql/pkg/database/mysql" "github.com/conductorone/baton-sql/pkg/database/oracle" "github.com/conductorone/baton-sql/pkg/database/postgres" @@ -26,6 +27,7 @@ const ( SQLite MSSQL Oracle + HDB ) func updateFromEnv(dsn string) (string, error) { @@ -105,6 +107,14 @@ func Connect(ctx context.Context, dsn string, user string, password string) (*sq return nil, Unknown, err } return db, PostgreSQL, nil + + case "hdb": + db, err := hdb.Connect(ctx, parsedDsn.String()) + if err != nil { + return nil, Unknown, err + } + return db, HDB, nil + default: return nil, Unknown, fmt.Errorf("unsupported database scheme: %s", parsedDsn.Scheme) } diff --git a/pkg/database/hdb/hdb.go b/pkg/database/hdb/hdb.go new file mode 100644 index 00000000..f9be6401 --- /dev/null +++ b/pkg/database/hdb/hdb.go @@ -0,0 +1,28 @@ +package hdb + +import ( + "context" + "database/sql" + "time" + + _ "github.com/SAP/go-hdb/driver" +) + +const ( + MaxIdleConns = 10 + MaxOpenConns = 10 + MaxConnLifetime = 5 * time.Minute +) + +func Connect(ctx context.Context, dsn string) (*sql.DB, error) { + db, err := sql.Open("hdb", dsn) + if err != nil { + return nil, err + } + + db.SetMaxOpenConns(MaxOpenConns) + db.SetMaxIdleConns(MaxIdleConns) + db.SetConnMaxLifetime(MaxConnLifetime) + + return db, nil +} diff --git a/vendor/github.com/SAP/go-hdb/LICENSE.md b/vendor/github.com/SAP/go-hdb/LICENSE.md new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/LICENSE.md @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [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. diff --git a/vendor/github.com/SAP/go-hdb/driver/bytes.go b/vendor/github.com/SAP/go-hdb/driver/bytes.go new file mode 100644 index 00000000..5e26f0e6 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/bytes.go @@ -0,0 +1,27 @@ +package driver + +import ( + "database/sql/driver" +) + +// NullBytes represents an []byte that may be null. +// NullBytes implements the Scanner interface so +// it can be used as a scan destination, similar to NullString. +type NullBytes struct { + Bytes []byte + Valid bool // Valid is true if Bytes is not NULL +} + +// Scan implements the Scanner interface. +func (n *NullBytes) Scan(value any) error { + n.Bytes, n.Valid = value.([]byte) + return nil +} + +// Value implements the driver Valuer interface. +func (n NullBytes) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + return n.Bytes, nil +} diff --git a/vendor/github.com/SAP/go-hdb/driver/calldriver.go b/vendor/github.com/SAP/go-hdb/driver/calldriver.go new file mode 100644 index 00000000..35f0cc62 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/calldriver.go @@ -0,0 +1,52 @@ +package driver + +import ( + "context" + "database/sql/driver" + "fmt" +) + +// Boilerplate to define a minimal sql driver implementation. +// To be used for converting stored procedure output parameters +// including sql.Rows output table parameters to guarantee +// exactly the same conversion behavior as for sql.Query. +var ( + _ driver.Driver = (*callDriver)(nil) + _ driver.Connector = (*callConnector)(nil) + _ driver.Conn = (*callConn)(nil) + _ driver.NamedValueChecker = (*callConn)(nil) + _ driver.QueryerContext = (*callConn)(nil) +) + +type callDriver struct{} + +var ( + defCallDriver = &callDriver{} + defCallConn = &callConn{} +) + +func (d *callDriver) Open(name string) (driver.Conn, error) { return defCallConn, nil } + +type callConnector struct{} + +func (c *callConnector) Connect(context.Context) (driver.Conn, error) { return defCallConn, nil } +func (c *callConnector) Driver() driver.Driver { return defCallDriver } + +type callConn struct{} + +func (c *callConn) Prepare(query string) (driver.Stmt, error) { panic("not implemented") } +func (c *callConn) Close() error { return nil } +func (c *callConn) Begin() (driver.Tx, error) { panic("not implemented") } +func (c *callConn) CheckNamedValue(nv *driver.NamedValue) error { return nil } + +// QueryContext is used to convert the stored procedure output parameters. +func (c *callConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { + if len(args) != 1 { + return nil, fmt.Errorf("invalid argument length %d - expected 1", len(args)) + } + cr, ok := args[0].Value.(*callResult) + if !ok { + return nil, fmt.Errorf("invalid argument type %T", args[0]) + } + return cr, nil +} diff --git a/vendor/github.com/SAP/go-hdb/driver/conn.go b/vendor/github.com/SAP/go-hdb/driver/conn.go new file mode 100644 index 00000000..434c8418 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/conn.go @@ -0,0 +1,425 @@ +package driver + +import ( + "context" + "database/sql" + "database/sql/driver" + "errors" + "fmt" + "log/slog" + "reflect" + "sync" + "sync/atomic" + "time" + + p "github.com/SAP/go-hdb/driver/internal/protocol" + "github.com/SAP/go-hdb/driver/internal/protocol/auth" + "github.com/SAP/go-hdb/driver/wgroup" +) + +// ErrUnsupportedIsolationLevel is the error raised if a transaction is started with a not supported isolation level. +var ErrUnsupportedIsolationLevel = errors.New("unsupported isolation level") + +// ErrNestedTransaction is the error raised if a transaction is created within a transaction as this is not supported by hdb. +var ErrNestedTransaction = errors.New("nested transactions are not supported") + +// ErrNestedQuery is the error raised if a new sql statement is sent to the database server before the resultset +// processing of a previous sql query statement is finalized. +// Currently this only can happen if connections are used concurrently and if stream enabled fields (LOBs) are part +// of the resultset. +// This error can be avoided in whether using a transaction or a dedicated connection (sql.Tx or sql.Conn). +var ErrNestedQuery = errors.New("nested sql queries are not supported") + +// queries. +const ( + pingQuery = "select 1 from dummy" + setIsolationLevelReadCommitted = "set transaction isolation level read committed" + setIsolationLevelRepeatableRead = "set transaction isolation level repeatable read" + setIsolationLevelSerializable = "set transaction isolation level serializable" + setAccessModeReadOnly = "set transaction read only" + setAccessModeReadWrite = "set transaction read write" +) + +var ( + // register as var to execute even before init() funcs are called. + _ = p.RegisterScanType(p.DtBytes, reflect.TypeFor[[]byte](), reflect.TypeFor[NullBytes]()) + _ = p.RegisterScanType(p.DtDecimal, reflect.TypeFor[Decimal](), reflect.TypeFor[NullDecimal]()) + _ = p.RegisterScanType(p.DtLob, reflect.TypeFor[Lob](), reflect.TypeFor[NullLob]()) +) + +// check if conn implements all required interfaces. +var ( + _ driver.Conn = (*conn)(nil) + _ driver.ConnPrepareContext = (*conn)(nil) + _ driver.Pinger = (*conn)(nil) + _ driver.ConnBeginTx = (*conn)(nil) + _ driver.ExecerContext = (*conn)(nil) + _ driver.QueryerContext = (*conn)(nil) + _ driver.NamedValueChecker = (*conn)(nil) + _ driver.SessionResetter = (*conn)(nil) + _ driver.Validator = (*conn)(nil) + _ Conn = (*conn)(nil) // go-hdb enhancements +) + +// connection hook for testing. +// use unexported type to avoid key collisions. +type connHookCtxKeyType struct{} + +var connHookCtxKey connHookCtxKeyType + +// ...connection hook operations. +const ( + choNone = iota + choStmtExec +) + +// ...connection hook function. +type connHookFn func(op int) + +func withConnHook(ctx context.Context, fn connHookFn) context.Context { + return context.WithValue(ctx, connHookCtxKey, fn) +} + +// Conn enhances a connection with go-hdb specific connection functions. +type Conn interface { + HDBVersion() *Version + DatabaseName() string + DBConnectInfo(ctx context.Context, databaseName string) (*DBConnectInfo, error) +} + +var stdConnTracker = &connTracker{} + +type connTracker struct { + mu sync.Mutex + _callDB *sql.DB + numConn int64 +} + +func (t *connTracker) add() { t.mu.Lock(); t.numConn++; t.mu.Unlock() } + +func (t *connTracker) remove() { + t.mu.Lock() + defer t.mu.Unlock() + t.numConn-- + if t.numConn > 0 { + return + } + t.numConn = 0 + if t._callDB != nil { + t._callDB.Close() + t._callDB = nil + } +} + +func (t *connTracker) callDB() *sql.DB { + t.mu.Lock() + defer t.mu.Unlock() + if t._callDB == nil { + t._callDB = sql.OpenDB(new(callConnector)) + } + return t._callDB +} + +// Conn is the implementation of the database/sql/driver Conn interface. +type conn struct { + attrs *connAttrs + metrics *metrics + logger *slog.Logger + session *session + wg *sync.WaitGroup // wait for concurrent db calls when closing connections. +} + +// isAuthError returns true in case of X509 certificate validation errrors or hdb authentication errors, else otherwise. +func isAuthError(err error) bool { + var certValidationError *auth.CertValidationError + if errors.As(err, &certValidationError) { + return true + } + var hdbErrors *p.HdbErrors + if !errors.As(err, &hdbErrors) { + return false + } + return hdbErrors.Code() == p.HdbErrAuthenticationFailed +} + +// unique connection number. +var connNo atomic.Uint64 + +func newConn(ctx context.Context, host string, metrics *metrics, attrs *connAttrs, authHnd *p.AuthHnd) (*conn, error) { + logger := attrs.logger.With(slog.Uint64("conn", connNo.Add(1))) + + metrics.lazyInit() + + session, err := newSession(ctx, host, logger, metrics, attrs, authHnd) + if err != nil { + return nil, err + } + + stdConnTracker.add() + metrics.msgCh <- gaugeMsg{idx: gaugeConn, v: 1} // increment open connections. + + return &conn{attrs: attrs, metrics: metrics, logger: logger, session: session, wg: new(sync.WaitGroup)}, nil +} + +// Close implements the driver.Conn interface. +func (c *conn) Close() error { + c.metrics.msgCh <- gaugeMsg{idx: gaugeConn, v: -1} // decrement open connections. + stdConnTracker.remove() + return c.session.close() +} + +// ResetSession implements the driver.SessionResetter interface. +func (c *conn) ResetSession(ctx context.Context) error { + if c.session.isBad() { + return driver.ErrBadConn + } + + lastRead := c.session.dbConn.lastRead() + + if c.attrs.pingInterval == 0 || lastRead.IsZero() || time.Since(lastRead) < c.attrs.pingInterval { + return nil + } + + if _, err := c.session.queryDirect(ctx, pingQuery, tracePing); err != nil { + return fmt.Errorf("%w: %w", driver.ErrBadConn, err) + } + return nil +} + +// IsValid implements the driver.Validator interface. +func (c *conn) IsValid() bool { return !c.session.isBad() } + +// Ping implements the driver.Pinger interface. +func (c *conn) Ping(ctx context.Context) error { + var sqlErr error + done := make(chan struct{}) + wgroup.Go(c.wg, func() { + defer close(done) + _, sqlErr = c.session.queryDirect(ctx, pingQuery, tracePing) + }) + + select { + case <-ctx.Done(): + c.session.cancel() + return ctx.Err() + case <-done: + return sqlErr + } +} + +// PrepareContext implements the driver.ConnPrepareContext interface. +func (c *conn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) { + var sqlErr error + var stmt driver.Stmt + done := make(chan struct{}) + wgroup.Go(c.wg, func() { + defer close(done) + if sqlErr = c.session.switchUser(ctx); sqlErr != nil { + return + } + var pr *prepareResult + if pr, sqlErr = c.session.prepare(ctx, query); sqlErr != nil { + return + } + stmt = newStmt(c.session, c.wg, c.attrs, c.metrics, query, pr) + if stmtMetadata, ok := ctx.Value(stmtMetadataCtxKey).(*StmtMetadata); ok { + *stmtMetadata = pr + } + }) + + select { + case <-ctx.Done(): + c.session.cancel() + return nil, ctx.Err() + case <-done: + return stmt, sqlErr + } +} + +// BeginTx implements the driver.ConnBeginTx interface. +func (c *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) { + if c.session.inTx.Load() { + return nil, ErrNestedTransaction + } + + var isolationLevelQuery string + switch sql.IsolationLevel(opts.Isolation) { + case sql.LevelDefault, sql.LevelReadCommitted: + isolationLevelQuery = setIsolationLevelReadCommitted + case sql.LevelRepeatableRead: + isolationLevelQuery = setIsolationLevelRepeatableRead + case sql.LevelSerializable: + isolationLevelQuery = setIsolationLevelSerializable + default: + return nil, ErrUnsupportedIsolationLevel + } + + var accessModeQuery string + if opts.ReadOnly { + accessModeQuery = setAccessModeReadOnly + } else { + accessModeQuery = setAccessModeReadWrite + } + + var sqlErr error + var tx driver.Tx + done := make(chan struct{}) + wgroup.Go(c.wg, func() { + defer close(done) + if sqlErr = c.session.switchUser(ctx); sqlErr != nil { + return + } + // set isolation level + if _, sqlErr = c.session.execDirect(ctx, isolationLevelQuery); sqlErr != nil { + return + } + // set access mode + if _, sqlErr = c.session.execDirect(ctx, accessModeQuery); sqlErr != nil { + return + } + tx = newTx(c) + c.session.inTx.Store(true) + }) + + select { + case <-ctx.Done(): + c.session.cancel() + return nil, ctx.Err() + case <-done: + return tx, sqlErr + } +} + +// QueryContext implements the driver.QueryerContext interface. +func (c *conn) QueryContext(ctx context.Context, query string, nvargs []driver.NamedValue) (driver.Rows, error) { + // accepts stored procedures (call) without parameters to avoid parsing + // the query string which might have comments, etc. + if len(nvargs) != 0 { + return nil, driver.ErrSkip // fast path not possible (prepare needed) + } + + var sqlErr error + var rows driver.Rows + done := make(chan struct{}) + wgroup.Go(c.wg, func() { + defer close(done) + if sqlErr = c.session.switchUser(ctx); sqlErr != nil { + return + } + rows, sqlErr = c.session.queryDirect(ctx, query, traceQuery) + }) + + select { + case <-ctx.Done(): + c.session.cancel() + return nil, ctx.Err() + case <-done: + return rows, sqlErr + } +} + +// ExecContext implements the driver.ExecerContext interface. +func (c *conn) ExecContext(ctx context.Context, query string, nvargs []driver.NamedValue) (driver.Result, error) { + if len(nvargs) != 0 { + return nil, driver.ErrSkip // fast path not possible (prepare needed) + } + + var sqlErr error + var result driver.Result + done := make(chan struct{}) + wgroup.Go(c.wg, func() { + defer close(done) + if sqlErr = c.session.switchUser(ctx); sqlErr != nil { + return + } + // handle procedure call without parameters here as well + result, sqlErr = c.session.execDirect(ctx, query) + }) + + select { + case <-ctx.Done(): + c.session.cancel() + return nil, ctx.Err() + case <-done: + return result, sqlErr + } +} + +// CheckNamedValue implements the NamedValueChecker interface. +func (c *conn) CheckNamedValue(nv *driver.NamedValue) error { + // - called by sql driver for ExecContext and QueryContext + // - no check needs to be performed as ExecContext and QueryContext provided + // with parameters will force the 'prepare way' (driver.ErrSkip) + // - Anyway, CheckNamedValue must be implemented to avoid default sql driver checks + // which would fail for custom arg types like Lob + return nil +} + +// Conn Raw access methods + +// HDBVersion implements the Conn interface. +func (c *conn) HDBVersion() *Version { return c.session.hdbVersion } + +// DatabaseName implements the Conn interface. +func (c *conn) DatabaseName() string { return c.session.databaseName } + +// DBConnectInfo implements the Conn interface. +func (c *conn) DBConnectInfo(ctx context.Context, databaseName string) (*DBConnectInfo, error) { + var sqlErr error + var ci *DBConnectInfo + done := make(chan struct{}) + wgroup.Go(c.wg, func() { + defer close(done) + ci, sqlErr = c.session.dbConnectInfo(ctx, databaseName) + }) + + select { + case <-ctx.Done(): + c.session.cancel() + return nil, ctx.Err() + case <-done: + return ci, sqlErr + } +} + +// transaction. + +// check if tx implements all required interfaces. +var ( + _ driver.Tx = (*tx)(nil) +) + +type tx struct { + conn *conn + closed atomic.Bool +} + +func newTx(conn *conn) *tx { + conn.metrics.msgCh <- gaugeMsg{idx: gaugeTx, v: 1} // increment number of transactions. + return &tx{conn: conn} +} + +func (t *tx) Commit() error { return t.close(false) } +func (t *tx) Rollback() error { return t.close(true) } + +func (t *tx) close(rollback bool) error { + c := t.conn + + c.metrics.msgCh <- gaugeMsg{idx: gaugeTx, v: -1} // decrement number of transactions. + + defer func() { + c.session.inTx.Store(false) + }() + + if c.session.isBad() { + return driver.ErrBadConn + } + if closed := t.closed.Swap(true); closed { + return nil + } + + if rollback { + return c.session.rollback(context.Background()) + } + return c.session.commit(context.Background()) +} diff --git a/vendor/github.com/SAP/go-hdb/driver/connector.go b/vendor/github.com/SAP/go-hdb/driver/connector.go new file mode 100644 index 00000000..7923c6ce --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/connector.go @@ -0,0 +1,966 @@ +package driver + +import ( + "context" + "crypto/tls" + "crypto/x509" + "database/sql/driver" + "fmt" + "log/slog" + "maps" + "math" + "net" + "os" + "path" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + "unique" + + "github.com/SAP/go-hdb/driver/dial" + p "github.com/SAP/go-hdb/driver/internal/protocol" + "github.com/SAP/go-hdb/driver/internal/protocol/auth" + "github.com/SAP/go-hdb/driver/unicode/cesu8" + "golang.org/x/text/transform" +) + +type redirectCacheKey struct { + host, databaseName string +} + +var redirectCache sync.Map + +/* +SessionVariables maps session variables to their values. +All defined session variables will be set once after a database connection is opened. +*/ +type SessionVariables map[string]string + +// conn attributes default values. +const ( + defaultBufferSize = 16276 // default value bufferSize. + defaultBulkSize = 10000 // default value bulkSize. + defaultTimeout = 300 * time.Second // default value connection timeout (300 seconds = 5 minutes). + defaultTCPKeepAlive = 15 * time.Second // default TCP keep-alive value (copied from net.dial.go) +) + +// minimal / maximal values. +const ( + minTimeout = 0 * time.Second // minimal timeout value. + minBulkSize = 1 // minimal bulkSize value. + maxBulkSize = p.MaxNumArg // maximum bulk size. +) + +const ( + defaultFetchSize = 128 // Default value fetchSize. + defaultLobChunkSize = 1 << 16 // Default value lobChunkSize. + defaultDfv = p.DfvLevel8 // Default data version format level. +) + +const ( + minFetchSize = 1 // Minimal fetchSize value. + minLobChunkSize = 128 // Minimal lobChunkSize + maxLobChunkSize = math.MaxInt32 // Maximal lobChunkSize +) + +var defaultTCPKeepAliveConfig = net.KeepAliveConfig{Enable: true} + +// connAttrs is holding connection relevant attributes. +type connAttrs struct { + timeout time.Duration + pingInterval time.Duration + bufferSize int + bulkSize int + tcpKeepAlive time.Duration // see net.Dialer + tcpKeepAliveConfig net.KeepAliveConfig // see net.Dialer + tlsConfig *tls.Config + defaultSchema string + dialer dial.Dialer + applicationName string + sessionVariables map[string]string + locale string + fetchSize int + lobChunkSize int + dfv int + cesu8Decoder transform.Transformer + cesu8Encoder transform.Transformer + emptyDateAsNull bool + logger *slog.Logger +} + +func (c *connAttrs) dialContext(ctx context.Context, host string) (net.Conn, error) { + return c.dialer.DialContext(ctx, host, dial.DialerOptions{Timeout: c.timeout, TCPKeepAlive: c.tcpKeepAlive, TCPKeepAliveConfig: c.tcpKeepAliveConfig}) +} + +func readCertKeyFiles(certFile, keyFile string) (unique.Handle[string], unique.Handle[string], error) { + var handle unique.Handle[string] + cert, err := os.ReadFile(certFile) + if err != nil { + return handle, handle, err + } + key, err := os.ReadFile(keyFile) + if err != nil { + return handle, handle, err + } + return unique.Make(string(cert)), unique.Make(string(key)), nil +} + +func isJWTToken(token string) bool { return strings.HasPrefix(token, "ey") } + +/* +A Connector represents a hdb driver in a fixed configuration. +A Connector can be passed to sql.OpenDB allowing users to bypass a string based data source name. +*/ +type Connector struct { + _host string + _databaseName string + + mu sync.RWMutex + + _timeout time.Duration + _pingInterval time.Duration + _bufferSize int + _bulkSize int + _tcpKeepAlive time.Duration // see net.Dialer + _tcpKeepAliveConfig net.KeepAliveConfig // see net.Dialer + _tlsConfig *tls.Config + _defaultSchema string + _dialer dial.Dialer + _applicationName string + _sessionVariables map[string]string + _locale string + _fetchSize int + _lobChunkSize int + _dfv int + _cesu8DecoderFn func() transform.Transformer + _cesu8EncoderFn func() transform.Transformer + _emptyDateAsNull bool + _logger *slog.Logger + + hasCookie atomic.Bool + _username, _password string // basic authentication + _certFile, _keyFile string + _certKey *auth.CertKey // X509 + _token string // JWT + _logonname string // session cookie login does need logon name provided by JWT authentication. + _sessionCookie []byte // authentication via session cookie (HDB currently does support only SAML and JWT - go-hdb JWT) + _refreshPasswordFn func() (password string, ok bool) + _refreshClientCertFn func() (clientCert, clientKey []byte, ok bool) + _refreshTokenFn func() (token string, ok bool) + cbmu sync.Mutex // prevents refresh callbacks from being called in parallel + + metrics *metrics +} + +// NewConnector returns a new Connector instance with default values. +func NewConnector() *Connector { + return &Connector{ + _timeout: defaultTimeout, + _bufferSize: defaultBufferSize, + _bulkSize: defaultBulkSize, + _tcpKeepAlive: defaultTCPKeepAlive, + _tcpKeepAliveConfig: defaultTCPKeepAliveConfig, + _dialer: dial.DefaultDialer, + _applicationName: defaultApplicationName, + _fetchSize: defaultFetchSize, + _lobChunkSize: defaultLobChunkSize, + _dfv: defaultDfv, + _cesu8DecoderFn: cesu8.DefaultDecoder, + _cesu8EncoderFn: cesu8.DefaultEncoder, + _logger: slog.Default(), + metrics: stdHdbDriver.metrics, // use default stdHdbDriver metrics + } +} + +// NewBasicAuthConnector creates a connector for basic authentication. +func NewBasicAuthConnector(host, username, password string) *Connector { + c := NewConnector() + c._host = host + c._username = username + c._password = password + return c +} + +// NewX509AuthConnector creates a connector for X509 (client certificate) authentication. +// Parameters clientCert and clientKey in PEM format, clientKey not password encryped. +func NewX509AuthConnector(host string, clientCert, clientKey []byte) (*Connector, error) { + c := NewConnector() + c._host = host + var err error + if c._certKey, err = auth.NewCertKey(unique.Make(string(clientCert)), unique.Make(string(clientKey))); err != nil { + return nil, err + } + return c, nil +} + +// NewX509AuthConnectorByFiles creates a connector for X509 (client certificate) authentication +// based on client certificate and client key files. +// Parameters clientCertFile and clientKeyFile in PEM format, clientKeyFile not password encryped. +func NewX509AuthConnectorByFiles(host, clientCertFile, clientKeyFile string) (*Connector, error) { + c := NewConnector() + c._host = host + + clientCertFile = path.Clean(clientCertFile) + clientKeyFile = path.Clean(clientKeyFile) + + certHandle, keyHandle, err := readCertKeyFiles(clientCertFile, clientKeyFile) + if err != nil { + return nil, err + } + if c._certKey, err = auth.NewCertKey(certHandle, keyHandle); err != nil { + return nil, err + } + + c._certFile = clientCertFile + c._keyFile = clientKeyFile + + return c, nil +} + +// NewJWTAuthConnector creates a connector for token (JWT) based authentication. +func NewJWTAuthConnector(host, token string) *Connector { + c := NewConnector() + c._host = host + c._token = token + return c +} + +func newDSNConnector(dsn *DSN) (*Connector, error) { + c := NewConnector() + c._host = dsn.host + c._databaseName = dsn.databaseName + c._pingInterval = dsn.pingInterval + c._defaultSchema = dsn.defaultSchema + c.setTimeout(dsn.timeout) + if dsn.tls != nil { + if err := c.setTLS(dsn.tls.ServerName, dsn.tls.InsecureSkipVerify, dsn.tls.RootCAFiles); err != nil { + return nil, err + } + } + c._username = dsn.username + c._password = dsn.password + return c, nil +} + +// NewDSNConnector creates a connector from a data source name. +func NewDSNConnector(dsnStr string) (*Connector, error) { + dsn, err := ParseDSN(dsnStr) + if err != nil { + return nil, err + } + return newDSNConnector(dsn) +} + +// NativeDriver returns the concrete underlying Driver of the Connector. +func (c *Connector) NativeDriver() Driver { return stdHdbDriver } + +// Host returns the host of the connector. +func (c *Connector) Host() string { return c._host } + +// DatabaseName returns the tenant database name of the connector. +func (c *Connector) DatabaseName() string { return c._databaseName } + +func (c *Connector) fetchRedirectHost(ctx context.Context) (string, error) { + conn, err := newConn(ctx, c._host, c.metrics, c.connAttrs(), nil) + if err != nil { + return "", err + } + defer conn.Close() + dbi, err := conn.session.dbConnectInfo(ctx, c._databaseName) + if err != nil { + return "", err + } + if dbi.IsConnected { // if databaseName == "SYSTEMDB" and isConnected == true host and port are initial + return c._host, nil + } + return net.JoinHostPort(dbi.Host, strconv.Itoa(dbi.Port)), nil +} + +func (c *Connector) connect(ctx context.Context, host string) (driver.Conn, error) { + var connAttrs = c.connAttrs() + + // can we connect via cookie? + if auth := c.cookieAuth(); auth != nil { + conn, err := newConn(ctx, host, c.metrics, connAttrs, auth) + if err == nil { + return conn, nil + } + if !isAuthError(err) { + return nil, err + } + c.invalidateCookie() // cookie auth was not successful - do not try again with the same data + } + + c.cbmu.Lock() // synchronize refresh calls + defer c.cbmu.Unlock() + for { + authHnd := c.authHnd() + + conn, connErr := newConn(ctx, host, c.metrics, connAttrs, authHnd) + if connErr == nil { + if method, ok := authHnd.Selected().(auth.CookieGetter); ok { + c.setCookie(method.Cookie()) + } + return conn, nil + } + if !isAuthError(connErr) { + return nil, connErr + } + + ok, err := c.refresh() + if err != nil { + return nil, err + } + if !ok { // no connection retry in case no refresh took place + return nil, connErr + } + } +} + +func (c *Connector) redirect(ctx context.Context) (driver.Conn, error) { + if redirectHost, found := redirectCache.Load(redirectCacheKey{host: c._host, databaseName: c._databaseName}); found { + if conn, err := c.connect(ctx, redirectHost.(string)); err == nil { + return conn, nil + } + } + + redirectHost, err := c.fetchRedirectHost(ctx) + if err != nil { + return nil, err + } + conn, err := c.connect(ctx, redirectHost) + if err != nil { + return nil, err + } + + redirectCache.Store(redirectCacheKey{host: c._host, databaseName: c._databaseName}, redirectHost) + + return conn, err +} + +// Connect implements the database/sql/driver/Connector interface. +func (c *Connector) Connect(ctx context.Context) (driver.Conn, error) { + if c._databaseName != "" { + return c.redirect(ctx) + } + return c.connect(ctx, c._host) +} + +// Driver implements the database/sql/driver/Connector interface. +func (c *Connector) Driver() driver.Driver { return stdHdbDriver } + +func (c *Connector) clone() *Connector { + c.mu.RLock() + defer c.mu.RUnlock() + + return &Connector{ + _host: c._host, + _databaseName: c._databaseName, + + _timeout: c._timeout, + _pingInterval: c._pingInterval, + _bufferSize: c._bufferSize, + _bulkSize: c._bulkSize, + _tcpKeepAlive: c._tcpKeepAlive, + _tcpKeepAliveConfig: c._tcpKeepAliveConfig, + _tlsConfig: c._tlsConfig.Clone(), + _defaultSchema: c._defaultSchema, + _dialer: c._dialer, + _applicationName: c._applicationName, + _sessionVariables: maps.Clone(c._sessionVariables), + _locale: c._locale, + _fetchSize: c._fetchSize, + _lobChunkSize: c._lobChunkSize, + _dfv: c._dfv, + _cesu8DecoderFn: c._cesu8DecoderFn, + _cesu8EncoderFn: c._cesu8EncoderFn, + _emptyDateAsNull: c._emptyDateAsNull, + _logger: c._logger, + + _username: c._username, + _password: c._password, + _certFile: c._certFile, + _keyFile: c._keyFile, + _certKey: c._certKey, + _token: c._token, + _refreshPasswordFn: c._refreshPasswordFn, + _refreshClientCertFn: c._refreshClientCertFn, + _refreshTokenFn: c._refreshTokenFn, + + metrics: c.metrics, + } +} + +// WithDatabase returns a new Connector supporting tenant database connections via database name. +func (c *Connector) WithDatabase(databaseName string) *Connector { + nc := c.clone() + nc._databaseName = databaseName + return nc +} + +// conn attributes. +func (c *Connector) connAttrs() *connAttrs { + c.mu.RLock() + defer c.mu.RUnlock() + + return &connAttrs{ + timeout: c._timeout, + pingInterval: c._pingInterval, + bufferSize: c._bufferSize, + bulkSize: c._bulkSize, + tcpKeepAlive: c._tcpKeepAlive, + tcpKeepAliveConfig: c._tcpKeepAliveConfig, + tlsConfig: c._tlsConfig.Clone(), + defaultSchema: c._defaultSchema, + dialer: c._dialer, + applicationName: c._applicationName, + sessionVariables: maps.Clone(c._sessionVariables), + locale: c._locale, + fetchSize: c._fetchSize, + lobChunkSize: c._lobChunkSize, + dfv: c._dfv, + cesu8Decoder: c._cesu8DecoderFn(), + cesu8Encoder: c._cesu8EncoderFn(), + emptyDateAsNull: c._emptyDateAsNull, + logger: c._logger, + } +} + +// TCPKeepAliveConfig returns the tcp keep-alive config value of the connector. +func (c *Connector) TCPKeepAliveConfig() net.KeepAliveConfig { + c.mu.RLock() + defer c.mu.RUnlock() + return c._tcpKeepAliveConfig +} + +/* +SetTCPKeepAliveConfig sets the tcp keep-alive config value of the connector. + +For more information please see net.Dialer structure. +*/ +func (c *Connector) SetTCPKeepAliveConfig(tcpKeepAliveConfig net.KeepAliveConfig) { + c.mu.Lock() + defer c.mu.Unlock() + c._tcpKeepAliveConfig = tcpKeepAliveConfig +} + +func (c *Connector) setTimeout(timeout time.Duration) { + if timeout < minTimeout { + timeout = minTimeout + } + c._timeout = timeout +} +func (c *Connector) setBulkSize(bulkSize int) { + switch { + case bulkSize < minBulkSize: + bulkSize = minBulkSize + case bulkSize > maxBulkSize: + bulkSize = maxBulkSize + } + c._bulkSize = bulkSize +} +func (c *Connector) setTLS(serverName string, insecureSkipVerify bool, rootCAFiles []string) error { + c._tlsConfig = &tls.Config{ + ServerName: serverName, + InsecureSkipVerify: insecureSkipVerify, //nolint:gosec + } + var certPool *x509.CertPool + for _, fn := range rootCAFiles { + rootPEM, err := os.ReadFile(path.Clean(fn)) + if err != nil { + return err + } + if certPool == nil { + certPool = x509.NewCertPool() + } + if ok := certPool.AppendCertsFromPEM(rootPEM); !ok { + return fmt.Errorf("failed to parse root certificate - filename: %s", fn) + } + } + if certPool != nil { + c._tlsConfig.RootCAs = certPool + } + return nil +} +func (c *Connector) setDialer(dialer dial.Dialer) { + if dialer == nil { + dialer = dial.DefaultDialer + } + c._dialer = dialer +} +func (c *Connector) setFetchSize(fetchSize int) { + if fetchSize < minFetchSize { + fetchSize = minFetchSize + } + c._fetchSize = fetchSize +} +func (c *Connector) setLobChunkSize(lobChunkSize int) { + switch { + case lobChunkSize < minLobChunkSize: + lobChunkSize = minLobChunkSize + case lobChunkSize > maxLobChunkSize: + lobChunkSize = maxLobChunkSize + } + c._lobChunkSize = lobChunkSize +} +func (c *Connector) setDfv(dfv int) { + if !p.IsSupportedDfv(dfv) { + dfv = defaultDfv + } + c._dfv = dfv +} + +// Timeout returns the timeout of the connector. +func (c *Connector) Timeout() time.Duration { c.mu.RLock(); defer c.mu.RUnlock(); return c._timeout } + +/* +SetTimeout sets the timeout of the connector. + +For more information please see DSNTimeout. +*/ +func (c *Connector) SetTimeout(timeout time.Duration) { + c.mu.Lock() + defer c.mu.Unlock() + c.setTimeout(timeout) +} + +// PingInterval returns the connection ping interval of the connector. +func (c *Connector) PingInterval() time.Duration { + c.mu.RLock() + defer c.mu.RUnlock() + return c._pingInterval +} + +/* +SetPingInterval sets the connection ping interval value of the connector. + +Using a ping interval supports detecting broken connections. In case the ping +is not successful a new or another connection out of the connection pool would +be used automatically instead of retuning an error. + +Parameter d defines the time between the pings as duration. +If d is zero no ping is executed. If d is not zero a database ping is executed if +an idle connection out of the connection pool is reused and the time since the +last connection access is greater or equal than d. +*/ +func (c *Connector) SetPingInterval(d time.Duration) { + c.mu.Lock() + defer c.mu.Unlock() + c._pingInterval = d +} + +// BufferSize returns the bufferSize of the connector. +func (c *Connector) BufferSize() int { c.mu.RLock(); defer c.mu.RUnlock(); return c._bufferSize } + +/* +SetBufferSize sets the bufferSize of the connector. +*/ +func (c *Connector) SetBufferSize(bufferSize int) { + c.mu.Lock() + defer c.mu.Unlock() + c._bufferSize = bufferSize +} + +// BulkSize returns the bulkSize of the connector. +func (c *Connector) BulkSize() int { c.mu.RLock(); defer c.mu.RUnlock(); return c._bulkSize } + +// SetBulkSize sets the bulkSize of the connector. +func (c *Connector) SetBulkSize(bulkSize int) { + c.mu.Lock() + defer c.mu.Unlock() + c.setBulkSize(bulkSize) +} + +// TCPKeepAlive returns the tcp keep-alive value of the connector. +func (c *Connector) TCPKeepAlive() time.Duration { + c.mu.RLock() + defer c.mu.RUnlock() + return c._tcpKeepAlive +} + +/* +SetTCPKeepAlive sets the tcp keep-alive value of the connector. + +For more information please see net.Dialer structure. +*/ +func (c *Connector) SetTCPKeepAlive(tcpKeepAlive time.Duration) { + c.mu.Lock() + defer c.mu.Unlock() + c._tcpKeepAlive = tcpKeepAlive +} + +// DefaultSchema returns the database default schema of the connector. +func (c *Connector) DefaultSchema() string { + c.mu.RLock() + defer c.mu.RUnlock() + return c._defaultSchema +} + +// SetDefaultSchema sets the database default schema of the connector. +func (c *Connector) SetDefaultSchema(schema string) { + c.mu.Lock() + defer c.mu.Unlock() + c._defaultSchema = schema +} + +// TLSConfig returns the TLS configuration of the connector. +func (c *Connector) TLSConfig() *tls.Config { + c.mu.RLock() + defer c.mu.RUnlock() + return c._tlsConfig.Clone() +} + +// SetTLS sets the TLS configuration of the connector with given parameters. An existing connector TLS configuration is replaced. +func (c *Connector) SetTLS(serverName string, insecureSkipVerify bool, rootCAFiles ...string) error { + c.mu.Lock() + defer c.mu.Unlock() + return c.setTLS(serverName, insecureSkipVerify, rootCAFiles) +} + +// SetTLSConfig sets the TLS configuration of the connector. +func (c *Connector) SetTLSConfig(tlsConfig *tls.Config) { + c.mu.Lock() + defer c.mu.Unlock() + c._tlsConfig = tlsConfig.Clone() +} + +// Dialer returns the dialer object of the connector. +func (c *Connector) Dialer() dial.Dialer { c.mu.RLock(); defer c.mu.RUnlock(); return c._dialer } + +// SetDialer sets the dialer object of the connector. +func (c *Connector) SetDialer(dialer dial.Dialer) { + c.mu.Lock() + defer c.mu.Unlock() + c.setDialer(dialer) +} + +// ApplicationName returns the application name of the connector. +func (c *Connector) ApplicationName() string { + c.mu.RLock() + defer c.mu.RUnlock() + return c._applicationName +} + +// SetApplicationName sets the application name of the connector. +func (c *Connector) SetApplicationName(name string) { + c.mu.Lock() + defer c.mu.Unlock() + c._applicationName = name +} + +// SessionVariables returns the session variables stored in connector. +func (c *Connector) SessionVariables() SessionVariables { + c.mu.RLock() + defer c.mu.RUnlock() + return maps.Clone(c._sessionVariables) +} + +// SetSessionVariables sets the session varibles of the connector. +func (c *Connector) SetSessionVariables(sessionVariables SessionVariables) { + c.mu.Lock() + defer c.mu.Unlock() + c._sessionVariables = maps.Clone(sessionVariables) +} + +// Locale returns the locale of the connector. +func (c *Connector) Locale() string { c.mu.RLock(); defer c.mu.RUnlock(); return c._locale } + +/* +SetLocale sets the locale of the connector. + +For more information please see http://help.sap.com/hana/SAP_HANA_SQL_Command_Network_Protocol_Reference_en.pdf. +*/ +func (c *Connector) SetLocale(locale string) { c.mu.Lock(); defer c.mu.Unlock(); c._locale = locale } + +// FetchSize returns the fetchSize of the connector. +func (c *Connector) FetchSize() int { c.mu.RLock(); defer c.mu.RUnlock(); return c._fetchSize } + +/* +SetFetchSize sets the fetchSize of the connector. + +For more information please see DSNFetchSize. +*/ +func (c *Connector) SetFetchSize(fetchSize int) { + c.mu.Lock() + defer c.mu.Unlock() + c.setFetchSize(fetchSize) +} + +// LobChunkSize returns the lobChunkSize of the connector. +func (c *Connector) LobChunkSize() int { c.mu.RLock(); defer c.mu.RUnlock(); return c._lobChunkSize } + +// SetLobChunkSize sets the lobChunkSize of the connector. +func (c *Connector) SetLobChunkSize(lobChunkSize int) { + c.mu.Lock() + defer c.mu.Unlock() + c.setLobChunkSize(lobChunkSize) +} + +// Dfv returns the client data format version of the connector. +func (c *Connector) Dfv() int { c.mu.RLock(); defer c.mu.RUnlock(); return c._dfv } + +// SetDfv sets the client data format version of the connector. +func (c *Connector) SetDfv(dfv int) { c.mu.Lock(); defer c.mu.Unlock(); c.setDfv(dfv) } + +// CESU8Decoder returns the CESU-8 decoder of the connector. +func (c *Connector) CESU8Decoder() func() transform.Transformer { + c.mu.RLock() + defer c.mu.RUnlock() + return c._cesu8DecoderFn +} + +// SetCESU8Decoder sets the CESU-8 decoder of the connector. +func (c *Connector) SetCESU8Decoder(cesu8DecoderFn func() transform.Transformer) { + c.mu.Lock() + defer c.mu.Unlock() + if cesu8DecoderFn == nil { + cesu8DecoderFn = cesu8.DefaultDecoder + } + c._cesu8DecoderFn = cesu8DecoderFn +} + +// CESU8Encoder returns the CESU-8 encoder of the connector. +func (c *Connector) CESU8Encoder() func() transform.Transformer { + c.mu.RLock() + defer c.mu.RUnlock() + return c._cesu8EncoderFn +} + +// SetCESU8Encoder sets the CESU-8 encoder of the connector. +func (c *Connector) SetCESU8Encoder(cesu8EncoderFn func() transform.Transformer) { + c.mu.Lock() + defer c.mu.Unlock() + if cesu8EncoderFn == nil { + cesu8EncoderFn = cesu8.DefaultEncoder + } + c._cesu8EncoderFn = cesu8EncoderFn +} + +/* +EmptyDateAsNull returns NULL for empty dates ('0000-00-00') if true, otherwise: + +For data format version 1 the backend does return the NULL indicator for empty date fields. +For data format version non equal 1 (field type daydate) the NULL indicator is not set and the return value is 0. +As value 1 represents '0001-01-01' (the minimal valid date) without setting EmptyDateAsNull '0000-12-31' is returned, +so that NULL, empty and valid dates can be distinguished. + +https://help.sap.com/docs/HANA_SERVICE_CF/7c78579ce9b14a669c1f3295b0d8ca16/3f81ccc7e35d44cbbc595c7d552c202a.html?locale=en-US +*/ +func (c *Connector) EmptyDateAsNull() bool { + c.mu.RLock() + defer c.mu.RUnlock() + return c._emptyDateAsNull +} + +// SetEmptyDateAsNull sets the EmptyDateAsNull flag of the connector. +func (c *Connector) SetEmptyDateAsNull(emptyDateAsNull bool) { + c.mu.Lock() + defer c.mu.Unlock() + c._emptyDateAsNull = emptyDateAsNull +} + +// Logger returns the Logger instance of the connector. +func (c *Connector) Logger() *slog.Logger { + c.mu.RLock() + defer c.mu.RUnlock() + return c._logger +} + +// SetLogger sets the Logger instance of the connector. +func (c *Connector) SetLogger(logger *slog.Logger) { + c.mu.Lock() + defer c.mu.Unlock() + if logger == nil { + logger = slog.Default() + } + c._logger = logger +} + +// auth attributes. +func (c *Connector) cookieAuth() *p.AuthHnd { + if !c.hasCookie.Load() { // fastpath without lock + return nil + } + + c.mu.RLock() + defer c.mu.RUnlock() + + auth := p.NewAuthHnd(c._logonname) // important: for session cookie auth we do need the logonname from JWT auth, + auth.AddSessionCookie(c._sessionCookie, c._logonname, clientID) // and for HANA onPrem the final session cookie req needs the logonname as well. + return auth +} + +func (c *Connector) authHnd() *p.AuthHnd { + c.mu.RLock() + defer c.mu.RUnlock() + + authHnd := p.NewAuthHnd(c._username) // use username as logonname + if c._certKey != nil { + authHnd.AddX509(c._certKey) + } + if c._token != "" { + authHnd.AddJWT(c._token) + } + // mimic standard drivers and use password as token if user is empty + if c._token == "" && c._username == "" && isJWTToken(c._password) { + authHnd.AddJWT(c._password) + } + if c._password != "" { + authHnd.AddBasic(c._username, c._password) + } + return authHnd +} + +func (c *Connector) refresh() (bool, error) { + refreshed := false + + callRefreshPassword := func(refreshPassword func() (string, bool)) (string, bool) { + defer c.mu.Lock() // finally lock attr again + c.mu.Unlock() // unlock attr, so that callback can call attr methods + return refreshPassword() + } + + callRefreshToken := func(refreshToken func() (token string, ok bool)) (string, bool) { + defer c.mu.Lock() // finally lock attr again + c.mu.Unlock() // unlock attr, so that callback can call attr methods + return refreshToken() + } + + callRefreshClientCert := func(refreshClientCert func() (clientCert, clientKey []byte, ok bool)) (unique.Handle[string], unique.Handle[string], bool) { + var handle unique.Handle[string] + defer c.mu.Lock() // finally lock attr again + c.mu.Unlock() // unlock attr, so that callback can call attr methods + clientCert, clientKey, ok := refreshClientCert() + if !ok { + return handle, handle, false + } + return unique.Make(string(clientCert)), unique.Make(string(clientKey)), true + } + + c.mu.Lock() + defer c.mu.Unlock() + + if c._refreshPasswordFn != nil { + if password, ok := callRefreshPassword(c._refreshPasswordFn); ok { + if password != c._password { + c._password = password + refreshed = true + } + } + } + if c._refreshTokenFn != nil { + if token, ok := callRefreshToken(c._refreshTokenFn); ok { + if token != c._token { + c._token = token + refreshed = true + } + } + } + if c._refreshClientCertFn != nil { + if certHandle, keyHandle, ok := callRefreshClientCert(c._refreshClientCertFn); ok { + if c._certKey == nil || !c._certKey.Equal(certHandle, keyHandle) { + certKey, err := auth.NewCertKey(certHandle, keyHandle) + if err != nil { + return refreshed, err + } + c._certKey = certKey + refreshed = true + } + } + } else if c._certFile != "" && c._keyFile != "" { + if certHandle, keyHandle, err := readCertKeyFiles(c._certFile, c._keyFile); err != nil { + if c._certKey == nil || !c._certKey.Equal(certHandle, keyHandle) { + certKey, err := auth.NewCertKey(certHandle, keyHandle) + if err != nil { + return refreshed, err + } + c._certKey = certKey + refreshed = true + } + } + } + return refreshed, nil +} + +func (c *Connector) invalidateCookie() { c.hasCookie.Store(false) } + +func (c *Connector) setCookie(logonname string, sessionCookie []byte) { + c.mu.Lock() + defer c.mu.Unlock() + c.hasCookie.Store(true) + c._logonname = logonname + c._sessionCookie = sessionCookie +} + +// Username returns the username of the connector. +func (c *Connector) Username() string { c.mu.RLock(); defer c.mu.RUnlock(); return c._username } + +// Password returns the basic authentication password of the connector. +func (c *Connector) Password() string { c.mu.RLock(); defer c.mu.RUnlock(); return c._password } + +// SetPassword sets the basic authentication password of the connector. +func (c *Connector) SetPassword(password string) { + c.mu.Lock() + defer c.mu.Unlock() + c._password = password +} + +// RefreshPassword returns the callback function for basic authentication password refresh. +func (c *Connector) RefreshPassword() func() (password string, ok bool) { + c.mu.RLock() + defer c.mu.RUnlock() + return c._refreshPasswordFn +} + +// SetRefreshPassword sets the callback function for basic authentication password refresh. +// The callback function might be called simultaneously from multiple goroutines only if registered +// for more than one Connector. +func (c *Connector) SetRefreshPassword(refreshPasswordFn func() (password string, ok bool)) { + c.mu.Lock() + defer c.mu.Unlock() + c._refreshPasswordFn = refreshPasswordFn +} + +// ClientCert returns the X509 authentication client certificate and key of the connector. +func (c *Connector) ClientCert() (clientCert, clientKey []byte) { + c.mu.RLock() + defer c.mu.RUnlock() + return c._certKey.Cert(), c._certKey.Key() +} + +// RefreshClientCert returns the callback function for X509 authentication client certificate and key refresh. +func (c *Connector) RefreshClientCert() func() (clientCert, clientKey []byte, ok bool) { + c.mu.RLock() + defer c.mu.RUnlock() + return c._refreshClientCertFn +} + +// SetRefreshClientCert sets the callback function for X509 authentication client certificate and key refresh. +// The callback function might be called simultaneously from multiple goroutines only if registered +// for more than one Connector. +func (c *Connector) SetRefreshClientCert(refreshClientCertFn func() (clientCert, clientKey []byte, ok bool)) { + c.mu.Lock() + defer c.mu.Unlock() + c._refreshClientCertFn = refreshClientCertFn +} + +// Token returns the JWT authentication token of the connector. +func (c *Connector) Token() string { c.mu.RLock(); defer c.mu.RUnlock(); return c._token } + +// RefreshToken returns the callback function for JWT authentication token refresh. +func (c *Connector) RefreshToken() func() (token string, ok bool) { + c.mu.RLock() + defer c.mu.RUnlock() + return c._refreshTokenFn +} + +// SetRefreshToken sets the callback function for JWT authentication token refresh. +// The callback function might be called simultaneously from multiple goroutines only if registered +// for more than one Connector. +func (c *Connector) SetRefreshToken(refreshTokenFn func() (token string, ok bool)) { + c.mu.Lock() + defer c.mu.Unlock() + c._refreshTokenFn = refreshTokenFn +} diff --git a/vendor/github.com/SAP/go-hdb/driver/convert.go b/vendor/github.com/SAP/go-hdb/driver/convert.go new file mode 100644 index 00000000..fafb157a --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/convert.go @@ -0,0 +1,308 @@ +package driver + +import ( + "database/sql" + "database/sql/driver" + "fmt" + "reflect" + + p "github.com/SAP/go-hdb/driver/internal/protocol" + "github.com/SAP/go-hdb/driver/internal/protocol/levenshtein" + "golang.org/x/text/transform" +) + +// TODO: test. +func reorderNVArgs(pos int, name string, nvargs []driver.NamedValue) { + for i := pos; i < len(nvargs); i++ { + if nvargs[i].Name != "" && nvargs[i].Name == name { + tmp := nvargs[i] + for j := i; j > pos; j-- { + nvargs[j] = nvargs[j-1] + } + nvargs[pos] = tmp + } + } +} + +func valuerValue(v driver.Valuer) (driver.Value, error) { + // This is taken from the database/sql package. + // The Elem() test is not needed bacause the function is only + // called for values implementing the driver.Valuer interface. + if rv := reflect.ValueOf(v); rv.Kind() == reflect.Pointer && rv.IsNil() { + // && rv.Type().Elem().Implements(valuerReflectType) { + return nil, nil + } + // changes in sql.Null Value handling + // <= go1.23 + /* + func (n Null[T]) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + return n.V, nil + } + */ + // >= 1.24 + /* + func (n Null[T]) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + v := any(n.V) + // See issue 69728. + if valuer, ok := v.(driver.Valuer); ok { + val, err := callValuerValue(valuer) + if err != nil { + return val, err + } + v = val + } + // See issue 69837. + return driver.DefaultParameterConverter.ConvertValue(v) + } + */ + // As sql.Null Value is now calling the default converter this totally breaks + // the generic usage on custom data types. + // Therefore we do need to handle custom types ourselves. + + switch v := v.(type) { + case sql.Null[Decimal]: + if !v.Valid { + return nil, nil + } + return v.V, nil + case sql.Null[*Decimal]: + if !v.Valid { + return nil, nil + } + return v.V, nil + case *sql.Null[Decimal]: + if !v.Valid { + return nil, nil + } + return v.V, nil + case *sql.Null[*Decimal]: + if !v.Valid { + return nil, nil + } + return v.V, nil + case sql.Null[Lob]: + if !v.Valid { + return nil, nil + } + return v.V, nil + case sql.Null[*Lob]: + if !v.Valid { + return nil, nil + } + return v.V, nil + case *sql.Null[Lob]: + if !v.Valid { + return nil, nil + } + return v.V, nil + case *sql.Null[*Lob]: + if !v.Valid { + return nil, nil + } + return v.V, nil + default: + return v.Value() + } +} + +func convertArg(field *p.ParameterField, arg any, cesu8Encoder transform.Transformer) (any, error) { + // let fields with own value converter convert themselves first (e.g. NullInt64, ...) + // .check nested Value converters as well (e.g. sql.Null[T] has driver.Decimal as value) + for { + valuer, ok := arg.(driver.Valuer) + if !ok { + break + } + var err error + if arg, err = valuerValue(valuer); err != nil { + return nil, err + } + } + + // convert field + return field.Convert(arg, cesu8Encoder) +} + +/* +convertExecArgs + - all fields need to be input fields + - out parameters are not supported + - named parameters are not supported +*/ +func convertExecArgs(fields []*p.ParameterField, nvargs []driver.NamedValue, cesu8Encoder transform.Transformer, lobChunkSize int) ([]int, error) { + numField := len(fields) + if (len(nvargs) % numField) != 0 { + return nil, fmt.Errorf("invalid number of arguments %d - multiple of %d expected", len(nvargs), numField) + } + numRow := len(nvargs) / numField + addLobDataRecs := []int{} + + for i := range numRow { + hasAddLobData := false + for j, field := range fields { + nvarg := &nvargs[(i*numField)+j] + + if field.Out() { + return nil, fmt.Errorf("invalid parameter %s - output not allowed", field) + } + if _, ok := nvarg.Value.(sql.Out); ok { + return nil, fmt.Errorf("invalid argument %v - output not allowed", nvarg) + } + if nvarg.Name != "" { + return nil, fmt.Errorf("invalid argument %s - named parameters not supported", nvarg.Name) + } + var err error + if nvarg.Value, err = convertArg(field, nvarg.Value, cesu8Encoder); err != nil { + return nil, fmt.Errorf("field %s conversion error - %w", field, err) + } + // fetch first lob chunk + if lobInDescr, ok := nvarg.Value.(*p.LobInDescr); ok { + if err := lobInDescr.FetchNext(lobChunkSize); err != nil { + return nil, err + } + if !lobInDescr.IsLastData() { + hasAddLobData = true + } + } + } + if hasAddLobData || i == numRow-1 { + addLobDataRecs = append(addLobDataRecs, i) + } + } + return addLobDataRecs, nil +} + +/* +_convertQueryArgs + - all fields need to be input fields + - out parameters are not supported + - named parameters are not supported +*/ +func convertQueryArgs(fields []*p.ParameterField, nvargs []driver.NamedValue, cesu8Encoder transform.Transformer, lobChunkSize int) error { + if len(nvargs) != len(fields) { + return fmt.Errorf("invalid number of arguments %d - %d expected", len(nvargs), len(fields)) + } + + for i, field := range fields { + nvarg := &nvargs[i] + if field.Out() { + return fmt.Errorf("invalid parameter %s - output not allowed", field) + } + if _, ok := nvarg.Value.(sql.Out); ok { + return fmt.Errorf("invalid argument %v - output not allowed", nvarg) + } + if nvarg.Name != "" { + return fmt.Errorf("invalid argument %s - named parameters not supported", nvarg.Name) + } + var err error + if nvarg.Value, err = convertArg(field, nvarg.Value, cesu8Encoder); err != nil { + return fmt.Errorf("field %s conversion error - %w", field, err) + } + // fetch first lob chunk + if lobInDescr, ok := nvarg.Value.(*p.LobInDescr); ok { + if err := lobInDescr.FetchNext(lobChunkSize); err != nil { + return err + } + } + } + return nil +} + +// convertCallArgs +// - fields could be input or output fields +// - number of args needs to be equal to number of fields +// - named parameters are supported + +type callArgs struct { + inFields, outFields []*p.ParameterField + inArgs, outArgs []driver.NamedValue +} + +func newCallArgs() *callArgs { + return &callArgs{ + inFields: []*p.ParameterField{}, + outFields: []*p.ParameterField{}, + inArgs: []driver.NamedValue{}, + outArgs: []driver.NamedValue{}, + } +} + +func convertCallArgs(fields []*p.ParameterField, nvargs []driver.NamedValue, cesu8Encoder transform.Transformer, lobChunkSize int) (*callArgs, error) { + callArgs := newCallArgs() + + if len(nvargs) < len(fields) { // number of fields needs to match number of args or be greater (add table output args) + return nil, fmt.Errorf("invalid number of arguments %d - %d expected", len(nvargs), len(fields)) + } + + prmnvargs := nvargs[:len(fields)] + + for i, field := range fields { + reorderNVArgs(i, field.Name(), prmnvargs) + + nvarg := &prmnvargs[i] + + if nvarg.Name != "" && nvarg.Name != field.Name() { + return nil, fmt.Errorf("invalid argument name %s - did you mean %s?", + nvarg.Name, + levenshtein.MinString(fields, func(field *p.ParameterField) string { return field.Name() }, nvarg.Name, false), + ) + } + + out, isOut := nvarg.Value.(sql.Out) + + var err error + if field.In() { + if isOut { + if !out.In { + return nil, fmt.Errorf("argument field %s mismatch - use in argument with out field", field) + } + if out.Dest, err = convertArg(field, out.Dest, cesu8Encoder); err != nil { + return nil, fmt.Errorf("field %s conversion error - %w", field, err) + } + } else { + if nvarg.Value, err = convertArg(field, nvarg.Value, cesu8Encoder); err != nil { + return nil, fmt.Errorf("field %s conversion error - %w", field, err) + } + } + // fetch first lob chunk + if lobInDescr, ok := nvarg.Value.(*p.LobInDescr); ok { + if err := lobInDescr.FetchNext(lobChunkSize); err != nil { + return nil, err + } + } + callArgs.inArgs = append(callArgs.inArgs, *nvarg) + callArgs.inFields = append(callArgs.inFields, field) + } + + if field.Out() { + if !isOut { + return nil, fmt.Errorf("argument field %s mismatch - use out argument with non-out field", field) + } + if _, ok := out.Dest.(*sql.Rows); ok { + return nil, fmt.Errorf("invalid output parameter type %T", out.Dest) + } + callArgs.outArgs = append(callArgs.outArgs, *nvarg) + callArgs.outFields = append(callArgs.outFields, field) + } + } + + // table output args + for i := len(fields); i < len(nvargs); i++ { + nvarg := &nvargs[i] + out, ok := nvarg.Value.(sql.Out) + if !ok { + return nil, fmt.Errorf("invalid parameter type %T at %d - output parameter expected", nvarg.Value, i) + } + if _, ok := out.Dest.(*sql.Rows); !ok { + return nil, fmt.Errorf("invalid output parameter %T at %d - sql.Rows expected", out.Dest, i) + } + callArgs.outArgs = append(callArgs.outArgs, *nvarg) + } + return callArgs, nil +} diff --git a/vendor/github.com/SAP/go-hdb/driver/dbconn.go b/vendor/github.com/SAP/go-hdb/driver/dbconn.go new file mode 100644 index 00000000..7020d68d --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/dbconn.go @@ -0,0 +1,116 @@ +package driver + +import ( + "context" + "crypto/tls" + "database/sql/driver" + "fmt" + "io" + "log/slog" + "net" + "runtime/pprof" + "time" +) + +var ( + cpuProfile = false +) + +type dbConn interface { + io.ReadWriteCloser + lastRead() time.Time + lastWrite() time.Time +} + +type profileDBConn struct { + dbConn +} + +func (c *profileDBConn) Read(b []byte) (n int, err error) { + pprof.Do(context.Background(), pprof.Labels("db", "read"), func(ctx context.Context) { + n, err = c.dbConn.Read(b) + }) + return +} + +func (c *profileDBConn) Write(b []byte) (n int, err error) { + pprof.Do(context.Background(), pprof.Labels("db", "write"), func(ctx context.Context) { + n, err = c.dbConn.Write(b) + }) + return +} + +// stdDBConn wraps the database tcp connection. It sets timeouts and handles driver ErrBadConn behavior. +type stdDBConn struct { + metrics *metrics + conn net.Conn + timeout time.Duration + logger *slog.Logger + _lastRead time.Time + _lastWrite time.Time +} + +func newDBConn(ctx context.Context, logger *slog.Logger, host string, metrics *metrics, attrs *connAttrs) (dbConn, error) { + conn, err := attrs.dialContext(ctx, host) + if err != nil { + return nil, err + } + // is TLS connection requested? + if attrs.tlsConfig != nil { + conn = tls.Client(conn, attrs.tlsConfig) + } + + dbConn := &stdDBConn{metrics: metrics, conn: conn, timeout: attrs.timeout, logger: logger} + if cpuProfile { + return &profileDBConn{dbConn: dbConn}, nil + } + return dbConn, nil +} + +func (c *stdDBConn) lastRead() time.Time { return c._lastRead } +func (c *stdDBConn) lastWrite() time.Time { return c._lastWrite } + +func (c *stdDBConn) deadline() (deadline time.Time) { + if c.timeout == 0 { + return + } + return time.Now().Add(c.timeout) +} + +func (c *stdDBConn) Close() error { return c.conn.Close() } + +// Read implements the io.Reader interface. +func (c *stdDBConn) Read(b []byte) (int, error) { + // set timeout + if err := c.conn.SetReadDeadline(c.deadline()); err != nil { + return 0, fmt.Errorf("%w: %w", driver.ErrBadConn, err) + } + c._lastRead = time.Now() + n, err := c.conn.Read(b) + c.metrics.msgCh <- timeMsg{idx: timeRead, d: time.Since(c._lastRead)} + c.metrics.msgCh <- counterMsg{idx: counterBytesRead, v: uint64(n)} //nolint:gosec + if err != nil { + c.logger.LogAttrs(context.Background(), slog.LevelError, "DB conn read error", slog.String("error", err.Error()), slog.String("local address", c.conn.LocalAddr().String()), slog.String("remote address", c.conn.RemoteAddr().String())) + // wrap error in driver.ErrBadConn + return n, fmt.Errorf("%w: %w", driver.ErrBadConn, err) + } + return n, nil +} + +// Write implements the io.Writer interface. +func (c *stdDBConn) Write(b []byte) (int, error) { + // set timeout + if err := c.conn.SetWriteDeadline(c.deadline()); err != nil { + return 0, fmt.Errorf("%w: %w", driver.ErrBadConn, err) + } + c._lastWrite = time.Now() + n, err := c.conn.Write(b) + c.metrics.msgCh <- timeMsg{idx: timeWrite, d: time.Since(c._lastWrite)} + c.metrics.msgCh <- counterMsg{idx: counterBytesWritten, v: uint64(n)} //nolint:gosec + if err != nil { + c.logger.LogAttrs(context.Background(), slog.LevelError, "DB conn write error", slog.String("error", err.Error()), slog.String("local address", c.conn.LocalAddr().String()), slog.String("remote address", c.conn.RemoteAddr().String())) + // wrap error in driver.ErrBadConn + return n, fmt.Errorf("%w: %w", driver.ErrBadConn, err) + } + return n, nil +} diff --git a/vendor/github.com/SAP/go-hdb/driver/dbconnectinfo.go b/vendor/github.com/SAP/go-hdb/driver/dbconnectinfo.go new file mode 100644 index 00000000..1c4acd3f --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/dbconnectinfo.go @@ -0,0 +1,17 @@ +package driver + +import ( + "fmt" +) + +// DBConnectInfo represents the connection information attributes returned by hdb. +type DBConnectInfo struct { + DatabaseName string + Host string + Port int + IsConnected bool +} + +func (ci *DBConnectInfo) String() string { + return fmt.Sprintf("Database Name: %s Host: %s Port: %d connected: %t", ci.DatabaseName, ci.Host, ci.Port, ci.IsConnected) +} diff --git a/vendor/github.com/SAP/go-hdb/driver/decimal.go b/vendor/github.com/SAP/go-hdb/driver/decimal.go new file mode 100644 index 00000000..588078f6 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/decimal.go @@ -0,0 +1,62 @@ +package driver + +import ( + "database/sql/driver" + "fmt" + "math/big" +) + +// A Decimal is the driver representation of a database decimal field value as big.Rat. +type Decimal big.Rat + +// Scan implements the database/sql/Scanner interface. +func (d *Decimal) Scan(src any) error { + r, ok := src.(*big.Rat) + if !ok { + return fmt.Errorf("decimal: invalid data type %T", src) + } + (*big.Rat)(d).Set(r) + return nil +} + +// Value implements the database/sql/Valuer interface. +func (d Decimal) Value() (driver.Value, error) { + return (*big.Rat)(&d), nil +} + +// NullDecimal represents an Decimal that may be null. +// NullDecimal implements the Scanner interface so +// it can be used as a scan destination, similar to NullString. +type NullDecimal struct { + Decimal *Decimal + Valid bool // Valid is true if Decimal is not NULL +} + +// Scan implements the Scanner interface. +func (n *NullDecimal) Scan(value any) error { + if value == nil { + n.Valid = false + return nil + } + r, ok := value.(*big.Rat) + if !ok { + return fmt.Errorf("decimal: invalid data type %T", value) + } + n.Valid = true + if n.Decimal == nil { + n.Decimal = &Decimal{} + } + (*big.Rat)(n.Decimal).Set(r) + return nil +} + +// Value implements the driver Valuer interface. +func (n NullDecimal) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + if n.Decimal == nil { + return nil, fmt.Errorf("invalid decimal value %v", n.Decimal) + } + return (*big.Rat)(n.Decimal), nil +} diff --git a/vendor/github.com/SAP/go-hdb/driver/deprecated.go b/vendor/github.com/SAP/go-hdb/driver/deprecated.go new file mode 100644 index 00000000..0c583e46 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/deprecated.go @@ -0,0 +1,13 @@ +package driver + +import ( + "database/sql/driver" +) + +// deprecated driver interface methods. +func (*conn) Prepare(query string) (driver.Stmt, error) { panic("deprecated") } +func (*conn) Begin() (driver.Tx, error) { panic("deprecated") } +func (*conn) Exec(query string, args []driver.Value) (driver.Result, error) { panic("deprecated") } +func (*conn) Query(query string, args []driver.Value) (driver.Rows, error) { panic("deprecated") } +func (*stmt) Exec(args []driver.Value) (driver.Result, error) { panic("deprecated") } +func (*stmt) Query(args []driver.Value) (rows driver.Rows, err error) { panic("deprecated") } diff --git a/vendor/github.com/SAP/go-hdb/driver/dial/dialer.go b/vendor/github.com/SAP/go-hdb/driver/dial/dialer.go new file mode 100644 index 00000000..6d566463 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/dial/dialer.go @@ -0,0 +1,31 @@ +// Package dial provides types to implement go-hdb custom dialers. +package dial + +import ( + "context" + "net" + "time" +) + +// DialerOptions contains optional parameters that might be used by a Dialer. +type DialerOptions struct { + Timeout, TCPKeepAlive time.Duration + TCPKeepAliveConfig net.KeepAliveConfig +} + +// The Dialer interface needs to be implemented by custom Dialers. A Dialer for providing a custom driver connection +// to the database can be set in the driver.Connector object. +type Dialer interface { + DialContext(ctx context.Context, address string, options DialerOptions) (net.Conn, error) +} + +// DefaultDialer is the default driver Dialer implementation. +var DefaultDialer Dialer = &dialer{} + +// default dialer implementation. +type dialer struct{} + +func (d *dialer) DialContext(ctx context.Context, address string, options DialerOptions) (net.Conn, error) { + dialer := net.Dialer{Timeout: options.Timeout, KeepAlive: options.TCPKeepAlive, KeepAliveConfig: options.TCPKeepAliveConfig} + return dialer.DialContext(ctx, "tcp", address) +} diff --git a/vendor/github.com/SAP/go-hdb/driver/doc.go b/vendor/github.com/SAP/go-hdb/driver/doc.go new file mode 100644 index 00000000..3e1066af --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/doc.go @@ -0,0 +1,4 @@ +// Package driver is a native Go SAP HANA driver implementation for the database/sql package. +// For the SAP HANA SQL Command Network Protocol Reference please see: +// https://help.sap.com/viewer/7e4aba181371442d9e4395e7ff71b777/2.0.03/en-US/9b9d8c894343424fac157c96dcb0a592.html +package driver diff --git a/vendor/github.com/SAP/go-hdb/driver/driver.go b/vendor/github.com/SAP/go-hdb/driver/driver.go new file mode 100644 index 00000000..f6d79397 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/driver.go @@ -0,0 +1,130 @@ +package driver + +import ( + "context" + "database/sql" + "database/sql/driver" + "os" + "strconv" + "strings" +) + +// DriverVersion is the version number of the hdb driver. +const DriverVersion = "1.14.5" + +// DriverName is the driver name to use with sql.Open for hdb databases. +const DriverName = "hdb" + +var clientID = func() string { + if hostname, err := os.Hostname(); err == nil { + return strings.Join([]string{strconv.Itoa(os.Getpid()), hostname}, "@") + } + return strconv.Itoa(os.Getpid()) +}() + +// clientType is the information provided to HDB identifying the driver. +// Previously the driver.DriverName "hdb" was used but we should be more specific in providing a unique client type to HANA backend. +const clientType = "go-hdb" + +var defaultApplicationName, _ = os.Executable() + +// driver singleton instance. +var stdHdbDriver *hdbDriver + +func init() { register() } + +func register() { + // load stats configuration + if err := loadStatsCfg(); err != nil { + panic(err) // invalid configuration file + } + // create driver + stdHdbDriver = &hdbDriver{metrics: newMetrics(nil, statsCfg.TimeUnit, statsCfg.TimeUpperBounds)} + // register driver + sql.Register(DriverName, stdHdbDriver) +} + +// Unregister unregisters the go-hdb driver and frees all allocated ressources. +// After calling any go-hdb access might panic. +func Unregister() error { + return stdHdbDriver.shutdown() +} + +// driver + +// check if driver implements all required interfaces. +var ( + _ driver.Driver = (*hdbDriver)(nil) + _ driver.DriverContext = (*hdbDriver)(nil) + _ Driver = (*hdbDriver)(nil) +) + +// Driver enhances a connection with go-hdb specific connection functions. +type Driver interface { + Name() string // Name returns the driver name. + Version() string // Version returns the driver version. + Stats() *Stats // Stats returns aggregated driver statistics. +} + +// hdbDriver represents the go sql driver implementation for hdb. +type hdbDriver struct { + metrics *metrics +} + +func (d hdbDriver) shutdown() error { + d.metrics.close() + return nil +} + +// Open implements the driver.Driver interface. +func (d *hdbDriver) Open(dsn string) (driver.Conn, error) { + connector, err := NewDSNConnector(dsn) + if err != nil { + return nil, err + } + return connector.Connect(context.Background()) +} + +// OpenConnector implements the driver.DriverContext interface. +func (d *hdbDriver) OpenConnector(dsn string) (driver.Connector, error) { return NewDSNConnector(dsn) } + +// Name returns the driver name. +func (d *hdbDriver) Name() string { return DriverName } + +// Version returns the driver version. +func (d *hdbDriver) Version() string { return DriverVersion } + +// Stats returns aggregated driver statistics. +func (d *hdbDriver) Stats() *Stats { return d.metrics.stats() } + +// DB represents a driver database and can be used as a replacement for sql.DB. +// It provides all of the sql.DB methods plus additional methods only available for driver.DB. +type DB struct { + // The embedded sql.DB instance. Please use only the methods of the wrapper (driver.DB). + // The field is exported to support use cases where a sql.DB object is requested, but please + // use with care as some of the sql.DB methods (e.g. Close) might be redefined in driver.DB. + *sql.DB + metrics *metrics +} + +// OpenDB opens and returns a database. It also calls the OpenDB method of the sql package and stores an embedded *sql.DB object. +func OpenDB(c *Connector) *DB { + metrics := newMetrics(stdHdbDriver.metrics, statsCfg.TimeUnit, statsCfg.TimeUpperBounds) + nc := c.clone() + nc.metrics = metrics + return &DB{ + DB: sql.OpenDB(nc), + metrics: metrics, + } +} + +// Close closes the database. It also calls the Close method of the sql package and returns its error. +func (db *DB) Close() error { + err := db.DB.Close() + // close metrics only after db is closed. + db.metrics.close() + return err +} + +// ExStats returns the extended database statistics. +func (db *DB) ExStats() *Stats { return db.metrics.stats() } diff --git a/vendor/github.com/SAP/go-hdb/driver/dsn.go b/vendor/github.com/SAP/go-hdb/driver/dsn.go new file mode 100644 index 00000000..ab77442f --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/dsn.go @@ -0,0 +1,232 @@ +package driver + +import ( + "errors" + "fmt" + "net/url" + "strconv" + "time" +) + +// DSN parameters. +const ( + DSNDatabaseName = "databaseName" // Tenant database name. + DSNDefaultSchema = "defaultSchema" // Database default schema. + DSNTimeout = "timeout" // Driver side connection timeout in seconds. + DSNPingInterval = "pingInterval" // Connection ping interval in seconds. +) + +/* +DSN TLS parameters. +For more information please see https://golang.org/pkg/crypto/tls/#Config. +For more flexibility in TLS configuration please see driver.Connector. +*/ +const ( + DSNTLSRootCAFile = "TLSRootCAFile" // Path,- filename to root certificate(s). + DSNTLSServerName = "TLSServerName" // ServerName to verify the hostname. + DSNTLSInsecureSkipVerify = "TLSInsecureSkipVerify" // Controls whether a client verifies the server's certificate chain and host name. +) + +// TLSPrms is holding the TLS parameters of a DSN structure. +type TLSPrms struct { + ServerName string + InsecureSkipVerify bool + RootCAFiles []string +} + +const urlSchema = "hdb" // mirrored from driver/DriverName + +/* +A DSN represents a parsed DSN string. A DSN string is an URL string with the following format + + "hdb://:@:" + +and optional query parameters (see DSN query parameters and DSN query default values). + +Examples: + + "hdb://myUser:myPassword@localhost:30015?databaseName=myTenantDatabaseName" + "hdb://myUser:myPassword@localhost:30015?timeout=60" + +Examples TLS connection: + + "hdb://myUser:myPassword@localhost:39013?TLSRootCAFile=trust.pem" + "hdb://myUser:myPassword@localhost:39013?TLSRootCAFile=trust.pem&TLSServerName=hostname" + "hdb://myUser:myPassword@localhost:39013?TLSInsecureSkipVerify" +*/ +type DSN struct { + host string + username, password string + databaseName string + defaultSchema string + timeout time.Duration + pingInterval time.Duration + tls *TLSPrms +} + +// ParseError is the error returned in case DSN is invalid. +type ParseError struct { + s string + err error +} + +func (e ParseError) Error() string { + if err := errors.Unwrap(e.err); err != nil { + return err.Error() + } + return e.s +} + +// Unwrap returns the nested error. +func (e ParseError) Unwrap() error { return e.err } + +// Cause returns the cause of the error. +func (e ParseError) Cause() error { return e.err } + +func parameterNotSupportedError(k string) error { + return &ParseError{s: fmt.Sprintf("parameter %s is not supported", k)} +} +func invalidNumberOfParametersError(k string, act, exp int) error { //nolint:unparam + return &ParseError{s: fmt.Sprintf("invalid number of parameters for %s %d - expected %d", k, act, exp)} +} +func invalidNumberOfParametersRangeError(k string, actPrm, minPrm, maxPrm int) error { + return &ParseError{s: fmt.Sprintf("invalid number of parameters for %s %d - expected %d - %d", k, actPrm, minPrm, maxPrm)} +} +func invalidNumberOfParametersMinError(k string, actPrm, minPrm int) error { + return &ParseError{s: fmt.Sprintf("invalid number of parameters for %s %d - expected at least %d", k, actPrm, minPrm)} +} +func parseError(k, v string) error { + return &ParseError{s: fmt.Sprintf("failed to parse %s: %s", k, v)} +} + +// ParseDSN parses a DSN string into a DSN structure. +func ParseDSN(s string) (*DSN, error) { + if s == "" { + return nil, &ParseError{s: "invalid parameter - DSN is empty"} + } + + u, err := url.Parse(s) + if err != nil { + return nil, &ParseError{err: err} + } + + dsn := &DSN{host: u.Host} + if u.User != nil { + dsn.username = u.User.Username() + password, _ := u.User.Password() + dsn.password = password + } + + for k, v := range u.Query() { + switch k { + + default: + return nil, parameterNotSupportedError(k) + + case DSNDatabaseName: + if len(v) != 1 { + return nil, invalidNumberOfParametersError(k, len(v), 1) + } + dsn.databaseName = v[0] + + case DSNDefaultSchema: + if len(v) != 1 { + return nil, invalidNumberOfParametersError(k, len(v), 1) + } + dsn.defaultSchema = v[0] + + case DSNTimeout: + if len(v) != 1 { + return nil, invalidNumberOfParametersError(k, len(v), 1) + } + t, err := strconv.Atoi(v[0]) + if err != nil { + return nil, parseError(k, v[0]) + } + dsn.timeout = time.Duration(t) * time.Second + + case DSNPingInterval: + if len(v) != 1 { + return nil, invalidNumberOfParametersError(k, len(v), 1) + } + t, err := strconv.Atoi(v[0]) + if err != nil { + return nil, parseError(k, v[0]) + } + dsn.pingInterval = time.Duration(t) * time.Second + + case DSNTLSServerName: + if len(v) != 1 { + return nil, invalidNumberOfParametersError(k, len(v), 1) + } + if dsn.tls == nil { + dsn.tls = &TLSPrms{} + } + dsn.tls.ServerName = v[0] + + case DSNTLSInsecureSkipVerify: + if len(v) > 1 { + return nil, invalidNumberOfParametersRangeError(k, len(v), 0, 1) + } + b := true + if len(v) > 0 && v[0] != "" { + b, err = strconv.ParseBool(v[0]) + if err != nil { + return nil, parseError(k, v[0]) + } + } + if dsn.tls == nil { + dsn.tls = &TLSPrms{} + } + dsn.tls.InsecureSkipVerify = b + + case DSNTLSRootCAFile: + if len(v) == 0 { + return nil, invalidNumberOfParametersMinError(k, len(v), 1) + } + if dsn.tls == nil { + dsn.tls = &TLSPrms{} + } + dsn.tls.RootCAFiles = v + } + } + return dsn, nil +} + +// String reassembles the DSN into a valid DSN string. +func (dsn *DSN) String() string { + values := url.Values{} + if dsn.databaseName != "" { + values.Set(DSNDatabaseName, dsn.databaseName) + } + if dsn.defaultSchema != "" { + values.Set(DSNDefaultSchema, dsn.defaultSchema) + } + if dsn.timeout != 0 { + values.Set(DSNTimeout, fmt.Sprintf("%d", dsn.timeout/time.Second)) + } + if dsn.pingInterval != 0 { + values.Set(DSNPingInterval, fmt.Sprintf("%d", dsn.pingInterval/time.Second)) + } + if dsn.tls != nil { + if dsn.tls.ServerName != "" { + values.Set(DSNTLSServerName, dsn.tls.ServerName) + } + values.Set(DSNTLSInsecureSkipVerify, strconv.FormatBool(dsn.tls.InsecureSkipVerify)) + for _, fn := range dsn.tls.RootCAFiles { + values.Add(DSNTLSRootCAFile, fn) + } + } + u := &url.URL{ + Scheme: urlSchema, + Host: dsn.host, + RawQuery: values.Encode(), + } + switch { + case dsn.username != "" && dsn.password != "": + u.User = url.UserPassword(dsn.username, dsn.password) + case dsn.username != "": + u.User = url.User(dsn.username) + } + return u.String() +} diff --git a/vendor/github.com/SAP/go-hdb/driver/error.go b/vendor/github.com/SAP/go-hdb/driver/error.go new file mode 100644 index 00000000..7857e40e --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/error.go @@ -0,0 +1,39 @@ +package driver + +import ( + p "github.com/SAP/go-hdb/driver/internal/protocol" +) + +// HDB error levels. +const ( + HdbWarning = 0 + HdbError = 1 + HdbFatalError = 2 +) + +// DBError represents a single error returned by the database server. +type DBError interface { + Error() string // Implements the golang error interface. + StmtNo() int // Returns the statement number of the error in multi statement contexts (e.g. bulk insert). + Code() int // Code return the database error code. + Position() int // Position returns the start position of erroneous sql statements sent to the database server. + Level() int // Level return one of the database server predefined error levels. + Text() string // Text return the error description sent from database server. + IsWarning() bool // IsWarning returns true if the HDB error level equals 0. + IsError() bool // IsError returns true if the HDB error level equals 1. + IsFatal() bool // IsFatal returns true if the HDB error level equals 2. +} + +// Error represents errors (an error collection) send by the database server. +type Error interface { + Error() string // Implements the golang error interface. + NumError() int // NumError returns the number of errors. + Unwrap() []error // Unwrap implements the standard error Unwrap function for errors wrapping multiple errors. + SetIdx(idx int) // SetIdx sets the error index in case number of errors are greater 1 in the range of 0 <= index < NumError(). + DBError // DBError functions for error in case of single error, for error set by SetIdx in case of error collection. +} + +var ( + _ DBError = (*p.HdbError)(nil) + _ Error = (*p.HdbErrors)(nil) +) diff --git a/vendor/github.com/SAP/go-hdb/driver/identifier.go b/vendor/github.com/SAP/go-hdb/driver/identifier.go new file mode 100644 index 00000000..3ae19d96 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/identifier.go @@ -0,0 +1,35 @@ +package driver + +import ( + "strconv" + + "github.com/SAP/go-hdb/driver/internal/rand/alphanum" +) + +// Identifier in hdb SQL statements like schema or table name. +type Identifier string + +// RandomIdentifier returns a random Identifier prefixed by the prefix parameter. +// This function is used to generate database objects with random names for test and example code. +func RandomIdentifier(prefix string) Identifier { + return Identifier(prefix + alphanum.ReadString(16)) +} + +func (i Identifier) isSimple() bool { + // var reSimple = regexp.MustCompile("^[_A-Z][_#$A-Z0-9]*$") + for i, r := range i { + switch { + case r == '_' || ('A' <= r && r <= 'Z'): // valid char + case i != 0 && (r == '#' || r == '$' || ('0' <= r && r <= '9')): // valid char for non first char + default: + return false + } + } + return true +} +func (i Identifier) String() string { + if i.isSimple() { + return string(i) + } + return strconv.Quote(string(i)) +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth.go new file mode 100644 index 00000000..661ed5f6 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth.go @@ -0,0 +1,146 @@ +package protocol + +import ( + "fmt" + + "github.com/SAP/go-hdb/driver/internal/protocol/auth" + "github.com/SAP/go-hdb/driver/internal/protocol/encoding" +) + +// AuthHnd holds the client authentication methods dependent on the driver.Connector attributes and handles the authentication hdb protocol. +type AuthHnd struct { + logonname string + methods auth.Methods + selected auth.Method // selected method +} + +// NewAuthHnd creates a new AuthHnd instance. +func NewAuthHnd(logonname string) *AuthHnd { + return &AuthHnd{logonname: logonname, methods: auth.Methods{}} +} + +func (a *AuthHnd) String() string { return "logonname " + a.logonname } + +// AddSessionCookie adds session cookie authentication method. +func (a *AuthHnd) AddSessionCookie(cookie []byte, logonname, clientID string) { + a.methods[auth.MtSessionCookie] = auth.NewSessionCookie(cookie, logonname, clientID) +} + +// AddBasic adds basic authentication methods. +func (a *AuthHnd) AddBasic(username, password string) { + a.methods[auth.MtSCRAMPBKDF2SHA256] = auth.NewSCRAMPBKDF2SHA256(username, password) + a.methods[auth.MtSCRAMSHA256] = auth.NewSCRAMSHA256(username, password) +} + +// AddJWT adds JWT authentication method. +func (a *AuthHnd) AddJWT(token string) { a.methods[auth.MtJWT] = auth.NewJWT(token) } + +// AddX509 adds X509 authentication method. +func (a *AuthHnd) AddX509(certKey *auth.CertKey) { a.methods[auth.MtX509] = auth.NewX509(certKey) } + +// Selected returns the selected authentication method. +func (a *AuthHnd) Selected() auth.Method { return a.selected } + +func (a *AuthHnd) setMethod(mt string) error { + var ok bool + if a.selected, ok = a.methods[mt]; !ok { + return fmt.Errorf("invalid method type: %s", mt) + } + return nil +} + +// InitRequest returns the init request part. +func (a *AuthHnd) InitRequest() (*AuthInitRequest, error) { + prms := &auth.Prms{} + prms.AddCESU8String(a.logonname) + for _, m := range a.methods.Order() { + if err := m.PrepareInitReq(prms); err != nil { + return nil, err + } + } + return &AuthInitRequest{prms: prms}, nil +} + +// InitReply returns the init reply part. +func (a *AuthHnd) InitReply() (*AuthInitReply, error) { return &AuthInitReply{authHnd: a}, nil } + +// FinalRequest returns the final request part. +func (a *AuthHnd) FinalRequest() (*AuthFinalRequest, error) { + prms := &auth.Prms{} + if err := a.selected.PrepareFinalReq(prms); err != nil { + return nil, err + } + return &AuthFinalRequest{prms}, nil +} + +// FinalReply returns the final reply part. +func (a *AuthHnd) FinalReply() (*AuthFinalReply, error) { + return &AuthFinalReply{method: a.selected}, nil +} + +// AuthInitRequest represents an authentication initial request. +type AuthInitRequest struct { + prms *auth.Prms +} + +func (r *AuthInitRequest) String() string { return r.prms.String() } +func (r *AuthInitRequest) size() int { return r.prms.Size() } +func (r *AuthInitRequest) decode(dec *encoding.Decoder) error { return r.prms.Decode(dec) } +func (r *AuthInitRequest) encode(enc *encoding.Encoder) error { return r.prms.Encode(enc) } + +// AuthInitReply represents an authentication initial reply. +type AuthInitReply struct { + authHnd *AuthHnd +} + +func (r *AuthInitReply) String() string { return r.authHnd.String() } +func (r *AuthInitReply) decode(dec *encoding.Decoder) error { + if r.authHnd == nil { + return nil + } + + d := auth.NewDecoder(dec) + + if err := d.NumPrm(2); err != nil { + return err + } + mt := d.String() + + if err := r.authHnd.setMethod(mt); err != nil { + return err + } + if err := r.authHnd.selected.InitRepDecode(d); err != nil { + return err + } + return dec.Error() +} + +// AuthFinalRequest represents an authentication final request. +type AuthFinalRequest struct { + prms *auth.Prms +} + +func (r *AuthFinalRequest) String() string { return r.prms.String() } +func (r *AuthFinalRequest) size() int { return r.prms.Size() } +func (r *AuthFinalRequest) decode(dec *encoding.Decoder) error { + return nil + // panic("not implemented yet") +} +func (r *AuthFinalRequest) encode(enc *encoding.Encoder) error { return r.prms.Encode(enc) } + +// AuthFinalReply represents an authentication final reply. +type AuthFinalReply struct { + method auth.Method +} + +func (r *AuthFinalReply) String() string { return r.method.String() } +func (r *AuthFinalReply) decode(dec *encoding.Decoder) error { + if r.method == nil { + return nil + } + + if err := r.method.FinalRepDecode(auth.NewDecoder(dec)); err != nil { + return err + } + return dec.Error() +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/auth.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/auth.go new file mode 100644 index 00000000..784f088c --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/auth.go @@ -0,0 +1,237 @@ +// Package auth provides authentication methods. +package auth + +import ( + "cmp" + "encoding/binary" + "fmt" + "math" + "slices" + + "github.com/SAP/go-hdb/driver/internal/protocol/encoding" +) + +/* +authentication method types supported by the driver: + - basic authentication (username, password based) (whether SCRAMSHA256 or SCRAMPBKDF2SHA256) and + - X509 (client certificate) authentication and + - JWT (token) authentication +*/ +const ( + MtSCRAMSHA256 = "SCRAMSHA256" // password + MtSCRAMPBKDF2SHA256 = "SCRAMPBKDF2SHA256" // password pbkdf2 + MtX509 = "X509" // client certificate + MtJWT = "JWT" // json web token + MtSessionCookie = "SessionCookie" // session cookie +) + +// authentication method orders. +const ( + MoSessionCookie byte = iota + MoX509 + MoJWT + MoSCRAMPBKDF2SHA256 + MoSCRAMSHA256 +) + +// A Method defines the interface for an authentication method. +type Method interface { + fmt.Stringer + Typ() string + Order() byte + PrepareInitReq(prms *Prms) error + InitRepDecode(d *Decoder) error + PrepareFinalReq(prms *Prms) error + FinalRepDecode(d *Decoder) error +} + +// Methods defines a collection of methods. +type Methods map[string]Method // key equals authentication method type. + +// Order returns an ordered method slice. +func (m Methods) Order() []Method { + methods := make([]Method, 0, len(m)) + for _, e := range m { + methods = append(methods, e) + } + slices.SortFunc(methods, func(m1, m2 Method) int { return cmp.Compare(m1.Order(), m2.Order()) }) + return methods +} + +// CookieGetter is implemented by authentication methods supporting cookies to reconnect. +type CookieGetter interface { + Cookie() (logonname string, cookie []byte) +} + +var ( + _ Method = (*SCRAMSHA256)(nil) + _ Method = (*SCRAMPBKDF2SHA256)(nil) + _ Method = (*JWT)(nil) + _ Method = (*X509)(nil) + _ Method = (*SessionCookie)(nil) +) + +// subPrmsSize is the type used to encode and decode the size of sub parameters. +// The hana protocoll supports whether: +// - a size <= 245 encoded in one byte or +// - an unsigned 2 byte integer size encoded in three bytes +// . first byte equals 255 +// . second and third byte is an big endian encoded uint16 +type subPrmsSize int + +const ( + maxSubPrmsSize1ByteLen = 245 + subPrmsSize2ByteIndicator = 255 +) + +func (s subPrmsSize) fieldSize() int { + if s > maxSubPrmsSize1ByteLen { + return 3 + } + return 1 +} + +func (s subPrmsSize) encode(e *encoding.Encoder) error { + switch { + case s <= maxSubPrmsSize1ByteLen: + e.Byte(byte(s)) + case s <= math.MaxUint16: + e.Byte(subPrmsSize2ByteIndicator) + // big endian + e.Uint16ByteOrder(uint16(s), binary.BigEndian) //nolint: gosec + default: + return fmt.Errorf("invalid subparameter size %d - maximum %d", s, 42) + } + return nil +} + +func (s *subPrmsSize) decode(d *encoding.Decoder) { + b := d.Byte() + switch { + case b <= maxSubPrmsSize1ByteLen: + *s = subPrmsSize(b) + case b == subPrmsSize2ByteIndicator: + *s = subPrmsSize(d.Uint16ByteOrder(binary.BigEndian)) + default: + panic("invalid sub parameter size indicator") + } +} + +// Decoder represents an authentication decoder. +type Decoder struct { + d *encoding.Decoder +} + +// NewDecoder returns a new decoder instance. +func NewDecoder(d *encoding.Decoder) *Decoder { + return &Decoder{d: d} +} + +// NumPrm ckecks the number of parameters and returns an error if not equal expected, nil otherwise. +func (d *Decoder) NumPrm(expected int) error { + numPrm := int(d.d.Int16()) + if numPrm != expected { + return fmt.Errorf("invalid number of parameters %d - expected %d", numPrm, expected) + } + return nil +} + +func (d *Decoder) String() string { _, s := d.d.LIString(); return s } +func (d *Decoder) cesu8String() (string, error) { _, s, err := d.d.CESU8LIString(); return s, err } +func (d *Decoder) bytes() []byte { _, b := d.d.LIBytes(); return b } +func (d *Decoder) bigUint32() (uint32, error) { + size := d.d.Byte() + if size != encoding.IntegerFieldSize { // 4 bytes + return 0, fmt.Errorf("invalid auth uint32 size %d - expected %d", size, encoding.IntegerFieldSize) + } + return d.d.Uint32ByteOrder(binary.BigEndian), nil // big endian coded (e.g. rounds param) +} +func (d *Decoder) subSize() int { + var subSize subPrmsSize + (&subSize).decode(d.d) + return int(subSize) +} + +// Prms represents authentication parameters. +type Prms struct { + prms []any +} + +func (p *Prms) String() string { return fmt.Sprintf("%v", p.prms) } + +// AddCESU8String adds a CESU8 string parameter. +func (p *Prms) AddCESU8String(s string) { p.prms = append(p.prms, s) } // unicode string +func (p *Prms) addEmpty() { p.prms = append(p.prms, []byte{}) } +func (p *Prms) addBytes(b []byte) { p.prms = append(p.prms, b) } +func (p *Prms) addString(s string) { p.prms = append(p.prms, []byte(s)) } // treat like bytes to distinguisch from unicode string +func (p *Prms) addPrms() *Prms { + prms := &Prms{} + p.prms = append(p.prms, prms) + return prms +} + +// Size returns the size in bytes of the parameters. +func (p *Prms) Size() int { + size := encoding.SmallintFieldSize // no of parameters (2 bytes) + for _, e := range p.prms { + switch e := e.(type) { + case []byte, string: + size += encoding.VarFieldSize(e) + case *Prms: + subSize := subPrmsSize(e.Size()) + size += (int(subSize) + subSize.fieldSize()) + default: + panic("invalid parameter") // should not happen + } + } + return size +} + +// Encode encodes the parameters. +func (p *Prms) Encode(enc *encoding.Encoder) error { + numPrms := len(p.prms) + if numPrms > math.MaxInt16 { + return fmt.Errorf("invalid number of parameters %d - maximum %d", numPrms, math.MaxInt16) + } + enc.Int16(int16(numPrms)) + + for _, e := range p.prms { + switch e := e.(type) { + case []byte: + if err := enc.LIBytes(e); err != nil { + return err + } + case string: + if err := enc.CESU8LIString(e); err != nil { + return err + } + case *Prms: + subSize := subPrmsSize(e.Size()) + if err := subSize.encode(enc); err != nil { + return err + } + if err := e.Encode(enc); err != nil { + return err + } + default: + panic("invalid parameter") // should not happen + } + } + return nil +} + +// Decode decodes the parameters. +func (p *Prms) Decode(dec *encoding.Decoder) error { + numPrms := int(dec.Int16()) + for range numPrms { + + } + return nil +} + +func checkAuthMethodType(mt, expected string) error { + if mt != expected { + return fmt.Errorf("invalid method %s - expected %s", mt, expected) + } + return nil +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/certkey.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/certkey.go new file mode 100644 index 00000000..0c5d678e --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/certkey.go @@ -0,0 +1,174 @@ +package auth + +import ( + "bytes" + "crypto" + "crypto/rand" + "crypto/sha256" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "strings" + "time" + "unique" +) + +// CertValidationError is returned in case of X09 certificate validation errors. +type CertValidationError struct { + t time.Time + cert *x509.Certificate +} + +func (e CertValidationError) Error() string { + return fmt.Sprintf("certificate issuer %s subject %s not in validity period from %s to %s - now %s", + e.cert.Issuer.ToRDNSequence().String(), + e.cert.Subject.ToRDNSequence().String(), + e.cert.NotBefore, + e.cert.NotAfter, + e.t, + ) +} + +// CertKey represents a X509 certificate and key. +type CertKey struct { + certHandle, keyHandle unique.Handle[string] + certBlocks []*pem.Block + certs []*x509.Certificate + keyBlock *pem.Block +} + +// NewCertKey returns a new certificate and key instance. +func NewCertKey(certHandle, keyHandle unique.Handle[string]) (*CertKey, error) { + certBlocks, err := decodeClientCert([]byte(certHandle.Value())) + if err != nil { + return nil, err + } + certs, err := parseCerts(certBlocks) + if err != nil { + return nil, err + } + keyBlock, err := decodeClientKey([]byte(keyHandle.Value())) + if err != nil { + return nil, err + } + return &CertKey{certHandle: certHandle, keyHandle: keyHandle, certBlocks: certBlocks, certs: certs, keyBlock: keyBlock}, nil +} + +func (ck *CertKey) String() string { + return fmt.Sprintf("cert %s key %s", ck.certHandle.Value(), ck.keyHandle.Value()) +} + +// Equal returns true if the certificate and key equals the instance data, false otherwise. +func (ck *CertKey) Equal(certHandle, keyHandle unique.Handle[string]) bool { + return certHandle == ck.certHandle && keyHandle == ck.keyHandle +} + +// Cert returns the certificate. +func (ck *CertKey) Cert() []byte { return []byte(ck.certHandle.Value()) } + +// Key returns the key. +func (ck *CertKey) Key() []byte { return []byte(ck.keyHandle.Value()) } + +// validate validates the certificate (currently validity period only). +func (ck *CertKey) validate(t time.Time) error { return validateCerts(ck.certs, t) } + +// validate validates the certificate (currently validity period only). +func validateCerts(certs []*x509.Certificate, t time.Time) error { + t = t.UTC() // cert.NotBefore and cert.NotAfter in UTC as well + for _, cert := range certs { + // checks + // .check validity period + if t.Before(cert.NotBefore) || t.After(cert.NotAfter) { + return &CertValidationError{t: t, cert: cert} + } + } + return nil +} + +// signer returns the cryptographic signer of the key. +func (ck *CertKey) signer() (crypto.Signer, error) { + switch ck.keyBlock.Type { + case "RSA PRIVATE KEY": + return x509.ParsePKCS1PrivateKey(ck.keyBlock.Bytes) + case "PRIVATE KEY": + key, err := x509.ParsePKCS8PrivateKey(ck.keyBlock.Bytes) + if err != nil { + return nil, err + } + signer, ok := key.(crypto.Signer) + if !ok { + return nil, errors.New("internal error: parsed PKCS8 private key is not a crypto.Signer") + } + return signer, nil + case "EC PRIVATE KEY": + return x509.ParseECPrivateKey(ck.keyBlock.Bytes) + default: + return nil, fmt.Errorf("unsupported key type %q", ck.keyBlock.Type) + } +} + +func (ck *CertKey) sign(message *bytes.Buffer) ([]byte, error) { + signer, err := ck.signer() + if err != nil { + return nil, err + } + + hashed := sha256.Sum256(message.Bytes()) + return signer.Sign(rand.Reader, hashed[:], crypto.SHA256) +} + +func decodePEM(data []byte) []*pem.Block { + var blocks []*pem.Block + block, rest := pem.Decode(data) + for block != nil { + blocks = append(blocks, block) + block, rest = pem.Decode(rest) + } + return blocks +} + +func decodeClientCert(data []byte) ([]*pem.Block, error) { + blocks := decodePEM(data) + switch { + case blocks == nil: + return nil, errors.New("invalid client certificate") + case len(blocks) < 1: + return nil, fmt.Errorf("invalid number of blocks in certificate file %d - expected min 1", len(blocks)) + } + return blocks, nil +} + +func parseCerts(blocks []*pem.Block) (certs []*x509.Certificate, err error) { + for _, block := range blocks { + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, err + } + certs = append(certs, cert) + } + return certs, nil +} + +// encryptedBlock tells whether a private key is +// encrypted by examining its Proc-Type header +// for a mention of ENCRYPTED +// according to RFC 1421 Section 4.6.1.1. +func encryptedBlock(block *pem.Block) bool { + return strings.Contains(block.Headers["Proc-Type"], "ENCRYPTED") +} + +func decodeClientKey(data []byte) (*pem.Block, error) { + blocks := decodePEM(data) + switch { + case blocks == nil: + return nil, errors.New("invalid client key") + case len(blocks) != 1: + return nil, fmt.Errorf("invalid number of blocks in key file %d - expected 1", len(blocks)) + } + block := blocks[0] + if encryptedBlock(block) { + return nil, errors.New("client key is password encrypted") + } + return block, nil +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/jwt.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/jwt.go new file mode 100644 index 00000000..d14fb2f9 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/jwt.go @@ -0,0 +1,60 @@ +package auth + +import ( + "fmt" +) + +// JWT implements JWT authentication. +type JWT struct { + token string + logonname string + _cookie []byte +} + +// NewJWT creates a new authJWT instance. +func NewJWT(token string) *JWT { return &JWT{token: token} } + +func (a *JWT) String() string { return fmt.Sprintf("method type %s token %s", a.Typ(), a.token) } + +// Cookie implements the AuthCookieGetter interface. +func (a *JWT) Cookie() (string, []byte) { return a.logonname, a._cookie } + +// Typ implements the Method interface. +func (a *JWT) Typ() string { return MtJWT } + +// Order implements the Method interface. +func (a *JWT) Order() byte { return MoJWT } + +// PrepareInitReq implements the Method interface. +func (a *JWT) PrepareInitReq(prms *Prms) error { + prms.addString(a.Typ()) + prms.addString(a.token) + return nil +} + +// InitRepDecode implements the Method interface. +func (a *JWT) InitRepDecode(d *Decoder) error { + a.logonname = d.String() + return nil +} + +// PrepareFinalReq implements the Method interface. +func (a *JWT) PrepareFinalReq(prms *Prms) error { + prms.AddCESU8String(a.logonname) + prms.addString(a.Typ()) + prms.addEmpty() // empty parameter + return nil +} + +// FinalRepDecode implements the Method interface. +func (a *JWT) FinalRepDecode(d *Decoder) error { + if err := d.NumPrm(2); err != nil { + return err + } + mt := d.String() + if err := checkAuthMethodType(mt, a.Typ()); err != nil { + return err + } + a._cookie = d.bytes() + return nil +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/list.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/list.go new file mode 100644 index 00000000..58c3953c --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/list.go @@ -0,0 +1,59 @@ +package auth + +import ( + "sync" +) + +type comparer[E any] interface { + Compare(e E) bool +} + +type list[K comparer[K], V any] struct { + valueFn func(k K) (V, error) + mu sync.RWMutex + idx int + keys []K + values []V +} + +func newList[K comparer[K], V any](maxEntry int, valueFn func(k K) (V, error)) *list[K, V] { + return &list[K, V]{ + valueFn: valueFn, + keys: make([]K, 0, maxEntry), + values: make([]V, 0, maxEntry), + } +} + +func (l *list[K, V]) find(k K) (v V, ok bool) { + l.mu.RLock() + defer l.mu.RUnlock() + for i, k1 := range l.keys { + if k1.Compare(k) { + return l.values[i], true + } + } + return +} + +func (l *list[K, V]) Get(k K) (V, error) { + if v, ok := l.find(k); ok { + return v, nil + } + l.mu.Lock() + defer l.mu.Unlock() + v, err := l.valueFn(k) + if err != nil { + return v, err + } + if l.idx < len(l.keys) { + l.keys[l.idx], l.values[l.idx] = k, v + } else { + l.keys = append(l.keys, k) + l.values = append(l.values, v) + } + l.idx++ + if l.idx >= cap(l.keys) { + l.idx = 0 + } + return v, nil +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/scram.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/scram.go new file mode 100644 index 00000000..f8aeb327 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/scram.go @@ -0,0 +1,65 @@ +package auth + +import ( + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "fmt" +) + +const ( + clientChallengeSize = 64 + serverChallengeSize = 48 + saltSize = 16 + clientProofSize = 32 +) + +func checkSalt(salt []byte) error { + if len(salt) != saltSize { + return fmt.Errorf("invalid salt size %d - expected %d", len(salt), saltSize) + } + return nil +} + +func checkServerChallenge(serverChallenge []byte) error { + if len(serverChallenge) != serverChallengeSize { + return fmt.Errorf("invalid server challenge size %d - expected %d", len(serverChallenge), serverChallengeSize) + } + return nil +} + +func clientChallenge() []byte { + r := make([]byte, clientChallengeSize) + // does not return err starting with go1.24 + rand.Read(r) //nolint: errcheck + return r +} + +func clientProof(key, salt, serverChallenge, clientChallenge []byte) ([]byte, error) { + if len(key) != clientProofSize { + return nil, fmt.Errorf("invalid key size %d - expected %d", len(key), clientProofSize) + } + sig := _hmac(_sha256(key), salt, serverChallenge, clientChallenge) + if len(sig) != clientProofSize { + return nil, fmt.Errorf("invalid sig size %d - expected %d", len(key), clientProofSize) + } + // xor sig and key into sig (inline: no further allocation). + for i, v := range key { + sig[i] ^= v + } + return sig, nil +} + +func _sha256(p []byte) []byte { + hash := sha256.New() + hash.Write(p) + return hash.Sum(nil) +} + +func _hmac(key []byte, prms ...[]byte) []byte { + hash := hmac.New(sha256.New, key) + for _, p := range prms { + hash.Write(p) + } + return hash.Sum(nil) +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/scrampbkdf2sha256.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/scrampbkdf2sha256.go new file mode 100644 index 00000000..f1dd42ca --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/scrampbkdf2sha256.go @@ -0,0 +1,122 @@ +package auth + +// Salted Challenge Response Authentication Mechanism (SCRAM) + +import ( + "bytes" + "crypto/pbkdf2" + "crypto/sha256" + "fmt" +) + +/* + + return _sha256(pbkdf2.Key(password, salt, rounds, clientProofSize, sha256.New)) +} +*/ + +// use cache as key calculation is expensive. +var scrampbkdf2KeyCache = newList(3, func(k *SCRAMPBKDF2SHA256) ([]byte, error) { + return scrampbkdf2sha256Key(k.password, k.salt, int(k.rounds)) +}) + +// SCRAMPBKDF2SHA256 implements SCRAMPBKDF2SHA256 authentication. +type SCRAMPBKDF2SHA256 struct { + username, password string + clientChallenge []byte + salt, serverChallenge []byte + serverProof []byte + rounds uint32 +} + +// NewSCRAMPBKDF2SHA256 creates a new authSCRAMPBKDF2SHA256 instance. +func NewSCRAMPBKDF2SHA256(username, password string) *SCRAMPBKDF2SHA256 { + return &SCRAMPBKDF2SHA256{username: username, password: password, clientChallenge: clientChallenge()} +} + +func (a *SCRAMPBKDF2SHA256) String() string { + return fmt.Sprintf("method type %s clientChallenge %v", a.Typ(), a.clientChallenge) +} + +// Compare implements cache.Compare interface. +func (a *SCRAMPBKDF2SHA256) Compare(a1 *SCRAMPBKDF2SHA256) bool { + return a.password == a1.password && bytes.Equal(a.salt, a1.salt) && a.rounds == a1.rounds +} + +// Typ implements the Method interface. +func (a *SCRAMPBKDF2SHA256) Typ() string { return MtSCRAMPBKDF2SHA256 } + +// Order implements the Method interface. +func (a *SCRAMPBKDF2SHA256) Order() byte { return MoSCRAMPBKDF2SHA256 } + +// PrepareInitReq implements the Method interface. +func (a *SCRAMPBKDF2SHA256) PrepareInitReq(prms *Prms) error { + prms.addString(a.Typ()) + prms.addBytes(a.clientChallenge) + return nil +} + +// InitRepDecode implements the Method interface. +func (a *SCRAMPBKDF2SHA256) InitRepDecode(d *Decoder) error { + d.subSize() // sub parameters + if err := d.NumPrm(3); err != nil { + return err + } + a.salt = d.bytes() + a.serverChallenge = d.bytes() + if err := checkSalt(a.salt); err != nil { + return err + } + if err := checkServerChallenge(a.serverChallenge); err != nil { + return err + } + var err error + if a.rounds, err = d.bigUint32(); err != nil { + return err + } + return nil +} + +// PrepareFinalReq implements the Method interface. +func (a *SCRAMPBKDF2SHA256) PrepareFinalReq(prms *Prms) error { + key, err := scrampbkdf2KeyCache.Get(a) + if err != nil { + return err + } + clientProof, err := clientProof(key, a.salt, a.serverChallenge, a.clientChallenge) + if err != nil { + return err + } + + prms.AddCESU8String(a.username) + prms.addString(a.Typ()) + subPrms := prms.addPrms() + subPrms.addBytes(clientProof) + + return nil +} + +// FinalRepDecode implements the Method interface. +func (a *SCRAMPBKDF2SHA256) FinalRepDecode(d *Decoder) error { + if err := d.NumPrm(2); err != nil { + return err + } + mt := d.String() + if err := checkAuthMethodType(mt, a.Typ()); err != nil { + return err + } + d.subSize() + if err := d.NumPrm(1); err != nil { + return err + } + a.serverProof = d.bytes() + return nil +} + +func scrampbkdf2sha256Key(password string, salt []byte, rounds int) ([]byte, error) { + b, err := pbkdf2.Key(sha256.New, password, salt, rounds, clientProofSize) + if err != nil { + return nil, err + } + return _sha256(b), nil +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/scramsha256.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/scramsha256.go new file mode 100644 index 00000000..e1a95a05 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/scramsha256.go @@ -0,0 +1,107 @@ +package auth + +// Salted Challenge Response Authentication Mechanism (SCRAM) + +import ( + "bytes" + "fmt" +) + +func scramsha256Key(password, salt []byte) ([]byte, error) { + return _sha256(_hmac(password, salt)), nil +} + +// use cache as key calculation is expensive. +var scramKeyCache = newList(3, func(k *SCRAMSHA256) ([]byte, error) { + return scramsha256Key([]byte(k.password), k.salt) +}) + +// SCRAMSHA256 implements SCRAMSHA256 authentication. +type SCRAMSHA256 struct { + username, password string + clientChallenge []byte + salt, serverChallenge []byte + serverProof []byte +} + +// NewSCRAMSHA256 creates a new authSCRAMSHA256 instance. +func NewSCRAMSHA256(username, password string) *SCRAMSHA256 { + return &SCRAMSHA256{username: username, password: password, clientChallenge: clientChallenge()} +} + +func (a *SCRAMSHA256) String() string { + return fmt.Sprintf("method type %s clientChallenge %v", a.Typ(), a.clientChallenge) +} + +// Compare implements cache.Compare interface. +func (a *SCRAMSHA256) Compare(a1 *SCRAMSHA256) bool { + return a.password == a1.password && bytes.Equal(a.salt, a1.salt) +} + +// Typ implements the Method interface. +func (a *SCRAMSHA256) Typ() string { return MtSCRAMSHA256 } + +// Order implements the Method interface. +func (a *SCRAMSHA256) Order() byte { return MoSCRAMSHA256 } + +// PrepareInitReq implements the Method interface. +func (a *SCRAMSHA256) PrepareInitReq(prms *Prms) error { + prms.addString(a.Typ()) + prms.addBytes(a.clientChallenge) + return nil +} + +// InitRepDecode implements the Method interface. +func (a *SCRAMSHA256) InitRepDecode(d *Decoder) error { + d.subSize() // sub parameters + if err := d.NumPrm(2); err != nil { + return err + } + a.salt = d.bytes() + a.serverChallenge = d.bytes() + if err := checkSalt(a.salt); err != nil { + return err + } + if err := checkServerChallenge(a.serverChallenge); err != nil { + return err + } + return nil +} + +// PrepareFinalReq implements the Method interface. +func (a *SCRAMSHA256) PrepareFinalReq(prms *Prms) error { + key, err := scramKeyCache.Get(a) + if err != nil { + return err + } + clientProof, err := clientProof(key, a.salt, a.serverChallenge, a.clientChallenge) + if err != nil { + return err + } + + prms.AddCESU8String(a.username) + prms.addString(a.Typ()) + subPrms := prms.addPrms() + subPrms.addBytes(clientProof) + + return nil +} + +// FinalRepDecode implements the Method interface. +func (a *SCRAMSHA256) FinalRepDecode(d *Decoder) error { + if err := d.NumPrm(2); err != nil { + return err + } + mt := d.String() + if err := checkAuthMethodType(mt, a.Typ()); err != nil { + return err + } + if d.subSize() == 0 { // mnSCRAMSHA256: server does not return server proof parameter + return nil + } + if err := d.NumPrm(1); err != nil { + return err + } + a.serverProof = d.bytes() + return nil +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/sessioncookie.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/sessioncookie.go new file mode 100644 index 00000000..f3704dcb --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/sessioncookie.go @@ -0,0 +1,60 @@ +package auth + +import ( + "fmt" +) + +// SessionCookie implements session cookie authentication. +type SessionCookie struct { + cookie []byte + logonname string + clientID string +} + +// NewSessionCookie creates a new authSessionCookie instance. +func NewSessionCookie(cookie []byte, logonname, clientID string) *SessionCookie { + return &SessionCookie{cookie: cookie, logonname: logonname, clientID: clientID} +} + +func (a *SessionCookie) String() string { + return fmt.Sprintf("method type %s cookie %v", a.Typ(), a.cookie) +} + +// Typ implements the Mthod interface. +func (a *SessionCookie) Typ() string { return MtSessionCookie } + +// Order implements the Method interface. +func (a *SessionCookie) Order() byte { return MoSessionCookie } + +// PrepareInitReq implements the Method interface. +func (a *SessionCookie) PrepareInitReq(prms *Prms) error { + prms.addString(a.Typ()) + prms.addBytes(append(a.cookie, a.clientID...)) // cookie + clientID !!! + return nil +} + +// InitRepDecode implements the Method interface. +func (a *SessionCookie) InitRepDecode(d *Decoder) error { + return nil +} + +// PrepareFinalReq implements the Method interface. +func (a *SessionCookie) PrepareFinalReq(prms *Prms) error { + prms.AddCESU8String(a.logonname) + prms.addString(a.Typ()) + prms.addEmpty() // empty parameter + return nil +} + +// FinalRepDecode implements the Method interface. +func (a *SessionCookie) FinalRepDecode(d *Decoder) error { + if err := d.NumPrm(2); err != nil { + return err + } + mt := d.String() + if err := checkAuthMethodType(mt, a.Typ()); err != nil { + return err + } + d.bytes() // second parameter seems to be empty + return nil +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/x509.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/x509.go new file mode 100644 index 00000000..1bc74a13 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/auth/x509.go @@ -0,0 +1,106 @@ +package auth + +import ( + "bytes" + "fmt" + "time" +) + +const ( + x509ServerNonceSize = 64 +) + +// X509 implements X509 authentication. +type X509 struct { + certKey *CertKey + serverNonce []byte + logonName string +} + +// NewX509 creates a new authX509 instance. +func NewX509(certKey *CertKey) *X509 { return &X509{certKey: certKey} } + +func (a *X509) String() string { + return fmt.Sprintf("method type %s %s", a.Typ(), a.certKey) +} + +// Typ implements the Method interface. +func (a *X509) Typ() string { return MtX509 } + +// Order implements the Method interface. +func (a *X509) Order() byte { return MoX509 } + +// PrepareInitReq implements the Method interface. +func (a *X509) PrepareInitReq(prms *Prms) error { + // prevent auth call to hdb with invalid certificate + // as hbd only allows a limited number of unsuccessful authentications + // - currently only validity period is checked + if err := a.certKey.validate(time.Now()); err != nil { + return err + } + prms.addString(a.Typ()) + prms.addEmpty() + return nil +} + +// InitRepDecode implements the Method interface. +func (a *X509) InitRepDecode(d *Decoder) error { + a.serverNonce = d.bytes() + if len(a.serverNonce) != x509ServerNonceSize { + return fmt.Errorf("invalid server nonce size %d - expected %d", len(a.serverNonce), x509ServerNonceSize) + } + return nil +} + +// PrepareFinalReq implements the Method interface. +func (a *X509) PrepareFinalReq(prms *Prms) error { + prms.addEmpty() // empty username + prms.addString(a.Typ()) + + subPrms := prms.addPrms() + + certBlocks := a.certKey.certBlocks + + numBlocks := len(certBlocks) + + message := bytes.NewBuffer(certBlocks[0].Bytes) + + subPrms.addBytes(certBlocks[0].Bytes) + + if numBlocks == 1 { + subPrms.addEmpty() + } else { + chainPrms := subPrms.addPrms() + for _, block := range certBlocks[1:] { + message.Write(block.Bytes) + chainPrms.addBytes(block.Bytes) + } + } + + message.Write(a.serverNonce) + + signature, err := a.certKey.sign(message) + if err != nil { + return err + } + subPrms.addBytes(signature) + return nil +} + +// FinalRepDecode implements the Method interface. +func (a *X509) FinalRepDecode(d *Decoder) error { + if err := d.NumPrm(2); err != nil { + return err + } + mt := d.String() + if err := checkAuthMethodType(mt, a.Typ()); err != nil { + return err + } + d.subSize() + if err := d.NumPrm(1); err != nil { + return err + } + var err error + a.logonName, err = d.cesu8String() + return err +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/convert.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/convert.go new file mode 100644 index 00000000..f3f8709a --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/convert.go @@ -0,0 +1,649 @@ +package protocol + +import ( + "bytes" + "errors" + "fmt" + "io" + "math" + "math/big" + "reflect" + "strconv" + "strings" + "time" + + "golang.org/x/text/transform" +) + +const ( + minTinyint = 0 + maxTinyint = math.MaxUint8 + minSmallint = math.MinInt16 + maxSmallint = math.MaxInt16 + minInteger = math.MinInt32 + maxInteger = math.MaxInt32 + minBigint = math.MinInt64 + maxBigint = math.MaxInt64 + maxReal = math.MaxFloat32 + maxDouble = math.MaxFloat64 +) + +var ( + timeReflectType = reflect.TypeFor[time.Time]() + bytesReflectType = reflect.TypeFor[[]byte]() + stringReflectType = reflect.TypeFor[string]() + ratReflectType = reflect.TypeFor[big.Rat]() +) + +var ( + errConversionNotSupported = errors.New("conversion not supported") + errUint64OutOfRange = errors.New("uint64 values with high bit set are not supported") + errIntegerOutOfRange = errors.New("integer out of range") + errFloatOutOfRange = errors.New("float out of range") +) + +/* +Conversion routines hdb parameters + - return value is any to avoid allocations in case + parameter is already of target type +*/ + +func convertBool(v any) (any, error) { + // check needs to be done on each type individually as if combining types in one case + // the v type stays on any and the comparison v != 0 would always be true. + switch v := v.(type) { + case bool: + return v, nil + case int: + return v != 0, nil + case int8: + return v != 0, nil + case int16: + return v != 0, nil + case int32: + return v != 0, nil + case int64: + return v != 0, nil + case uint: + return v != 0, nil + case uint8: + return v != 0, nil + case uint16: + return v != 0, nil + case uint32: + return v != 0, nil + case uint64: + return v != 0, nil + case float32: + return v != 0, nil + case float64: + return v != 0, nil + case string: + return strconv.ParseBool(v) + } + + rv := reflect.ValueOf(v) + switch rv.Kind() { + case reflect.Bool: + return rv.Bool(), nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return rv.Int() != 0, nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return rv.Uint() != 0, nil + case reflect.Float32, reflect.Float64: + return rv.Float() != 0, nil + case reflect.String: + return strconv.ParseBool(rv.String()) + case reflect.Ptr: + if rv.IsNil() { + return nil, nil + } + return convertBool(rv.Elem().Interface()) + default: + if rv.Type().ConvertibleTo(stringReflectType) { + return convertBool(rv.Convert(stringReflectType).Interface()) + } + return nil, errConversionNotSupported + } +} + +var ( + i64Zero = int64(0) + i64One = int64(1) +) + +func convertInteger(v any, minI64, maxI64 int64) (any, error) { //nolint: gocyclo + switch v := v.(type) { + case bool: + if v { + return i64One, nil + } + return i64Zero, nil + case int: + i64 := int64(v) + if i64 > maxI64 || i64 < minI64 { + return nil, errIntegerOutOfRange + } + return i64, nil + case int8: + i64 := int64(v) + if i64 > maxI64 || i64 < minI64 { + return nil, errIntegerOutOfRange + } + return i64, nil + case int16: + i64 := int64(v) + if i64 > maxI64 || i64 < minI64 { + return nil, errIntegerOutOfRange + } + return i64, nil + case int32: + i64 := int64(v) + if i64 > maxI64 || i64 < minI64 { + return nil, errIntegerOutOfRange + } + return i64, nil + case int64: + if v > maxI64 || v < minI64 { + return nil, errIntegerOutOfRange + } + return v, nil + case uint: + u64 := uint64(v) + if u64 > math.MaxInt64 { + return nil, errUint64OutOfRange + } + i64 := int64(u64) + if i64 > maxI64 || i64 < minI64 { + return nil, errIntegerOutOfRange + } + return i64, nil + case uint8: + i64 := int64(v) + if i64 > maxI64 || i64 < minI64 { + return nil, errIntegerOutOfRange + } + return i64, nil + case uint16: + i64 := int64(v) + if i64 > maxI64 || i64 < minI64 { + return nil, errIntegerOutOfRange + } + return i64, nil + case uint32: + i64 := int64(v) + if i64 > maxI64 || i64 < minI64 { + return nil, errIntegerOutOfRange + } + return i64, nil + case uint64: + if v > math.MaxInt64 { + return nil, errUint64OutOfRange + } + i64 := int64(v) + if i64 > maxI64 || i64 < minI64 { + return nil, errIntegerOutOfRange + } + return i64, nil + case float32: + i64 := int64(v) + if v != float32(i64) { // should work for overflow, NaN, +-INF as well + return nil, errConversionNotSupported + } + if i64 > maxI64 || i64 < minI64 { + return nil, errConversionNotSupported + } + return i64, nil + case float64: + i64 := int64(v) + if v != float64(i64) { // should work for overflow, NaN, +-INF as well + return nil, errConversionNotSupported + } + if i64 > maxI64 || i64 < minI64 { + return nil, errIntegerOutOfRange + } + return i64, nil + case string: + i64, err := strconv.ParseInt(v, 10, 64) + if err != nil { + return nil, err + } + if i64 > maxI64 || i64 < minI64 { + return nil, errIntegerOutOfRange + } + return i64, nil + } + + rv := reflect.ValueOf(v) + switch rv.Kind() { + case reflect.Bool: + if rv.Bool() { + return i64One, nil + } + return i64Zero, nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + i64 := rv.Int() + if i64 > maxI64 || i64 < minI64 { + return nil, errIntegerOutOfRange + } + return i64, nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32: + i64 := int64(rv.Uint()) //nolint: gosec + if i64 > maxI64 || i64 < minI64 { + return nil, errIntegerOutOfRange + } + return i64, nil + case reflect.Uint64: + u64 := rv.Uint() + if u64 > math.MaxInt64 { + return nil, errUint64OutOfRange + } + i64 := int64(u64) + if i64 > maxI64 || i64 < minI64 { + return nil, errIntegerOutOfRange + } + return i64, nil + case reflect.Float32, reflect.Float64: + f64 := rv.Float() + i64 := int64(f64) + if f64 != float64(i64) { // should work for overflow, NaN, +-INF as well + return nil, errConversionNotSupported + } + if i64 > maxI64 || i64 < minI64 { + return nil, errIntegerOutOfRange + } + return i64, nil + case reflect.String: + i64, err := strconv.ParseInt(rv.String(), 10, 64) + if err != nil { + return nil, errConversionNotSupported + } + if i64 > maxI64 || i64 < minI64 { + return nil, errIntegerOutOfRange + } + return i64, nil + case reflect.Ptr: + if rv.IsNil() { + return nil, nil + } + return convertInteger(rv.Elem().Interface(), minI64, maxI64) + default: + if rv.Type().ConvertibleTo(stringReflectType) { + return convertInteger(rv.Convert(stringReflectType).Interface(), minI64, maxI64) + } + return nil, errConversionNotSupported + } +} + +var ( + f64Zero = float64(0.0) + f64One = float64(1.0) +) + +func convertFloat(v any, maxF64 float64) (any, error) { //nolint: gocyclo + switch v := v.(type) { + case float32: + f64 := float64(v) + if math.Abs(f64) > maxF64 { + return nil, errFloatOutOfRange + } + return f64, nil + case float64: + if math.Abs(v) > maxF64 { + return nil, errFloatOutOfRange + } + return v, nil + case bool: + if v { + return f64One, nil + } + return f64Zero, nil + case int: + f64 := float64(v) + if math.Abs(f64) > maxF64 { + return nil, errFloatOutOfRange + } + return f64, nil + case int8: + f64 := float64(v) + if math.Abs(f64) > maxF64 { + return nil, errFloatOutOfRange + } + return f64, nil + case int16: + f64 := float64(v) + if math.Abs(f64) > maxF64 { + return nil, errFloatOutOfRange + } + return f64, nil + case int32: + f64 := float64(v) + if math.Abs(f64) > maxF64 { + return nil, errFloatOutOfRange + } + return f64, nil + case int64: + f64 := float64(v) + if math.Abs(f64) > maxF64 { + return nil, errFloatOutOfRange + } + return f64, nil + case uint: + f64 := float64(v) + if math.Abs(f64) > maxF64 { + return nil, errFloatOutOfRange + } + return f64, nil + case uint8: + f64 := float64(v) + if math.Abs(f64) > maxF64 { + return nil, errFloatOutOfRange + } + return f64, nil + case uint16: + f64 := float64(v) + if math.Abs(f64) > maxF64 { + return nil, errFloatOutOfRange + } + return f64, nil + case uint32: + f64 := float64(v) + if math.Abs(f64) > maxF64 { + return nil, errFloatOutOfRange + } + return f64, nil + case uint64: + f64 := float64(v) + if math.Abs(f64) > maxF64 { + return nil, errFloatOutOfRange + } + return f64, nil + case string: + f64, err := strconv.ParseFloat(v, 64) + if err != nil { + return nil, err + } + if math.Abs(f64) > maxF64 { + return nil, errFloatOutOfRange + } + return f64, nil + } + + rv := reflect.ValueOf(v) + switch rv.Kind() { + case reflect.Bool: + if rv.Bool() { + return f64One, nil + } + return f64Zero, nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + f64 := float64(rv.Int()) + if math.Abs(f64) > maxF64 { + return nil, errFloatOutOfRange + } + return f64, nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + f64 := float64(rv.Uint()) + if math.Abs(f64) > maxF64 { + return nil, errFloatOutOfRange + } + return f64, nil + case reflect.Float32, reflect.Float64: + f64 := rv.Float() + if math.Abs(f64) > maxF64 { + return nil, errFloatOutOfRange + } + return f64, nil + case reflect.String: + f64, err := strconv.ParseFloat(rv.String(), 64) + if err != nil { + return nil, err + } + if math.Abs(f64) > maxF64 { + return nil, errFloatOutOfRange + } + return f64, nil + case reflect.Ptr: + if rv.IsNil() { + return nil, nil + } + return convertFloat(rv.Elem().Interface(), maxF64) + default: + if rv.Type().ConvertibleTo(stringReflectType) { + return convertFloat(rv.Convert(stringReflectType).Interface(), maxF64) + } + return nil, errConversionNotSupported + } +} + +func convertTime(v any) (any, error) { + if v, ok := v.(time.Time); ok { + return v, nil + } + + rv := reflect.ValueOf(v) + switch rv.Kind() { + case reflect.Ptr: + if rv.IsNil() { + return nil, nil + } + return convertTime(rv.Elem().Interface()) + default: + if rv.Type().ConvertibleTo(timeReflectType) { + tv := rv.Convert(timeReflectType) + return tv.Interface().(time.Time), nil + } + return nil, errConversionNotSupported + } +} + +var ( + ratZero = big.NewRat(0, 1) + ratOne = big.NewRat(1, 1) +) + +/* +Currently the min, max check is done during encoding, as the check is expensive and +we want to avoid doing the conversion twice (convert + encode). +These checks could be done in convert only, but then we would need a +struct{m *big.Int, exp int} for decimals as intermediate format. + +The conversion does support other types as well (int, *big.Int, string, ...) +even though the user needs to use Decimal for scanning. +*/ +func convertDecimal(v any) (any, error) { //nolint: gocyclo + switch v := v.(type) { + case *big.Rat: + return v, nil + case *big.Int: + return new(big.Rat).SetInt(v), nil + case *big.Float: + r, _ := v.Rat(nil) // ignore accuracy + return r, nil + case bool: + if v { + return ratOne, nil + } + return ratZero, nil + case int: + return new(big.Rat).SetInt64(int64(v)), nil + case int8: + return new(big.Rat).SetInt64(int64(v)), nil + case int16: + return new(big.Rat).SetInt64(int64(v)), nil + case int32: + return new(big.Rat).SetInt64(int64(v)), nil + case int64: + return new(big.Rat).SetInt64(v), nil + case uint: + return new(big.Rat).SetUint64(uint64(v)), nil + case uint8: + return new(big.Rat).SetUint64(uint64(v)), nil + case uint16: + return new(big.Rat).SetUint64(uint64(v)), nil + case uint32: + return new(big.Rat).SetUint64(uint64(v)), nil + case uint64: + return new(big.Rat).SetUint64(v), nil + case float32: + r := new(big.Rat).SetFloat64(float64(v)) + if r == nil { + return nil, errConversionNotSupported + } + case float64: + r := new(big.Rat).SetFloat64(v) + if r == nil { + return nil, errConversionNotSupported + } + case string: + r, ok := new(big.Rat).SetString(v) + if !ok { + return nil, errConversionNotSupported + } + return r, nil + } + + rv := reflect.ValueOf(v) + switch rv.Kind() { + case reflect.Bool: + if rv.Bool() { + return ratOne, nil + } + return ratZero, nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return new(big.Rat).SetInt64(rv.Int()), nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return new(big.Rat).SetUint64(rv.Uint()), nil + case reflect.Float32, reflect.Float64: + r := new(big.Rat).SetFloat64(rv.Float()) + if r == nil { + return nil, errConversionNotSupported + } + return r, nil + case reflect.String: + r, ok := new(big.Rat).SetString(rv.String()) + if !ok { + return nil, errConversionNotSupported + } + return r, nil + case reflect.Ptr: + if rv.IsNil() { + return nil, nil + } + return convertDecimal(rv.Elem().Interface()) + default: + if rv.Type().ConvertibleTo(ratReflectType) { + tv := rv.Convert(ratReflectType) + return tv.Interface().(big.Rat), nil + } + return nil, errConversionNotSupported + } +} + +func convertBytes(v any) (any, error) { + switch v := v.(type) { + case string, []byte: + return v, nil + } + + rv := reflect.ValueOf(v) + switch rv.Kind() { + case reflect.String: + return rv.String(), nil + case reflect.Ptr: + if rv.IsNil() { + return nil, nil + } + return convertBytes(rv.Elem().Interface()) + case reflect.Slice: + if rv.Type() == bytesReflectType { + return rv.Bytes(), nil + } + fallthrough + default: + if rv.Type().ConvertibleTo(bytesReflectType) { + bv := rv.Convert(bytesReflectType) + return bv.Interface().([]byte), nil + } + return nil, errConversionNotSupported + } +} + +// readProvider is the interface wrapping the Reader which provides an io.Reader. +type readProvider interface { + Reader() io.Reader +} + +func convertLob(v any, cesu8Encoder transform.Transformer) (any, error) { + var rd io.Reader = nil + switch v := v.(type) { + case io.Reader: + rd = v + case readProvider: + rd = v.Reader() + default: + // check if string or []byte + if v, err := convertBytes(v); err == nil { + switch v := v.(type) { + case string: + rd = strings.NewReader(v) + case []byte: + rd = bytes.NewReader(v) + } + } + } + if rd != nil { + if cesu8Encoder != nil { + rd = transform.NewReader(rd, cesu8Encoder) + } + return newLobInDescr(rd), nil + } + + rv := reflect.ValueOf(v) + switch rv.Kind() { + case reflect.Ptr: + if rv.IsNil() { + return nil, nil + } + return convertLob(rv.Elem().Interface(), cesu8Encoder) + default: + return nil, errConversionNotSupported + } +} + +func convertField(tc typeCode, v any, cesu8Encoder transform.Transformer) (any, error) { + if v == nil { + return nil, nil + } + + switch tc { + case tcBoolean: + return convertBool(v) + case tcTinyint: + return convertInteger(v, minTinyint, maxTinyint) + case tcSmallint: + return convertInteger(v, minSmallint, maxSmallint) + case tcInteger: + return convertInteger(v, minInteger, maxInteger) + case tcBigint: + return convertInteger(v, minBigint, maxBigint) + case tcReal: + return convertFloat(v, maxReal) + case tcDouble: + return convertFloat(v, maxDouble) + case tcDate, tcTime, tcTimestamp, tcLongdate, tcSeconddate, tcDaydate, tcSecondtime: + return convertTime(v) + case tcDecimal, tcFixed8, tcFixed12, tcFixed16: + return convertDecimal(v) + case tcChar, tcVarchar, tcString, tcBstring, tcAlphanum, tcNchar, tcNvarchar, tcNstring, tcShorttext, tcBinary, tcVarbinary, tcStPoint, tcStGeometry: + return convertBytes(v) + case tcBlob, tcClob, tcLocator: + return convertLob(v, nil) + case tcNclob, tcText, tcNlocator: + return convertLob(v, cesu8Encoder) + case tcBintext: // ?? lobCESU8Type + return convertLob(v, nil) + default: + panic(fmt.Errorf("invalid type code %[1]d %[1]s", tc)) // should never happen + } +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/datatype.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/datatype.go new file mode 100644 index 00000000..737b0a78 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/datatype.go @@ -0,0 +1,63 @@ +package protocol + +import ( + "database/sql" + "reflect" + "time" +) + +// DataType is the type definition for data types supported by this package. +type DataType byte + +// Data type constants. +const ( + DtUnknown DataType = iota // unknown data type + DtBoolean + DtTinyint + DtSmallint + DtInteger + DtBigint + DtReal + DtDouble + DtDecimal + DtTime + DtString + DtBytes + DtLob + DtRows +) + +// RegisterScanType registers driver owned datatype scantypes (e.g. Decimal, Lob). +func RegisterScanType(dt DataType, scanType, scanNullType reflect.Type) bool { + scanTypes[dt].scanType = scanType + scanTypes[dt].scanNullType = scanNullType + return true +} + +var scanTypes = []struct { + scanType reflect.Type + scanNullType reflect.Type +}{ + DtUnknown: {reflect.TypeFor[any](), reflect.TypeFor[any]()}, + DtBoolean: {reflect.TypeFor[bool](), reflect.TypeFor[sql.NullBool]()}, + DtTinyint: {reflect.TypeFor[uint8](), reflect.TypeFor[sql.NullByte]()}, + DtSmallint: {reflect.TypeFor[int16](), reflect.TypeFor[sql.NullInt16]()}, + DtInteger: {reflect.TypeFor[int32](), reflect.TypeFor[sql.NullInt32]()}, + DtBigint: {reflect.TypeFor[int64](), reflect.TypeFor[sql.NullInt64]()}, + DtReal: {reflect.TypeFor[float32](), reflect.TypeFor[sql.NullFloat64]()}, + DtDouble: {reflect.TypeFor[float64](), reflect.TypeFor[sql.NullFloat64]()}, + DtTime: {reflect.TypeFor[time.Time](), reflect.TypeFor[sql.NullTime]()}, + DtString: {reflect.TypeFor[string](), reflect.TypeFor[sql.NullString]()}, + DtBytes: {nil, nil}, // to be registered by driver + DtDecimal: {nil, nil}, // to be registered by driver + DtLob: {nil, nil}, // to be registered by driver + DtRows: {reflect.TypeFor[sql.Rows](), reflect.TypeFor[sql.Rows]()}, +} + +// ScanType return the scan type (reflect.Type) of the corresponding data type. +func (dt DataType) ScanType(nullable bool) reflect.Type { + if nullable { + return scanTypes[dt].scanNullType + } + return scanTypes[dt].scanType +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/decode.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/decode.go new file mode 100644 index 00000000..609418e0 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/decode.go @@ -0,0 +1,148 @@ +package protocol + +import ( + "github.com/SAP/go-hdb/driver/internal/protocol/encoding" + "golang.org/x/text/transform" +) + +func decodeResult(tc typeCode, d *encoding.Decoder, tr transform.Transformer, lobReader LobReader, lobChunkSize, scale int) (any, error) { //nolint: gocyclo + switch tc { + case tcBoolean: + return d.BooleanField() + case tcTinyint: + if !d.Bool() { // null value + return nil, nil + } + return int64(d.Byte()), nil + case tcSmallint: + if !d.Bool() { // null value + return nil, nil + } + return int64(d.Int16()), nil + case tcInteger: + if !d.Bool() { // null value + return nil, nil + } + return int64(d.Int32()), nil + case tcBigint: + if !d.Bool() { // null value + return nil, nil + } + return d.Int64(), nil + case tcReal: + return d.RealField() + case tcDouble: + return d.DoubleField() + case tcDate: + return d.DateField() + case tcTime: + return d.TimeField() + case tcTimestamp: + return d.TimestampField() + case tcLongdate: + return d.LongdateField() + case tcSeconddate: + return d.SeconddateField() + case tcDaydate: + return d.DaydateField() + case tcSecondtime: + return d.SecondtimeField() + case tcDecimal: + return d.DecimalField() + case tcFixed8: + return d.Fixed8Field(scale) + case tcFixed12: + return d.Fixed12Field(scale) + case tcFixed16: + return d.Fixed16Field(scale) + case tcChar, tcVarchar, tcString, tcBstring, tcBinary, tcVarbinary: + return d.VarField() + case tcAlphanum: + return d.AlphanumField() + case tcNchar, tcNvarchar, tcNstring, tcShorttext: + return d.Cesu8Field() + case tcStPoint, tcStGeometry: + return d.HexField() + case tcBlob, tcClob, tcLocator, tcBintext: + descr := newLobOutDescr(nil, lobReader, lobChunkSize) + if descr.decode(d) { + return nil, nil + } + return descr, nil + case tcText, tcNclob, tcNlocator: + descr := newLobOutDescr(tr, lobReader, lobChunkSize) + if descr.decode(d) { + return nil, nil + } + return descr, nil + default: + panic("invalid type code") + } +} + +func decodeLobParameter(d *encoding.Decoder) (any, error) { + // real decoding (sniffer) not yet supported + // descr := &LobInDescr{} + // descr.Opt = LobOptions(d.Byte()) + // descr._size = int(d.Int32()) + // descr.pos = int(d.Int32()) + d.Byte() + d.Int32() + d.Int32() + return nil, nil +} + +func decodeParameter(tc typeCode, d *encoding.Decoder, scale int) (any, error) { + switch tc { + case tcBoolean: + return d.BooleanField() + case tcTinyint: + return int64(d.Byte()), nil + case tcSmallint: + return int64(d.Int16()), nil + case tcInteger: + return int64(d.Int32()), nil + case tcBigint: + return d.Int64(), nil + case tcReal: + return d.RealField() + case tcDouble: + return d.DoubleField() + case tcDate: + return d.DateField() + case tcTime: + return d.TimeField() + case tcTimestamp: + return d.TimestampField() + case tcLongdate: + return d.LongdateField() + case tcSeconddate: + return d.SeconddateField() + case tcDaydate: + return d.DaydateField() + case tcSecondtime: + return d.SecondtimeField() + case tcDecimal: + return d.DecimalField() + case tcFixed8: + return d.Fixed8Field(scale) + case tcFixed12: + return d.Fixed12Field(scale) + case tcFixed16: + return d.Fixed16Field(scale) + case tcChar, tcVarchar, tcString, tcBstring, tcBinary, tcVarbinary: + return d.VarField() + case tcAlphanum: + return d.AlphanumField() + case tcNchar, tcNvarchar, tcNstring, tcShorttext: + return d.Cesu8Field() + case tcStPoint, tcStGeometry: + return d.HexField() + case tcBlob, tcClob, tcLocator, tcBintext: + return decodeLobParameter(d) + case tcText, tcNclob, tcNlocator: + return decodeLobParameter(d) + default: + panic("invalid type code") + } +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/decodeerror.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/decodeerror.go new file mode 100644 index 00000000..e90de039 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/decodeerror.go @@ -0,0 +1,47 @@ +package protocol + +import ( + "errors" + "fmt" +) + +// DecodeError represents a decoding error. +type DecodeError struct { + row int + fieldName string + err error +} + +func (e *DecodeError) Unwrap() error { return e.err } + +func (e *DecodeError) Error() string { + return fmt.Sprintf("decode error: %s row: %d fieldname: %s", e.err, e.row, e.fieldName) +} + +// DecodeErrors represents a list of decoding errors. +type DecodeErrors []*DecodeError + +func (errs DecodeErrors) rowErrors(row int) error { + var rowErrs []error + for _, err := range errs { + if err.row == row { + rowErrs = append(rowErrs, err) + } + } + switch len(rowErrs) { + case 0: + return nil + case 1: + return rowErrs[0] + default: + return errors.Join(rowErrs...) + } +} + +// RowErrors returns errors if they were assigned to a row, nil otherwise. +func (errs DecodeErrors) RowErrors(row int) error { + if len(errs) == 0 { + return nil + } + return errs.rowErrors(row) +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/dfv.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/dfv.go new file mode 100644 index 00000000..d072cbbf --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/dfv.go @@ -0,0 +1,34 @@ +package protocol + +// Data format version values. +const ( + DfvLevel0 int = 0 // base data format + DfvLevel1 int = 1 // eval types support all data types + DfvLevel2 int = 2 // reserved, broken, do not use + DfvLevel3 int = 3 // additional types Longdate, Secondate, Daydate, Secondtime supported for NGAP + DfvLevel4 int = 4 // generic support for new date/time types + DfvLevel5 int = 5 // spatial types in ODBC on request + DfvLevel6 int = 6 // BINTEXT + DfvLevel7 int = 7 // with boolean support + DfvLevel8 int = 8 // with FIXED8/12/16 support +) + +var ( + defaultDfv = DfvLevel8 + supportedDfvs = []int{DfvLevel1, DfvLevel4, DfvLevel6, DfvLevel8} +) + +// SupportedDfvs returns a slice of data format versions supported by the driver. +// If parameter defaultOnly is set only the default dfv is returned, otherwise +// all supported dfv values are returned. +func SupportedDfvs(defaultOnly bool) []int { + if defaultOnly { + return []int{defaultDfv} + } + return supportedDfvs +} + +// IsSupportedDfv returns true if the data format version dfv is supported by the driver, false otherwise. +func IsSupportedDfv(dfv int) bool { + return dfv == DfvLevel1 || dfv == DfvLevel4 || dfv == DfvLevel6 || dfv == DfvLevel8 +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/doc.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/doc.go new file mode 100644 index 00000000..099944a4 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/doc.go @@ -0,0 +1,4 @@ +// Package protocol implements the hdb command network protocol. +// +// http://help.sap.com/hana/SAP_HANA_SQL_Command_Network_Protocol_Reference_en.pdf +package protocol diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/encoding/datetime.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/encoding/datetime.go new file mode 100644 index 00000000..8bf5de95 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/encoding/datetime.go @@ -0,0 +1,51 @@ +package encoding + +import ( + "time" + + "github.com/SAP/go-hdb/driver/internal/protocol/julian" +) + +// Longdate. +func convertLongdateToTime(longdate int64) time.Time { + const dayfactor = 10000000 * 24 * 60 * 60 + longdate-- + d := (longdate % dayfactor) * 100 + t := convertDaydateToTime((longdate / dayfactor) + 1) + return t.Add(time.Duration(d)) +} + +// nanosecond: HDB - 7 digits precision (not 9 digits). +func convertTimeToLongdate(t time.Time) int64 { + return (((((((convertTimeToDayDate(t)-1)*24)+int64(t.Hour()))*60)+int64(t.Minute()))*60)+int64(t.Second()))*1e7 + int64(t.Nanosecond()/1e2) + 1 +} + +// Seconddate. +func convertSeconddateToTime(seconddate int64) time.Time { + const dayfactor = 24 * 60 * 60 + seconddate-- + d := (seconddate % dayfactor) * 1e9 + t := convertDaydateToTime((seconddate / dayfactor) + 1) + return t.Add(time.Duration(d)) +} +func convertTimeToSeconddate(t time.Time) int64 { + return (((((convertTimeToDayDate(t)-1)*24)+int64(t.Hour()))*60)+int64(t.Minute()))*60 + int64(t.Second()) + 1 +} + +const julianHdb = 1721423 // 1 January 0001 00:00:00 (1721424) - 1 + +// Daydate. +func convertDaydateToTime(daydate int64) time.Time { + return julian.DayToTime(int(daydate) + julianHdb) +} +func convertTimeToDayDate(t time.Time) int64 { + return int64(julian.TimeToDay(t) - julianHdb) +} + +// Secondtime. +func convertSecondtimeToTime(secondtime int) time.Time { + return time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC).Add(time.Duration(int64(secondtime-1) * 1e9)) +} +func convertTimeToSecondtime(t time.Time) int { + return (t.Hour()*60+t.Minute())*60 + t.Second() + 1 +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/encoding/decimal.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/encoding/decimal.go new file mode 100644 index 00000000..8b3b7717 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/encoding/decimal.go @@ -0,0 +1,233 @@ +package encoding + +import ( + "errors" + "math" + "math/big" + "math/bits" +) + +// ErrDecimalOutOfRange means that a big.Rat exceeds the size of hdb decimal fields. +var ErrDecimalOutOfRange = errors.New("decimal out of range error") + +const _S = bits.UintSize / 8 // word size in bytes +// http://en.wikipedia.org/wiki/Decimal128_floating-point_format +const dec128Bias = 6176 +const decSize = 16 + +// decimals. +const ( + // http://en.wikipedia.org/wiki/Decimal128_floating-point_format + dec128Digits = 34 + // dec128Bias = 6176 + dec128MinExp = -6176 + dec128MaxExp = 6111 +) + +var ( + natZero = big.NewInt(0) + natOne = big.NewInt(1) + natTen = big.NewInt(10) +) + +const maxNatExp10 = 38 // maximal fixed decimal precision + +var natExp10 = make([]*big.Int, maxNatExp10) + +func init() { + natExp10[0], natExp10[1] = natOne, natTen + for i := 2; i < maxNatExp10; i++ { + natExp10[i] = new(big.Int).Mul(natExp10[i-1], natTen) + } +} + +/* +performance: tested with reference work variable + - but int.Set is expensive, so let's live with big.Int creation for n >= len(nat) +*/ +func exp10(n int) *big.Int { + if n < len(natExp10) { + return natExp10[n] + } + r := big.NewInt(int64(n)) + return r.Exp(natTen, r, nil) +} + +var lg10 = math.Log2(10) + +func digits10(p *big.Int) int { + k := p.BitLen() // 2^k <= p < 2^(k+1) - 1 + i := int(float64(k) / lg10) + if i < 1 { + i = 1 + } + // i <= digit10(p) + for ; ; i++ { + if p.Cmp(exp10(i)) < 0 { + return i + } + } +} + +// decimal flag. +const ( + dfNotExact byte = 1 << iota + dfOverflow + dfUnderflow +) + +func convertRatToDecimal(x *big.Rat, m *big.Int, digits, minExp, maxExp int) (int, byte) { + if x.Num().Cmp(natZero) == 0 { // zero + m.Set(natZero) + return 0, 0 + } + + var tmp big.Rat + + c := (&tmp).Set(x) // copy + a := c.Num() + b := c.Denom() + + var exp int + shift := 0 + + if c.IsInt() { + exp = digits10(a) - 1 + } else { + shift = digits10(a) - digits10(b) + switch { + case shift < 0: + a.Mul(a, exp10(shift*-1)) + case shift > 0: + b.Mul(b, exp10(shift)) + } + if a.Cmp(b) == -1 { + exp = shift - 1 + } else { + exp = shift + } + } + + var df byte + + switch { + default: + exp = max(exp-digits+1, minExp) + case exp < minExp: + df |= dfUnderflow + exp = exp - digits + 1 + } + + if exp > maxExp { + df |= dfOverflow + } + + shift = exp - shift + switch { + case shift < 0: + a.Mul(a, exp10(shift*-1)) + case exp > 0: + b.Mul(b, exp10(shift)) + } + + m.QuoRem(a, b, a) // reuse a as rest + if a.Cmp(natZero) != 0 { + // round (business >= 0.5 up) + df |= dfNotExact + if a.Add(a, a).Cmp(b) >= 0 { + m.Add(m, natOne) + if m.Cmp(exp10(digits)) == 0 { + shift := min(digits, maxExp-exp) + if shift < 1 { // overflow -> shift one at minimum + df |= dfOverflow + shift = 1 + } + m.Set(exp10(digits - shift)) + exp += shift + } + } + } + + // norm + for exp < maxExp { + a.QuoRem(m, natTen, b) // reuse a, b + if b.Cmp(natZero) != 0 { + break + } + m.Set(a) + exp++ + } + + return exp, df +} + +func convertDecimalToRat(m *big.Int, exp int) *big.Rat { + if m == nil { + return nil + } + + v := new(big.Rat).SetInt(m) + p := v.Num() + q := v.Denom() + + switch { + case exp < 0: + q.Set(exp10(exp * -1)) + case exp == 0: + q.Set(natOne) + case exp > 0: + p.Mul(p, exp10(exp)) + q.Set(natOne) + } + return v +} + +func convertRatToFixed(r *big.Rat, m *big.Int, prec, scale int) byte { + if scale < 0 { + panic("fixed: invalid scale") + } + + var df byte + + m.Set(r.Num()) + m.Mul(m, exp10(scale)) + + var tmp big.Rat + + c := (&tmp).SetFrac(m, r.Denom()) // norm + a := c.Num() + b := c.Denom() + + if b.Cmp(natZero) == 0 { // + m.Set(a) + return df + } + + m.QuoRem(a, b, a) // reuse a as rest + if a.Cmp(natZero) != 0 { + // round (business >= 0.5 up) + df |= dfNotExact + if a.Add(a, a).Cmp(b) >= 0 { + m.Add(m, natOne) + } + } + + maxInt := exp10(prec) + minInt := new(big.Int).Neg(maxInt) + + if m.Cmp(minInt) <= 0 || m.Cmp(maxInt) >= 0 { + df |= dfOverflow + } + return df +} + +func convertFixedToRat(m *big.Int, scale int) *big.Rat { + if m == nil { + return nil + } + if scale < 0 { + panic("fixed: invalid scale") + } + q := exp10(scale) + return new(big.Rat).SetFrac(m, q) +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/encoding/decode.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/encoding/decode.go new file mode 100644 index 00000000..1ccf9b80 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/encoding/decode.go @@ -0,0 +1,581 @@ +package encoding + +import ( + "encoding/binary" + "encoding/hex" + "fmt" + "io" + "math" + "math/big" + "time" + + "github.com/SAP/go-hdb/driver/internal/unsafe" + "golang.org/x/text/transform" +) + +const readScratchSize = 4096 + +// Decoder decodes hdb protocol datatypes on basis of an io.Reader. +type Decoder struct { + rd io.Reader + /* err: fatal read error + - not set by conversion errors + - conversion errors are returned by the reader function itself + */ + err error + b []byte // scratch buffer (used for skip, CESU8Bytes - define size not too small!) + tr transform.Transformer + cnt int + + // decoder options + alphanumDfv1 bool + emptyDateAsNull bool +} + +// NewDecoder creates a new Decoder instance based on an io.Reader. +func NewDecoder(rd io.Reader, tr transform.Transformer, emptyDateAsNull bool) *Decoder { + return &Decoder{ + rd: rd, + b: make([]byte, readScratchSize), + tr: tr, + emptyDateAsNull: emptyDateAsNull, + } +} + +// SetAlphanumDfv1 sets the alphanum dfv1 flag decoder. +func (d *Decoder) SetAlphanumDfv1(alphanumDfv1 bool) { d.alphanumDfv1 = alphanumDfv1 } + +// Cnt returns the value of the byte read counter. +func (d *Decoder) Cnt() int { return d.cnt } + +// Error returns the last decoder error. +func (d *Decoder) Error() error { return d.err } + +// ResetError resets reader error. +func (d *Decoder) ResetError() { d.err = nil } + +// readFull reads data from reader + read counter and error handling. +func (d *Decoder) readFull(buf []byte) error { + if d.err != nil { + return d.err + } + var n int + n, d.err = io.ReadFull(d.rd, buf) + d.cnt += n + return d.err +} + +// Skip skips cnt bytes from reading. +func (d *Decoder) Skip(cnt int) { + if cnt <= readScratchSize { + d.readFull(d.b[:cnt]) //nolint: errcheck + return + } + var n int64 + n, d.err = io.CopyN(io.Discard, d.rd, int64(cnt)) + d.cnt += int(n) +} + +// Byte decodes a byte. +func (d *Decoder) Byte() byte { + if err := d.readFull(d.b[:1]); err != nil { + return 0 + } + return d.b[0] +} + +// Bytes decodes bytes. +func (d *Decoder) Bytes(p []byte) { + d.readFull(p) //nolint:errcheck +} + +// Bool decodes a boolean. +func (d *Decoder) Bool() bool { + return d.Byte() != 0 +} + +// Int8 decodes an int8. +func (d *Decoder) Int8() int8 { + return int8(d.Byte()) +} + +// Int16 decodes an int16. +func (d *Decoder) Int16() int16 { + if err := d.readFull(d.b[:2]); err != nil { + return 0 + } + return int16(binary.LittleEndian.Uint16(d.b[:2])) //nolint: gosec +} + +// Uint16 decodes an uint16. +func (d *Decoder) Uint16() uint16 { + if err := d.readFull(d.b[:2]); err != nil { + return 0 + } + return binary.LittleEndian.Uint16(d.b[:2]) +} + +// Uint16ByteOrder decodes an uint16 in given byte order. +func (d *Decoder) Uint16ByteOrder(byteOrder binary.ByteOrder) uint16 { + if err := d.readFull(d.b[:2]); err != nil { + return 0 + } + return byteOrder.Uint16(d.b[:2]) +} + +// Int32 decodes an int32. +func (d *Decoder) Int32() int32 { + if err := d.readFull(d.b[:4]); err != nil { + return 0 + } + return int32(binary.LittleEndian.Uint32(d.b[:4])) //nolint: gosec +} + +// Uint32 decodes an uint32. +func (d *Decoder) Uint32() uint32 { + if err := d.readFull(d.b[:4]); err != nil { + return 0 + } + return binary.LittleEndian.Uint32(d.b[:4]) +} + +// Uint32ByteOrder decodes an uint32 in given byte order. +func (d *Decoder) Uint32ByteOrder(byteOrder binary.ByteOrder) uint32 { + if err := d.readFull(d.b[:4]); err != nil { + return 0 + } + return byteOrder.Uint32(d.b[:4]) +} + +// Int64 decodes an int64. +func (d *Decoder) Int64() int64 { + if err := d.readFull(d.b[:8]); err != nil { + return 0 + } + return int64(binary.LittleEndian.Uint64(d.b[:8])) //nolint: gosec +} + +// Uint64 decodes an uint64. +func (d *Decoder) Uint64() uint64 { + if err := d.readFull(d.b[:8]); err != nil { + return 0 + } + return binary.LittleEndian.Uint64(d.b[:8]) +} + +// Float32 decodes a float32. +func (d *Decoder) Float32() float32 { + if err := d.readFull(d.b[:4]); err != nil { + return 0 + } + bits := binary.LittleEndian.Uint32(d.b[:4]) + return math.Float32frombits(bits) +} + +// Float64 decodes a float64. +func (d *Decoder) Float64() float64 { + if err := d.readFull(d.b[:8]); err != nil { + return 0 + } + bits := binary.LittleEndian.Uint64(d.b[:8]) + return math.Float64frombits(bits) +} + +// Decimal decodes a decimal. +// - error is only returned in case of conversion errors. +func (d *Decoder) Decimal() (*big.Int, int, error) { // m, exp + bs := d.b[:decSize] + + if err := d.readFull(bs); err != nil { + return nil, 0, nil + } + + if (bs[15] & 0x70) == 0x70 { // null value (bit 4,5,6 set) + return nil, 0, nil + } + + if (bs[15] & 0x60) == 0x60 { + return nil, 0, fmt.Errorf("decimal: format (infinity, nan, ...) not supported : %v", bs) + } + + neg := (bs[15] & 0x80) != 0 + exp := int((((uint16(bs[15])<<8)|uint16(bs[14]))<<1)>>2) - dec128Bias + + // b14 := b[14] // save b[14] + bs[14] &= 0x01 // keep the mantissa bit (rest: sign and exp) + + // most significand byte + msb := 14 + for msb > 0 && bs[msb] == 0 { + msb-- + } + + // calc number of words + numWords := (msb / _S) + 1 + ws := make([]big.Word, numWords) + + bs = bs[:msb+1] + for i, b := range bs { + ws[i/_S] |= (big.Word(b) << (i % _S * 8)) + } + + m := new(big.Int).SetBits(ws) + if neg { + m = m.Neg(m) + } + return m, exp, nil +} + +// Fixed decodes a fixed decimal. +func (d *Decoder) Fixed(size int) *big.Int { // m, exp + bs := d.b[:size] + + if err := d.readFull(bs); err != nil { + return nil + } + + neg := (bs[size-1] & 0x80) != 0 // is negative number (2s complement) + + // most significand byte + msb := size - 1 + for msb > 0 && bs[msb] == 0 { + msb-- + } + + // calc number of words + numWords := (msb / _S) + 1 + ws := make([]big.Word, numWords) + + bs = bs[:msb+1] + for i, b := range bs { + // if negative: invert byte (2s complement) + if neg { + b = ^b + } + ws[i/_S] |= (big.Word(b) << (i % _S * 8)) + } + + m := new(big.Int).SetBits(ws) + + if neg { + m.Add(m, natOne) // 2s complement - add 1 + m.Neg(m) // set sign + } + return m +} + +// CESU8Bytes decodes CESU-8 into UTF-8 bytes. +// - error is only returned in case of conversion errors. +func (d *Decoder) CESU8Bytes(size int) ([]byte, error) { + if d.err != nil { + return nil, nil + } + + var p []byte + if size > readScratchSize { + p = make([]byte, size) + } else { + p = d.b[:size] + } + + if err := d.readFull(p); err != nil { + return nil, nil + } + + b, _, err := transform.Bytes(d.tr, p) + return b, err +} + +// varFieldInd decodes a variable field indicator. +func (d *Decoder) varFieldInd() (n, size int, null bool) { + ind := d.Byte() // length indicator + switch { + default: + return 1, 0, false + case ind == bytesLenIndNullValue: + return 1, 0, true + case ind <= bytesLenIndSmall: + return 1, int(ind), false + case ind == bytesLenIndMedium: + return 3, int(d.Int16()), false + case ind == bytesLenIndBig: + return 5, int(d.Int32()), false + } +} + +// LIBytes decodes bytes with length indicator. +func (d *Decoder) LIBytes() (n int, b []byte) { + n, size, null := d.varFieldInd() + if null { + return n, nil + } + b = make([]byte, size) + d.Bytes(b) + return n + size, b +} + +// LIString decodes a string with length indicator. +func (d *Decoder) LIString() (n int, s string) { + n, b := d.LIBytes() + return n, unsafe.ByteSlice2String(b) +} + +// CESU8LIBytes decodes CESU-8 into UTF-8 bytes with length indicator. +func (d *Decoder) CESU8LIBytes() (int, []byte, error) { + n, size, null := d.varFieldInd() + if null { + return n, nil, nil + } + b, err := d.CESU8Bytes(size) + return n + size, b, err +} + +// CESU8LIString decodes a CESU-8 into a UTF-8 string with length indicator. +func (d *Decoder) CESU8LIString() (int, string, error) { + n, b, err := d.CESU8LIBytes() + return n, unsafe.ByteSlice2String(b), err +} + +// Fields. + +// BooleanField decodes a boolean field. +func (d *Decoder) BooleanField() (any, error) { + b := d.Byte() + switch b { + case booleanNullValue: + return nil, nil + case booleanFalseValue: + return false, nil + default: + return true, nil + } +} + +// RealField decodes a real field. +func (d *Decoder) RealField() (any, error) { + v := d.Uint32() + if v == realNullValue { + return nil, nil + } + return float64(math.Float32frombits(v)), nil +} + +// DoubleField decodes a double field. +func (d *Decoder) DoubleField() (any, error) { + v := d.Uint64() + if v == doubleNullValue { + return nil, nil + } + return math.Float64frombits(v), nil +} + +func (d *Decoder) decodeDate() (int, time.Month, int, bool) { + // decode. + /* + null values: most sig bit unset + year: unset second most sig bit (subtract 2^15) + --> read year as unsigned + month is 0-based + day is 1 byte. + */ + year := d.Uint16() + null := ((year & 0x8000) == 0) // null value + year &= 0x3fff + month := d.Int8() + month++ + day := d.Int8() + return int(year), time.Month(month), int(day), null +} + +// DateField decodes a date field. +func (d *Decoder) DateField() (any, error) { + year, month, day, null := d.decodeDate() + if null { + return nil, nil + } + return time.Date(year, month, day, 0, 0, 0, 0, time.UTC), nil +} + +func (d *Decoder) decodeTime() (int, int, int, int, bool) { + hour := d.Byte() + null := (hour & 0x80) == 0 // null value + hour &= 0x7f + minute := d.Int8() + msec := d.Uint16() + + sec := msec / 1000 + msec %= 1000 + nsec := int(msec) * 1000000 + + return int(hour), int(minute), int(sec), nsec, null +} + +// TimeField decodes a time field. +func (d *Decoder) TimeField() (any, error) { + // time read gives only seconds (cut), no milliseconds + hour, minute, sec, nsec, null := d.decodeTime() + if null { + return nil, nil + } + return time.Date(1, 1, 1, hour, minute, sec, nsec, time.UTC), nil +} + +// TimestampField decodes a timestamp field. +func (d *Decoder) TimestampField() (any, error) { + year, month, day, dateNull := d.decodeDate() + hour, minute, sec, nsec, timeNull := d.decodeTime() + if dateNull || timeNull { + return nil, nil + } + return time.Date(year, month, day, hour, minute, sec, nsec, time.UTC), nil +} + +// LongdateField decodes a longdate field. +func (d *Decoder) LongdateField() (any, error) { + longdate := d.Int64() + if longdate == longdateNullValue { + return nil, nil + } + return convertLongdateToTime(longdate), nil +} + +// SeconddateField decodes a seconddate field. +func (d *Decoder) SeconddateField() (any, error) { + seconddate := d.Int64() + if seconddate == seconddateNullValue { + return nil, nil + } + return convertSeconddateToTime(seconddate), nil +} + +// DaydateField decodes a daydate field. +func (d *Decoder) DaydateField() (any, error) { + daydate := d.Int32() + if daydate == daydateNullValue || (d.emptyDateAsNull && daydate == 0) { + return nil, nil + } + return convertDaydateToTime(int64(daydate)), nil +} + +// SecondtimeField decodes a secondtime field. +func (d *Decoder) SecondtimeField() (any, error) { + secondtime := d.Int32() + if secondtime == secondtimeNullValue { + return nil, nil + } + return convertSecondtimeToTime(int(secondtime)), nil +} + +// DecimalField decodes a decimal field. +func (d *Decoder) DecimalField() (any, error) { + m, exp, err := d.Decimal() + if err != nil { + return nil, err + } + if m == nil { + return nil, nil + } + return convertDecimalToRat(m, exp), nil +} + +func (d *Decoder) decodeFixed(size, scale int) (any, error) { + m := d.Fixed(size) + if m == nil { // important: return nil and not m (as m is of type *big.Int) + return nil, nil + } + return convertFixedToRat(m, scale), nil +} + +// Fixed8Field decodes a fixed8 field. +func (d *Decoder) Fixed8Field(scale int) (any, error) { + if !d.Bool() { // null value + return nil, nil + } + return d.decodeFixed(Fixed8FieldSize, scale) +} + +// Fixed12Field decodes a fixed12 field. +func (d *Decoder) Fixed12Field(scale int) (any, error) { + if !d.Bool() { // null value + return nil, nil + } + return d.decodeFixed(Fixed12FieldSize, scale) +} + +// Fixed16Field decodes a fixed16 field. +func (d *Decoder) Fixed16Field(scale int) (any, error) { + if !d.Bool() { // null value + return nil, nil + } + return d.decodeFixed(Fixed16FieldSize, scale) +} + +// VarField decodes a var field. +func (d *Decoder) VarField() (any, error) { + _, b := d.LIBytes() + /* + caution: + - result is used as driver.Value and we do need to provide a 'real' nil value + - returning b == nil does not work because b is of type []byte + */ + if b == nil { + return nil, nil + } + return b, nil +} + +// AlphanumField decodes a alphanum field. +func (d *Decoder) AlphanumField() (any, error) { + if d.alphanumDfv1 { // like VarField + return d.VarField() + } + _, b := d.LIBytes() + /* + caution: + - result is used as driver.Value and we do need to provide a 'real' nil value + - returning b == nil does not work because b is of type []byte + */ + if b == nil { + return nil, nil + } + /* + first byte: + - high bit set -> numeric + - high bit unset -> alpha + - bits 0-6: field size + + ignore first byte for now + */ + return b[1:], nil +} + +// Cesu8Field decodes a cesu8 field. +func (d *Decoder) Cesu8Field() (any, error) { + _, b, err := d.CESU8LIBytes() + if err != nil { + return nil, err + } + /* + caution: + - result is used as driver.Value and we do need to provide a 'real' nil value + - returning b == nil does not work because b is of type []byte + */ + if b == nil { + return nil, nil + } + return b, nil +} + +// HexField decodes a hex field. +func (d *Decoder) HexField() (any, error) { + _, b := d.LIBytes() + /* + caution: + - result is used as driver.Value and we do need to provide a 'real' nil value + - returning b == nil does not work because b is of type []byte + */ + if b == nil { + return nil, nil + } + return hex.EncodeToString(b), nil +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/encoding/doc.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/encoding/doc.go new file mode 100644 index 00000000..1f13dabe --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/encoding/doc.go @@ -0,0 +1,2 @@ +// Package encoding implements hdb field type en,- and decodings. +package encoding diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/encoding/encode.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/encoding/encode.go new file mode 100644 index 00000000..53f732cc --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/encoding/encode.go @@ -0,0 +1,535 @@ +package encoding + +import ( + "encoding/binary" + "encoding/hex" + "errors" + "fmt" + "io" + "math" + "math/big" + "time" + + "github.com/SAP/go-hdb/driver/internal/unsafe" + "github.com/SAP/go-hdb/driver/unicode/cesu8" + "golang.org/x/text/transform" +) + +const writeScratchSize = 4096 + +// Encoder encodes hdb protocol datatypes on basis of an io.Writer. +type Encoder struct { + wr io.Writer + b []byte // scratch buffer (min 15 Bytes - Decimal) + tr transform.Transformer +} + +// NewEncoder creates a new Encoder instance. +func NewEncoder(wr io.Writer, tr transform.Transformer) *Encoder { + return &Encoder{ + wr: wr, + b: make([]byte, writeScratchSize), + tr: tr, + } +} + +// Zeroes encodes cnt zero byte values. +func (e *Encoder) Zeroes(cnt int) { + // zero out scratch area + l := cnt + if l > len(e.b) { + l = len(e.b) + } + for i := range l { + e.b[i] = 0 + } + + for i := 0; i < cnt; { + j := cnt - i + if j > len(e.b) { + j = len(e.b) + } + n, _ := e.wr.Write(e.b[:j]) + if n != j { + return + } + i += n + } +} + +// Bytes encodes bytes. +func (e *Encoder) Bytes(p []byte) { + e.wr.Write(p) //nolint:errcheck +} + +// Byte encodes a byte. +func (e *Encoder) Byte(b byte) { // WriteB as sig differs from WriteByte (vet issues) + e.b[0] = b + e.Bytes(e.b[:1]) +} + +// Bool encodes a boolean. +func (e *Encoder) Bool(v bool) { + if v { + e.Byte(1) + } else { + e.Byte(0) + } +} + +// Int8 encodes an int8. +func (e *Encoder) Int8(i int8) { + e.Byte(byte(i)) +} + +// Int16 encodes an int16. +func (e *Encoder) Int16(i int16) { + binary.LittleEndian.PutUint16(e.b[:2], uint16(i)) //nolint: gosec + e.wr.Write(e.b[:2]) //nolint:errcheck +} + +// Uint16 encodes an uint16. +func (e *Encoder) Uint16(i uint16) { + binary.LittleEndian.PutUint16(e.b[:2], i) + e.wr.Write(e.b[:2]) //nolint:errcheck +} + +// Uint16ByteOrder encodes an uint16 in given byte order. +func (e *Encoder) Uint16ByteOrder(i uint16, byteOrder binary.ByteOrder) { + byteOrder.PutUint16(e.b[:2], i) + e.wr.Write(e.b[:2]) //nolint:errcheck +} + +// Int32 encodes an int32. +func (e *Encoder) Int32(i int32) { + binary.LittleEndian.PutUint32(e.b[:4], uint32(i)) //nolint:gosec + e.wr.Write(e.b[:4]) //nolint:errcheck +} + +// Uint32 encodes an uint32. +func (e *Encoder) Uint32(i uint32) { + binary.LittleEndian.PutUint32(e.b[:4], i) + e.wr.Write(e.b[:4]) //nolint:errcheck +} + +// Int64 encodes an int64. +func (e *Encoder) Int64(i int64) { + binary.LittleEndian.PutUint64(e.b[:8], uint64(i)) //nolint:gosec + e.wr.Write(e.b[:8]) //nolint:errcheck +} + +// Uint64 encodes an uint64. +func (e *Encoder) Uint64(i uint64) { + binary.LittleEndian.PutUint64(e.b[:8], i) + e.wr.Write(e.b[:8]) //nolint:errcheck +} + +// Float32 encodes a float32. +func (e *Encoder) Float32(f float32) { + bits := math.Float32bits(f) + binary.LittleEndian.PutUint32(e.b[:4], bits) + e.wr.Write(e.b[:4]) //nolint:errcheck +} + +// Float64 encodes a float64. +func (e *Encoder) Float64(f float64) { + bits := math.Float64bits(f) + binary.LittleEndian.PutUint64(e.b[:8], bits) + e.wr.Write(e.b[:8]) //nolint:errcheck +} + +// Decimal encodes a decimal value. +func (e *Encoder) Decimal(m *big.Int, exp int) { + b := e.b[:decSize] + + // little endian bigint words (significand) -> little endian db decimal format + j := 0 + for _, d := range m.Bits() { + for range _S { + b[j] = byte(d) + d >>= 8 + j++ + } + } + + // clear scratch buffer + for i := j; i < decSize; i++ { + b[i] = 0 + } + + exp += dec128Bias + b[14] |= (byte(exp) << 1) + b[15] = byte(uint16(exp) >> 7) //nolint: gosec + + if m.Sign() == -1 { + b[15] |= 0x80 + } + + e.wr.Write(b) //nolint:errcheck +} + +// Fixed encodes a fixed decimal value. +func (e *Encoder) Fixed(m *big.Int, size int) { + b := e.b[:size] + + neg := m.Sign() == -1 + fill := byte(0) + + if neg { + // make positive + m.Neg(m) + // 2s complement + bits := m.Bits() + // - invert all bits + for i := 0; i < len(bits); i++ { + bits[i] = ^bits[i] + } + // - add 1 + m.Add(m, natOne) + fill = 0xff + } + + // little endian bigint words (significand) -> little endian db decimal format + j := 0 + for _, d := range m.Bits() { + /* + check j < size as number of bytes in m.Bits words can exceed number of fixed size bytes + e.g. 64 bit architecture: + - two words equals 16 bytes but fixed size might be 12 bytes + - invariant: all 'skipped' bytes in most significant word are zero + */ + for i := 0; i < _S && j < size; i++ { + b[j] = byte(d) + d >>= 8 + j++ + } + } + + // clear scratch buffer + for i := j; i < size; i++ { + b[i] = fill + } + + e.wr.Write(b) //nolint:errcheck +} + +// String encodes a string. +func (e *Encoder) String(s string) { e.Bytes(unsafe.String2ByteSlice(s)) } + +// CESU8Bytes encodes UTF-8 bytes into CESU-8 and returns the CESU-8 bytes written. +func (e *Encoder) CESU8Bytes(p []byte) (int, error) { + e.tr.Reset() + cnt := 0 + for i := 0; i < len(p); { + nDst, nSrc, err := e.tr.Transform(e.b, p[i:], true) + if nDst != 0 { + n, _ := e.wr.Write(e.b[:nDst]) + cnt += n + } + if err != nil && !errors.Is(err, transform.ErrShortDst) { + return cnt, err + } + i += nSrc + } + return cnt, nil +} + +// CESU8String encodes an UTF-8 string into CESU-8 and returns the CESU-8 bytes written. +func (e *Encoder) CESU8String(s string) (int, error) { return e.CESU8Bytes(unsafe.String2ByteSlice(s)) } + +// varFieldInd encodes a variable field indicator. +func (e *Encoder) varFieldInd(size int) error { + switch { + default: + return fmt.Errorf("max argument length %d of string exceeded", size) + case size <= int(bytesLenIndSmall): + e.Byte(byte(size)) + case size <= math.MaxInt16: + e.Byte(bytesLenIndMedium) + e.Int16(int16(size)) + case size <= math.MaxInt32: + e.Byte(bytesLenIndBig) + e.Int32(int32(size)) + } + return nil +} + +// LIBytes encodes bytes with length indicator. +func (e *Encoder) LIBytes(p []byte) error { + if err := e.varFieldInd(len(p)); err != nil { + return err + } + e.Bytes(p) + return nil +} + +// LIString encodes a string with length indicator. +func (e *Encoder) LIString(s string) error { + if err := e.varFieldInd(len(s)); err != nil { + return err + } + e.String(s) + return nil +} + +// CESU8LIBytes encodes UTF-8 into CESU-8 bytes with length indicator. +func (e *Encoder) CESU8LIBytes(p []byte) error { + size := cesu8.Size(p) + if err := e.varFieldInd(size); err != nil { + return err + } + _, err := e.CESU8Bytes(p) + return err +} + +// CESU8LIString encodes an UTF-8 into a CESU-8 string with length indicator. +func (e *Encoder) CESU8LIString(s string) error { + size := cesu8.StringSize(s) + if err := e.varFieldInd(size); err != nil { + return err + } + _, err := e.CESU8String(s) + return err +} + +// Fields. +func asInt[E byte | int16 | int32 | int64](v any) E { + i64, ok := v.(int64) + if !ok { + panic("invalid integer") // should never happen + } + return E(i64) +} + +func asTime(v any) time.Time { + t, ok := v.(time.Time) + if !ok { + panic("invalid time") // should never happen + } + // store in utc + return t.UTC() +} + +// BooleanField encodes a boolean field. +func (e *Encoder) BooleanField(v any) error { + if v == nil { + e.Byte(booleanNullValue) + return nil + } + b, ok := v.(bool) + if !ok { + panic("invalid boolean") // should never happen + } + if b { + e.Byte(booleanTrueValue) + } else { + e.Byte(booleanFalseValue) + } + return nil +} + +// TinyintField encodes a tinyint field. +func (e *Encoder) TinyintField(v any) error { + e.Byte(asInt[byte](v)) + return nil +} + +// SmallintField encodes a smallint field. +func (e *Encoder) SmallintField(v any) error { + e.Int16(asInt[int16](v)) + return nil +} + +// IntegerField encodes a integer field. +func (e *Encoder) IntegerField(v any) error { + e.Int32(asInt[int32](v)) + return nil +} + +// BigintField encodes a bigint field. +func (e *Encoder) BigintField(v any) error { + e.Int64(asInt[int64](v)) + return nil +} + +// RealField encodes a real field. +func (e *Encoder) RealField(v any) error { + f64, ok := v.(float64) + if !ok { + panic("invalid real") // should never happen + } + e.Float32(float32(f64)) + return nil +} + +// DoubleField encodes a double field. +func (e *Encoder) DoubleField(v any) error { + f64, ok := v.(float64) + if !ok { + panic("invalid double") // should never happen + } + e.Float64(f64) + return nil +} + +func (e *Encoder) encodeDate(t time.Time) { + // year: set most sig bit + // month 0 based + year, month, day := t.Date() + e.Uint16(uint16(year) | 0x8000) //nolint: gosec + e.Int8(int8(month) - 1) //nolint: gosec + e.Int8(int8(day)) //nolint: gosec +} + +// DateField encodes a dayte field. +func (e *Encoder) DateField(v any) error { + e.encodeDate(asTime(v)) + return nil +} + +func (e *Encoder) encodeTime(t time.Time) { + e.Byte(byte(t.Hour()) | 0x80) + e.Int8(int8(t.Minute())) //nolint: gosec + msec := t.Second()*1000 + t.Nanosecond()/1000000 + e.Uint16(uint16(msec)) //nolint: gosec +} + +// TimeField encodes a time field. +func (e *Encoder) TimeField(v any) error { + e.encodeTime(asTime(v)) + return nil +} + +// TimestampField encodes a timestamp field. +func (e *Encoder) TimestampField(v any) error { + t := asTime(v) + e.encodeDate(t) + e.encodeTime(t) + return nil +} + +// LongdateField encodea a longdate field. +func (e *Encoder) LongdateField(v any) error { + e.Int64(convertTimeToLongdate(asTime(v))) + return nil +} + +// SeconddateField encodes a seconddate field. +func (e *Encoder) SeconddateField(v any) error { + e.Int64(convertTimeToSeconddate(asTime(v))) + return nil +} + +// DaydateField encodes a daydate field. +func (e *Encoder) DaydateField(v any) error { + e.Int32(int32(convertTimeToDayDate(asTime(v)))) //nolint: gosec + return nil +} + +// SecondtimeField encodes a secondtime field. +func (e *Encoder) SecondtimeField(v any) error { + if v == nil { + e.Int32(secondtimeNullValue) + return nil + } + e.Int32(int32(convertTimeToSecondtime(asTime(v)))) //nolint: gosec + return nil +} + +func (e *Encoder) encodeFixed(v any, size, prec, scale int) error { + r, ok := v.(*big.Rat) + if !ok { + panic("invalid fixed") // should never happen + } + + var m big.Int + df := convertRatToFixed(r, &m, prec, scale) + + if df&dfOverflow != 0 { + return ErrDecimalOutOfRange + } + + e.Fixed(&m, size) + return nil +} + +// DecimalField encodes a decimal field. +func (e *Encoder) DecimalField(v any) error { + r, ok := v.(*big.Rat) + if !ok { + panic("invalid decimal") // should never happen + } + + var m big.Int + exp, df := convertRatToDecimal(r, &m, dec128Digits, dec128MinExp, dec128MaxExp) + + if df&dfOverflow != 0 { + return ErrDecimalOutOfRange + } + + if df&dfUnderflow != 0 { // set to zero + e.Decimal(natZero, 0) + } else { + e.Decimal(&m, exp) + } + return nil +} + +// Fixed8Field encodes a fixed8 field. +func (e *Encoder) Fixed8Field(v any, prec, scale int) error { + return e.encodeFixed(v, Fixed8FieldSize, prec, scale) +} + +// Fixed12Field encodes a fixed12 field. +func (e *Encoder) Fixed12Field(v any, prec, scale int) error { + return e.encodeFixed(v, Fixed12FieldSize, prec, scale) +} + +// Fixed16Field encodes a fixed16 field. +func (e *Encoder) Fixed16Field(v any, prec, scale int) error { + return e.encodeFixed(v, Fixed16FieldSize, prec, scale) +} + +// VarField encodes a var field. +func (e *Encoder) VarField(v any) error { + switch v := v.(type) { + case []byte: + return e.LIBytes(v) + case string: + return e.LIString(v) + default: + panic("invalid var value") // should never happen + } +} + +// Cesu8Field encodes a cesu8 field. +func (e *Encoder) Cesu8Field(v any) error { + switch v := v.(type) { + case []byte: + return e.CESU8LIBytes(v) + case string: + return e.CESU8LIString(v) + default: + panic("invalid cesu8 value") // should never happen + } +} + +// HexField encodes a hex field. +func (e *Encoder) HexField(v any) error { + switch v := v.(type) { + case []byte: + b, err := hex.DecodeString(string(v)) + if err != nil { + return err + } + return e.LIBytes(b) + case string: + b, err := hex.DecodeString(v) + if err != nil { + return err + } + return e.LIBytes(b) + default: + panic("invalid hex value") // should never happen + } +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/encoding/field.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/encoding/field.go new file mode 100644 index 00000000..a1906a5e --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/encoding/field.go @@ -0,0 +1,114 @@ +package encoding + +import ( + "math" + + "github.com/SAP/go-hdb/driver/unicode/cesu8" +) + +const ( + booleanFalseValue byte = 0 + booleanNullValue byte = 1 + booleanTrueValue byte = 2 +) + +const ( + realNullValue uint32 = ^uint32(0) + doubleNullValue uint64 = ^uint64(0) +) + +const ( + longdateNullValue int64 = 3155380704000000001 + seconddateNullValue int64 = 315538070401 + daydateNullValue int32 = 3652062 + secondtimeNullValue int32 = 86402 +) + +// Field size constants. +const ( + BooleanFieldSize = 1 + TinyintFieldSize = 1 + SmallintFieldSize = 2 + IntegerFieldSize = 4 + BigintFieldSize = 8 + RealFieldSize = 4 + DoubleFieldSize = 8 + DateFieldSize = 4 + TimeFieldSize = 4 + TimestampFieldSize = DateFieldSize + TimeFieldSize + LongdateFieldSize = 8 + SeconddateFieldSize = 8 + DaydateFieldSize = 4 + SecondtimeFieldSize = 4 + DecimalFieldSize = 16 + Fixed8FieldSize = 8 + Fixed12FieldSize = 12 + Fixed16FieldSize = 16 + LobInputParametersSize = 9 +) + +// string / binary length indicators. +const ( + bytesLenIndNullValue byte = 255 + bytesLenIndSmall byte = 245 + bytesLenIndMedium byte = 246 + bytesLenIndBig byte = 247 +) + +// VarFieldSize returns the size of a varible field variable ([]byte, string and unicode variants). +func varSize(size int) int { + switch { + default: + return -1 + case size <= int(bytesLenIndSmall): + return size + 1 + case size <= math.MaxInt16: + return size + 3 + case size <= math.MaxInt32: + return size + 5 + } +} + +// Cesu8FieldSize returns the size of a cesu8 field. +func Cesu8FieldSize(v any) int { + switch v := v.(type) { + case []byte: + return varSize(cesu8.Size(v)) + case string: + return varSize(cesu8.StringSize(v)) + default: + panic("invalid type for cesu8 field") // should never happen + } +} + +// VarFieldSize returns the size of a var field. +func VarFieldSize(v any) int { + switch v := v.(type) { + case []byte: + return varSize(len(v)) + case string: + return varSize(len(v)) + default: + panic("invalid type for var field") // should never happen + } +} + +// HexFieldSize returns the size of a hex field. +func HexFieldSize(v any) int { + switch v := v.(type) { + case []byte: + l := len(v) + if l%2 != 0 { + panic("even hex field length required") + } + return varSize(l / 2) + case string: + l := len(v) + if l%2 != 0 { + panic("even hex field length required") + } + return varSize(l / 2) + default: + panic("invalid hex field type") + } +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/error.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/error.go new file mode 100644 index 00000000..e876352a --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/error.go @@ -0,0 +1,215 @@ +package protocol + +import ( + "fmt" + + "github.com/SAP/go-hdb/driver/internal/protocol/encoding" +) + +// ErrorLevel send from database server. +type errorLevel int8 + +var errorLevelStrs = [...]string{"Warning", "Error", "FatalError"} + +func (e errorLevel) String() string { + if int(e) < 0 || int(e) >= len(errorLevelStrs) { + return "" + } + return errorLevelStrs[e] +} + +// HDB error level constants. +const ( + errorLevelWarning errorLevel = 0 + errorLevelError errorLevel = 1 + errorLevelFatalError errorLevel = 2 +) + +const ( + sqlStateSize = 5 + /* + bytes of fix length fields mod 8 + - errorCode = 4, errorPosition = 4, errortextLength = 4, errorLevel = 1, sqlState = 5 => 18 bytes + - 18 mod 8 = 2 + */ + fixLength = 2 +) + +// HANA Database errors. +const ( + HdbErrAuthenticationFailed = 10 + HdbErrWhileParsingProtocol = 1033 +) + +type sqlState [sqlStateSize]byte + +// HdbError represents a single error returned by the server. +type HdbError struct { + errorCode int32 + errorPosition int32 + errorTextLength int32 + errorLevel errorLevel + sqlState sqlState + stmtNo int + errorText []byte +} + +func (e *HdbError) String() string { + return fmt.Sprintf("errorCode %d errorPosition %d errorTextLength %d errorLevel %s sqlState %s stmtNo %d errorText %s", + e.errorCode, + e.errorPosition, + e.errorTextLength, + e.errorLevel, + e.sqlState, + e.stmtNo, + e.errorText, + ) +} + +func (e *HdbError) Error() string { + if e.stmtNo != -1 { + return fmt.Sprintf("SQL %s %d - %s (statement no: %d)", e.errorLevel, e.errorCode, e.errorText, e.stmtNo) + } + return fmt.Sprintf("SQL %s %d - %s", e.errorLevel, e.errorCode, e.errorText) +} + +// StmtNo implements the driver.DBError interface. +func (e *HdbError) StmtNo() int { return e.stmtNo } + +// Code implements the driver.DBError interface. +func (e *HdbError) Code() int { return int(e.errorCode) } + +// Position implements the driver.DBError interface. +func (e *HdbError) Position() int { return int(e.errorPosition) } + +// Level implements the driver.DBError interface. +func (e *HdbError) Level() int { return int(e.errorLevel) } + +// Text implements the driver.DBError interface. +func (e *HdbError) Text() string { return string(e.errorText) } + +// IsWarning implements the driver.DBError interface. +func (e *HdbError) IsWarning() bool { return e.errorLevel == errorLevelWarning } + +// IsError implements the driver.DBError interface. +func (e *HdbError) IsError() bool { return e.errorLevel == errorLevelError } + +// IsFatal implements the driver.DBError interface. +func (e *HdbError) IsFatal() bool { return e.errorLevel == errorLevelFatalError } + +// HdbErrors represent the collection of errors return by the server. +type HdbErrors struct { + onlyWarnings bool + errs []*HdbError + *HdbError +} + +func (e *HdbErrors) String() string { + var b []byte + for i, err := range e.errs { + if i > 0 { + b = append(b, '\n') + } + b = append(b, err.String()...) + } + return string(b) +} + +func (e *HdbErrors) Error() string { + var b []byte + for i, err := range e.errs { + if i > 0 { + b = append(b, '\n') + } + b = append(b, err.Error()...) + } + return string(b) +} + +// NumError implements the driver.Error interface. +// NumErrors returns the number of all errors, including warnings. +func (e *HdbErrors) NumError() int { return len(e.errs) } + +func (e *HdbErrors) Unwrap() []error { + errs := make([]error, 0, len(e.errs)) + for _, err := range e.errs { + errs = append(errs, err) + } + return errs +} + +// SetIdx implements the driver.Error interface. +func (e *HdbErrors) SetIdx(idx int) { + if idx >= 0 && idx < len(e.errs) { + e.HdbError = e.errs[idx] + } +} + +// setStmtNo sets the statement number of the error. +func (e *HdbErrors) setStmtNo(idx, no int) { + if idx >= 0 && idx < len(e.errs) { + e.errs[idx].stmtNo = no + } +} + +func (e *HdbErrors) decodeNumArg(dec *encoding.Decoder, numArg int) error { + e.onlyWarnings = true + e.errs = nil + + for range numArg { + err := new(HdbError) + e.errs = append(e.errs, err) + + // err.stmtNo = -1 + err.stmtNo = 0 + /* + in case of an hdb error when inserting one record (e.g. duplicate) + - hdb does not return a rowsAffected part + - SetStmtNo is not called and + - the default value (formerly -1) is kept + --> initialize stmtNo with zero + */ + err.errorCode = dec.Int32() + err.errorPosition = dec.Int32() + err.errorTextLength = dec.Int32() + err.errorLevel = errorLevel(dec.Int8()) + dec.Bytes(err.sqlState[:]) + + // read error text as ASCII data as some errors return invalid CESU-8 characters + // e.g: SQL HdbError 7 - feature not supported: invalid character encoding: + // if e.errorText, err = rd.ReadCesu8(int(e.errorTextLength)); err != nil { + // return err + // } + err.errorText = make([]byte, int(err.errorTextLength)) + dec.Bytes(err.errorText) + + if e.onlyWarnings && !err.IsWarning() { + e.onlyWarnings = false + } + + if numArg == 1 { + // Error (protocol error?): + // if only one error (numArg == 1): s.ph.bufferLength is one byte greater than data to be read + // if more than one error: s.ph.bufferlength matches read bytes + padding + // + // Examples: + // driver test TestHDBWarning + // --> 18 bytes fix error bytes + 103 bytes error text => 121 bytes (7 bytes padding needed) + // but s.ph.bufferLength = 122 (standard padding would only consume 6 bytes instead of 7) + // driver test TestBulkInsertDuplicates + // --> returns 3 errors (number of total bytes matches s.ph.bufferLength) + dec.Skip(1) + break + } + + pad := padBytes(int(fixLength + err.errorTextLength)) + if pad != 0 { + dec.Skip(pad) + } + } + if len(e.errs) > 0 { + e.HdbError = e.errs[0] // set default to first error + } + + return dec.Error() +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/fieldnames.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/fieldnames.go new file mode 100644 index 00000000..edf23220 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/fieldnames.go @@ -0,0 +1,74 @@ +package protocol + +import ( + "cmp" + "errors" + "slices" + "unique" + + "github.com/SAP/go-hdb/driver/internal/protocol/encoding" +) + +const noFieldName uint32 = 0xFFFFFFFF + +type ofsHandle struct { + ofs uint32 + handle unique.Handle[string] +} + +type fieldNames struct { // use struct here to get a stable pointer + ofsHandles []ofsHandle +} + +func (fn *fieldNames) search(ofs uint32) (int, bool) { + return slices.BinarySearchFunc(fn.ofsHandles, ofsHandle{ofs: ofs}, func(a, b ofsHandle) int { + return cmp.Compare(a.ofs, b.ofs) + }) +} + +func (fn *fieldNames) insertOfs(ofs uint32) { + if ofs == noFieldName { + return + } + i, found := fn.search(ofs) + if found { // duplicate + return + } + + if i >= len(fn.ofsHandles) { // append + fn.ofsHandles = append(fn.ofsHandles, ofsHandle{ofs: ofs}) + } else { + fn.ofsHandles = append(fn.ofsHandles, ofsHandle{}) + copy(fn.ofsHandles[i+1:], fn.ofsHandles[i:]) + fn.ofsHandles[i] = ofsHandle{ofs: ofs} + } +} + +func (fn *fieldNames) name(ofs uint32) string { + if i, found := fn.search(ofs); found { + return fn.ofsHandles[i].handle.Value() + } + return "" +} + +func (fn *fieldNames) decode(dec *encoding.Decoder) error { + // TODO sniffer - python client texts are returned differently? + // - double check offset calc (CESU8 issue?) + var errs []error + + pos := uint32(0) + for i, ofsHandle := range fn.ofsHandles { + diff := int(ofsHandle.ofs - pos) + if diff > 0 { + dec.Skip(diff) + } + n, s, err := dec.CESU8LIString() + if err != nil { + errs = append(errs, err) + } + fn.ofsHandles[i].handle = unique.Make(s) + // len byte + size + diff + pos += uint32(n + diff) //nolint: gosec + } + return errors.Join(errs...) +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/functioncode.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/functioncode.go new file mode 100644 index 00000000..24df4e14 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/functioncode.go @@ -0,0 +1,37 @@ +package protocol + +// FunctionCode represents a function code. +type FunctionCode int16 + +// FunctionCode constants. +const ( + fcNil FunctionCode = 0 + FcDDL FunctionCode = 1 + fcInsert FunctionCode = 2 + fcUpdate FunctionCode = 3 + fcDelete FunctionCode = 4 + fcSelect FunctionCode = 5 + fcSelectForUpdate FunctionCode = 6 + fcExplain FunctionCode = 7 + fcDBProcedureCall FunctionCode = 8 + fcDBProcedureCallWithResult FunctionCode = 9 + fcFetch FunctionCode = 10 + fcCommit FunctionCode = 11 + fcRollback FunctionCode = 12 + fcSavepoint FunctionCode = 13 + fcConnect FunctionCode = 14 + fcWriteLob FunctionCode = 15 + fcReadLob FunctionCode = 16 + fcPing FunctionCode = 17 //reserved: do not use + fcDisconnect FunctionCode = 18 + fcCloseCursor FunctionCode = 19 + fcFindLob FunctionCode = 20 + fcAbapStream FunctionCode = 21 + fcXAStart FunctionCode = 22 + fcXAJoin FunctionCode = 23 +) + +// IsProcedureCall returns true if the function code is a procedure call, false otherwise. +func (fc FunctionCode) IsProcedureCall() bool { + return fc == fcDBProcedureCall +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/headers.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/headers.go new file mode 100644 index 00000000..3a645311 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/headers.go @@ -0,0 +1,294 @@ +package protocol + +import ( + "fmt" + "math" + + "github.com/SAP/go-hdb/driver/internal/protocol/encoding" +) + +// Message header (size: 32 bytes). +type messageHeader struct { + sessionID int64 + packetCount int32 + varPartLength uint32 + varPartSize uint32 + noOfSegm int16 +} + +func (h *messageHeader) String() string { + return fmt.Sprintf("session id %d packetCount %d varPartLength %d, varPartSize %d noOfSegm %d", + h.sessionID, + h.packetCount, + h.varPartLength, + h.varPartSize, + h.noOfSegm) +} + +func (h *messageHeader) encode(enc *encoding.Encoder) error { + enc.Int64(h.sessionID) + enc.Int32(h.packetCount) + enc.Uint32(h.varPartLength) + enc.Uint32(h.varPartSize) + enc.Int16(h.noOfSegm) + enc.Zeroes(10) // size: 32 bytes + return nil +} + +func (h *messageHeader) decode(dec *encoding.Decoder) error { + h.sessionID = dec.Int64() + h.packetCount = dec.Int32() + h.varPartLength = dec.Uint32() + h.varPartSize = dec.Uint32() + h.noOfSegm = dec.Int16() + dec.Skip(10) // size: 32 bytes + return dec.Error() +} + +const ( + segmentHeaderSize = 24 +) + +type segmentKind int8 + +const ( + skInvalid segmentKind = 0 + skRequest segmentKind = 1 + skReply segmentKind = 2 + skError segmentKind = 5 +) + +type commandOptions int8 + +const ( + coNil commandOptions = 0x00 + coSelfetchOff commandOptions = 0x01 + coScrollableCursorOn commandOptions = 0x02 + coNoResultsetCloseNeeded commandOptions = 0x04 + coHoldCursorOverCommtit commandOptions = 0x08 + coExecuteLocally commandOptions = 0x10 +) + +var ( + coList = []commandOptions{coNil, coSelfetchOff, coScrollableCursorOn, coNoResultsetCloseNeeded, coHoldCursorOverCommtit, coExecuteLocally} + coListText = []string{"", "selfetchOff", "scrollableCursorOn", "noResltsetCloseNeeded", "holdCursorOverCommit", "executLocally"} +) + +func (k commandOptions) String() string { + var s []string + + for i, option := range coList { + if (k & option) != 0 { + s = append(s, coListText[i]) + } + } + return fmt.Sprintf("%v", s) +} + +// segment header. +type segmentHeader struct { + segmentLength int32 + segmentOfs int32 + noOfParts int16 + segmentNo int16 + segmentKind segmentKind + messageType MessageType + commit bool + commandOptions commandOptions + functionCode FunctionCode +} + +func (h *segmentHeader) String() string { + switch h.segmentKind { + default: // error + return fmt.Sprintf( + "segmentLength %d segmentOfs %d noOfParts %d, segmentNo %d segmentKind %s", + h.segmentLength, + h.segmentOfs, + h.noOfParts, + h.segmentNo, + h.segmentKind, + ) + case skRequest: + return fmt.Sprintf( + "segmentLength %d segmentOfs %d noOfParts %d, segmentNo %d segmentKind %s messageType %s commit %t commandOptions %s", + h.segmentLength, + h.segmentOfs, + h.noOfParts, + h.segmentNo, + h.segmentKind, + h.messageType, + h.commit, + h.commandOptions, + ) + case skReply: + return fmt.Sprintf( + "segmentLength %d segmentOfs %d noOfParts %d, segmentNo %d segmentKind %s functionCode %s", + h.segmentLength, + h.segmentOfs, + h.noOfParts, + h.segmentNo, + h.segmentKind, + h.functionCode, + ) + } +} + +// request. +func (h *segmentHeader) encode(enc *encoding.Encoder) error { + enc.Int32(h.segmentLength) + enc.Int32(h.segmentOfs) + enc.Int16(h.noOfParts) + enc.Int16(h.segmentNo) + enc.Int8(int8(h.segmentKind)) + + switch h.segmentKind { + default: // error + enc.Zeroes(11) // segmentHeaderLength + + case skRequest: + enc.Int8(int8(h.messageType)) + enc.Bool(h.commit) + enc.Int8(int8(h.commandOptions)) + enc.Zeroes(8) // segmentHeaderSize + + case skReply: + enc.Zeroes(1) // reserved + enc.Int16(int16(h.functionCode)) + enc.Zeroes(8) // segmentHeaderSize + } + return nil +} + +// reply || error. +func (h *segmentHeader) decode(dec *encoding.Decoder) error { + h.segmentLength = dec.Int32() + h.segmentOfs = dec.Int32() + h.noOfParts = dec.Int16() + h.segmentNo = dec.Int16() + h.segmentKind = segmentKind(dec.Int8()) + + switch h.segmentKind { + default: // error + dec.Skip(11) // segmentHeaderLength + + case skRequest: + h.messageType = MessageType(dec.Int8()) + h.commit = dec.Bool() + h.commandOptions = commandOptions(dec.Int8()) + dec.Skip(8) // segmentHeaderLength + + case skReply: + dec.Skip(1) // reserved + h.functionCode = FunctionCode(dec.Int16()) + dec.Skip(8) // segmentHeaderLength + } + return dec.Error() +} + +const ( + partHeaderSize = 16 + bigNumArgInd = -1 +) + +// MaxNumArg is the maximum number of arguments allowed to send in a part. +const MaxNumArg = math.MaxInt32 + +// PartAttributes represents the part attributes. +type PartAttributes int8 + +const ( + paLastPacket PartAttributes = 0x01 + paNextPacket PartAttributes = 0x02 + paFirstPacket PartAttributes = 0x04 + paRowNotFound PartAttributes = 0x08 + paResultsetClosed PartAttributes = 0x10 +) + +var ( + paList = [...]PartAttributes{paLastPacket, paNextPacket, paFirstPacket, paRowNotFound, paResultsetClosed} + paListText = [...]string{"lastPacket", "nextPacket", "firstPacket", "rowNotFound", "resultsetClosed"} +) + +func (k PartAttributes) String() string { + var s []string + + for i, attr := range paList { + if (k & attr) != 0 { + s = append(s, paListText[i]) + } + } + return fmt.Sprintf("%v", s) +} + +// ResultsetClosed returns true if the result set is closed, false otherwise. +func (k PartAttributes) ResultsetClosed() bool { return (k & paResultsetClosed) == paResultsetClosed } + +// LastPacket returns true if the last packet is sent, false otherwise. +func (k PartAttributes) LastPacket() bool { return (k & paLastPacket) == paLastPacket } + +// partHeader represents the part header. +type partHeader struct { + partKind PartKind + partAttributes PartAttributes + argumentCount int16 + bigArgumentCount int32 + bufferLength int32 + bufferSize int32 +} + +func (h *partHeader) String() string { + return fmt.Sprintf("kind %s partAttributes %s argumentCount %d bigArgumentCount %d bufferLength %d bufferSize %d", + h.partKind, + h.partAttributes, + h.argumentCount, + h.bigArgumentCount, + h.bufferLength, + h.bufferSize, + ) +} + +func (h *partHeader) setNumArg(numArg int) error { + switch { + default: + return fmt.Errorf("maximum number of arguments %d exceeded", numArg) + case numArg <= math.MaxInt16: + h.argumentCount = int16(numArg) //nolint: gosec + h.bigArgumentCount = 0 + case numArg <= math.MaxInt32: + h.argumentCount = bigNumArgInd + h.bigArgumentCount = int32(numArg) + } + return nil +} + +func (h *partHeader) numArg() int { + if h.argumentCount == bigNumArgInd { + return int(h.bigArgumentCount) + } + return int(h.argumentCount) +} + +func (h *partHeader) bufLen() int { return int(h.bufferLength) } + +func (h *partHeader) encode(enc *encoding.Encoder) error { + enc.Int8(int8(h.partKind)) + enc.Int8(int8(h.partAttributes)) + enc.Int16(h.argumentCount) + enc.Int32(h.bigArgumentCount) + enc.Int32(h.bufferLength) + enc.Int32(h.bufferSize) + // no filler + return nil +} + +func (h *partHeader) decode(dec *encoding.Decoder) error { + h.partKind = PartKind(dec.Int8()) + h.partAttributes = PartAttributes(dec.Int8()) + h.argumentCount = dec.Int16() + h.bigArgumentCount = dec.Int32() + h.bufferLength = dec.Int32() + h.bufferSize = dec.Int32() + // no filler + return dec.Error() +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/init.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/init.go new file mode 100644 index 00000000..c0f7d136 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/init.go @@ -0,0 +1,113 @@ +package protocol + +import ( + "fmt" + + "github.com/SAP/go-hdb/driver/internal/protocol/encoding" +) + +type endianess int8 + +const ( + bigEndian endianess = 0 + littleEndian endianess = 1 +) + +const ( + initRequestFillerSize = 4 +) + +var initRequestFiller uint32 = 0xffffffff + +type version struct { + major int8 + minor int16 +} + +func (v version) String() string { + return fmt.Sprintf("%d.%d", v.major, v.minor) +} + +type initRequest struct { + product version + protocol version + numOptions int8 + endianess endianess +} + +func (r *initRequest) String() string { + switch r.numOptions { + default: + return fmt.Sprintf("productVersion %s protocolVersion %s", r.product, r.protocol) + case 1: + return fmt.Sprintf("productVersion %s protocolVersion %s endianess %s", r.product, r.protocol, r.endianess) + } +} + +func (r *initRequest) decode(dec *encoding.Decoder) error { + dec.Skip(initRequestFillerSize) // filler + r.product.major = dec.Int8() + r.product.minor = dec.Int16() + r.protocol.major = dec.Int8() + r.protocol.minor = dec.Int16() + dec.Skip(1) // reserved filler + r.numOptions = dec.Int8() + + switch r.numOptions { + default: + panic("invalid number of options") + + case 0: + dec.Skip(2) + + case 1: + cnt := dec.Int8() + if cnt != 1 { + panic("invalid number of options - 1 expected") + } + r.endianess = endianess(dec.Int8()) + } + return dec.Error() +} + +func (r *initRequest) encode(enc *encoding.Encoder) error { + enc.Uint32(initRequestFiller) + enc.Int8(r.product.major) + enc.Int16(r.product.minor) + enc.Int8(r.protocol.major) + enc.Int16(r.protocol.minor) + + switch r.numOptions { + default: + panic("invalid number of options") + + case 0: + enc.Zeroes(4) + + case 1: + // reserved + enc.Zeroes(1) + enc.Int8(r.numOptions) + enc.Int8(int8(littleEndian)) + enc.Int8(int8(r.endianess)) + } + return nil +} + +type initReply struct { + product version + protocol version +} + +func (r *initReply) String() string { + return fmt.Sprintf("productVersion %s protocolVersion %s", r.product, r.protocol) +} + +func (r *initReply) decode(dec *encoding.Decoder) error { + r.product.major = dec.Int8() + r.product.minor = dec.Int16() + r.protocol.major = dec.Int8() + r.protocol.minor = dec.Int16() + dec.Skip(2) // commitInitReplySize + return dec.Error() +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/julian/julian.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/julian/julian.go new file mode 100644 index 00000000..24498193 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/julian/julian.go @@ -0,0 +1,48 @@ +// Package julian provided julian time conversion functions. +package julian + +import ( + "time" +) + +const gregorianDay = 2299161 // Start date of Gregorian Calendar as Julian Day Number +var gregorianDate = DayToTime(gregorianDay) // Start date of Gregorian Calendar (1582-10-15) + +// TimeToDay returns the Julian Date Number of time's date components. +// The algorithm is taken from https://en.wikipedia.org/wiki/Julian_day. +func TimeToDay(t time.Time) int { + t = t.UTC() + + month := int(t.Month()) + + a := (14 - month) / 12 + y := t.Year() + 4800 - a + m := month + (12 * a) - 3 + + if t.Before(gregorianDate) { // Julian Calendar + return t.Day() + (153*m+2)/5 + 365*y + y/4 - 32083 + } + // Gregorian Calendar + return t.Day() + (153*m+2)/5 + 365*y + y/4 - y/100 + y/400 - 32045 +} + +// DayToTime returns the correcponding UTC date for a Julian Day Number. +// The algorithm is taken from https://en.wikipedia.org/wiki/Julian_day. +func DayToTime(jd int) time.Time { + var f int + + if jd < gregorianDay { + f = jd + 1401 + } else { + f = jd + 1401 + (((4*jd+274277)/146097)*3)/4 - 38 + } + + e := 4*f + 3 + g := (e % 1461) / 4 + h := 5*g + 2 + day := (h%153)/5 + 1 + month := (h/153+2)%12 + 1 + year := (e / 1461) - 4716 + (12+2-month)/12 + + return time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC) +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/keyvaluesparts.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/keyvaluesparts.go new file mode 100644 index 00000000..77b7403f --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/keyvaluesparts.go @@ -0,0 +1,52 @@ +package protocol + +import ( + "fmt" + + "github.com/SAP/go-hdb/driver/internal/protocol/encoding" +) + +type clientInfo map[string]string + +func (c clientInfo) String() string { return fmt.Sprintf("%v", map[string]string(c)) } + +func (c clientInfo) size() int { + size := 0 + for k, v := range c { + size += encoding.Cesu8FieldSize(k) + size += encoding.Cesu8FieldSize(v) + } + return size +} + +func (c clientInfo) numArg() int { return len(c) } + +func (c *clientInfo) decodeNumArg(dec *encoding.Decoder, numArg int) error { + *c = clientInfo{} // no reuse of maps - create new one + + for range numArg { + k, err := dec.Cesu8Field() + if err != nil { + return err + } + v, err := dec.Cesu8Field() + if err != nil { + return err + } + // set key value + (*c)[string(k.([]byte))] = string(v.([]byte)) + } + return dec.Error() +} + +func (c clientInfo) encode(enc *encoding.Encoder) error { + for k, v := range c { + if err := enc.Cesu8Field(k); err != nil { + return err + } + if err := enc.Cesu8Field(v); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/levenshtein/levenshtein.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/levenshtein/levenshtein.go new file mode 100644 index 00000000..e1dab862 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/levenshtein/levenshtein.go @@ -0,0 +1,57 @@ +// Package levenshtein includes the levenshtein distance algorithm plus additional helper functions. +// The algorithm is taken from https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#Go. +package levenshtein + +import ( + "math" + "strings" + "unicode/utf8" +) + +// Distance returns the Lewenshtein distance. +func Distance(a, b string, caseSensitive bool) int { + if caseSensitive { + return distance(a, b) + } + return distance(strings.ToLower(a), strings.ToLower(b)) +} + +// MinString returns the string attribute determined by fn out of x with the minimal Lewenshtein distance to s. +func MinString[S ~[]E, E any](x S, fn func(a E) string, s string, caseSensitive bool) (rv string) { + minInt := math.MaxInt + for _, e := range x { + xs := fn(e) + if d := Distance(xs, s, caseSensitive); d < minInt { + rv = xs + minInt = d + } + } + return +} + +func distance(a, b string) int { + f := make([]int, utf8.RuneCountInString(b)+1) + + for j := range f { + f[j] = j + } + + for _, ca := range a { + j := 1 + fj1 := f[0] // fj1 is the value of f[j - 1] in last iteration + f[0]++ + for _, cb := range b { + mn := min(f[j]+1, f[j-1]+1) // delete & insert + if cb != ca { + mn = min(mn, fj1+1) // change + } else { + mn = min(mn, fj1) // matched + } + + fj1, f[j] = f[j], mn // save f[j] to fj1(j is about to increase), update f[j] to mn + j++ + } + } + + return f[len(f)-1] +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/lob.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/lob.go new file mode 100644 index 00000000..2a7357f4 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/lob.go @@ -0,0 +1,448 @@ +package protocol + +import ( + "bytes" + "errors" + "fmt" + "io" + "slices" + "sync" + "unicode/utf8" + + "github.com/SAP/go-hdb/driver/internal/protocol/encoding" + "github.com/SAP/go-hdb/driver/internal/unsafe" + "golang.org/x/text/transform" +) + +const ( + writeLobRequestSize = 21 +) + +// lobOptions represents a lob option set. +type lobOptions int8 + +const ( + loNullindicator lobOptions = 0x01 + loDataincluded lobOptions = 0x02 + loLastdata lobOptions = 0x04 +) + +const ( + loNullindicatorText = "null indicator" + loDataincludedText = "data included" + loLastdataText = "last data" +) + +func (o lobOptions) String() string { + var s []string + if o&loNullindicator != 0 { + s = append(s, loNullindicatorText) + } + if o&loDataincluded != 0 { + s = append(s, loDataincludedText) + } + if o&loLastdata != 0 { + s = append(s, loLastdataText) + } + return fmt.Sprintf("%v", s) +} + +// IsLastData return true if the last data package was read, false otherwise. +func (o lobOptions) isLastData() bool { return (o & loLastdata) != 0 } +func (o lobOptions) isNull() bool { return (o & loNullindicator) != 0 } + +// lob typecode. +type lobTypecode int8 + +const ( + ltcUndefined lobTypecode = 0 + ltcBlob lobTypecode = 1 + ltcClob lobTypecode = 2 + ltcNclob lobTypecode = 3 +) + +// not used +// type lobFlags bool + +// func (f lobFlags) String() string { return fmt.Sprintf("%t", f) } +// func (f *lobFlags) decode(dec *encoding.Decoder, ph *partHeader) error { +// *f = lobFlags(dec.Bool()) +// return dec.Error() +// } +// func (f lobFlags) encode(enc *encoding.Encoder) error { enc.Bool(bool(f)); return nil } + +// LobScanner is the interface wrapping the Scan method for Lob reading. +type LobScanner interface { + Scan(w io.Writer) error +} + +var _ LobScanner = (*lobOutDescr)(nil) + +// LobInDescr represents a lob input descriptor. +type LobInDescr struct { + rd io.Reader + opt lobOptions + pos int + buf bytes.Buffer +} + +func newLobInDescr(rd io.Reader) *LobInDescr { + return &LobInDescr{rd: rd} +} + +func (d *LobInDescr) String() string { + // restrict output size + return fmt.Sprintf("options %s size %d pos %d bytes %v", d.opt, d.buf.Len(), d.pos, d.buf.Bytes()[:min(d.buf.Len(), 25)]) +} + +// IsLastData returns true in case of last data package read, false otherwise. +func (d *LobInDescr) IsLastData() bool { return d.opt.isLastData() } + +// FetchNext fetches the next lob chunk. +func (d *LobInDescr) FetchNext(chunkSize int) error { + /* + We need to guarantee, that a max amount of data is read to prevent + piece wise LOB writing when avoidable + --> copy up to chunkSize + */ + d.buf.Reset() + _, err := io.CopyN(&d.buf, d.rd, int64(chunkSize)) + d.opt = loDataincluded + if !errors.Is(err, io.EOF) && !errors.Is(err, io.ErrUnexpectedEOF) { + return err + } + d.opt |= loLastdata + return nil +} + +func (d *LobInDescr) setPos(pos int) { d.pos = pos } + +func (d *LobInDescr) size() int { return d.buf.Len() } + +func (d *LobInDescr) writeFirst(enc *encoding.Encoder) { enc.Bytes(d.buf.Bytes()) } + +// LocatorID represents a locotor id. +type LocatorID uint64 // byte[locatorIdSize] + +// LobReader is the interface for reading lob streams. +type LobReader interface { + ReadLob(request *ReadLobRequest, reply *ReadLobReply) error +} + +var lobOutDescrPool = sync.Pool{New: func() any { return new(lobOutDescr) }} + +// lobOutDescr represents a lob output descriptor. +type lobOutDescr struct { + // if set -> char based + tr transform.Transformer + /* + readFn is set by decode if additional data packages need to be read (not last data) + */ + lobReader LobReader + chunkSize int + /* + HDB does not return lob type code but undefined only + --> ltc is always ltcUndefined + --> use isCharBased instead of type code check + */ + ltc lobTypecode + opt lobOptions + numChar int64 + numByte int64 + id LocatorID + b []byte + + // scan attributes. + wr io.Writer + lobRequest *ReadLobRequest + lobReply *ReadLobReply +} + +func newLobOutDescr(tr transform.Transformer, lobReader LobReader, chunkSize int) *lobOutDescr { + descr := lobOutDescrPool.Get().(*lobOutDescr) + descr.tr = tr + descr.lobReader = lobReader + descr.chunkSize = chunkSize + return descr +} + +func (d *lobOutDescr) String() string { + return fmt.Sprintf("typecode %s options %s numChar %d numByte %d id %d bytes %v", d.ltc, d.opt, d.numChar, d.numByte, d.id, d.b) +} + +func (d *lobOutDescr) decode(dec *encoding.Decoder) bool { + d.ltc = lobTypecode(dec.Int8()) + d.opt = lobOptions(dec.Int8()) + if d.opt.isNull() { + return true + } + dec.Skip(2) + d.numChar = dec.Int64() + d.numByte = dec.Int64() + d.id = LocatorID(dec.Uint64()) + size := int(dec.Int32()) + d.b = slices.Grow(d.b, size)[:size] + dec.Bytes(d.b) + return false +} + +func (d *lobOutDescr) write(b []byte) (int, error) { + if d.tr == nil { + if _, err := d.wr.Write(b); err != nil { + return len(b), err + } + return len(b), nil + } + d.tr.Reset() + // cesu8 -> utf8 (always enough space) + nDst, _, err := d.tr.Transform(b, b, false) + if err != nil && err != transform.ErrShortSrc { //nolint: errorlint + return nDst, err + } + + // inline count runes + numChar := 0 + for _, r := range unsafe.ByteSlice2String(b[:nDst]) { + numChar++ + if utf8.RuneLen(r) == 4 { + numChar++ // caution: hdb counts 2 chars in case of surrogate pair + } + } + + if _, err := d.wr.Write(b[:nDst]); err != nil { + return numChar, err + } + return numChar, nil +} + +func (d *lobOutDescr) scan(wr io.Writer) error { + d.wr = wr + + numChar, err := d.write(d.b) + if err != nil { + return err + } + + if d.opt.isLastData() { + return nil + } + + if d.lobRequest == nil { + d.lobRequest = new(ReadLobRequest) + } + if d.lobReply == nil { + d.lobReply = &ReadLobReply{lobOutDescr: d} + } + d.lobRequest.id = d.id + d.lobRequest.ofs = int64(numChar) + d.lobRequest.chunkSize = d.chunkSize + return d.lobReader.ReadLob(d.lobRequest, d.lobReply) +} + +// Scan implements the LobScanner interface. +func (d *lobOutDescr) Scan(wr io.Writer) error { + err := d.scan(wr) + // if the writer is a pipe-end -> close at the end + if pwr, ok := wr.(*io.PipeWriter); ok { + if err != nil { + pwr.CloseWithError(err) + } else { + pwr.Close() + } + } + lobOutDescrPool.Put(d) + return err +} + +func (d *lobOutDescr) Write() (int, error) { + n, err := d.write(d.b) + if err != nil { + return n, err + } + if d.opt.isLastData() { + return n, io.EOF + } + d.lobRequest.ofs += int64(n) + return n, nil +} + +/* +write lobs: +- write lob field to database in chunks +- loop: + - writeLobRequest + - writeLobReply +*/ + +// WriteLobDescr represents a lob descriptor for writes (lob -> db). +type WriteLobDescr struct { + LobInDescr *LobInDescr + ID LocatorID + opt lobOptions + ofs int64 + b []byte +} + +func (d WriteLobDescr) String() string { + return fmt.Sprintf("id %d options %s offset %d bytes %v", d.ID, d.opt, d.ofs, d.b) +} + +// IsLastData returns true in case of last data package read, false otherwise. +func (d *WriteLobDescr) IsLastData() bool { return d.opt.isLastData() } + +// FetchNext fetches the next lob chunk. +func (d *WriteLobDescr) FetchNext(chunkSize int) error { + if err := d.LobInDescr.FetchNext(chunkSize); err != nil { + return err + } + d.opt = d.LobInDescr.opt + d.ofs = -1 // offset (-1 := append) + d.b = d.LobInDescr.buf.Bytes() + return nil +} + +// sniffer. +func (d *WriteLobDescr) decode(dec *encoding.Decoder) error { + d.ID = LocatorID(dec.Uint64()) + d.opt = lobOptions(dec.Int8()) + d.ofs = dec.Int64() + size := dec.Int32() + d.b = make([]byte, size) + dec.Bytes(d.b) + return nil +} + +// write chunk to db. +func (d *WriteLobDescr) encode(enc *encoding.Encoder) error { + enc.Uint64(uint64(d.ID)) + enc.Int8(int8(d.opt)) + enc.Int64(d.ofs) + enc.Int32(int32(len(d.b))) //nolint: gosec + enc.Bytes(d.b) + return nil +} + +// WriteLobRequest represents a lob write request part. +type WriteLobRequest struct { + Descrs []*WriteLobDescr +} + +func (r *WriteLobRequest) String() string { return fmt.Sprintf("descriptors %v", r.Descrs) } + +func (r *WriteLobRequest) size() int { + size := 0 + for _, descr := range r.Descrs { + size += (writeLobRequestSize + len(descr.b)) + } + return size +} + +func (r *WriteLobRequest) numArg() int { return len(r.Descrs) } + +// sniffer. +func (r *WriteLobRequest) decodeNumArg(dec *encoding.Decoder, numArg int) error { + r.Descrs = make([]*WriteLobDescr, numArg) + for i := range numArg { + r.Descrs[i] = &WriteLobDescr{} + if err := r.Descrs[i].decode(dec); err != nil { + return err + } + } + return nil +} + +func (r *WriteLobRequest) encode(enc *encoding.Encoder) error { + for _, descr := range r.Descrs { + if err := descr.encode(enc); err != nil { + return err + } + } + return nil +} + +// WriteLobReply represents a lob write reply part. +type WriteLobReply struct { + // write lob fields to db (reply) + // - returns ids which have not been written completely + IDs []LocatorID +} + +func (r *WriteLobReply) String() string { return fmt.Sprintf("ids %v", r.IDs) } + +func (r *WriteLobReply) decodeNumArg(dec *encoding.Decoder, numArg int) error { + r.IDs = resizeSlice(r.IDs, numArg) + + for i := range numArg { + r.IDs[i] = LocatorID(dec.Uint64()) + } + return dec.Error() +} + +// ReadLobRequest represents a lob read request part. +type ReadLobRequest struct { + /* + read lobs: + - read lob field from database in chunks + - loop: + - readLobRequest + - readLobReply + + - read lob reply + seems like readLobreply returns only a result for one lob - even if more then one is requested + --> read single lobs + */ + id LocatorID + ofs int64 + chunkSize int +} + +func (r *ReadLobRequest) String() string { + return fmt.Sprintf("id %d offset %d size %d", r.id, r.ofs, r.chunkSize) +} + +// sniffer. +func (r *ReadLobRequest) decode(dec *encoding.Decoder) error { + r.id = LocatorID(dec.Uint64()) + r.ofs = dec.Int64() + r.chunkSize = int(dec.Int32()) + dec.Skip(4) + return nil +} + +func (r *ReadLobRequest) encode(enc *encoding.Encoder) error { + enc.Uint64(uint64(r.id)) + enc.Int64(r.ofs + 1) // 1-based + enc.Int32(int32(r.chunkSize)) //nolint: gosec + enc.Zeroes(4) + return nil +} + +// ReadLobReply represents a lob read reply part. +type ReadLobReply struct { + *lobOutDescr +} + +func (r *ReadLobReply) String() string { + return fmt.Sprintf("id %d options %s bytes %v", r.id, r.opt, r.b) +} + +// needed if instantiated generically (e.g.sniffer). +func (r *ReadLobReply) init() { + r.lobOutDescr = new(lobOutDescr) +} + +func (r *ReadLobReply) decodeNumArg(dec *encoding.Decoder, numArg int) error { + if numArg != 1 { + panic("numArg == 1 expected") + } + id := LocatorID(dec.Uint64()) + if id != r.id { + return fmt.Errorf("invalid locator id %d - expected %d", id, r.id) + } + r.opt = lobOptions(dec.Int8()) + size := int(dec.Int32()) + dec.Skip(3) + r.b = slices.Grow(r.b, size)[:size] + dec.Bytes(r.b) + return nil +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/messagetype.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/messagetype.go new file mode 100644 index 00000000..15bfe23c --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/messagetype.go @@ -0,0 +1,55 @@ +package protocol + +// MessageType represents the message type. +type MessageType int8 + +// MessageType constants. +const ( + mtNil MessageType = 0 + MtExecuteDirect MessageType = 2 + MtPrepare MessageType = 3 + mtAbapStream MessageType = 4 + mtXAStart MessageType = 5 + mtXAJoin MessageType = 6 + MtExecute MessageType = 13 + MtWriteLob MessageType = 16 + MtReadLob MessageType = 17 + mtFindLob MessageType = 18 + MtAuthenticate MessageType = 65 + MtConnect MessageType = 66 + MtCommit MessageType = 67 + MtRollback MessageType = 68 + MtCloseResultset MessageType = 69 + MtDropStatementID MessageType = 70 + MtFetchNext MessageType = 71 + mtFetchAbsolute MessageType = 72 + mtFetchRelative MessageType = 73 + mtFetchFirst MessageType = 74 + mtFetchLast MessageType = 75 + MtDisconnect MessageType = 77 + mtExecuteITab MessageType = 78 + mtFetchNextITab MessageType = 79 + mtInsertNextITab MessageType = 80 + mtBatchPrepare MessageType = 81 + MtDBConnectInfo MessageType = 82 + mtXopenXAStart MessageType = 83 + mtXopenXAEnd MessageType = 84 + mtXopenXAPrepare MessageType = 85 + mtXopenXACommit MessageType = 86 + mtXopenXARollback MessageType = 87 + mtXopenXARecover MessageType = 88 + mtXopenXAForget MessageType = 89 +) + +// ClientInfoSupported returns true if message does support client info, false otherwise. +func (mt MessageType) ClientInfoSupported() bool { + /* + mtConnect is only supported since 2.00.042 + As server version is only available after connect we do not use it + to support especially version 1.00.122 until maintenance + will end in sommer 2021 + + return mt == mtConnect || mt == mtPrepare || mt == mtExecuteDirect || mt == mtExecute + */ + return mt == MtPrepare || mt == MtExecuteDirect || mt == MtExecute +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/optionsparts.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/optionsparts.go new file mode 100644 index 00000000..4e6dd26d --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/optionsparts.go @@ -0,0 +1,400 @@ +package protocol + +import ( + "fmt" + "slices" + + "github.com/SAP/go-hdb/driver/internal/protocol/encoding" +) + +// ClientContextOption represents a client context option. +type clientContextOption int8 + +func (k clientContextOption) valueString(v any) string { + return fmt.Sprintf("%s: %v", k, v) +} + +// ClientContextOption constants. +const ( + ccoVersion clientContextOption = 1 + ccoType clientContextOption = 2 + ccoApplicationProgram clientContextOption = 3 +) + +// ClientContext represents a client context part. +type ClientContext struct { + options[clientContextOption] +} + +// SetVersion sets the client version option. +func (cc *ClientContext) SetVersion(v string) { cc.options.set(ccoVersion, v) } + +// SetType sets the client type option. +func (cc *ClientContext) SetType(v string) { cc.options.set(ccoType, v) } + +// SetApplicationProgram sets the client application program option. +func (cc *ClientContext) SetApplicationProgram(v string) { cc.options.set(ccoApplicationProgram, v) } + +// Cdm represents a ConnectOption ClientDistributionMode. +type Cdm byte + +// ConnectOption ClientDistributionMode constants. +const ( + CdmOff Cdm = 0 + CdmConnection Cdm = 1 + CdmStatement Cdm = 2 + CdmConnectionStatement Cdm = 3 +) + +// dpv represents a ConnectOption DistributionProtocolVersion. +type dpv byte + +// distribution protocol version + +// ConnectOption DistributionProtocolVersion constants. +const ( + dpvBaseline dpv = 0 + dpvClientHandlesStatementSequence dpv = 1 +) + +// ConnectOption represents a connect option. +type connectOption int8 + +func (k connectOption) valueString(v any) string { + // TODO: sub options + return fmt.Sprintf("%s: %v", k, v) +} + +// ConnectOption constants. +const ( + coConnectionID connectOption = 1 + coCompleteArrayExecution connectOption = 2 //!< @deprecated Array execution semantics, always true. + coClientLocale connectOption = 3 //!< Client locale information. + coSupportsLargeBulkOperations connectOption = 4 //!< Bulk operations >32K are supported. + coDistributionEnabled connectOption = 5 //!< @deprecated Distribution (topology & call routing) enabled + coPrimaryConnectionID connectOption = 6 //!< @deprecated Id of primary connection (unused). + coPrimaryConnectionHost connectOption = 7 //!< @deprecated Primary connection host name (unused). + coPrimaryConnectionPort connectOption = 8 //!< @deprecated Primary connection port (unused). + coCompleteDatatypeSupport connectOption = 9 //!< @deprecated All data types supported (always on). + coLargeNumberOfParametersSupport connectOption = 10 //!< Number of parameters >32K is supported. + coSystemID connectOption = 11 //!< SID of SAP HANA Database system (output only). + coDataFormatVersion connectOption = 12 //!< Version of data format used in communication (@see DataFormatVersionEnum). + coAbapVarcharMode connectOption = 13 //!< ABAP varchar mode is enabled (trailing blanks in string constants are trimmed off). + coSelectForUpdateSupported connectOption = 14 //!< SELECT FOR UPDATE function code understood by client + coClientDistributionMode connectOption = 15 //!< client distribution mode + coEngineDataFormatVersion connectOption = 16 //!< Engine version of data format used in communication (@see DataFormatVersionEnum). + coDistributionProtocolVersion connectOption = 17 //!< version of distribution protocol handling (@see DistributionProtocolVersionEnum) + coSplitBatchCommands connectOption = 18 //!< permit splitting of batch commands + coUseTransactionFlagsOnly connectOption = 19 //!< use transaction flags only for controlling transaction + coRowSlotImageParameter connectOption = 20 //!< row-slot image parameter passing + coIgnoreUnknownParts connectOption = 21 //!< server does not abort on unknown parts + coTableOutputParameterMetadataSupport connectOption = 22 //!< support table type output parameter metadata. + coDataFormatVersion2 connectOption = 23 //!< Version of data format used in communication (as DataFormatVersion used wrongly in old servers) + coItabParameter connectOption = 24 //!< bool option to signal abap itab parameter support + coDescribeTableOutputParameter connectOption = 25 //!< override "omit table output parameter" setting in this session + coColumnarResultSet connectOption = 26 //!< column wise result passing + coScrollableResultSet connectOption = 27 //!< scrollable result set + coClientInfoNullValueSupported connectOption = 28 //!< can handle null values in client info + coAssociatedConnectionID connectOption = 29 //!< associated connection id + coNonTransactionalPrepare connectOption = 30 //!< can handle and uses non-transactional prepare + coFdaEnabled connectOption = 31 //!< Fast Data Access at all enabled + coOSUser connectOption = 32 //!< client OS user name + coRowSlotImageResultSet connectOption = 33 //!< row-slot image result passing + coEndianness connectOption = 34 //!< endianness (@see EndiannessEnumType) + coUpdateTopologyAnwhere connectOption = 35 //!< Allow update of topology from any reply + coEnableArrayType connectOption = 36 //!< Enable supporting Array data type + coImplicitLobStreaming connectOption = 37 //!< implicit lob streaming + coCachedViewProperty connectOption = 38 //!< provide cached view timestamps to the client + coXOpenXAProtocolSupported connectOption = 39 //!< JTA(X/Open XA) Protocol + coPrimaryCommitRedirectionSupported connectOption = 40 //!< S2PC routing control + coActiveActiveProtocolVersion connectOption = 41 //!< Version of Active/Active protocol + coActiveActiveConnectionOriginSite connectOption = 42 //!< Tell where is the anchor connection located. This is unidirectional property from client to server. + coQueryTimeoutSupported connectOption = 43 //!< support query timeout (e.g., Statement.setQueryTimeout) + coFullVersionString connectOption = 44 //!< Full version string of the client or server (the sender) (added to hana2sp0) + coDatabaseName connectOption = 45 //!< Database name (string) that we connected to (sent by server) (added to hana2sp0) + coBuildPlatform connectOption = 46 //!< Build platform of the client or server (the sender) (added to hana2sp0) + coImplicitXASessionSupported connectOption = 47 //!< S2PC routing control - implicit XA join support after prepare and before execute in MessageType_Prepare, MessageType_Execute and MessageType_PrepareAndExecute + coClientSideColumnEncryptionVersion connectOption = 48 //!< Version of client-side column encryption + coCompressionLevelAndFlags connectOption = 49 //!< Network compression level and flags (added to hana2sp02) + coClientSideReExecutionSupported connectOption = 50 //!< support client-side re-execution for client-side encryption (added to hana2sp03) + coClientReconnectWaitTimeout connectOption = 51 //!< client reconnection wait timeout for transparent session recovery + coOriginalAnchorConnectionID connectOption = 52 //!< original anchor connectionID to notify client's RECONNECT + coFlagSet1 connectOption = 53 //!< flags for aggregating several options + coTopologyNetworkGroup connectOption = 54 //!< NetworkGroup name sent by client to choose topology mapping (added to hana2sp04) + coIPAddress connectOption = 55 //!< IP Address of the sender (added to hana2sp04) + coLRRPingTime connectOption = 56 //!< Long running request ping time + coRedirectionType connectOption = 57 //!< Type of HANA Cloud redirection + coRedirectedHost connectOption = 58 //!< Cloud redirected hostname, if redirected + coRedirectedPort connectOption = 59 //!< Cloud redirected port, if redirected + coEndPointHost connectOption = 60 //!< Original hostname from user, before redirection + coEndPointPort connectOption = 61 //!< Original port from user, before redirection + coEndPointList connectOption = 62 //!< Original host:port;host:port list (including scale-out) from user +) + +// ConnectOptions represents a connect options part. +type ConnectOptions struct { + options[connectOption] +} + +// DataFormatVersion2OrZero returns the data format version2 option if available, the zero value otherwise. +func (co *ConnectOptions) DataFormatVersion2OrZero() int { + var v int32 + co.options.get(coDataFormatVersion2, &v) + return int(v) +} + +// SetDataFormatVersion2 sets the data format version 2 option. +func (co *ConnectOptions) SetDataFormatVersion2(v int) { + co.options.set(coDataFormatVersion2, int32(v)) //nolint: gosec +} + +// SetClientDistributionMode sets the client distribution mode option. +func (co *ConnectOptions) SetClientDistributionMode(v Cdm) { + co.options.set(coClientDistributionMode, int32(v)) +} + +// SetSelectForUpdateSupported sets the select for update supported option. +func (co *ConnectOptions) SetSelectForUpdateSupported(v bool) { + co.options.set(coSelectForUpdateSupported, v) +} + +// DatabaseNameOrZero returns the database name option if available, the zero value otherwise. +func (co *ConnectOptions) DatabaseNameOrZero() string { + var v string + co.options.get(coDatabaseName, &v) + return v +} + +// FullVersionOrZero returns the full version option if available, the zero value otherwise. +func (co *ConnectOptions) FullVersionOrZero() string { + var v string + co.options.get(coFullVersionString, &v) + return v +} + +// SetClientLocale sets the client locale option. +func (co *ConnectOptions) SetClientLocale(v string) { co.options.set(coClientLocale, v) } + +// DBConnectInfoType represents a database connect info type. +type dbConnectInfoType int8 + +func (k dbConnectInfoType) valueString(v any) string { + return fmt.Sprintf("%s: %v", k, v) +} + +// DBConnectInfoType constants. +const ( + ciDatabaseName dbConnectInfoType = 1 // string + ciHost dbConnectInfoType = 2 // string + ciPort dbConnectInfoType = 3 // int4 + ciIsConnected dbConnectInfoType = 4 // bool +) + +// DBConnectInfo represents a database connect info part. +type DBConnectInfo struct { + options[dbConnectInfoType] +} + +// SetDatabaseName sets the database name option. +func (ci *DBConnectInfo) SetDatabaseName(v string) { ci.options.set(ciDatabaseName, v) } + +// HostOrZero returns the host option, the zero value otherwise. +func (ci *DBConnectInfo) HostOrZero() string { var v string; ci.options.get(ciHost, &v); return v } + +// PortOrZero returns the port option, the zero value otherwise. +func (ci *DBConnectInfo) PortOrZero() int { var v int32; ci.options.get(ciPort, &v); return int(v) } + +// IsConnectedOrZero returns this IsConnected option, the zero value otherwise. +func (ci *DBConnectInfo) IsConnectedOrZero() bool { + var v bool + ci.options.get(ciIsConnected, &v) + return v +} + +type statementContextType int8 + +func (k statementContextType) valueString(v any) string { + return fmt.Sprintf("%s: %v", k, v) +} + +const ( + scStatementSequenceInfo statementContextType = 1 + scServerProcessingTime statementContextType = 2 + scSchemaName statementContextType = 3 + scFlagSet statementContextType = 4 + scQueryTimeout statementContextType = 5 + scClientReconnectionWaitTimeout statementContextType = 6 + scServerCPUTime statementContextType = 7 + scServerMemoryUsage statementContextType = 8 +) + +type statementContext struct { + options[statementContextType] +} + +// transaction flags. +type transactionFlagType int8 + +func (k transactionFlagType) valueString(v any) string { + return fmt.Sprintf("%s: %v", k, v) +} + +const ( + tfRolledback transactionFlagType = 0 + tfCommited transactionFlagType = 1 + tfNewIsolationLevel transactionFlagType = 2 + tfDDLCommitmodeChanged transactionFlagType = 3 + tfWriteTransactionStarted transactionFlagType = 4 + tfNowriteTransactionStarted transactionFlagType = 5 + tfSessionClosingTransactionError transactionFlagType = 6 + tfSessionClosingTransactionErrror transactionFlagType = 7 + tfReadOnlyMode transactionFlagType = 8 +) + +type transactionFlags struct { + options[transactionFlagType] +} + +type topologyOption int8 + +func (k topologyOption) valueString(v any) string { + switch k { + case toServiceType: + v := v.(int32) + return fmt.Sprintf("%s: %v", k, ServiceType(v)) + default: + return fmt.Sprintf("%s: %v", k, v) + } +} + +const ( + toHostName topologyOption = 1 + toHostPortnumber topologyOption = 2 + toTenantName topologyOption = 3 + toLoadfactor topologyOption = 4 + toVolumeID topologyOption = 5 + toIsPrimary topologyOption = 6 + toIsCurrentSession topologyOption = 7 + toServiceType topologyOption = 8 + toNetworkDomain topologyOption = 9 // deprecated + toIsStandby topologyOption = 10 + toAllIPAddresses topologyOption = 11 // deprecated + toAllHostNames topologyOption = 12 // deprecated + toSiteType topologyOption = 13 +) + +// ServiceType represents a service type. +type ServiceType int32 + +// Service type constants. +const ( + StOther ServiceType = 0 + StNameServer ServiceType = 1 + StPreprocessor ServiceType = 2 + StIndexServer ServiceType = 3 + StStatisticsServer ServiceType = 4 + StXSEngine ServiceType = 5 + StReserved6 ServiceType = 6 + StCompileServer ServiceType = 7 + StDPServer ServiceType = 8 + StDIServer ServiceType = 9 + StComputeServer ServiceType = 10 + StScriptServer ServiceType = 11 +) + +// TopologyInformation represents a topology information part. +type TopologyInformation struct { + hosts []*options[topologyOption] +} + +func (ti TopologyInformation) String() string { return fmt.Sprintf("%v", ti.hosts) } + +func (ti *TopologyInformation) decodeNumArg(dec *encoding.Decoder, numArg int) error { + ti.hosts = resizeSlice(ti.hosts, numArg) + for i := range numArg { + host := &options[topologyOption]{} + ti.hosts[i] = host + hostNumArg := int(dec.Int16()) + if err := host.decodeNumArg(dec, hostNumArg); err != nil { + return err + } + } + return dec.Error() +} + +type optionsType interface { + ~int8 + valueString(v any) string +} + +// options represents a generic option part. +type options[K optionsType] map[K]any + +func (ops options[K]) String() string { + s := []string{} + for k, v := range ops { + s = append(s, k.valueString(v)) + } + slices.Sort(s) + return fmt.Sprintf("%v", s) +} + +func (ops *options[K]) get(k K, v any) bool { + if *ops == nil { + return false + } + mv, ok := (*ops)[k] + if !ok { + return false + } + switch v := v.(type) { + case *string: + *v = mv.(string) + case *bool: + *v = mv.(bool) + case *int32: + *v = mv.(int32) + default: + panic("invalid option type") + } + return true +} + +func (ops *options[K]) set(k K, v any) { + if *ops == nil { + *ops = options[K]{} + } + (*ops)[k] = v +} + +func (ops options[K]) size() int { + size := 2 * len(ops) // option + type + for _, v := range ops { + ot := optTypeViaType(v) + size += ot.size(v) + } + return size +} + +func (ops options[K]) numArg() int { return len(ops) } + +func (ops *options[K]) decodeNumArg(dec *encoding.Decoder, numArg int) error { + *ops = options[K]{} // no reuse of maps - create new one + for range numArg { + k := K(dec.Int8()) + tc := typeCode(dec.Byte()) + ot := optTypeViaTypeCode(tc) + (*ops)[k] = ot.decode(dec) + } + return dec.Error() +} + +func (ops options[K]) encode(enc *encoding.Encoder) error { + for k, v := range ops { + enc.Int8(int8(k)) + ot := optTypeViaType(v) + enc.Int8(int8(ot.typeCode())) + ot.encode(enc, v) + } + return nil +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/optiontype.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/optiontype.go new file mode 100644 index 00000000..1b52667e --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/optiontype.go @@ -0,0 +1,145 @@ +package protocol + +import ( + "fmt" + + "github.com/SAP/go-hdb/driver/internal/protocol/encoding" +) + +type optType interface { + fmt.Stringer + typeCode() typeCode + size(v any) int + encode(e *encoding.Encoder, v any) + decode(d *encoding.Decoder) any +} + +var ( + optBooleanType = _optBooleanType{} + optTinyintType = _optTinyintType{} + optIntegerType = _optIntegerType{} + optBigintType = _optBigintType{} + optDoubleType = _optDoubleType{} + optStringType = _optStringType{} + optBstringType = _optBstringType{} +) + +type ( + _optBooleanType struct{} + _optTinyintType struct{} + _optIntegerType struct{} + _optBigintType struct{} + _optDoubleType struct{} + _optStringType struct{} + _optBstringType struct{} +) + +var ( + _ optType = (*_optBooleanType)(nil) + _ optType = (*_optTinyintType)(nil) + _ optType = (*_optIntegerType)(nil) + _ optType = (*_optBigintType)(nil) + _ optType = (*_optDoubleType)(nil) + _ optType = (*_optStringType)(nil) + _ optType = (*_optBstringType)(nil) +) + +func (_optBooleanType) String() string { return "booleanType" } +func (_optTinyintType) String() string { return "tinyintType" } +func (_optIntegerType) String() string { return "integerType" } +func (_optBigintType) String() string { return "bigintType" } +func (_optDoubleType) String() string { return "doubleType" } +func (_optStringType) String() string { return "stringType" } +func (_optBstringType) String() string { return "bstringType" } + +func (_optBooleanType) typeCode() typeCode { return tcBoolean } +func (_optTinyintType) typeCode() typeCode { return tcTinyint } +func (_optIntegerType) typeCode() typeCode { return tcInteger } +func (_optBigintType) typeCode() typeCode { return tcBigint } +func (_optDoubleType) typeCode() typeCode { return tcDouble } +func (_optStringType) typeCode() typeCode { return tcString } +func (_optBstringType) typeCode() typeCode { return tcBstring } + +func (_optBooleanType) size(any) int { return encoding.BooleanFieldSize } +func (_optTinyintType) size(any) int { return encoding.TinyintFieldSize } +func (_optIntegerType) size(any) int { return encoding.IntegerFieldSize } +func (_optBigintType) size(any) int { return encoding.BigintFieldSize } +func (_optDoubleType) size(any) int { return encoding.DoubleFieldSize } +func (_optStringType) size(v any) int { return 2 + len(v.(string)) } // length int16 + string length +func (_optBstringType) size(v any) int { return 2 + len(v.([]byte)) } // length int16 + bytes length + +func (_optBooleanType) encode(e *encoding.Encoder, v any) { e.Bool(v.(bool)) } +func (_optTinyintType) encode(e *encoding.Encoder, v any) { e.Int8(v.(int8)) } +func (_optIntegerType) encode(e *encoding.Encoder, v any) { e.Int32(v.(int32)) } +func (_optBigintType) encode(e *encoding.Encoder, v any) { e.Int64(v.(int64)) } +func (_optDoubleType) encode(e *encoding.Encoder, v any) { e.Float64(v.(float64)) } +func (_optStringType) encode(e *encoding.Encoder, v any) { + s := v.(string) + e.Int16(int16(len(s))) //nolint: gosec + e.Bytes([]byte(s)) +} +func (_optBstringType) encode(e *encoding.Encoder, v any) { + b := v.([]byte) + e.Int16(int16(len(b))) //nolint: gosec + e.Bytes(b) +} + +func (_optBooleanType) decode(d *encoding.Decoder) any { return d.Bool() } +func (_optTinyintType) decode(d *encoding.Decoder) any { return d.Int8() } +func (_optIntegerType) decode(d *encoding.Decoder) any { return d.Int32() } +func (_optBigintType) decode(d *encoding.Decoder) any { return d.Int64() } +func (_optDoubleType) decode(d *encoding.Decoder) any { return d.Float64() } +func (_optStringType) decode(d *encoding.Decoder) any { + l := d.Int16() + b := make([]byte, l) + d.Bytes(b) + return string(b) +} +func (_optBstringType) decode(d *encoding.Decoder) any { + l := d.Int16() + b := make([]byte, l) + d.Bytes(b) + return b +} + +func optTypeViaType(v any) optType { + switch v.(type) { + case bool: + return optBooleanType + case int8: + return optTinyintType + case int32: + return optIntegerType + case int64: + return optBigintType + case float64: + return optDoubleType + case string: + return optStringType + case []byte: + return optBstringType + default: + panic("type not implemented") // should never happen + } +} + +func optTypeViaTypeCode(tc typeCode) optType { + switch tc { + case tcBoolean: + return optBooleanType + case tcTinyint: + return optTinyintType + case tcInteger: + return optIntegerType + case tcBigint: + return optBigintType + case tcDouble: + return optDoubleType + case tcString: + return optStringType + case tcBstring: + return optBstringType + default: + panic("missing optType for typeCode") // should never happen + } +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/parameter.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/parameter.go new file mode 100644 index 00000000..111c9e00 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/parameter.go @@ -0,0 +1,455 @@ +package protocol + +import ( + "database/sql/driver" + "fmt" + "reflect" + + "github.com/SAP/go-hdb/driver/internal/protocol/encoding" + "golang.org/x/text/transform" +) + +type parameterOptions int8 + +const ( + poMandatory parameterOptions = 0x01 + poOptional parameterOptions = 0x02 + poDefault parameterOptions = 0x04 +) + +const ( + poMandatoryText = "mandatory" + poOptionalText = "optional" + poDefaultText = "default" +) + +func (k parameterOptions) String() string { + var s []string + if k&poMandatory != 0 { + s = append(s, poMandatoryText) + } + if k&poOptional != 0 { + s = append(s, poOptionalText) + } + if k&poDefault != 0 { + s = append(s, poDefaultText) + } + return fmt.Sprintf("%v", s) +} + +// ParameterMode represents the parameter mode set. +type ParameterMode int8 + +// ParameterMode constants. +const ( + pmIn ParameterMode = 0x01 + pmInout ParameterMode = 0x02 + pmOut ParameterMode = 0x04 +) + +const ( + pmInText = "in" + pmInoutText = "inout" + pmOutText = "out" +) + +func (k ParameterMode) String() string { + var s []string + if k&pmIn != 0 { + s = append(s, pmInText) + } + if k&pmInout != 0 { + s = append(s, pmInoutText) + } + if k&pmOut != 0 { + s = append(s, pmOutText) + } + return fmt.Sprintf("%v", s) +} + +// ParameterField contains database field attributes for parameters. +type ParameterField struct { + names *fieldNames + ofs int // field name offset & used for index in case of tableRef or tableRows type + prec int // length + scale int // fraction + parameterOptions parameterOptions + tc typeCode + mode ParameterMode +} + +// NewTableRowsParameterField returns a ParameterField representing table rows. +func NewTableRowsParameterField(idx int) *ParameterField { + return &ParameterField{ofs: idx, tc: TcTableRows, mode: pmOut} +} + +func (f *ParameterField) fieldName() string { + switch f.tc { + case TcTableRows: + return fmt.Sprintf("table %d", f.ofs) + default: + return f.names.name(uint32(f.ofs)) //nolint: gosec + } +} + +func (f *ParameterField) isNullable() bool { return f.parameterOptions == poOptional } + +func (f *ParameterField) String() string { + return fmt.Sprintf("parameterOptions %s typeCode %s mode %s precision %d scale %d name %s", + f.parameterOptions, + f.tc, + f.mode, + f.prec, + f.scale, + f.fieldName(), + ) +} + +// IsLob returns true if the ParameterField is of type lob, false otherwise. +func (f *ParameterField) IsLob() bool { return f.tc.isLob() } + +// Convert returns the result of the fieldType conversion. +func (f *ParameterField) Convert(v any, cesu8Encoder transform.Transformer) (any, error) { + cv, err := convertField(f.tc, v, cesu8Encoder) + if err != nil { + return nil, fmt.Errorf("field %[1]s type code %[2]s type %[3]T value %[3]v coversion error %[4]w", f.fieldName(), f.tc, v, err) + } + return cv, nil +} + +// DatabaseTypeName returns the type name of the field. +// It implements the go-hdb driver ColumnType interface. +func (f *ParameterField) DatabaseTypeName() string { return f.tc.typeName() } + +// DecimalSize returns the type precision and scale of the field. +// It implements the go-hdb driver ColumnType interface. +func (f *ParameterField) DecimalSize() (int64, int64, bool) { + if f.tc.isDecimalType() { + return int64(f.prec), int64(f.scale), true + } + return 0, 0, false +} + +// Length returns the type length of the field. +// It implements the go-hdb driver ColumnType interface. +func (f *ParameterField) Length() (int64, bool) { + if f.tc.isVariableLength() { + return int64(f.prec), true + } + return 0, false +} + +// Name returns the parameter field name. +// It implements the go-hdb driver ColumnType interface. +func (f *ParameterField) Name() string { return f.fieldName() } + +// Nullable returns true if the field may be null, false otherwise. +// It implements the go-hdb driver ColumnType interface. +func (f *ParameterField) Nullable() (bool, bool) { return f.isNullable(), true } + +// ScanType returns the scan type of the field. +// It implements the go-hdb driver ColumnType interface. +func (f *ParameterField) ScanType() reflect.Type { return f.tc.dataType().ScanType(f.isNullable()) } + +// In returns true if the parameter field is an input field. +// It implements the go-hdb driver ParameterType interface. +func (f *ParameterField) In() bool { return f.mode == pmInout || f.mode == pmIn } + +// Out returns true if the parameter field is an output field. +// It implements the go-hdb driver ParameterType interface. +func (f *ParameterField) Out() bool { return f.mode == pmInout || f.mode == pmOut } + +// InOut returns true if the parameter field is an in,- output field. +// It implements the go-hdb driver ParameterType interface. +func (f *ParameterField) InOut() bool { return f.mode == pmInout } + +func (f *ParameterField) decode(dec *encoding.Decoder) { + f.parameterOptions = parameterOptions(dec.Int8()) + f.tc = typeCode(dec.Int8()) + f.mode = ParameterMode(dec.Int8()) + dec.Skip(1) // filler + f.ofs = int(dec.Uint32()) + f.prec = int(dec.Int16()) + f.scale = int(dec.Int16()) + dec.Skip(4) // filler + f.names.insertOfs(uint32(f.ofs)) //nolint: gosec +} + +func (f *ParameterField) prmSize(v any) int { + if v == nil && f.tc.supportNullValue() { + return 0 + } + switch f.tc { + case tcBoolean: + return encoding.BooleanFieldSize + case tcTinyint: + return encoding.TinyintFieldSize + case tcSmallint: + return encoding.SmallintFieldSize + case tcInteger: + return encoding.IntegerFieldSize + case tcBigint: + return encoding.BigintFieldSize + case tcReal: + return encoding.RealFieldSize + case tcDouble: + return encoding.DoubleFieldSize + case tcDate: + return encoding.DateFieldSize + case tcTime: + return encoding.TimeFieldSize + case tcTimestamp: + return encoding.TimestampFieldSize + case tcLongdate: + return encoding.LongdateFieldSize + case tcSeconddate: + return encoding.SeconddateFieldSize + case tcDaydate: + return encoding.DaydateFieldSize + case tcSecondtime: + return encoding.SecondtimeFieldSize + case tcDecimal: + return encoding.DecimalFieldSize + case tcFixed8: + return encoding.Fixed8FieldSize + case tcFixed12: + return encoding.Fixed12FieldSize + case tcFixed16: + return encoding.Fixed16FieldSize + case tcChar, tcVarchar, tcString, tcBstring, tcAlphanum, tcBinary, tcVarbinary: + return encoding.VarFieldSize(v) + case tcNchar, tcNvarchar, tcNstring, tcShorttext: + return encoding.Cesu8FieldSize(v) + case tcStPoint, tcStGeometry: + return encoding.HexFieldSize(v) + case tcBlob, tcClob, tcLocator, tcNclob, tcText, tcNlocator, tcBintext: + return encoding.LobInputParametersSize + default: + panic(fmt.Errorf("invalid type code %[1]d %[1]s", f.tc)) // should never happen + } +} + +func (f *ParameterField) encodePrm(enc *encoding.Encoder, v any) error { + encTc := f.tc.encTc() + if v == nil && f.tc.supportNullValue() { + enc.Byte(byte(f.tc.nullValue())) // null value type code + return nil + } + enc.Byte(byte(encTc)) // type code + switch f.tc { + case tcBoolean: + return enc.BooleanField(v) + case tcTinyint: + return enc.TinyintField(v) + case tcSmallint: + return enc.SmallintField(v) + case tcInteger: + return enc.IntegerField(v) + case tcBigint: + return enc.BigintField(v) + case tcReal: + return enc.RealField(v) + case tcDouble: + return enc.DoubleField(v) + case tcDate: + return enc.DateField(v) + case tcTime: + return enc.TimeField(v) + case tcTimestamp: + return enc.TimestampField(v) + case tcLongdate: + return enc.LongdateField(v) + case tcSeconddate: + return enc.SeconddateField(v) + case tcDaydate: + return enc.DaydateField(v) + case tcSecondtime: + return enc.SecondtimeField(v) + case tcDecimal: + return enc.DecimalField(v) + case tcFixed8: + return enc.Fixed8Field(v, f.prec, f.scale) + case tcFixed12: + return enc.Fixed12Field(v, f.prec, f.scale) + case tcFixed16: + return enc.Fixed16Field(v, f.prec, f.scale) + case tcChar, tcVarchar, tcString, tcBstring, tcAlphanum, tcBinary, tcVarbinary: + return enc.VarField(v) + case tcNchar, tcNvarchar, tcNstring, tcShorttext: + return enc.Cesu8Field(v) + case tcStPoint, tcStGeometry: + return enc.HexField(v) + case tcBlob, tcClob, tcLocator, tcNclob, tcText, tcNlocator, tcBintext: + descr, ok := v.(*LobInDescr) + if !ok { + panic("invalid lob value") // should never happen + } + enc.Byte(byte(descr.opt)) + enc.Int32(int32(descr.size())) //nolint: gosec + enc.Int32(int32(descr.pos)) //nolint: gosec + return nil + default: + panic(fmt.Errorf("invalid type code %[1]d %[1]s", f.tc)) // should never happen + } +} + +func (f *ParameterField) decodeResult(dec *encoding.Decoder, tr transform.Transformer, lobReader LobReader, lobChunkSize int) (any, error) { + return decodeResult(f.tc, dec, tr, lobReader, lobChunkSize, f.scale) +} + +/* +decode parameter +- currently not used +- type code is first byte (see encodePrm). +*/ +var _ = (*ParameterField)(nil).decodeParameter // mark decodeParameter as used + +func (f *ParameterField) decodeParameter(dec *encoding.Decoder) (any, error) { + tc := typeCode(dec.Byte()) + if tc&0x80 != 0 { // high bit set -> null value + return nil, nil + } + return decodeParameter(f.tc, dec, f.scale) +} + +// ParameterMetadata represents the metadata of a parameter. +type ParameterMetadata struct { + ParameterFields []*ParameterField +} + +func (m *ParameterMetadata) String() string { + return fmt.Sprintf("parameter %v", m.ParameterFields) +} + +func (m *ParameterMetadata) decodeNumArg(dec *encoding.Decoder, numArg int) error { + m.ParameterFields = make([]*ParameterField, numArg) + names := &fieldNames{} + for i := range len(m.ParameterFields) { + f := &ParameterField{names: names} + f.decode(dec) + m.ParameterFields[i] = f + } + if err := names.decode(dec); err != nil { + return err + } + return dec.Error() +} + +// InputParameters represents the set of input parameters. +type InputParameters struct { + InputFields []*ParameterField + nvargs []driver.NamedValue +} + +// NewInputParameters returns a InputParameters instance. +func NewInputParameters(inputFields []*ParameterField, nvargs []driver.NamedValue) (*InputParameters, error) { + return &InputParameters{InputFields: inputFields, nvargs: nvargs}, nil +} + +func (p *InputParameters) String() string { + return fmt.Sprintf("fields %s len(args) %d args %v", p.InputFields, len(p.nvargs), p.nvargs) +} + +func (p *InputParameters) size() int { + size := 0 + numColumns := len(p.InputFields) + if numColumns == 0 { // avoid divide-by-zero (e.g. prepare without parameters) + return 0 + } + + for i := range len(p.nvargs) / numColumns { // row-by-row + size += numColumns + + hasInLob := false + + for j := range numColumns { + f := p.InputFields[j] + size += f.prmSize(p.nvargs[i*numColumns+j].Value) + if f.IsLob() && f.In() { + hasInLob = true + } + } + + // lob input parameter: set offset position of lob data + if hasInLob { + for j := range numColumns { + if lobInDescr, ok := p.nvargs[i*numColumns+j].Value.(*LobInDescr); ok { + lobInDescr.setPos(size) + size += lobInDescr.size() + } + } + } + } + return size +} + +func (p *InputParameters) numArg() int { + numColumns := len(p.InputFields) + if numColumns == 0 { // avoid divide-by-zero (e.g. prepare without parameters) + return 0 + } + return len(p.nvargs) / numColumns +} + +func (p *InputParameters) decodeNumArg(dec *encoding.Decoder, numArg int) error { + // TODO Sniffer + // return fmt.Errorf("not implemented") + return nil +} + +func (p *InputParameters) encode(enc *encoding.Encoder) error { + numColumns := len(p.InputFields) + if numColumns == 0 { // avoid divide-by-zero (e.g. prepare without parameters) + return nil + } + + for i := range len(p.nvargs) / numColumns { // row-by-row + hasInLob := false + + for j := range numColumns { + // mass insert + f := p.InputFields[j] + if err := f.encodePrm(enc, p.nvargs[i*numColumns+j].Value); err != nil { + return err + } + if f.IsLob() && f.In() { + hasInLob = true + } + } + // lob input parameter: write first data chunk + if hasInLob { + for j := range numColumns { + if lobInDescr, ok := p.nvargs[i*numColumns+j].Value.(*LobInDescr); ok { + lobInDescr.writeFirst(enc) + } + } + } + } + return nil +} + +// OutputParameters represents the set of output parameters. +type OutputParameters struct { + OutputFields []*ParameterField + FieldValues []driver.Value + DecodeErrors DecodeErrors +} + +func (p *OutputParameters) String() string { + return fmt.Sprintf("fields %v values %v", p.OutputFields, p.FieldValues) +} + +func (p *OutputParameters) decodeResult(dec *encoding.Decoder, tr transform.Transformer, numArg int, lobReader LobReader, lobChunkSize int) error { + cols := len(p.OutputFields) + p.FieldValues = resizeSlice(p.FieldValues, numArg*cols) + + for i := range numArg { + for j, f := range p.OutputFields { + var err error + if p.FieldValues[i*cols+j], err = f.decodeResult(dec, tr, lobReader, lobChunkSize); err != nil { + p.DecodeErrors = append(p.DecodeErrors, &DecodeError{row: i, fieldName: f.Name(), err: err}) // collect decode / conversion errors + } + } + } + return dec.Error() +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/partkind.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/partkind.go new file mode 100644 index 00000000..194b811c --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/partkind.go @@ -0,0 +1,63 @@ +package protocol + +// PartKind represents the part kind. +type PartKind int8 + +// PartKind constants. +const ( + pkNil PartKind = 0 + PkCommand PartKind = 3 + PkResultset PartKind = 5 + pkError PartKind = 6 + PkStatementID PartKind = 10 + pkTransactionID PartKind = 11 + pkRowsAffected PartKind = 12 + PkResultsetID PartKind = 13 + PkTopologyInformation PartKind = 15 + pkTableLocation PartKind = 16 + PkReadLobRequest PartKind = 17 + PkReadLobReply PartKind = 18 + pkAbapIStream PartKind = 25 + pkAbapOStream PartKind = 26 + pkCommandInfo PartKind = 27 + PkWriteLobRequest PartKind = 28 + PkClientContext PartKind = 29 + PkWriteLobReply PartKind = 30 + PkParameters PartKind = 32 + PkAuthentication PartKind = 33 + pkSessionContext PartKind = 34 + PkClientID PartKind = 35 + pkProfile PartKind = 38 + PkStatementContext PartKind = 39 + pkPartitionInformation PartKind = 40 + PkOutputParameters PartKind = 41 + PkConnectOptions PartKind = 42 + pkCommitOptions PartKind = 43 + pkFetchOptions PartKind = 44 + PkFetchSize PartKind = 45 + PkParameterMetadata PartKind = 47 + PkResultMetadata PartKind = 48 + pkFindLobRequest PartKind = 49 + pkFindLobReply PartKind = 50 + pkItabSHM PartKind = 51 + pkItabChunkMetadata PartKind = 53 + pkItabMetadata PartKind = 55 + pkItabResultChunk PartKind = 56 + PkClientInfo PartKind = 57 + pkStreamData PartKind = 58 + pkOStreamResult PartKind = 59 + pkFDARequestMetadata PartKind = 60 + pkFDAReplyMetadata PartKind = 61 + pkBatchPrepare PartKind = 62 //Reserved: do not use + pkBatchExecute PartKind = 63 //Reserved: do not use + PkTransactionFlags PartKind = 64 + pkRowSlotImageParamMetadata PartKind = 65 //Reserved: do not use + pkRowSlotImageResultset PartKind = 66 //Reserved: do not use + PkDBConnectInfo PartKind = 67 + pkLobFlags PartKind = 68 + pkResultsetOptions PartKind = 69 + pkXATransactionInfo PartKind = 70 + pkSessionVariable PartKind = 71 + pkWorkLoadReplayContext PartKind = 72 + pkSQLReplyOptions PartKind = 73 +) diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/parts.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/parts.go new file mode 100644 index 00000000..a5b79e21 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/parts.go @@ -0,0 +1,177 @@ +package protocol + +import ( + "reflect" + + "github.com/SAP/go-hdb/driver/internal/protocol/encoding" + "golang.org/x/text/transform" +) + +// Part represents a protocol part. +type Part interface { + String() string // should support Stringer interface + kind() PartKind +} + +type partDecoder interface { + Part + decode(dec *encoding.Decoder) error +} +type numArgPartDecoder interface { + Part + decodeNumArg(dec *encoding.Decoder, numArg int) error +} +type bufLenPartDecoder interface { + Part + decodeBufLen(dec *encoding.Decoder, bufLen int) error +} +type resultPartDecoder interface { + Part + decodeResult(dec *encoding.Decoder, tr transform.Transformer, numArg int, lobReader LobReader, lobChunkSize int) error +} + +// PartEncoder represents a protocol part the driver is able to encode. +type PartEncoder interface { + Part + numArg() int + size() int + encode(enc *encoding.Encoder) error +} + +func (*HdbErrors) kind() PartKind { return pkError } +func (*AuthInitRequest) kind() PartKind { return PkAuthentication } +func (*AuthInitReply) kind() PartKind { return PkAuthentication } +func (*AuthFinalRequest) kind() PartKind { return PkAuthentication } +func (*AuthFinalReply) kind() PartKind { return PkAuthentication } +func (ClientID) kind() PartKind { return PkClientID } +func (clientInfo) kind() PartKind { return PkClientInfo } +func (*TopologyInformation) kind() PartKind { return PkTopologyInformation } +func (Command) kind() PartKind { return PkCommand } +func (*rowsAffected) kind() PartKind { return pkRowsAffected } +func (StatementID) kind() PartKind { return PkStatementID } +func (*ParameterMetadata) kind() PartKind { return PkParameterMetadata } +func (*InputParameters) kind() PartKind { return PkParameters } +func (*OutputParameters) kind() PartKind { return PkOutputParameters } +func (*ResultMetadata) kind() PartKind { return PkResultMetadata } +func (ResultsetID) kind() PartKind { return PkResultsetID } +func (*Resultset) kind() PartKind { return PkResultset } +func (Fetchsize) kind() PartKind { return PkFetchSize } +func (*ReadLobRequest) kind() PartKind { return PkReadLobRequest } +func (*ReadLobReply) kind() PartKind { return PkReadLobReply } +func (*WriteLobRequest) kind() PartKind { return PkWriteLobRequest } +func (*WriteLobReply) kind() PartKind { return PkWriteLobReply } +func (*ClientContext) kind() PartKind { return PkClientContext } +func (*ConnectOptions) kind() PartKind { return PkConnectOptions } +func (*DBConnectInfo) kind() PartKind { return PkDBConnectInfo } +func (*statementContext) kind() PartKind { return PkStatementContext } +func (*transactionFlags) kind() PartKind { return PkTransactionFlags } + +// numArg methods (result == 1). +func (*AuthInitRequest) numArg() int { return 1 } +func (*AuthFinalRequest) numArg() int { return 1 } +func (ClientID) numArg() int { return 1 } +func (Command) numArg() int { return 1 } +func (StatementID) numArg() int { return 1 } +func (ResultsetID) numArg() int { return 1 } +func (Fetchsize) numArg() int { return 1 } +func (*ReadLobRequest) numArg() int { return 1 } + +// size methods (fixed size). +const ( + statementIDSize = 8 + resultsetIDSize = 8 + fetchsizeSize = 4 + readLobRequestSize = 24 +) + +func (StatementID) size() int { return statementIDSize } +func (ResultsetID) size() int { return resultsetIDSize } +func (Fetchsize) size() int { return fetchsizeSize } +func (ReadLobRequest) size() int { return readLobRequestSize } + +// func (lobFlags) size() int { return tinyintFieldSize } + +// check if part types implement the part encoder interface. +var ( + _ PartEncoder = (*AuthInitRequest)(nil) + _ PartEncoder = (*AuthFinalRequest)(nil) + _ PartEncoder = (*ClientID)(nil) + _ PartEncoder = (*clientInfo)(nil) + _ PartEncoder = (*Command)(nil) + _ PartEncoder = (*StatementID)(nil) + _ PartEncoder = (*InputParameters)(nil) + _ PartEncoder = (*ResultsetID)(nil) + _ PartEncoder = (*Fetchsize)(nil) + _ PartEncoder = (*ReadLobRequest)(nil) + _ PartEncoder = (*WriteLobRequest)(nil) + _ PartEncoder = (*ClientContext)(nil) + _ PartEncoder = (*ConnectOptions)(nil) + _ PartEncoder = (*DBConnectInfo)(nil) +) + +// check if part types implement the right part decoder interface. +var ( + _ numArgPartDecoder = (*HdbErrors)(nil) + _ partDecoder = (*AuthInitRequest)(nil) + _ partDecoder = (*AuthInitReply)(nil) + _ partDecoder = (*AuthFinalRequest)(nil) + _ partDecoder = (*AuthFinalReply)(nil) + _ bufLenPartDecoder = (*ClientID)(nil) + _ numArgPartDecoder = (*clientInfo)(nil) + _ numArgPartDecoder = (*TopologyInformation)(nil) + _ bufLenPartDecoder = (*Command)(nil) + _ numArgPartDecoder = (*rowsAffected)(nil) + _ partDecoder = (*StatementID)(nil) + _ numArgPartDecoder = (*ParameterMetadata)(nil) + _ numArgPartDecoder = (*InputParameters)(nil) + _ resultPartDecoder = (*OutputParameters)(nil) + _ numArgPartDecoder = (*ResultMetadata)(nil) + _ partDecoder = (*ResultsetID)(nil) + _ resultPartDecoder = (*Resultset)(nil) + _ partDecoder = (*Fetchsize)(nil) + _ partDecoder = (*ReadLobRequest)(nil) + _ numArgPartDecoder = (*WriteLobRequest)(nil) + _ numArgPartDecoder = (*ReadLobReply)(nil) + _ numArgPartDecoder = (*WriteLobReply)(nil) + _ numArgPartDecoder = (*ClientContext)(nil) + _ numArgPartDecoder = (*ConnectOptions)(nil) + _ numArgPartDecoder = (*DBConnectInfo)(nil) + _ numArgPartDecoder = (*statementContext)(nil) + _ numArgPartDecoder = (*transactionFlags)(nil) +) + +var genPartTypeMap = map[PartKind]reflect.Type{ + pkError: reflect.TypeFor[HdbErrors](), + PkClientID: reflect.TypeFor[ClientID](), + PkClientInfo: reflect.TypeFor[clientInfo](), + PkTopologyInformation: reflect.TypeFor[TopologyInformation](), + PkCommand: reflect.TypeFor[Command](), + pkRowsAffected: reflect.TypeFor[rowsAffected](), + PkStatementID: reflect.TypeFor[StatementID](), + PkResultsetID: reflect.TypeFor[ResultsetID](), + PkFetchSize: reflect.TypeFor[Fetchsize](), + PkReadLobRequest: reflect.TypeFor[ReadLobRequest](), + PkReadLobReply: reflect.TypeFor[ReadLobReply](), + PkWriteLobReply: reflect.TypeFor[WriteLobReply](), + PkWriteLobRequest: reflect.TypeFor[WriteLobRequest](), + PkClientContext: reflect.TypeFor[ClientContext](), + PkConnectOptions: reflect.TypeFor[ConnectOptions](), + PkTransactionFlags: reflect.TypeFor[transactionFlags](), + PkStatementContext: reflect.TypeFor[statementContext](), + PkDBConnectInfo: reflect.TypeFor[DBConnectInfo](), + /* + parts that cannot be used generically as additional parameters are needed + + PkParameterMetadata + PkParameters + PkOutputParameters + PkResultMetadata + PkResultset + */ +} + +// to be implemented by parts needing initialization +// in case the part is instatiated generically. +type initer interface { + init() +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/parts1.24.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/parts1.24.go new file mode 100644 index 00000000..a3980c7e --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/parts1.24.go @@ -0,0 +1,27 @@ +//go:build !go1.25 + +package protocol + +import "reflect" + +// newGenPartReader returns a generic part reader. +func newGenPartReader(kind PartKind) Part { + if kind == PkAuthentication { + return nil // cannot instantiate generically + } + pt, ok := genPartTypeMap[kind] + if !ok { + // whether part cannot be instantiated generically or + // part is not (yet) known to the driver + return nil + } + // create instance + part, ok := reflect.New(pt).Interface().(Part) + if !ok { + panic("part kind does not implement part reader interface") // should never happen + } + if part, ok := part.(initer); ok { + part.init() + } + return part +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/parts1.25.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/parts1.25.go new file mode 100644 index 00000000..6ab413d5 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/parts1.25.go @@ -0,0 +1,27 @@ +//go:build go1.25 + +package protocol + +import "reflect" + +// newGenPartReader returns a generic part reader. +func newGenPartReader(kind PartKind) Part { + if kind == PkAuthentication { + return nil // cannot instantiate generically + } + pt, ok := genPartTypeMap[kind] + if !ok { + // whether part cannot be instantiated generically or + // part is not (yet) known to the driver + return nil + } + // create instance + part, ok := reflect.TypeAssert[Part](reflect.New(pt)) + if !ok { + panic("part kind does not implement part reader interface") // should never happen + } + if part, ok := part.(initer); ok { + part.init() + } + return part +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/protocol.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/protocol.go new file mode 100644 index 00000000..87053db5 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/protocol.go @@ -0,0 +1,479 @@ +package protocol + +import ( + "bufio" + "context" + "errors" + "fmt" + "log/slog" + "math" + + "github.com/SAP/go-hdb/driver/internal/protocol/encoding" + "golang.org/x/text/transform" +) + +const ( + traceMsg = "PROT" + + prefixDB = "←" + prefixClient = "→" + + textIni = "INI" + textMsgHdr = "MSH" + textSegHdr = "SGH" + textParHdr = "PRH" + textPar = "PRT" + textSkip = "*skipped" +) + +// padding. +const padding = 8 + +func padBytes(size int) int { + if r := size % padding; r != 0 { + return padding - r + } + return 0 +} + +type partCache map[PartKind]Part + +func (c *partCache) get(kind PartKind) (Part, bool) { + if part, ok := (*c)[kind]; ok { + return part, true + } + part := newGenPartReader(kind) + if part == nil { // part cannot be instantiated generically + return nil, false + } + (*c)[kind] = part + return part, true +} + +// Reader represents a protocol reader. +type Reader struct { + dec *encoding.Decoder + tr transform.Transformer + + protTrace bool + logger *slog.Logger + + lobChunkSize int + + readFromDB bool + prefix string + + mh *messageHeader + sh *segmentHeader + ph *partHeader + + partCache partCache + + hdbErrors *HdbErrors + rowsAffected *rowsAffected +} + +func newReader(dec *encoding.Decoder, tr transform.Transformer, protTrace bool, logger *slog.Logger, lobChunkSize int, readFromDB bool, prefix string) *Reader { + return &Reader{ + dec: dec, + tr: tr, + protTrace: protTrace, + logger: logger, + lobChunkSize: lobChunkSize, + readFromDB: readFromDB, + prefix: prefix, + partCache: partCache{}, + mh: &messageHeader{}, + sh: &segmentHeader{}, + ph: &partHeader{}, + hdbErrors: &HdbErrors{}, + rowsAffected: &rowsAffected{}, + } +} + +// NewDBReader returns an instance of a database protocol reader. +func NewDBReader(dec *encoding.Decoder, tr transform.Transformer, protTrace bool, logger *slog.Logger, lobChunkSize int) *Reader { + return newReader(dec, tr, protTrace, logger, lobChunkSize, true, prefixDB) +} + +// NewClientReader returns an instance of a client protocol reader. +func NewClientReader(dec *encoding.Decoder, tr transform.Transformer, protTrace bool, logger *slog.Logger, lobChunkSize int) *Reader { + return newReader(dec, tr, protTrace, logger, lobChunkSize, false, prefixClient) +} + +// SkipParts reads and discards all protocol parts. +func (r *Reader) SkipParts(ctx context.Context) error { + _, err := r.IterateParts(ctx, 0, nil) + return err +} + +// SessionID returns the session ID. +func (r *Reader) SessionID() int64 { return r.mh.sessionID } + +// FunctionCode returns the function code of the protocol. +func (r *Reader) FunctionCode() FunctionCode { return r.sh.functionCode } + +// ReadProlog reads the protocol prolog. +func (r *Reader) ReadProlog(ctx context.Context) error { + if r.readFromDB { + rep := &initReply{} + if err := rep.decode(r.dec); err != nil { + return err + } + if r.protTrace { + r.logger.LogAttrs(ctx, slog.LevelInfo, traceMsg, slog.String(r.prefix+textIni, rep.String())) + } + return nil + } + req := &initRequest{} + if err := req.decode(r.dec); err != nil { + return err + } + if r.protTrace { + r.logger.LogAttrs(ctx, slog.LevelInfo, traceMsg, slog.String(r.prefix+textIni, req.String())) + } + return nil +} + +func (r *Reader) skipPadding() int { + padBytes := padBytes(int(r.ph.bufferLength)) + r.dec.Skip(padBytes) + return padBytes +} + +func (r *Reader) skipPaddingLastPart(numReadByte int64) { + // last part: + // skip difference between real read bytes and message header var part length + padBytes := int64(r.mh.varPartLength) - numReadByte + switch { + case padBytes < 0: + panic(fmt.Sprintf("protocol error: bytes read %d > variable part length %d", numReadByte, r.mh.varPartLength)) + case padBytes > 0: + r.dec.Skip(int(padBytes)) + } +} + +// ReadPart reads a one protocol part. +func (r *Reader) ReadPart(ctx context.Context, part Part, lobReader LobReader) (err error) { + cntBefore := r.dec.Cnt() + + switch part := part.(type) { + // do not return here in case of error -> read stream would be broken + case partDecoder: + err = part.decode(r.dec) + case bufLenPartDecoder: + err = part.decodeBufLen(r.dec, r.ph.bufLen()) + case numArgPartDecoder: + err = part.decodeNumArg(r.dec, r.ph.numArg()) + case resultPartDecoder: + if lobReader == nil { + panic("missing lob reader") // should never happen + } + err = part.decodeResult(r.dec, r.tr, r.ph.numArg(), lobReader, r.lobChunkSize) + default: + panic("invalid part decoder") // should never happen + } + // do not return here in case of error -> read stream would be broken + + cnt := r.dec.Cnt() - cntBefore + + if r.protTrace { + r.logger.LogAttrs(ctx, slog.LevelInfo, traceMsg, slog.String(r.prefix+textPar, part.String())) + } + + bufferLen := int(r.ph.bufferLength) + switch { + case cnt < bufferLen: // protocol buffer length > read bytes -> skip the unread bytes + r.dec.Skip(bufferLen - cnt) + case cnt > bufferLen: // read bytes > protocol buffer length -> should never happen + panic(fmt.Sprintf("protocol error: read bytes %d > buffer length %d", cnt, bufferLen)) + } + return err +} + +// ErrSkipped is used by the caller of the iterator to indicate that no read was executed. +var ErrSkipped = errors.New("perts iterator: skipped read") + +// IterateParts iterates through all protocol parts. +func (r *Reader) IterateParts(ctx context.Context, offset int, fn func(kind PartKind, attrs PartAttributes) error) (int64, error) { + var hdbErrors *HdbErrors + var rowsAffected *rowsAffected + + if err := r.mh.decode(r.dec); err != nil { + return 0, err + } + + var numReadByte int64 = 0 // header bytes are not calculated in header varPartBytes: start with zero + if r.protTrace { + r.logger.LogAttrs(ctx, slog.LevelInfo, traceMsg, slog.String(r.prefix+textMsgHdr, r.mh.String())) + } + + for range int(r.mh.noOfSegm) { + if err := r.sh.decode(r.dec); err != nil { + return 0, err + } + + numReadByte += segmentHeaderSize + + if r.protTrace { + r.logger.LogAttrs(ctx, slog.LevelInfo, traceMsg, slog.String(r.prefix+textSegHdr, r.sh.String())) + } + + lastPart := int(r.sh.noOfParts) - 1 + for j := range lastPart + 1 { // <= + if err := r.ph.decode(r.dec); err != nil { + return 0, err + } + kind := r.ph.partKind + + numReadByte += partHeaderSize + + if r.protTrace { + r.logger.LogAttrs(ctx, slog.LevelInfo, traceMsg, slog.String(r.prefix+textParHdr, r.ph.String())) + } + + cntBefore := r.dec.Cnt() + + switch kind { + case pkRowsAffected: + if err := r.ReadPart(ctx, r.rowsAffected, nil); err != nil { + return 0, err + } + rowsAffected = r.rowsAffected + case pkError: + if err := r.ReadPart(ctx, r.hdbErrors, nil); err != nil { + return 0, err + } + hdbErrors = r.hdbErrors + default: + err := ErrSkipped + // caller must not handle hdb errors and rows affected. + if fn != nil { + if err = fn(kind, r.ph.partAttributes); err != nil && err != ErrSkipped { //nolint:errorlint + return 0, err + } + } + if err == ErrSkipped { //nolint:errorlint + // if trace is on or mandatory parts need to be read we cannot skip + if r.protTrace { + if part, ok := r.partCache.get(kind); ok { + if err := r.ReadPart(ctx, part, nil); err != nil { + return 0, err + } + } else { + r.dec.Skip(int(r.ph.bufferLength)) + r.logger.LogAttrs(ctx, slog.LevelInfo, traceMsg, slog.String(r.prefix+textSkip, kind.String())) + } + } else { + r.dec.Skip(int(r.ph.bufferLength)) + } + } + } + + numReadByte += int64(r.dec.Cnt()) - int64(cntBefore) + + if j != lastPart { // not last part + numReadByte += int64(r.skipPadding()) + } + + } + } + + r.skipPaddingLastPart(numReadByte) + + if err := r.dec.Error(); err != nil { + r.dec.ResetError() + return 0, err + } + + var numRow int64 + if rowsAffected != nil { + numRow = rowsAffected.Total() + } + + if hdbErrors == nil { + return numRow, nil + } + + if rowsAffected != nil { // link statement to error + j := 0 + for i, rows := range rowsAffected.rows { + if rows == raExecutionFailed { + hdbErrors.setStmtNo(j, offset+i) + j++ + } + } + } + if hdbErrors.onlyWarnings { + for _, err := range hdbErrors.errs { + r.logger.LogAttrs(ctx, slog.LevelWarn, err.Error()) + } + return numRow, nil + } + return numRow, hdbErrors +} + +const defaultSessionID = -1 + +// Writer represents a protocol writer. +type Writer struct { + wr *bufio.Writer + enc *encoding.Encoder + + protTrace bool + logger *slog.Logger + + sv map[string]string + svSent bool + + sessionID int64 + + // reuse header + mh *messageHeader + sh *segmentHeader + ph *partHeader + + hasError bool +} + +// NewWriter returns an instance of a protocol writer. +func NewWriter(wr *bufio.Writer, enc *encoding.Encoder, protTrace bool, logger *slog.Logger, sv map[string]string) *Writer { + return &Writer{ + wr: wr, + enc: enc, + protTrace: protTrace, + logger: logger, + sv: sv, + sessionID: defaultSessionID, + mh: new(messageHeader), + sh: new(segmentHeader), + ph: new(partHeader), + } +} + +const ( + productVersionMajor = 4 + productVersionMinor = 20 + protocolVersionMajor = 4 + protocolVersionMinor = 1 +) + +// HasError returns true if writing raised an error, false otherwise. +func (w *Writer) HasError() bool { return w.hasError } + +// WriteProlog writes the protocol prolog. +func (w *Writer) WriteProlog(ctx context.Context) error { + req := &initRequest{} + req.product.major = productVersionMajor + req.product.minor = productVersionMinor + req.protocol.major = protocolVersionMajor + req.protocol.minor = protocolVersionMinor + req.numOptions = 1 + req.endianess = littleEndian + if err := req.encode(w.enc); err != nil { + return err + } + if w.protTrace { + w.logger.LogAttrs(ctx, slog.LevelInfo, traceMsg, slog.String(prefixClient+textIni, req.String())) + } + return w.wr.Flush() +} + +// SetSessionID sets the session ID after a successful authentication. +func (w *Writer) SetSessionID(sessionID int64) { w.sessionID = sessionID } + +func (w *Writer) Write(ctx context.Context, messageType MessageType, commit bool, parts ...PartEncoder) error { + err := w._write(ctx, messageType, commit, parts...) + if err != nil { + w.hasError = true + } + return err +} + +func (w *Writer) _write(ctx context.Context, messageType MessageType, commit bool, parts ...PartEncoder) error { + // check on session variables to be send as ClientInfo + if w.sv != nil && !w.svSent && messageType.ClientInfoSupported() { + parts = append([]PartEncoder{(*clientInfo)(&w.sv)}, parts...) + w.svSent = true + } + + numPart := len(parts) + partSize := make([]int, numPart) + size := int64(segmentHeaderSize + numPart*partHeaderSize) // int64 to hold MaxUInt32 in 32bit OS + + for i, part := range parts { + s := part.size() + size += int64(s + padBytes(s)) + partSize[i] = s // buffer size (expensive calculation) + } + + if size > math.MaxUint32 { + return fmt.Errorf("message size %d exceeds maximum message header value %d", size, int64(math.MaxUint32)) // int64: without cast overflow error in 32bit OS + } + + bufferSize := size + + w.mh.sessionID = w.sessionID + w.mh.varPartLength = uint32(size) //nolint: gosec + w.mh.varPartSize = uint32(bufferSize) //nolint: gosec + w.mh.noOfSegm = 1 + + if err := w.mh.encode(w.enc); err != nil { + return err + } + if w.protTrace { + w.logger.LogAttrs(ctx, slog.LevelInfo, traceMsg, slog.String(prefixClient+textMsgHdr, w.mh.String())) + } + + if size > math.MaxInt32 { + return fmt.Errorf("message size %d exceeds maximum part header value %d", size, math.MaxInt32) + } + + w.sh.messageType = messageType + w.sh.commit = commit + w.sh.segmentKind = skRequest + w.sh.segmentLength = int32(size) //nolint: gosec + w.sh.segmentOfs = 0 + w.sh.noOfParts = int16(numPart) //nolint: gosec + w.sh.segmentNo = 1 + + if err := w.sh.encode(w.enc); err != nil { + return err + } + if w.protTrace { + w.logger.LogAttrs(ctx, slog.LevelInfo, traceMsg, slog.String(prefixClient+textSegHdr, w.sh.String())) + } + + bufferSize -= segmentHeaderSize + + for i, part := range parts { + size := partSize[i] + pad := padBytes(size) + + w.ph.partKind = part.kind() + if err := w.ph.setNumArg(part.numArg()); err != nil { + return err + } + w.ph.bufferLength = int32(size) //nolint: gosec + w.ph.bufferSize = int32(bufferSize) //nolint: gosec + + if err := w.ph.encode(w.enc); err != nil { + return err + } + if w.protTrace { + w.logger.LogAttrs(ctx, slog.LevelInfo, traceMsg, slog.String(prefixClient+textParHdr, w.ph.String())) + } + + if err := part.encode(w.enc); err != nil { + return err + } + if w.protTrace { + w.logger.LogAttrs(ctx, slog.LevelInfo, traceMsg, slog.String(prefixClient+textPar, part.String())) + } + + w.enc.Zeroes(pad) + + bufferSize -= int64(partHeaderSize + size + pad) + } + return w.wr.Flush() +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/resizeslice.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/resizeslice.go new file mode 100644 index 00000000..40cce12f --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/resizeslice.go @@ -0,0 +1,11 @@ +package protocol + +func resizeSlice[S ~[]E, E any](s S, n int) S { + switch { + case s == nil: + s = make(S, n) + case n > cap(s): + s = append(s, make(S, n-len(s))...) + } + return s[:n] +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/result.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/result.go new file mode 100644 index 00000000..a4b983ef --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/result.go @@ -0,0 +1,180 @@ +package protocol + +import ( + "database/sql/driver" + "fmt" + "reflect" + + "github.com/SAP/go-hdb/driver/internal/protocol/encoding" + "golang.org/x/text/transform" +) + +type columnOptions int8 + +const ( + coMandatory columnOptions = 0x01 + coOptional columnOptions = 0x02 +) + +const ( + coMandatoryText = "mandatory" + coOptionalText = "optional" +) + +func (k columnOptions) String() string { + var s []string + if k&coMandatory != 0 { + s = append(s, coMandatoryText) + } + if k&coOptional != 0 { + s = append(s, coOptionalText) + } + return fmt.Sprintf("%v", s) +} + +// ResultsetID represents a resultset id. +type ResultsetID uint64 + +func (id ResultsetID) String() string { return fmt.Sprintf("%d", id) } +func (id *ResultsetID) decode(dec *encoding.Decoder) error { + *id = ResultsetID(dec.Uint64()) + return dec.Error() +} +func (id ResultsetID) encode(enc *encoding.Encoder) error { enc.Uint64(uint64(id)); return nil } + +func newResultFields(size int) []*ResultField { + return make([]*ResultField, size) +} + +// ResultField represents a database result field. +type ResultField struct { + names *fieldNames + tableNameOfs uint32 + schemaNameOfs uint32 + columnNameOfs uint32 + columnDisplayNameOfs uint32 + prec int // length + scale int // fraction + columnOptions columnOptions + tc typeCode +} + +// String implements the Stringer interface. +func (f *ResultField) String() string { + return fmt.Sprintf("columnsOptions %s typeCode %s precision %d scale %d tablename %s schemaname %s columnname %s columnDisplayname %s", + f.columnOptions, + f.tc, + f.prec, + f.scale, + f.names.name(f.tableNameOfs), + f.names.name(f.schemaNameOfs), + f.names.name(f.columnNameOfs), + f.names.name(f.columnDisplayNameOfs), + ) +} + +func (f *ResultField) isNullable() bool { return f.columnOptions == coOptional } + +// DatabaseTypeName returns the type name of the field. +// It implements the go-hdb driver ColumnType interface. +func (f *ResultField) DatabaseTypeName() string { return f.tc.typeName() } + +// DecimalSize returns the type precision and scale of the field. +// It implements the go-hdb driver ColumnType interface. +func (f *ResultField) DecimalSize() (int64, int64, bool) { + if f.tc.isDecimalType() { + return int64(f.prec), int64(f.scale), true + } + return 0, 0, false +} + +// Length returns the type length of the field. +// It implements the go-hdb driver ColumnType interface. +func (f *ResultField) Length() (int64, bool) { + if f.tc.isVariableLength() { + return int64(f.prec), true + } + return 0, false +} + +// Name returns the result field name. +// It implements the go-hdb driver ColumnType interface. +func (f *ResultField) Name() string { return f.names.name(f.columnDisplayNameOfs) } + +// Nullable returns true if the field may be null, false otherwise. +// It implements the go-hdb driver ColumnType interface. +func (f *ResultField) Nullable() (bool, bool) { return f.isNullable(), true } + +// ScanType returns the scan type of the field. +// It implements the go-hdb driver ColumnType interface. +func (f *ResultField) ScanType() reflect.Type { return f.tc.dataType().ScanType(f.isNullable()) } + +func (f *ResultField) decode(dec *encoding.Decoder) { + f.columnOptions = columnOptions(dec.Int8()) + f.tc = typeCode(dec.Int8()) + f.scale = int(dec.Int16()) + f.prec = int(dec.Int16()) + dec.Skip(2) // filler + f.tableNameOfs = dec.Uint32() + f.schemaNameOfs = dec.Uint32() + f.columnNameOfs = dec.Uint32() + f.columnDisplayNameOfs = dec.Uint32() + + f.names.insertOfs(f.tableNameOfs) + f.names.insertOfs(f.schemaNameOfs) + f.names.insertOfs(f.columnNameOfs) + f.names.insertOfs(f.columnDisplayNameOfs) +} + +func (f *ResultField) decodeResult(dec *encoding.Decoder, tr transform.Transformer, lobReader LobReader, lobChunkSize int) (any, error) { + return decodeResult(f.tc, dec, tr, lobReader, lobChunkSize, f.scale) +} + +// ResultMetadata represents the metadata of a set of database result fields. +type ResultMetadata struct { + ResultFields []*ResultField +} + +func (r *ResultMetadata) String() string { + return fmt.Sprintf("result fields %v", r.ResultFields) +} + +func (r *ResultMetadata) decodeNumArg(dec *encoding.Decoder, numArg int) error { + r.ResultFields = newResultFields(numArg) + names := &fieldNames{} + for i := range len(r.ResultFields) { + f := &ResultField{names: names} + f.decode(dec) + r.ResultFields[i] = f + } + if err := names.decode(dec); err != nil { + return err + } + return dec.Error() +} + +// Resultset represents a database result set. +type Resultset struct { + ResultFields []*ResultField + FieldValues []driver.Value + DecodeErrors DecodeErrors +} + +func (r *Resultset) String() string { + return fmt.Sprintf("result fields %v field values %v", r.ResultFields, r.FieldValues) +} + +func (r *Resultset) decodeResult(dec *encoding.Decoder, tr transform.Transformer, numArg int, lobReader LobReader, lobChunkSize int) error { + cols := len(r.ResultFields) + r.FieldValues = resizeSlice(r.FieldValues, numArg*cols) + + for i := range numArg { + for j, f := range r.ResultFields { + var err error + if r.FieldValues[i*cols+j], err = f.decodeResult(dec, tr, lobReader, lobChunkSize); err != nil { + r.DecodeErrors = append(r.DecodeErrors, &DecodeError{row: i, fieldName: f.Name(), err: err}) // collect decode / conversion errors + } + } + } + return dec.Error() +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/rowsaffected.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/rowsaffected.go new file mode 100644 index 00000000..7dc101d7 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/rowsaffected.go @@ -0,0 +1,42 @@ +package protocol + +import ( + "fmt" + + "github.com/SAP/go-hdb/driver/internal/protocol/encoding" +) + +// rows affected. +const ( + raSuccessNoInfo = -2 + raExecutionFailed = -3 +) + +// rowsAffected represents a rows affected part. +type rowsAffected struct { + rows []int32 +} + +func (r rowsAffected) String() string { + return fmt.Sprintf("%v", r.rows) +} + +func (r *rowsAffected) decodeNumArg(dec *encoding.Decoder, numArg int) error { + r.rows = resizeSlice(r.rows, numArg) + + for i := range numArg { + r.rows[i] = dec.Int32() + } + return dec.Error() +} + +// Total return the total number of all affected rows. +func (r rowsAffected) Total() int64 { + total := int64(0) + for _, rows := range r.rows { + if rows > 0 { + total += int64(rows) + } + } + return total +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/simpleparts.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/simpleparts.go new file mode 100644 index 00000000..28727256 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/simpleparts.go @@ -0,0 +1,60 @@ +package protocol + +import ( + "fmt" + + "github.com/SAP/go-hdb/driver/internal/protocol/encoding" + "github.com/SAP/go-hdb/driver/unicode/cesu8" +) + +// ClientID represents a client id part. +type ClientID []byte + +func (id ClientID) String() string { return string(id) } +func (id ClientID) size() int { return len(id) } +func (id *ClientID) decodeBufLen(dec *encoding.Decoder, bufLen int) error { + *id = resizeSlice(*id, bufLen) + dec.Bytes(*id) + return dec.Error() +} +func (id ClientID) encode(enc *encoding.Encoder) error { enc.Bytes(id); return nil } + +// Command represents a command part with cesu8 content. +type Command []byte + +func (c Command) String() string { return string(c) } +func (c Command) size() int { return cesu8.Size(c) } +func (c *Command) decodeBufLen(dec *encoding.Decoder, bufLen int) error { + *c = resizeSlice(*c, bufLen) + var err error + *c, err = dec.CESU8Bytes(len(*c)) + if err != nil { + return err + } + return dec.Error() +} +func (c Command) encode(enc *encoding.Encoder) error { _, err := enc.CESU8Bytes(c); return err } + +// Fetchsize represents a fetch size part. +type Fetchsize int32 + +func (s Fetchsize) String() string { return fmt.Sprintf("fetchsize %d", s) } +func (s *Fetchsize) decode(dec *encoding.Decoder) error { + *s = Fetchsize(dec.Int32()) + return dec.Error() +} +func (s Fetchsize) encode(enc *encoding.Encoder) error { enc.Int32(int32(s)); return nil } + +// StatementID represents the statement id part type. +type StatementID uint64 + +func (id StatementID) String() string { return fmt.Sprintf("%d", id) } + +// Decode implements the partDecoder interface. +func (id *StatementID) decode(dec *encoding.Decoder) error { + *id = StatementID(dec.Uint64()) + return dec.Error() +} + +// Encode implements the partEncoder interface. +func (id StatementID) encode(enc *encoding.Encoder) error { enc.Uint64(uint64(id)); return nil } diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/typecode.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/typecode.go new file mode 100644 index 00000000..4b203b1f --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/typecode.go @@ -0,0 +1,176 @@ +package protocol + +import ( + "strings" +) + +// typeCode identify the type of a field transferred to or from the database. +type typeCode byte + +// null value indicator is high bit + +const ( + tcNull typeCode = 0x00 + tcTinyint typeCode = 0x01 + tcSmallint typeCode = 0x02 + tcInteger typeCode = 0x03 + tcBigint typeCode = 0x04 + tcDecimal typeCode = 0x05 + tcReal typeCode = 0x06 + tcDouble typeCode = 0x07 + tcChar typeCode = 0x08 + tcVarchar typeCode = 0x09 // changed from tcVarchar1 to tcVarchar (ref hdbclient) + tcNchar typeCode = 0x0A + tcNvarchar typeCode = 0x0B + tcBinary typeCode = 0x0C + tcVarbinary typeCode = 0x0D + tcDate typeCode = 0x0E + tcTime typeCode = 0x0F + tcTimestamp typeCode = 0x10 + tcTimetz typeCode = 0x11 + tcTimeltz typeCode = 0x12 + tcTimestampTz typeCode = 0x13 + tcTimestampLtz typeCode = 0x14 + tcIntervalYm typeCode = 0x15 + tcIntervalDs typeCode = 0x16 + tcRowid typeCode = 0x17 + tcUrowid typeCode = 0x18 + tcClob typeCode = 0x19 + tcNclob typeCode = 0x1A + tcBlob typeCode = 0x1B + tcBoolean typeCode = 0x1C + tcString typeCode = 0x1D + tcNstring typeCode = 0x1E + tcLocator typeCode = 0x1F + tcNlocator typeCode = 0x20 + tcBstring typeCode = 0x21 + tcDecimalDigitArray typeCode = 0x22 + tcVarchar2 typeCode = 0x23 + tcTable typeCode = 0x2D + tcSmalldecimal typeCode = 0x2f // inserted (not existent in hdbclient) + tcAbapstream typeCode = 0x30 + tcAbapstruct typeCode = 0x31 + tcAarray typeCode = 0x32 + tcText typeCode = 0x33 + tcShorttext typeCode = 0x34 + tcBintext typeCode = 0x35 + tcAlphanum typeCode = 0x37 + tcLongdate typeCode = 0x3D + tcSeconddate typeCode = 0x3E + tcDaydate typeCode = 0x3F + tcSecondtime typeCode = 0x40 + tcClocator typeCode = 0x46 + tcBlobDiskReserved typeCode = 0x47 + tcClobDiskReserved typeCode = 0x48 + tcNclobDiskReserved typeCode = 0x49 + tcStGeometry typeCode = 0x4A + tcStPoint typeCode = 0x4B + tcFixed16 typeCode = 0x4C + tcAbapItab typeCode = 0x4D + tcRecordRowStore typeCode = 0x4E + tcRecordColumnStore typeCode = 0x4F + tcFixed8 typeCode = 0x51 + tcFixed12 typeCode = 0x52 + tcCiphertext typeCode = 0x5A + + // special null values. + tcSecondtimeNull typeCode = 0xB0 + + // TcTableRows is the TypeCode for table rows. + TcTableRows typeCode = 0x7f // 127 +) + +// isLob returns true if the TypeCode represents a Lob, false otherwise. +func (tc typeCode) isLob() bool { + return tc == tcClob || tc == tcNclob || tc == tcBlob || tc == tcText || tc == tcBintext || tc == tcLocator || tc == tcNlocator +} + +func (tc typeCode) isVariableLength() bool { + return tc == tcChar || tc == tcNchar || tc == tcVarchar || tc == tcNvarchar || tc == tcBinary || tc == tcVarbinary || tc == tcShorttext || tc == tcAlphanum +} + +func (tc typeCode) isDecimalType() bool { + return tc == tcSmalldecimal || tc == tcDecimal || tc == tcFixed8 || tc == tcFixed12 || tc == tcFixed16 +} + +func (tc typeCode) supportNullValue() bool { + // boolean values: false =:= 0; null =:= 1; true =:= 2 + return !(tc == tcBoolean) +} + +func (tc typeCode) nullValue() typeCode { + if tc == tcSecondtime { + /* + HDB bug: secondtime null value cannot be set by setting high bit + - trying so, gives: + SQL HdbError 1033 - error while parsing protocol: no such data type: type_code=192, index=2 + + HDB version 2: Traffic analysis of python client (https://pypi.org/project/hdbcli) resulted in: + - set null value constant directly instead of using high bit + + HDB version 4: Setting null value constant does not work anymore + - secondtime null value typecode is 0xb0 (decimal: 176) instead of 0xc0 (decimal: 192) + - null typecode 0xb0 does work for HDB version 2 as well + */ + return tcSecondtimeNull + } + return tc | 0x80 // type code null value: set high bit (like documented in hdb protocol spec) +} + +// see hdbclient. +func (tc typeCode) encTc() typeCode { + switch tc { + default: + return tc + case tcText, tcBintext, tcLocator: + return tcNclob + } +} + +/* +tcBintext: +- protocol returns tcLocator for tcBintext +- see dataTypeMap and encTc +*/ + +func (tc typeCode) dataType() DataType { + // performance: use switch instead of map + switch tc { + case tcBoolean: + return DtBoolean + case tcTinyint: + return DtTinyint + case tcSmallint: + return DtSmallint + case tcInteger: + return DtInteger + case tcBigint: + return DtBigint + case tcReal: + return DtReal + case tcDouble: + return DtDouble + case tcDate: + return DtTime + case tcTime, tcTimestamp, tcLongdate, tcSeconddate, tcDaydate, tcSecondtime: + return DtTime + case tcDecimal, tcFixed8, tcFixed12, tcFixed16: + return DtDecimal + case tcChar, tcVarchar, tcString, tcAlphanum, tcNchar, tcNvarchar, tcNstring, tcShorttext, tcStPoint, tcStGeometry: + return DtString + case tcBinary, tcVarbinary, tcBstring: + return DtBytes + case tcBlob, tcClob, tcNclob, tcText, tcBintext: + return DtLob + case TcTableRows: + return DtRows + default: + panic("missing DataType for typeCode") + } +} + +// typeName returns the database type name. +// see https://golang.org/pkg/database/sql/driver/#RowsColumnTypeDatabaseTypeName +func (tc typeCode) typeName() string { + return strings.ToUpper(tc.String()[2:]) +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/x_generator.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/x_generator.go new file mode 100644 index 00000000..e9cc46f7 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/x_generator.go @@ -0,0 +1,3 @@ +package protocol + +//go:generate stringer -type=typeCode,MessageType,clientContextOption,connectOption,dbConnectInfoType,DataType,FunctionCode,PartKind,Cdm,endianess,segmentKind,statementContextType,topologyOption,ServiceType,transactionFlagType,dpv,lobTypecode -output=x_stringer.go diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/protocol/x_stringer.go b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/x_stringer.go new file mode 100644 index 00000000..66c4f7c9 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/protocol/x_stringer.go @@ -0,0 +1,735 @@ +// Code generated by "stringer -type=typeCode,MessageType,clientContextOption,connectOption,dbConnectInfoType,DataType,FunctionCode,PartKind,Cdm,endianess,segmentKind,statementContextType,topologyOption,ServiceType,transactionFlagType,dpv,lobTypecode -output=x_stringer.go"; DO NOT EDIT. + +package protocol + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[tcNull-0] + _ = x[tcTinyint-1] + _ = x[tcSmallint-2] + _ = x[tcInteger-3] + _ = x[tcBigint-4] + _ = x[tcDecimal-5] + _ = x[tcReal-6] + _ = x[tcDouble-7] + _ = x[tcChar-8] + _ = x[tcVarchar-9] + _ = x[tcNchar-10] + _ = x[tcNvarchar-11] + _ = x[tcBinary-12] + _ = x[tcVarbinary-13] + _ = x[tcDate-14] + _ = x[tcTime-15] + _ = x[tcTimestamp-16] + _ = x[tcTimetz-17] + _ = x[tcTimeltz-18] + _ = x[tcTimestampTz-19] + _ = x[tcTimestampLtz-20] + _ = x[tcIntervalYm-21] + _ = x[tcIntervalDs-22] + _ = x[tcRowid-23] + _ = x[tcUrowid-24] + _ = x[tcClob-25] + _ = x[tcNclob-26] + _ = x[tcBlob-27] + _ = x[tcBoolean-28] + _ = x[tcString-29] + _ = x[tcNstring-30] + _ = x[tcLocator-31] + _ = x[tcNlocator-32] + _ = x[tcBstring-33] + _ = x[tcDecimalDigitArray-34] + _ = x[tcVarchar2-35] + _ = x[tcTable-45] + _ = x[tcSmalldecimal-47] + _ = x[tcAbapstream-48] + _ = x[tcAbapstruct-49] + _ = x[tcAarray-50] + _ = x[tcText-51] + _ = x[tcShorttext-52] + _ = x[tcBintext-53] + _ = x[tcAlphanum-55] + _ = x[tcLongdate-61] + _ = x[tcSeconddate-62] + _ = x[tcDaydate-63] + _ = x[tcSecondtime-64] + _ = x[tcClocator-70] + _ = x[tcBlobDiskReserved-71] + _ = x[tcClobDiskReserved-72] + _ = x[tcNclobDiskReserved-73] + _ = x[tcStGeometry-74] + _ = x[tcStPoint-75] + _ = x[tcFixed16-76] + _ = x[tcAbapItab-77] + _ = x[tcRecordRowStore-78] + _ = x[tcRecordColumnStore-79] + _ = x[tcFixed8-81] + _ = x[tcFixed12-82] + _ = x[tcCiphertext-90] + _ = x[tcSecondtimeNull-176] + _ = x[TcTableRows-127] +} + +const ( + _typeCode_name_0 = "tcNulltcTinyinttcSmallinttcIntegertcBiginttcDecimaltcRealtcDoubletcChartcVarchartcNchartcNvarchartcBinarytcVarbinarytcDatetcTimetcTimestamptcTimetztcTimeltztcTimestampTztcTimestampLtztcIntervalYmtcIntervalDstcRowidtcUrowidtcClobtcNclobtcBlobtcBooleantcStringtcNstringtcLocatortcNlocatortcBstringtcDecimalDigitArraytcVarchar2" + _typeCode_name_1 = "tcTable" + _typeCode_name_2 = "tcSmalldecimaltcAbapstreamtcAbapstructtcAarraytcTexttcShorttexttcBintext" + _typeCode_name_3 = "tcAlphanum" + _typeCode_name_4 = "tcLongdatetcSeconddatetcDaydatetcSecondtime" + _typeCode_name_5 = "tcClocatortcBlobDiskReservedtcClobDiskReservedtcNclobDiskReservedtcStGeometrytcStPointtcFixed16tcAbapItabtcRecordRowStoretcRecordColumnStore" + _typeCode_name_6 = "tcFixed8tcFixed12" + _typeCode_name_7 = "tcCiphertext" + _typeCode_name_8 = "TcTableRows" + _typeCode_name_9 = "tcSecondtimeNull" +) + +var ( + _typeCode_index_0 = [...]uint16{0, 6, 15, 25, 34, 42, 51, 57, 65, 71, 80, 87, 97, 105, 116, 122, 128, 139, 147, 156, 169, 183, 195, 207, 214, 222, 228, 235, 241, 250, 258, 267, 276, 286, 295, 314, 324} + _typeCode_index_2 = [...]uint8{0, 14, 26, 38, 46, 52, 63, 72} + _typeCode_index_4 = [...]uint8{0, 10, 22, 31, 43} + _typeCode_index_5 = [...]uint8{0, 10, 28, 46, 65, 77, 86, 95, 105, 121, 140} + _typeCode_index_6 = [...]uint8{0, 8, 17} +) + +func (i typeCode) String() string { + switch { + case i <= 35: + return _typeCode_name_0[_typeCode_index_0[i]:_typeCode_index_0[i+1]] + case i == 45: + return _typeCode_name_1 + case 47 <= i && i <= 53: + i -= 47 + return _typeCode_name_2[_typeCode_index_2[i]:_typeCode_index_2[i+1]] + case i == 55: + return _typeCode_name_3 + case 61 <= i && i <= 64: + i -= 61 + return _typeCode_name_4[_typeCode_index_4[i]:_typeCode_index_4[i+1]] + case 70 <= i && i <= 79: + i -= 70 + return _typeCode_name_5[_typeCode_index_5[i]:_typeCode_index_5[i+1]] + case 81 <= i && i <= 82: + i -= 81 + return _typeCode_name_6[_typeCode_index_6[i]:_typeCode_index_6[i+1]] + case i == 90: + return _typeCode_name_7 + case i == 127: + return _typeCode_name_8 + case i == 176: + return _typeCode_name_9 + default: + return "typeCode(" + strconv.FormatInt(int64(i), 10) + ")" + } +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[mtNil-0] + _ = x[MtExecuteDirect-2] + _ = x[MtPrepare-3] + _ = x[mtAbapStream-4] + _ = x[mtXAStart-5] + _ = x[mtXAJoin-6] + _ = x[MtExecute-13] + _ = x[MtWriteLob-16] + _ = x[MtReadLob-17] + _ = x[mtFindLob-18] + _ = x[MtAuthenticate-65] + _ = x[MtConnect-66] + _ = x[MtCommit-67] + _ = x[MtRollback-68] + _ = x[MtCloseResultset-69] + _ = x[MtDropStatementID-70] + _ = x[MtFetchNext-71] + _ = x[mtFetchAbsolute-72] + _ = x[mtFetchRelative-73] + _ = x[mtFetchFirst-74] + _ = x[mtFetchLast-75] + _ = x[MtDisconnect-77] + _ = x[mtExecuteITab-78] + _ = x[mtFetchNextITab-79] + _ = x[mtInsertNextITab-80] + _ = x[mtBatchPrepare-81] + _ = x[MtDBConnectInfo-82] + _ = x[mtXopenXAStart-83] + _ = x[mtXopenXAEnd-84] + _ = x[mtXopenXAPrepare-85] + _ = x[mtXopenXACommit-86] + _ = x[mtXopenXARollback-87] + _ = x[mtXopenXARecover-88] + _ = x[mtXopenXAForget-89] +} + +const ( + _MessageType_name_0 = "mtNil" + _MessageType_name_1 = "MtExecuteDirectMtPreparemtAbapStreammtXAStartmtXAJoin" + _MessageType_name_2 = "MtExecute" + _MessageType_name_3 = "MtWriteLobMtReadLobmtFindLob" + _MessageType_name_4 = "MtAuthenticateMtConnectMtCommitMtRollbackMtCloseResultsetMtDropStatementIDMtFetchNextmtFetchAbsolutemtFetchRelativemtFetchFirstmtFetchLast" + _MessageType_name_5 = "MtDisconnectmtExecuteITabmtFetchNextITabmtInsertNextITabmtBatchPrepareMtDBConnectInfomtXopenXAStartmtXopenXAEndmtXopenXAPreparemtXopenXACommitmtXopenXARollbackmtXopenXARecovermtXopenXAForget" +) + +var ( + _MessageType_index_1 = [...]uint8{0, 15, 24, 36, 45, 53} + _MessageType_index_3 = [...]uint8{0, 10, 19, 28} + _MessageType_index_4 = [...]uint8{0, 14, 23, 31, 41, 57, 74, 85, 100, 115, 127, 138} + _MessageType_index_5 = [...]uint8{0, 12, 25, 40, 56, 70, 85, 99, 111, 127, 142, 159, 175, 190} +) + +func (i MessageType) String() string { + switch { + case i == 0: + return _MessageType_name_0 + case 2 <= i && i <= 6: + i -= 2 + return _MessageType_name_1[_MessageType_index_1[i]:_MessageType_index_1[i+1]] + case i == 13: + return _MessageType_name_2 + case 16 <= i && i <= 18: + i -= 16 + return _MessageType_name_3[_MessageType_index_3[i]:_MessageType_index_3[i+1]] + case 65 <= i && i <= 75: + i -= 65 + return _MessageType_name_4[_MessageType_index_4[i]:_MessageType_index_4[i+1]] + case 77 <= i && i <= 89: + i -= 77 + return _MessageType_name_5[_MessageType_index_5[i]:_MessageType_index_5[i+1]] + default: + return "MessageType(" + strconv.FormatInt(int64(i), 10) + ")" + } +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[ccoVersion-1] + _ = x[ccoType-2] + _ = x[ccoApplicationProgram-3] +} + +const _clientContextOption_name = "ccoVersionccoTypeccoApplicationProgram" + +var _clientContextOption_index = [...]uint8{0, 10, 17, 38} + +func (i clientContextOption) String() string { + i -= 1 + if i < 0 || i >= clientContextOption(len(_clientContextOption_index)-1) { + return "clientContextOption(" + strconv.FormatInt(int64(i+1), 10) + ")" + } + return _clientContextOption_name[_clientContextOption_index[i]:_clientContextOption_index[i+1]] +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[coConnectionID-1] + _ = x[coCompleteArrayExecution-2] + _ = x[coClientLocale-3] + _ = x[coSupportsLargeBulkOperations-4] + _ = x[coDistributionEnabled-5] + _ = x[coPrimaryConnectionID-6] + _ = x[coPrimaryConnectionHost-7] + _ = x[coPrimaryConnectionPort-8] + _ = x[coCompleteDatatypeSupport-9] + _ = x[coLargeNumberOfParametersSupport-10] + _ = x[coSystemID-11] + _ = x[coDataFormatVersion-12] + _ = x[coAbapVarcharMode-13] + _ = x[coSelectForUpdateSupported-14] + _ = x[coClientDistributionMode-15] + _ = x[coEngineDataFormatVersion-16] + _ = x[coDistributionProtocolVersion-17] + _ = x[coSplitBatchCommands-18] + _ = x[coUseTransactionFlagsOnly-19] + _ = x[coRowSlotImageParameter-20] + _ = x[coIgnoreUnknownParts-21] + _ = x[coTableOutputParameterMetadataSupport-22] + _ = x[coDataFormatVersion2-23] + _ = x[coItabParameter-24] + _ = x[coDescribeTableOutputParameter-25] + _ = x[coColumnarResultSet-26] + _ = x[coScrollableResultSet-27] + _ = x[coClientInfoNullValueSupported-28] + _ = x[coAssociatedConnectionID-29] + _ = x[coNonTransactionalPrepare-30] + _ = x[coFdaEnabled-31] + _ = x[coOSUser-32] + _ = x[coRowSlotImageResultSet-33] + _ = x[coEndianness-34] + _ = x[coUpdateTopologyAnwhere-35] + _ = x[coEnableArrayType-36] + _ = x[coImplicitLobStreaming-37] + _ = x[coCachedViewProperty-38] + _ = x[coXOpenXAProtocolSupported-39] + _ = x[coPrimaryCommitRedirectionSupported-40] + _ = x[coActiveActiveProtocolVersion-41] + _ = x[coActiveActiveConnectionOriginSite-42] + _ = x[coQueryTimeoutSupported-43] + _ = x[coFullVersionString-44] + _ = x[coDatabaseName-45] + _ = x[coBuildPlatform-46] + _ = x[coImplicitXASessionSupported-47] + _ = x[coClientSideColumnEncryptionVersion-48] + _ = x[coCompressionLevelAndFlags-49] + _ = x[coClientSideReExecutionSupported-50] + _ = x[coClientReconnectWaitTimeout-51] + _ = x[coOriginalAnchorConnectionID-52] + _ = x[coFlagSet1-53] + _ = x[coTopologyNetworkGroup-54] + _ = x[coIPAddress-55] + _ = x[coLRRPingTime-56] + _ = x[coRedirectionType-57] + _ = x[coRedirectedHost-58] + _ = x[coRedirectedPort-59] + _ = x[coEndPointHost-60] + _ = x[coEndPointPort-61] + _ = x[coEndPointList-62] +} + +const _connectOption_name = "coConnectionIDcoCompleteArrayExecutioncoClientLocalecoSupportsLargeBulkOperationscoDistributionEnabledcoPrimaryConnectionIDcoPrimaryConnectionHostcoPrimaryConnectionPortcoCompleteDatatypeSupportcoLargeNumberOfParametersSupportcoSystemIDcoDataFormatVersioncoAbapVarcharModecoSelectForUpdateSupportedcoClientDistributionModecoEngineDataFormatVersioncoDistributionProtocolVersioncoSplitBatchCommandscoUseTransactionFlagsOnlycoRowSlotImageParametercoIgnoreUnknownPartscoTableOutputParameterMetadataSupportcoDataFormatVersion2coItabParametercoDescribeTableOutputParametercoColumnarResultSetcoScrollableResultSetcoClientInfoNullValueSupportedcoAssociatedConnectionIDcoNonTransactionalPreparecoFdaEnabledcoOSUsercoRowSlotImageResultSetcoEndiannesscoUpdateTopologyAnwherecoEnableArrayTypecoImplicitLobStreamingcoCachedViewPropertycoXOpenXAProtocolSupportedcoPrimaryCommitRedirectionSupportedcoActiveActiveProtocolVersioncoActiveActiveConnectionOriginSitecoQueryTimeoutSupportedcoFullVersionStringcoDatabaseNamecoBuildPlatformcoImplicitXASessionSupportedcoClientSideColumnEncryptionVersioncoCompressionLevelAndFlagscoClientSideReExecutionSupportedcoClientReconnectWaitTimeoutcoOriginalAnchorConnectionIDcoFlagSet1coTopologyNetworkGroupcoIPAddresscoLRRPingTimecoRedirectionTypecoRedirectedHostcoRedirectedPortcoEndPointHostcoEndPointPortcoEndPointList" + +var _connectOption_index = [...]uint16{0, 14, 38, 52, 81, 102, 123, 146, 169, 194, 226, 236, 255, 272, 298, 322, 347, 376, 396, 421, 444, 464, 501, 521, 536, 566, 585, 606, 636, 660, 685, 697, 705, 728, 740, 763, 780, 802, 822, 848, 883, 912, 946, 969, 988, 1002, 1017, 1045, 1080, 1106, 1138, 1166, 1194, 1204, 1226, 1237, 1250, 1267, 1283, 1299, 1313, 1327, 1341} + +func (i connectOption) String() string { + i -= 1 + if i < 0 || i >= connectOption(len(_connectOption_index)-1) { + return "connectOption(" + strconv.FormatInt(int64(i+1), 10) + ")" + } + return _connectOption_name[_connectOption_index[i]:_connectOption_index[i+1]] +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[ciDatabaseName-1] + _ = x[ciHost-2] + _ = x[ciPort-3] + _ = x[ciIsConnected-4] +} + +const _dbConnectInfoType_name = "ciDatabaseNameciHostciPortciIsConnected" + +var _dbConnectInfoType_index = [...]uint8{0, 14, 20, 26, 39} + +func (i dbConnectInfoType) String() string { + i -= 1 + if i < 0 || i >= dbConnectInfoType(len(_dbConnectInfoType_index)-1) { + return "dbConnectInfoType(" + strconv.FormatInt(int64(i+1), 10) + ")" + } + return _dbConnectInfoType_name[_dbConnectInfoType_index[i]:_dbConnectInfoType_index[i+1]] +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[DtUnknown-0] + _ = x[DtBoolean-1] + _ = x[DtTinyint-2] + _ = x[DtSmallint-3] + _ = x[DtInteger-4] + _ = x[DtBigint-5] + _ = x[DtReal-6] + _ = x[DtDouble-7] + _ = x[DtDecimal-8] + _ = x[DtTime-9] + _ = x[DtString-10] + _ = x[DtBytes-11] + _ = x[DtLob-12] + _ = x[DtRows-13] +} + +const _DataType_name = "DtUnknownDtBooleanDtTinyintDtSmallintDtIntegerDtBigintDtRealDtDoubleDtDecimalDtTimeDtStringDtBytesDtLobDtRows" + +var _DataType_index = [...]uint8{0, 9, 18, 27, 37, 46, 54, 60, 68, 77, 83, 91, 98, 103, 109} + +func (i DataType) String() string { + if i >= DataType(len(_DataType_index)-1) { + return "DataType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _DataType_name[_DataType_index[i]:_DataType_index[i+1]] +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[fcNil-0] + _ = x[FcDDL-1] + _ = x[fcInsert-2] + _ = x[fcUpdate-3] + _ = x[fcDelete-4] + _ = x[fcSelect-5] + _ = x[fcSelectForUpdate-6] + _ = x[fcExplain-7] + _ = x[fcDBProcedureCall-8] + _ = x[fcDBProcedureCallWithResult-9] + _ = x[fcFetch-10] + _ = x[fcCommit-11] + _ = x[fcRollback-12] + _ = x[fcSavepoint-13] + _ = x[fcConnect-14] + _ = x[fcWriteLob-15] + _ = x[fcReadLob-16] + _ = x[fcPing-17] + _ = x[fcDisconnect-18] + _ = x[fcCloseCursor-19] + _ = x[fcFindLob-20] + _ = x[fcAbapStream-21] + _ = x[fcXAStart-22] + _ = x[fcXAJoin-23] +} + +const _FunctionCode_name = "fcNilFcDDLfcInsertfcUpdatefcDeletefcSelectfcSelectForUpdatefcExplainfcDBProcedureCallfcDBProcedureCallWithResultfcFetchfcCommitfcRollbackfcSavepointfcConnectfcWriteLobfcReadLobfcPingfcDisconnectfcCloseCursorfcFindLobfcAbapStreamfcXAStartfcXAJoin" + +var _FunctionCode_index = [...]uint8{0, 5, 10, 18, 26, 34, 42, 59, 68, 85, 112, 119, 127, 137, 148, 157, 167, 176, 182, 194, 207, 216, 228, 237, 245} + +func (i FunctionCode) String() string { + if i < 0 || i >= FunctionCode(len(_FunctionCode_index)-1) { + return "FunctionCode(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _FunctionCode_name[_FunctionCode_index[i]:_FunctionCode_index[i+1]] +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[pkNil-0] + _ = x[PkCommand-3] + _ = x[PkResultset-5] + _ = x[pkError-6] + _ = x[PkStatementID-10] + _ = x[pkTransactionID-11] + _ = x[pkRowsAffected-12] + _ = x[PkResultsetID-13] + _ = x[PkTopologyInformation-15] + _ = x[pkTableLocation-16] + _ = x[PkReadLobRequest-17] + _ = x[PkReadLobReply-18] + _ = x[pkAbapIStream-25] + _ = x[pkAbapOStream-26] + _ = x[pkCommandInfo-27] + _ = x[PkWriteLobRequest-28] + _ = x[PkClientContext-29] + _ = x[PkWriteLobReply-30] + _ = x[PkParameters-32] + _ = x[PkAuthentication-33] + _ = x[pkSessionContext-34] + _ = x[PkClientID-35] + _ = x[pkProfile-38] + _ = x[PkStatementContext-39] + _ = x[pkPartitionInformation-40] + _ = x[PkOutputParameters-41] + _ = x[PkConnectOptions-42] + _ = x[pkCommitOptions-43] + _ = x[pkFetchOptions-44] + _ = x[PkFetchSize-45] + _ = x[PkParameterMetadata-47] + _ = x[PkResultMetadata-48] + _ = x[pkFindLobRequest-49] + _ = x[pkFindLobReply-50] + _ = x[pkItabSHM-51] + _ = x[pkItabChunkMetadata-53] + _ = x[pkItabMetadata-55] + _ = x[pkItabResultChunk-56] + _ = x[PkClientInfo-57] + _ = x[pkStreamData-58] + _ = x[pkOStreamResult-59] + _ = x[pkFDARequestMetadata-60] + _ = x[pkFDAReplyMetadata-61] + _ = x[pkBatchPrepare-62] + _ = x[pkBatchExecute-63] + _ = x[PkTransactionFlags-64] + _ = x[pkRowSlotImageParamMetadata-65] + _ = x[pkRowSlotImageResultset-66] + _ = x[PkDBConnectInfo-67] + _ = x[pkLobFlags-68] + _ = x[pkResultsetOptions-69] + _ = x[pkXATransactionInfo-70] + _ = x[pkSessionVariable-71] + _ = x[pkWorkLoadReplayContext-72] + _ = x[pkSQLReplyOptions-73] +} + +const _PartKind_name = "pkNilPkCommandPkResultsetpkErrorPkStatementIDpkTransactionIDpkRowsAffectedPkResultsetIDPkTopologyInformationpkTableLocationPkReadLobRequestPkReadLobReplypkAbapIStreampkAbapOStreampkCommandInfoPkWriteLobRequestPkClientContextPkWriteLobReplyPkParametersPkAuthenticationpkSessionContextPkClientIDpkProfilePkStatementContextpkPartitionInformationPkOutputParametersPkConnectOptionspkCommitOptionspkFetchOptionsPkFetchSizePkParameterMetadataPkResultMetadatapkFindLobRequestpkFindLobReplypkItabSHMpkItabChunkMetadatapkItabMetadatapkItabResultChunkPkClientInfopkStreamDatapkOStreamResultpkFDARequestMetadatapkFDAReplyMetadatapkBatchPreparepkBatchExecutePkTransactionFlagspkRowSlotImageParamMetadatapkRowSlotImageResultsetPkDBConnectInfopkLobFlagspkResultsetOptionspkXATransactionInfopkSessionVariablepkWorkLoadReplayContextpkSQLReplyOptions" + +var _PartKind_map = map[PartKind]string{ + 0: _PartKind_name[0:5], + 3: _PartKind_name[5:14], + 5: _PartKind_name[14:25], + 6: _PartKind_name[25:32], + 10: _PartKind_name[32:45], + 11: _PartKind_name[45:60], + 12: _PartKind_name[60:74], + 13: _PartKind_name[74:87], + 15: _PartKind_name[87:108], + 16: _PartKind_name[108:123], + 17: _PartKind_name[123:139], + 18: _PartKind_name[139:153], + 25: _PartKind_name[153:166], + 26: _PartKind_name[166:179], + 27: _PartKind_name[179:192], + 28: _PartKind_name[192:209], + 29: _PartKind_name[209:224], + 30: _PartKind_name[224:239], + 32: _PartKind_name[239:251], + 33: _PartKind_name[251:267], + 34: _PartKind_name[267:283], + 35: _PartKind_name[283:293], + 38: _PartKind_name[293:302], + 39: _PartKind_name[302:320], + 40: _PartKind_name[320:342], + 41: _PartKind_name[342:360], + 42: _PartKind_name[360:376], + 43: _PartKind_name[376:391], + 44: _PartKind_name[391:405], + 45: _PartKind_name[405:416], + 47: _PartKind_name[416:435], + 48: _PartKind_name[435:451], + 49: _PartKind_name[451:467], + 50: _PartKind_name[467:481], + 51: _PartKind_name[481:490], + 53: _PartKind_name[490:509], + 55: _PartKind_name[509:523], + 56: _PartKind_name[523:540], + 57: _PartKind_name[540:552], + 58: _PartKind_name[552:564], + 59: _PartKind_name[564:579], + 60: _PartKind_name[579:599], + 61: _PartKind_name[599:617], + 62: _PartKind_name[617:631], + 63: _PartKind_name[631:645], + 64: _PartKind_name[645:663], + 65: _PartKind_name[663:690], + 66: _PartKind_name[690:713], + 67: _PartKind_name[713:728], + 68: _PartKind_name[728:738], + 69: _PartKind_name[738:756], + 70: _PartKind_name[756:775], + 71: _PartKind_name[775:792], + 72: _PartKind_name[792:815], + 73: _PartKind_name[815:832], +} + +func (i PartKind) String() string { + if str, ok := _PartKind_map[i]; ok { + return str + } + return "PartKind(" + strconv.FormatInt(int64(i), 10) + ")" +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[CdmOff-0] + _ = x[CdmConnection-1] + _ = x[CdmStatement-2] + _ = x[CdmConnectionStatement-3] +} + +const _Cdm_name = "CdmOffCdmConnectionCdmStatementCdmConnectionStatement" + +var _Cdm_index = [...]uint8{0, 6, 19, 31, 53} + +func (i Cdm) String() string { + if i >= Cdm(len(_Cdm_index)-1) { + return "Cdm(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Cdm_name[_Cdm_index[i]:_Cdm_index[i+1]] +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[bigEndian-0] + _ = x[littleEndian-1] +} + +const _endianess_name = "bigEndianlittleEndian" + +var _endianess_index = [...]uint8{0, 9, 21} + +func (i endianess) String() string { + if i < 0 || i >= endianess(len(_endianess_index)-1) { + return "endianess(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _endianess_name[_endianess_index[i]:_endianess_index[i+1]] +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[skInvalid-0] + _ = x[skRequest-1] + _ = x[skReply-2] + _ = x[skError-5] +} + +const ( + _segmentKind_name_0 = "skInvalidskRequestskReply" + _segmentKind_name_1 = "skError" +) + +var ( + _segmentKind_index_0 = [...]uint8{0, 9, 18, 25} +) + +func (i segmentKind) String() string { + switch { + case 0 <= i && i <= 2: + return _segmentKind_name_0[_segmentKind_index_0[i]:_segmentKind_index_0[i+1]] + case i == 5: + return _segmentKind_name_1 + default: + return "segmentKind(" + strconv.FormatInt(int64(i), 10) + ")" + } +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[scStatementSequenceInfo-1] + _ = x[scServerProcessingTime-2] + _ = x[scSchemaName-3] + _ = x[scFlagSet-4] + _ = x[scQueryTimeout-5] + _ = x[scClientReconnectionWaitTimeout-6] + _ = x[scServerCPUTime-7] + _ = x[scServerMemoryUsage-8] +} + +const _statementContextType_name = "scStatementSequenceInfoscServerProcessingTimescSchemaNamescFlagSetscQueryTimeoutscClientReconnectionWaitTimeoutscServerCPUTimescServerMemoryUsage" + +var _statementContextType_index = [...]uint8{0, 23, 45, 57, 66, 80, 111, 126, 145} + +func (i statementContextType) String() string { + i -= 1 + if i < 0 || i >= statementContextType(len(_statementContextType_index)-1) { + return "statementContextType(" + strconv.FormatInt(int64(i+1), 10) + ")" + } + return _statementContextType_name[_statementContextType_index[i]:_statementContextType_index[i+1]] +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[toHostName-1] + _ = x[toHostPortnumber-2] + _ = x[toTenantName-3] + _ = x[toLoadfactor-4] + _ = x[toVolumeID-5] + _ = x[toIsPrimary-6] + _ = x[toIsCurrentSession-7] + _ = x[toServiceType-8] + _ = x[toNetworkDomain-9] + _ = x[toIsStandby-10] + _ = x[toAllIPAddresses-11] + _ = x[toAllHostNames-12] + _ = x[toSiteType-13] +} + +const _topologyOption_name = "toHostNametoHostPortnumbertoTenantNametoLoadfactortoVolumeIDtoIsPrimarytoIsCurrentSessiontoServiceTypetoNetworkDomaintoIsStandbytoAllIPAddressestoAllHostNamestoSiteType" + +var _topologyOption_index = [...]uint8{0, 10, 26, 38, 50, 60, 71, 89, 102, 117, 128, 144, 158, 168} + +func (i topologyOption) String() string { + i -= 1 + if i < 0 || i >= topologyOption(len(_topologyOption_index)-1) { + return "topologyOption(" + strconv.FormatInt(int64(i+1), 10) + ")" + } + return _topologyOption_name[_topologyOption_index[i]:_topologyOption_index[i+1]] +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[StOther-0] + _ = x[StNameServer-1] + _ = x[StPreprocessor-2] + _ = x[StIndexServer-3] + _ = x[StStatisticsServer-4] + _ = x[StXSEngine-5] + _ = x[StReserved6-6] + _ = x[StCompileServer-7] + _ = x[StDPServer-8] + _ = x[StDIServer-9] + _ = x[StComputeServer-10] + _ = x[StScriptServer-11] +} + +const _ServiceType_name = "StOtherStNameServerStPreprocessorStIndexServerStStatisticsServerStXSEngineStReserved6StCompileServerStDPServerStDIServerStComputeServerStScriptServer" + +var _ServiceType_index = [...]uint8{0, 7, 19, 33, 46, 64, 74, 85, 100, 110, 120, 135, 149} + +func (i ServiceType) String() string { + if i < 0 || i >= ServiceType(len(_ServiceType_index)-1) { + return "ServiceType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _ServiceType_name[_ServiceType_index[i]:_ServiceType_index[i+1]] +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[tfRolledback-0] + _ = x[tfCommited-1] + _ = x[tfNewIsolationLevel-2] + _ = x[tfDDLCommitmodeChanged-3] + _ = x[tfWriteTransactionStarted-4] + _ = x[tfNowriteTransactionStarted-5] + _ = x[tfSessionClosingTransactionError-6] + _ = x[tfSessionClosingTransactionErrror-7] + _ = x[tfReadOnlyMode-8] +} + +const _transactionFlagType_name = "tfRolledbacktfCommitedtfNewIsolationLeveltfDDLCommitmodeChangedtfWriteTransactionStartedtfNowriteTransactionStartedtfSessionClosingTransactionErrortfSessionClosingTransactionErrrortfReadOnlyMode" + +var _transactionFlagType_index = [...]uint8{0, 12, 22, 41, 63, 88, 115, 147, 180, 194} + +func (i transactionFlagType) String() string { + if i < 0 || i >= transactionFlagType(len(_transactionFlagType_index)-1) { + return "transactionFlagType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _transactionFlagType_name[_transactionFlagType_index[i]:_transactionFlagType_index[i+1]] +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[dpvBaseline-0] + _ = x[dpvClientHandlesStatementSequence-1] +} + +const _dpv_name = "dpvBaselinedpvClientHandlesStatementSequence" + +var _dpv_index = [...]uint8{0, 11, 44} + +func (i dpv) String() string { + if i >= dpv(len(_dpv_index)-1) { + return "dpv(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _dpv_name[_dpv_index[i]:_dpv_index[i+1]] +} +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[ltcUndefined-0] + _ = x[ltcBlob-1] + _ = x[ltcClob-2] + _ = x[ltcNclob-3] +} + +const _lobTypecode_name = "ltcUndefinedltcBlobltcClobltcNclob" + +var _lobTypecode_index = [...]uint8{0, 12, 19, 26, 34} + +func (i lobTypecode) String() string { + if i < 0 || i >= lobTypecode(len(_lobTypecode_index)-1) { + return "lobTypecode(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _lobTypecode_name[_lobTypecode_index[i]:_lobTypecode_index[i+1]] +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/rand/alphanum/rand.go b/vendor/github.com/SAP/go-hdb/driver/internal/rand/alphanum/rand.go new file mode 100644 index 00000000..5c1cebbe --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/rand/alphanum/rand.go @@ -0,0 +1,28 @@ +// Package alphanum implements functions for randomized alphanum content. +package alphanum + +import ( + "crypto/rand" + + "github.com/SAP/go-hdb/driver/internal/unsafe" +) + +const csAlphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" // alphanumeric character set. +var numAlphanum = byte(len(csAlphanum)) // len character sets <= max(byte) + +// Read fills p with random alphanumeric characters and returns the number of read bytes. It never returns an error, and always fills b entirely. +func Read(p []byte) (n int, err error) { + // starting with go1.24 rand.Read is never returning a error. + rand.Read(p) //nolint: errcheck + for i, b := range p { + p[i] = csAlphanum[b%numAlphanum] + } + return n, nil +} + +// ReadString returns a random string of alphanumeric characters and panics if crypto random reader returns an error. +func ReadString(n int) string { + b := make([]byte, n) + Read(b) //nolint: errcheck + return unsafe.ByteSlice2String(b) +} diff --git a/vendor/github.com/SAP/go-hdb/driver/internal/unsafe/unsafe.go b/vendor/github.com/SAP/go-hdb/driver/internal/unsafe/unsafe.go new file mode 100644 index 00000000..b7da938c --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/internal/unsafe/unsafe.go @@ -0,0 +1,20 @@ +// Package unsafe provides wrapper functions for 'unsafe' type conversions. +package unsafe + +import "unsafe" + +// String2ByteSlice converts a string to a byte slice. +func String2ByteSlice(str string) []byte { + if str == "" { + return nil + } + return unsafe.Slice(unsafe.StringData(str), len(str)) +} + +// ByteSlice2String converts a byte slice to a string. +func ByteSlice2String(bs []byte) string { + if len(bs) == 0 { + return "" + } + return unsafe.String(unsafe.SliceData(bs), len(bs)) +} diff --git a/vendor/github.com/SAP/go-hdb/driver/lob.go b/vendor/github.com/SAP/go-hdb/driver/lob.go new file mode 100644 index 00000000..295b22df --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/lob.go @@ -0,0 +1,178 @@ +package driver + +import ( + "bytes" + "database/sql/driver" + "errors" + "fmt" + "io" + "strings" + + p "github.com/SAP/go-hdb/driver/internal/protocol" + "github.com/SAP/go-hdb/driver/internal/unsafe" +) + +func scanLob(src any, wr io.Writer) error { + switch src := src.(type) { + + // standard case with go-hdb connected to HANA + case p.LobScanner: + if err := src.Scan(wr); err != nil { + var dbErr Error + if errors.As(err, &dbErr) && dbErr.Code() == p.HdbErrWhileParsingProtocol { + return ErrNestedQuery + } + return err + } + return nil + + default: + return fmt.Errorf("lob: invalid scan type %T", src) + + // the following cases do support types which might be used in + // db mock scenarios + case string: + rd := strings.NewReader(src) + _, err := io.Copy(wr, rd) + return err + + case []byte: + rd := bytes.NewBuffer(src) + _, err := io.Copy(wr, rd) + return err + + case io.Reader: + _, err := io.Copy(wr, src) + return err + } +} + +// ScanLobBytes supports scanning Lob data into a byte slice. +// This enables using []byte based custom types for scanning Lobs instead of using a Lob object. +// For usage please refer to the example. +func ScanLobBytes(src any, b *[]byte) error { + if b == nil { + return fmt.Errorf("lob scan error: parameter b %T is nil", b) + } + wr := new(bytes.Buffer) // cannot pool as we use the underlaying buffer (*). + if err := scanLob(src, wr); err != nil { + return err + } + *b = wr.Bytes() // (*) use underlaying buffer. + return nil +} + +// ScanLobString supports scanning Lob data into a string. +// This enables using string based custom types for scanning Lobs instead of using a Lob object. +// For usage please refer to the example. +func ScanLobString(src any, s *string) error { + if s == nil { + return fmt.Errorf("lob scan error: parameter s %T is nil", s) + } + wr := new(bytes.Buffer) // cannot pool as we use the underlaying buffer (*). + if err := scanLob(src, wr); err != nil { + return err + } + *s = unsafe.ByteSlice2String(wr.Bytes()) // (*) use underlaying buffer. + return nil +} + +// ScanLobWriter supports scanning Lob data into a io.Writer object. +// This enables using io.Writer based custom types for scanning Lobs instead of using a Lob object. +// For usage please refer to the example. +func ScanLobWriter(src any, wr io.Writer) error { + if wr == nil { + return fmt.Errorf("lob scan error: parameter wr %T is nil", wr) + } + return scanLob(src, wr) +} + +// A Lob is the driver representation of a database large object field. +// A Lob object uses an io.Reader object as source for writing content to a database lob field. +// A Lob object uses an io.Writer object as destination for reading content from a database lob field. +// A Lob can be created by contructor method NewLob with io.Reader and io.Writer as parameters or +// created by new, setting io.Reader and io.Writer by SetReader and SetWriter methods. +type Lob struct { + rd io.Reader + wr io.Writer +} + +// NewLob creates a new Lob instance with the io.Reader and io.Writer given as parameters. +func NewLob(rd io.Reader, wr io.Writer) *Lob { + return &Lob{rd: rd, wr: wr} +} + +// Reader returns the io.Reader of the Lob. +func (l Lob) Reader() io.Reader { + return l.rd +} + +// SetReader sets the io.Reader source for a lob field to be written to database +// and return *Lob, to enable simple call chaining. +func (l *Lob) SetReader(rd io.Reader) *Lob { + l.rd = rd + return l +} + +// Writer returns the io.Writer of the Lob. +func (l Lob) Writer() io.Writer { + return l.wr +} + +// SetWriter sets the io.Writer destination for a lob field to be read from database +// and return *Lob, to enable simple call chaining. +func (l *Lob) SetWriter(wr io.Writer) *Lob { + l.wr = wr + return l +} + +// Scan implements the database/sql/Scanner interface. +func (l *Lob) Scan(src any) error { + if l.wr == nil { + l.wr = new(bytes.Buffer) + } + return ScanLobWriter(src, l.wr) +} + +// NullLob represents an Lob that may be null. +// NullLob implements the Scanner interface so +// it can be used as a scan destination, similar to NullString. +type NullLob struct { + Lob *Lob + Valid bool // Valid is true if Lob is not NULL +} + +// Scan implements the database/sql/Scanner interface. +func (n *NullLob) Scan(value any) error { + /* + In contrast to the Null[T] Scan implementation we do not + create a new lob instance in case of value == nil to + enable reuse of n.Lob. + + func (n *Null[T]) Scan(value any) error { + if value == nil { + n.V, n.Valid = *new(T), false + return nil + } + n.Valid = true + return convertAssign(&n.V, value) + } + */ + if value == nil { + n.Valid = false + return nil + } + if n.Lob == nil { + n.Lob = new(Lob) + } + n.Valid = true + return n.Lob.Scan(value) +} + +// Value implements the database/sql/Valuer interface. +func (n NullLob) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + return n.Lob, nil +} diff --git a/vendor/github.com/SAP/go-hdb/driver/metadata.go b/vendor/github.com/SAP/go-hdb/driver/metadata.go new file mode 100644 index 00000000..56d2e150 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/metadata.go @@ -0,0 +1,49 @@ +package driver + +import ( + "context" + "reflect" + + p "github.com/SAP/go-hdb/driver/internal/protocol" +) + +// ColumnType equals sql.ColumnType. +type ColumnType interface { + DatabaseTypeName() string + DecimalSize() (precision, scale int64, ok bool) + Length() (length int64, ok bool) + Name() string + Nullable() (nullable bool, ok bool) + ScanType() reflect.Type +} + +// ParameterType extends ColumnType with stored procedure metadata. +type ParameterType interface { + ColumnType + In() bool + Out() bool + InOut() bool +} + +// StmtMetadata provides access to the parameter and result metadata of a prepared statement. +type StmtMetadata interface { + ParameterTypes() []ParameterType + ColumnTypes() []ColumnType +} + +// use unexported type to avoid key collisions. +type stmtMetadataCtxKeyType struct{} + +var stmtMetadataCtxKey stmtMetadataCtxKeyType + +// WithStmtMetadata can be used to add a statement metadata reference to the context used for a Prepare call. +// The Prepare call will set the stmtMetadata reference on successful preparation. +func WithStmtMetadata(ctx context.Context, stmtMetadata *StmtMetadata) context.Context { + return context.WithValue(ctx, stmtMetadataCtxKey, stmtMetadata) +} + +var ( + _ StmtMetadata = (*prepareResult)(nil) + _ ParameterType = (*p.ParameterField)(nil) + _ ColumnType = (*p.ResultField)(nil) +) diff --git a/vendor/github.com/SAP/go-hdb/driver/metrics.go b/vendor/github.com/SAP/go-hdb/driver/metrics.go new file mode 100644 index 00000000..b8237b8f --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/metrics.go @@ -0,0 +1,234 @@ +package driver + +import ( + "slices" + "sync" + "time" + + "github.com/SAP/go-hdb/driver/wgroup" +) + +const ( + counterBytesRead = iota + counterBytesWritten + counterSessionConnects + numCounter +) + +const ( + gaugeConn = iota + gaugeTx + gaugeStmt + numGauge +) + +const ( + timeRead = iota + timeWrite + timeAuth + numTime +) + +const ( + sqlTimeQuery = iota + sqlTimePrepare + sqlTimeExec + sqlTimeCall + sqlTimeFetch + sqlTimeFetchLob + sqlTimeRollback + sqlTimeCommit + numSQLTime +) + +type histogram struct { + count uint64 + sum float64 + upperBounds []float64 + boundCounts []uint64 + underflowCount uint64 // in case of negative duration (will add to zero bucket) +} + +func newHistogram(upperBounds []float64) *histogram { + return &histogram{upperBounds: upperBounds, boundCounts: make([]uint64, len(upperBounds))} +} + +func (h *histogram) stats() *StatsHistogram { + rv := &StatsHistogram{ + Count: h.count, + Sum: h.sum, + Buckets: make(map[float64]uint64, len(h.upperBounds)), + } + for i, upperBound := range h.upperBounds { + rv.Buckets[upperBound] = h.boundCounts[i] + } + return rv +} + +func (h *histogram) add(v float64) { + h.count++ + if v < 0 { + h.underflowCount++ + v = 0 + } + h.sum += v + // determine index + idx, _ := slices.BinarySearch(h.upperBounds, v) + for i := idx; i < len(h.upperBounds); i++ { + h.boundCounts[i]++ + } +} + +type counterMsg struct { + v uint64 + idx int +} + +type gaugeMsg struct { + v int64 + idx int +} + +type timeMsg struct { + d time.Duration + idx int +} + +type sqlTimeMsg struct { + d time.Duration + idx int +} + +const numMetricCollectorCh = 100 + +type metrics struct { + mu sync.RWMutex + once sync.Once // lazy init + wg *sync.WaitGroup + msgCh chan any + closed bool + + parentMetrics *metrics + + timeUnit string + divider float64 + + counters []uint64 + gauges []int64 + times []*histogram + sqlTimes []*histogram +} + +func newMetrics(parentMetrics *metrics, timeUnit string, timeUpperBounds []float64) *metrics { + d, ok := timeUnitMap[timeUnit] + if !ok { + panic("invalid unit") + } + rv := &metrics{ + wg: new(sync.WaitGroup), + msgCh: make(chan any, numMetricCollectorCh), + parentMetrics: parentMetrics, + timeUnit: timeUnit, + divider: float64(d), + counters: make([]uint64, numCounter), + gauges: make([]int64, numGauge), + times: make([]*histogram, numTime), + sqlTimes: make([]*histogram, numSQLTime), + } + for i := range int(numTime) { + rv.times[i] = newHistogram(timeUpperBounds) + } + for i := range int(numSQLTime) { + rv.sqlTimes[i] = newHistogram(timeUpperBounds) + } + return rv +} + +/* +func (m *metrics) collect(msgCh <-chan any) { + for msg := range msgCh { + m.handleMsg(msg) + } +} +*/ + +func (m *metrics) lazyInit() { + /* + start collect go routine only if go-hdb driver is used + not to leak a go-routine in case only the package is + imported by any other package. + */ + m.once.Do(func() { + wgroup.Go(m.wg, func() { + // collect + for msg := range m.msgCh { + m.handleMsg(msg) + } + }) + }) +} + +func (m *metrics) close() { + m.mu.Lock() + if m.closed { // make close idempotent + m.mu.Unlock() + return + } + m.closed = true + m.mu.Unlock() + + close(m.msgCh) + m.wg.Wait() +} + +func (m *metrics) stats() *Stats { + m.mu.RLock() + defer m.mu.RUnlock() + + sqlTimes := make(map[string]*StatsHistogram, len(m.sqlTimes)) + for i, sqlTime := range m.sqlTimes { + sqlTimes[statsCfg.SQLTimeTexts[i]] = sqlTime.stats() + } + return &Stats{ + OpenConnections: int(m.gauges[gaugeConn]), + OpenTransactions: int(m.gauges[gaugeTx]), + OpenStatements: int(m.gauges[gaugeStmt]), + ReadBytes: m.counters[counterBytesRead], + WrittenBytes: m.counters[counterBytesWritten], + SessionConnects: m.counters[counterSessionConnects], + TimeUnit: m.timeUnit, + ReadTime: m.times[timeRead].stats(), + WriteTime: m.times[timeWrite].stats(), + AuthTime: m.times[timeAuth].stats(), + SQLTimes: sqlTimes, + } +} + +func (m *metrics) handleMsg(msg any) { + m.mu.Lock() + switch msg := msg.(type) { + case counterMsg: + m.counters[msg.idx] += msg.v + case gaugeMsg: + m.gauges[msg.idx] += msg.v + case timeMsg: + m.times[msg.idx].add(float64(msg.d.Nanoseconds()) / m.divider) + case sqlTimeMsg: + m.sqlTimes[msg.idx].add(float64(msg.d.Nanoseconds()) / m.divider) + default: + panic("invalid metric message type") + } + m.mu.Unlock() + + if m.parentMetrics != nil { + m.parentMetrics.handleMsg(msg) + } +} + +func metricsAddTimeValue(metrics *metrics, start time.Time, k int) { + metrics.msgCh <- timeMsg{idx: k, d: time.Since(start)} +} + +func metricsAddSQLTimeValue(metrics *metrics, start time.Time, k int) { + metrics.msgCh <- sqlTimeMsg{idx: k, d: time.Since(start)} +} diff --git a/vendor/github.com/SAP/go-hdb/driver/result.go b/vendor/github.com/SAP/go-hdb/driver/result.go new file mode 100644 index 00000000..c496aee9 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/result.go @@ -0,0 +1,234 @@ +package driver + +import ( + "context" + "database/sql/driver" + "errors" + "io" + "reflect" + + p "github.com/SAP/go-hdb/driver/internal/protocol" +) + +// check if rows types do implement all driver row interfaces. +var ( + // queryResult. + _ driver.Rows = (*queryResult)(nil) + _ driver.RowsColumnTypeDatabaseTypeName = (*queryResult)(nil) + _ driver.RowsColumnTypeLength = (*queryResult)(nil) + _ driver.RowsColumnTypeNullable = (*queryResult)(nil) + _ driver.RowsColumnTypePrecisionScale = (*queryResult)(nil) + _ driver.RowsColumnTypeScanType = (*queryResult)(nil) + // currently not used + // could be implemented as pointer to next queryResult (advancing by copying data from next) + // _ driver.RowsNextResultSet = (*queryResult)(nil) + + // noResultType. + _ driver.Rows = (*noResultType)(nil) + // callResult. + _ driver.Rows = (*callResult)(nil) +) + +type prepareResult struct { + fc p.FunctionCode + stmtID uint64 + parameterFields []*p.ParameterField + resultFields []*p.ResultField +} + +// ParameterTypes implements the PrepareMetadata interface. +func (pr *prepareResult) ParameterTypes() []ParameterType { + parameterTypes := make([]ParameterType, len(pr.parameterFields)) + for i, f := range pr.parameterFields { + parameterTypes[i] = f + } + return parameterTypes +} + +func (pr *prepareResult) columnTypes() []ColumnType { + columnTypes := make([]ColumnType, len(pr.resultFields)) + for i, f := range pr.resultFields { + columnTypes[i] = f + } + return columnTypes +} + +func (pr *prepareResult) procedureCallColumnTypes() []ColumnType { + var columnTypes []ColumnType + for _, f := range pr.parameterFields { + if f.InOut() || f.Out() { + columnTypes = append(columnTypes, f) + } + } + return columnTypes +} + +// ColumnTypes implements the PrepareMetadata interface. +func (pr *prepareResult) ColumnTypes() []ColumnType { + if pr.isProcedureCall() { + return pr.procedureCallColumnTypes() + } + return pr.columnTypes() +} + +// isProcedureCall returns true if the statement is a call statement. +func (pr *prepareResult) isProcedureCall() bool { return pr.fc.IsProcedureCall() } + +// numField returns the number of parameter fields in a database statement. +func (pr *prepareResult) numField() int { return len(pr.parameterFields) } + +// NoResult is the driver.Rows drop-in replacement if driver Query or QueryRow is used for statements that do not return rows. +var noResult = new(noResultType) + +var noColumns = []string{} + +type noResultType struct{} + +func (r *noResultType) Columns() []string { return noColumns } +func (r *noResultType) Close() error { return nil } +func (r *noResultType) Next(dest []driver.Value) error { return io.EOF } + +// queryResult represents the resultset of a query. +type queryResult struct { + // field alignment + fields []*p.ResultField + fieldValues []driver.Value + decodeErrors p.DecodeErrors + _columns []string + lastErr error + session *session + rsID uint64 + pos int + attrs p.PartAttributes + closed bool +} + +// ErrScanOnClosedResultset is the error raised in case a scan is executed on a closed resultset. +var ErrScanOnClosedResultset = errors.New("scan on closed resultset") + +// Columns implements the driver.Rows interface. +func (qr *queryResult) Columns() []string { + if qr._columns != nil { + return qr._columns + } + qr._columns = make([]string, len(qr.fields)) + for i, f := range qr.fields { + qr._columns[i] = f.Name() + } + return qr._columns +} + +// Close implements the driver.Rows interface. +func (qr *queryResult) Close() error { + qr.closed = true + if qr.attrs.ResultsetClosed() { + return nil + } + // if lastError is set, attrs are nil + if qr.lastErr != nil { + return qr.lastErr + } + return qr.session.closeResultsetID(context.Background(), qr.rsID) +} + +func (qr *queryResult) numRow() int { + if len(qr.fieldValues) == 0 { + return 0 + } + return len(qr.fieldValues) / len(qr.fields) +} + +// Next implements the driver.Rows interface. +func (qr *queryResult) Next(dest []driver.Value) error { + if qr.pos >= qr.numRow() { + if qr.attrs.LastPacket() { + return io.EOF + } + if err := qr.session.fetchNext(context.Background(), qr); err != nil { + qr.lastErr = err // fieldValues and attrs are nil + return err + } + if qr.numRow() == 0 { + return io.EOF + } + qr.pos = 0 + } + + // copy row. + cols := len(qr.fields) + copy(dest, qr.fieldValues[qr.pos*cols:(qr.pos+1)*cols]) + err := qr.decodeErrors.RowErrors(qr.pos) + qr.pos++ + return err +} + +// ColumnTypeDatabaseTypeName implements the driver.RowsColumnTypeDatabaseTypeName interface. +func (qr *queryResult) ColumnTypeDatabaseTypeName(idx int) string { + return qr.fields[idx].DatabaseTypeName() +} + +// ColumnTypeLength implements the driver.RowsColumnTypeLength interface. +func (qr *queryResult) ColumnTypeLength(idx int) (int64, bool) { return qr.fields[idx].Length() } + +// ColumnTypeNullable implements the driver.RowsColumnTypeNullable interface. +func (qr *queryResult) ColumnTypeNullable(idx int) (bool, bool) { return qr.fields[idx].Nullable() } + +// ColumnTypePrecisionScale implements the driver.RowsColumnTypePrecisionScale interface. +func (qr *queryResult) ColumnTypePrecisionScale(idx int) (int64, int64, bool) { + return qr.fields[idx].DecimalSize() +} + +// ColumnTypeScanType implements the driver.RowsColumnTypeScanType interface. +func (qr *queryResult) ColumnTypeScanType(idx int) reflect.Type { return qr.fields[idx].ScanType() } + +// ReadLob used by protocol LobReader. +func (qr *queryResult) ReadLob(request *p.ReadLobRequest, reply *p.ReadLobReply) error { + if qr.closed { + return ErrScanOnClosedResultset + } + return qr.session.readLob(context.Background(), request, reply) +} + +type callResult struct { // call output parameters + session *session + outFields []*p.ParameterField + fieldValues []driver.Value + decodeErrors p.DecodeErrors + _columns []string + eof bool + closed bool +} + +// Columns implements the driver.Rows interface. +func (cr *callResult) Columns() []string { + if cr._columns != nil { + return cr._columns + } + cr._columns = make([]string, len(cr.outFields)) + for i, f := range cr.outFields { + cr._columns[i] = f.Name() + } + return cr._columns +} + +// Next implements the driver.Rows interface. +func (cr *callResult) Next(dest []driver.Value) error { + if len(cr.fieldValues) == 0 || cr.eof { + return io.EOF + } + + cr.eof = true + copy(dest, cr.fieldValues) + return cr.decodeErrors.RowErrors(0) +} + +// Close implements the driver.Rows interface. +func (cr *callResult) Close() error { cr.closed = true; return nil } + +// ReadLob used by protocol LobReader. +func (cr *callResult) ReadLob(request *p.ReadLobRequest, reply *p.ReadLobReply) error { + if cr.closed { + return ErrScanOnClosedResultset + } + return cr.session.readLob(context.Background(), request, reply) +} diff --git a/vendor/github.com/SAP/go-hdb/driver/scanner.go b/vendor/github.com/SAP/go-hdb/driver/scanner.go new file mode 100644 index 00000000..494baeed --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/scanner.go @@ -0,0 +1,301 @@ +package driver + +import ( + "database/sql" + "fmt" + "reflect" + "strings" + "time" + + "github.com/SAP/go-hdb/driver/internal/unsafe" +) + +const sqlTagKey = "sql" + +// parseSQLTag return name, type and options. +func parseSQLTag(tag string) (string, string, sqlTagOptions) { + name, rest, _ := strings.Cut(tag, ",") + typ, opts, _ := strings.Cut(rest, ",") + return name, typ, sqlTagOptions(opts) +} + +type sqlTagOptions string + +func (o sqlTagOptions) Contains(optionName string) bool { + if len(o) == 0 { + return false + } + s := string(o) + for s != "" { + var name string + name, s, _ = strings.Cut(s, ",") + if name == optionName { + return true + } + } + return false +} + +// Tagger is an interface used to tag structure fields dynamically. +type Tagger interface { + Tag(fieldName string) (value string, ok bool) +} + +type structColumn struct { + fieldName string + fieldType reflect.Type + fieldIndex []int + + sqlName string + sqlType string + sqlOptions sqlTagOptions +} + +func newStructColumn(name string, typ reflect.Type, index []int, tag reflect.StructTag) (*structColumn, bool) { + c := &structColumn{fieldName: name, fieldType: typ, fieldIndex: index} + if sqlTag, ok := tag.Lookup(sqlTagKey); ok { + if sqlTag == "-" { // ignore field + return nil, false + } + c.sqlName, c.sqlType, c.sqlOptions = parseSQLTag(sqlTag) + } + return c, true +} + +func (c *structColumn) Name() string { + if c.sqlName != "" { + return c.sqlName + } + return c.fieldName +} + +func (c *structColumn) Type() (string, error) { + if c.sqlType == "" { + var err error + if c.sqlType, err = inferSQLDatatype(c.fieldType); err != nil { + return "", err + } + } + return c.sqlType, nil +} + +func (c *structColumn) def() (string, error) { + typ, err := c.Type() + if err != nil { + return "", err + } + s := Identifier(c.Name()).String() + " " + typ + if c.sqlOptions.Contains("not null") { + s += " not null" + } + return s, nil +} + +type structColumns []*structColumn + +func (c structColumns) defs() (string, error) { + if len(c) == 0 { + return "", nil + } + buf := []byte{'('} + definition, err := c[0].def() + if err != nil { + return "", err + } + buf = append(buf, definition...) + for i := 1; i < len(c); i++ { + definition, err := c[i].def() + if err != nil { + return "", err + } + buf = append(buf, ',') + buf = append(buf, definition...) + } + buf = append(buf, ')') + return unsafe.ByteSlice2String(buf), nil +} + +func (c structColumns) queryPlaceholders() string { + if len(c) == 0 { + return "" + } + buf := []byte{'(', '?'} + for i := 1; i < len(c); i++ { + buf = append(buf, ",?"...) + } + buf = append(buf, ')') + return unsafe.ByteSlice2String(buf) +} + +// StructScanner is a database scanner to scan rows into a struct of type S. +// This enables using structs as scan targets for the exported fields of the struct. +// For usage please refer to the example. +type StructScanner[S any] struct { + columns structColumns + nameColumnMap map[string]*structColumn +} + +// NewStructScanner returns a new struct scanner. +func NewStructScanner[S any]() (*StructScanner[S], error) { + var s *S + + rt := reflect.TypeOf(s).Elem() + if rt.Kind() != reflect.Struct { + return nil, fmt.Errorf("invalid type %s", rt.Kind()) + } + + tagger, hasTagger := any(s).(Tagger) + + columns := []*structColumn{} + nameColumnMap := map[string]*structColumn{} + + for _, field := range reflect.VisibleFields(rt) { + if field.IsExported() { + fieldTag := field.Tag + if hasTagger { + if tag, ok := tagger.Tag(field.Name); ok { + fieldTag = reflect.StructTag(tag) + } + } + column, ok := newStructColumn(field.Name, field.Type, field.Index, fieldTag) + if !ok { + continue + } + name := column.Name() + if _, ok := nameColumnMap[name]; ok { + return nil, fmt.Errorf("duplicate column name %s", name) + } + columns = append(columns, column) + nameColumnMap[name] = column + } + } + return &StructScanner[S]{columns: columns, nameColumnMap: nameColumnMap}, nil +} + +// ScanRow scans the field values of the first row in rows into struct s of type *S and closes rows. +func (sc StructScanner[S]) ScanRow(rows *sql.Rows, s *S) error { + if rows.Err() != nil { + return rows.Err() + } + defer rows.Close() + if !rows.Next() { + if err := rows.Err(); err != nil { + return err + } + return sql.ErrNoRows + } + err := sc.Scan(rows, s) + if err != nil { + return err + } + return rows.Close() +} + +// Scan scans row field values into struct s of type *S. +func (sc StructScanner[S]) Scan(rows *sql.Rows, s *S) error { + columns, err := rows.Columns() + if err != nil { + return err + } + rv := reflect.ValueOf(s).Elem() + values := make([]any, len(columns)) + for i, name := range columns { + column, ok := sc.nameColumnMap[name] + if !ok { + return fmt.Errorf("field for column name %s not found", name) + } + values[i] = rv.FieldByIndex(column.fieldIndex).Addr().Interface() + } + return rows.Scan(values...) +} + +// columnDefs returns the column definitions for a sql create statement. +// experimental: before 'export' completion of inferSQLType is needed +func (sc StructScanner[S]) columnDefs() (string, error) { return sc.columns.defs() } + +func (sc StructScanner[S]) queryPlaceholders() string { return sc.columns.queryPlaceholders() } + +var kindSQLDatatypes = map[reflect.Kind]string{ + reflect.Bool: "boolean", + reflect.Int: "bigint", + reflect.Int8: "smallint", + reflect.Int16: "smallint", + reflect.Int32: "integer", + reflect.Int64: "bigint", + reflect.Uint: "bigint", + reflect.Uint8: "tinyint", + reflect.Uint16: "smallint", + reflect.Uint32: "integer", + reflect.Uint64: "bigint", + reflect.Float32: "real", + reflect.Float64: "double", + reflect.String: "nvarchar(256)", +} + +var ( + decimalType = reflect.TypeFor[Decimal]() + lobType = reflect.TypeFor[Lob]() + timeType = reflect.TypeFor[time.Time]() + bytesType = reflect.TypeFor[[]byte]() + nullBoolType = reflect.TypeFor[sql.NullBool]() + nullByteType = reflect.TypeFor[sql.NullByte]() + nullFloat64Type = reflect.TypeFor[sql.NullFloat64]() + nullInt16Type = reflect.TypeFor[sql.NullInt16]() + nullInt32Type = reflect.TypeFor[sql.NullInt32]() + nullInt64Type = reflect.TypeFor[sql.NullInt64]() + nullStringType = reflect.TypeFor[sql.NullString]() + nullTimeType = reflect.TypeFor[sql.NullTime]() + nullBytesType = reflect.TypeFor[NullBytes]() + nullDecimalType = reflect.TypeFor[NullDecimal]() + nullLobType = reflect.TypeFor[NullLob]() +) + +var typeSQLDatatypes = map[reflect.Type]string{ + decimalType: "decimal", + lobType: "blob", + timeType: "timestamp", + bytesType: "varchar(256)", + nullBoolType: "boolean", + nullByteType: "varchar", + nullFloat64Type: "double", + nullInt16Type: "smallint", + nullInt32Type: "integer", + nullInt64Type: "bigint", + nullStringType: "nvarchar(256)", + nullTimeType: "timestamp", + nullBytesType: "varchar(256)", + nullDecimalType: "decimal", + nullLobType: "blob", +} + +// inferSQLDatatype tries to infer the hdb sql datatype. +func inferSQLDatatype(typ reflect.Type) (string, error) { + kind := typ.Kind() + + if kind == reflect.Pointer { + return inferSQLDatatype(typ.Elem()) + } + + // dedicated datatypes. + for ctyp, sqlType := range typeSQLDatatypes { + if typ.ConvertibleTo(ctyp) { + return sqlType, nil + } + } + + // generic Null[T]. + if kind == reflect.Struct { + // see https://github.com/golang/go/issues/54393 + if strings.HasPrefix(typ.String(), "sql.Null[") { + if f, ok := typ.FieldByName("V"); ok { + return inferSQLDatatype(f.Type) + } + } + } + + // basic datatypes. + if sqlType, ok := kindSQLDatatypes[kind]; ok { + return sqlType, nil + } + return "", fmt.Errorf("could not infer sql type kind %s for %s", kind, typ) +} diff --git a/vendor/github.com/SAP/go-hdb/driver/session.go b/vendor/github.com/SAP/go-hdb/driver/session.go new file mode 100644 index 00000000..7e13dce5 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/session.go @@ -0,0 +1,794 @@ +package driver + +import ( + "bufio" + "context" + "database/sql/driver" + "errors" + "fmt" + "io" + "log/slog" + "sync/atomic" + "time" + + p "github.com/SAP/go-hdb/driver/internal/protocol" + "github.com/SAP/go-hdb/driver/internal/protocol/encoding" +) + +// SessionUser provides the fields for a hdb 'connect' (switch user) statement. +type SessionUser struct { + Username, Password string + Schema string +} + +func (u *SessionUser) equal(cmp *SessionUser) bool { + if cmp == nil { + return false + } + return u.Username == cmp.Username && u.Password == cmp.Password +} + +func (u *SessionUser) clone() *SessionUser { + return &SessionUser{Username: u.Username, Password: u.Password} +} + +// use unexported type to avoid key collisions. +type switchUserCtxKeyType struct{} + +var switchUserCtxKey switchUserCtxKeyType + +// WithUserSwitch can be used to switch a user on a new or an existing connection +// (see https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-sql-reference-guide/connect-statement-session-management). +func WithUserSwitch(ctx context.Context, u *SessionUser) context.Context { + return context.WithValue(ctx, switchUserCtxKey, u) +} + +type session struct { + dbConn dbConn + metrics *metrics + attrs *connAttrs + + prd *p.Reader + pwr *p.Writer + + hdbVersion *Version + databaseName string + + user *SessionUser // session user + + // atomic as data race got reported on closeTx + exec in parallel + inTx atomic.Bool + + sqlTracer *sqlTracer + + /* + bad connection flag (can be set by 'done' and 'write' concurrently). + we cannot work with nested errors containing driver.ErrBadConn + as go sql retries these statements. + */ + cancelled bool +} + +func newSession(ctx context.Context, host string, logger *slog.Logger, metrics *metrics, attrs *connAttrs, authHnd *p.AuthHnd) (*session, error) { + dbConn, err := newDBConn(ctx, logger, host, metrics, attrs) + if err != nil { + return nil, err + } + + rd := bufio.NewReaderSize(dbConn, attrs.bufferSize) + wr := bufio.NewWriterSize(dbConn, attrs.bufferSize) + + dec := encoding.NewDecoder(rd, attrs.cesu8Decoder, attrs.emptyDateAsNull) + enc := encoding.NewEncoder(wr, attrs.cesu8Encoder) + + protTrace := protTrace.Load() + + prd := p.NewDBReader(dec, attrs.cesu8Decoder, protTrace, logger, attrs.lobChunkSize) + pwr := p.NewWriter(wr, enc, protTrace, logger, attrs.sessionVariables) + + // prolog + if err := pwr.WriteProlog(ctx); err != nil { + dbConn.Close() + return nil, err + } + if err := prd.ReadProlog(ctx); err != nil { + dbConn.Close() + return nil, err + } + + var sqlTracer *sqlTracer + if sqlTrace.Load() { + sqlTracer = newSQLTracer(logger, 0) + } + s := &session{dbConn: dbConn, metrics: metrics, attrs: attrs, prd: prd, pwr: pwr, sqlTracer: sqlTracer} + + if authHnd != nil { // authenticate + serverOptions, err := s.authenticate(ctx, authHnd, attrs) + if err != nil { + dbConn.Close() + return nil, err + } + s.hdbVersion = parseVersion(serverOptions.FullVersionOrZero()) + s.databaseName = serverOptions.DatabaseNameOrZero() + + dec.SetAlphanumDfv1(serverOptions.DataFormatVersion2OrZero() == p.DfvLevel1) + + if err := s.setSchema(ctx); err != nil { + dbConn.Close() + return nil, err + } + } + return s, nil +} + +// we cannot work with nested errors containing driver.ErrBadConn +// as go sql retries these statements. +func (s *session) isBad() bool { return s.cancelled || s.pwr.HasError() } +func (s *session) cancel() { s.cancelled = true } + +func (s *session) close() error { + // do not disconnect if isBad. + var disconnectErr error + if !s.isBad() { + disconnectErr = s.disconnect(context.Background()) + } + closeErr := s.dbConn.Close() + return errors.Join(disconnectErr, closeErr) +} + +func (s *session) authenticate(ctx context.Context, authHnd *p.AuthHnd, attrs *connAttrs) (*p.ConnectOptions, error) { + defer metricsAddTimeValue(s.metrics, time.Now(), timeAuth) + + // client context + clientContext := &p.ClientContext{} + clientContext.SetVersion(DriverVersion) + clientContext.SetType(clientType) + clientContext.SetApplicationProgram(attrs.applicationName) + + initRequest, err := authHnd.InitRequest() + if err != nil { + return nil, err + } + if err := s.pwr.Write(ctx, p.MtAuthenticate, false, clientContext, initRequest); err != nil { + return nil, err + } + + initReply, err := authHnd.InitReply() + if err != nil { + return nil, err + } + if _, err := s.prd.IterateParts(ctx, 0, func(kind p.PartKind, attrs p.PartAttributes) error { + if kind == p.PkAuthentication { + return s.prd.ReadPart(ctx, initReply, nil) + } + return p.ErrSkipped + }); err != nil { + return nil, err + } + + finalRequest, err := authHnd.FinalRequest() + if err != nil { + return nil, err + } + + co := &p.ConnectOptions{} + co.SetDataFormatVersion2(attrs.dfv) + co.SetClientDistributionMode(p.CdmOff) + // co.SetClientDistributionMode(p.CdmConnectionStatement) + // co.SetSelectForUpdateSupported(true) // doesn't seem to make a difference + /* + p.CoSplitBatchCommands: true, + p.CoCompleteArrayExecution: true, + */ + + if attrs.locale != "" { + co.SetClientLocale(attrs.locale) + } + + if err := s.pwr.Write(ctx, p.MtConnect, false, finalRequest, p.ClientID(clientID), co); err != nil { + return nil, err + } + + finalReply, err := authHnd.FinalReply() + if err != nil { + return nil, err + } + + ti := new(p.TopologyInformation) + + if _, err := s.prd.IterateParts(ctx, 0, func(kind p.PartKind, attrs p.PartAttributes) error { + switch kind { + case p.PkAuthentication: + return s.prd.ReadPart(ctx, finalReply, nil) + case p.PkConnectOptions: + return s.prd.ReadPart(ctx, co, nil) + case p.PkTopologyInformation: + return s.prd.ReadPart(ctx, ti, nil) + default: + return p.ErrSkipped + } + }); err != nil { + return nil, err + } + + sessionID := s.prd.SessionID() + if sessionID <= 0 { + return nil, fmt.Errorf("invalid session id %d", sessionID) + } + s.pwr.SetSessionID(sessionID) + // log.Printf("co: %s", co) + // log.Printf("ti: %s", ti) + return co, nil +} + +func (s *session) setSchema(ctx context.Context) error { + switch { + case s.user != nil && s.user.Schema != "": + _, err := s.execDirect(ctx, "set schema "+Identifier(s.user.Schema).String()) + return err + case s.attrs.defaultSchema != "": + _, err := s.execDirect(ctx, "set schema "+Identifier(s.attrs.defaultSchema).String()) + return err + default: + return nil + } +} + +// ErrSwitchUser is the error raised if a switch user is requested in a not allowed context. +var ErrSwitchUser = errors.New("switch user inside transaction or in statement scope (prepared query) is not allowed") + +func (s *session) switchUser(ctx context.Context) error { + user, ok := ctx.Value(switchUserCtxKey).(*SessionUser) + if !ok || user.equal(s.user) { + return nil + } + if s.inTx.Load() { + return ErrSwitchUser + } + s.user = user.clone() + if _, err := s.execDirect(ctx, "connect "+user.Username+" password "+user.Password); err != nil { + return err + } + s.metrics.msgCh <- counterMsg{idx: counterSessionConnects, v: uint64(1)} + return s.setSchema(ctx) +} + +func (s *session) preventSwitchUser(ctx context.Context) error { + user, ok := ctx.Value(switchUserCtxKey).(*SessionUser) + if !ok || user.equal(s.user) { + return nil + } + return ErrSwitchUser +} + +func (s *session) dbConnectInfo(ctx context.Context, databaseName string) (*DBConnectInfo, error) { + ci := &p.DBConnectInfo{} + ci.SetDatabaseName(databaseName) + if err := s.pwr.Write(ctx, p.MtDBConnectInfo, false, ci); err != nil { + return nil, err + } + + if _, err := s.prd.IterateParts(ctx, 0, func(kind p.PartKind, attrs p.PartAttributes) error { + if kind == p.PkDBConnectInfo { + return s.prd.ReadPart(ctx, ci, nil) + } + return p.ErrSkipped + }); err != nil { + return nil, err + } + + return &DBConnectInfo{ + DatabaseName: databaseName, + Host: ci.HostOrZero(), + Port: ci.PortOrZero(), + IsConnected: ci.IsConnectedOrZero(), + }, nil +} + +func (s *session) queryDirect(ctx context.Context, query string, traceKind string) (driver.Rows, error) { + t := time.Now() + defer metricsAddSQLTimeValue(s.metrics, time.Now(), sqlTimeQuery) + + // allow e.g inserts as query -> handle commit like in _execDirect + if err := s.pwr.Write(ctx, p.MtExecuteDirect, !s.inTx.Load(), p.Command(query)); err != nil { + return nil, err + } + + qr := &queryResult{session: s} + meta := &p.ResultMetadata{} + resSet := &p.Resultset{} + + if _, err := s.prd.IterateParts(ctx, 0, func(kind p.PartKind, attrs p.PartAttributes) error { + switch kind { + case p.PkResultMetadata: + if err := s.prd.ReadPart(ctx, meta, nil); err != nil { + return err + } + qr.fields = meta.ResultFields + return nil + case p.PkResultsetID: + return s.prd.ReadPart(ctx, (*p.ResultsetID)(&qr.rsID), nil) + case p.PkResultset: + resSet.ResultFields = qr.fields + if err := s.prd.ReadPart(ctx, resSet, qr); err != nil { + return err + } + qr.fieldValues = resSet.FieldValues + qr.decodeErrors = resSet.DecodeErrors + qr.attrs = attrs + return nil + default: + return p.ErrSkipped + } + }); err != nil { + return nil, err + } + if s.sqlTracer != nil { + s.sqlTracer.log(ctx, t, traceKind, query) + } + if qr.rsID == 0 { // non select query + return noResult, nil + } + return qr, nil +} + +func (s *session) execDirect(ctx context.Context, query string) (driver.Result, error) { + t := time.Now() + defer metricsAddSQLTimeValue(s.metrics, time.Now(), sqlTimeExec) + + if err := s.pwr.Write(ctx, p.MtExecuteDirect, !s.inTx.Load(), p.Command(query)); err != nil { + return nil, err + } + + numRow, err := s.prd.IterateParts(ctx, 0, nil) + if err != nil { + return nil, err + } + if s.sqlTracer != nil { + s.sqlTracer.log(ctx, t, traceExec, query) + } + if s.prd.FunctionCode() == p.FcDDL { + return driver.ResultNoRows, nil + } + return driver.RowsAffected(numRow), nil +} + +func (s *session) prepare(ctx context.Context, query string) (*prepareResult, error) { + t := time.Now() + defer metricsAddSQLTimeValue(s.metrics, time.Now(), sqlTimePrepare) + + if err := s.pwr.Write(ctx, p.MtPrepare, false, p.Command(query)); err != nil { + return nil, err + } + + pr := &prepareResult{} + resMeta := &p.ResultMetadata{} + prmMeta := &p.ParameterMetadata{} + + if _, err := s.prd.IterateParts(ctx, 0, func(kind p.PartKind, attrs p.PartAttributes) error { + switch kind { + case p.PkStatementID: + return s.prd.ReadPart(ctx, (*p.StatementID)(&pr.stmtID), nil) + case p.PkResultMetadata: + if err := s.prd.ReadPart(ctx, resMeta, nil); err != nil { + return err + } + pr.resultFields = resMeta.ResultFields + return nil + case p.PkParameterMetadata: + if err := s.prd.ReadPart(ctx, prmMeta, nil); err != nil { + return err + } + pr.parameterFields = prmMeta.ParameterFields + return nil + default: + return p.ErrSkipped + } + }); err != nil { + return nil, err + } + pr.fc = s.prd.FunctionCode() + if s.sqlTracer != nil { + s.sqlTracer.log(ctx, t, tracePrepare, query) + } + return pr, nil +} + +func (s *session) query(ctx context.Context, query string, pr *prepareResult, nvargs []driver.NamedValue) (driver.Rows, error) { + t := time.Now() + defer metricsAddSQLTimeValue(s.metrics, time.Now(), sqlTimeQuery) + + // allow e.g inserts as query -> handle commit like in exec + + if err := convertQueryArgs(pr.parameterFields, nvargs, s.attrs.cesu8Encoder, s.attrs.lobChunkSize); err != nil { + return nil, err + } + inputParameters, err := p.NewInputParameters(pr.parameterFields, nvargs) + if err != nil { + return nil, err + } + if err := s.pwr.Write(ctx, p.MtExecute, !s.inTx.Load(), p.StatementID(pr.stmtID), inputParameters); err != nil { + return nil, err + } + + qr := &queryResult{session: s, fields: pr.resultFields} + resSet := &p.Resultset{} + + if _, err := s.prd.IterateParts(ctx, 0, func(kind p.PartKind, attrs p.PartAttributes) error { + switch kind { + case p.PkResultsetID: + return s.prd.ReadPart(ctx, (*p.ResultsetID)(&qr.rsID), nil) + case p.PkResultset: + resSet.ResultFields = qr.fields + if err := s.prd.ReadPart(ctx, resSet, qr); err != nil { + return err + } + qr.fieldValues = resSet.FieldValues + qr.decodeErrors = resSet.DecodeErrors + qr.attrs = attrs + return nil + default: + return p.ErrSkipped + } + }); err != nil { + return nil, err + } + if s.sqlTracer != nil { + s.sqlTracer.log(ctx, t, traceQuery, query, nvargs...) + } + if qr.rsID == 0 { // non select query + return noResult, nil + } + return qr, nil +} + +func (s *session) exec(ctx context.Context, query string, pr *prepareResult, nvargs []driver.NamedValue, offset int) (driver.Result, error) { + t := time.Now() + defer metricsAddSQLTimeValue(s.metrics, time.Now(), sqlTimeExec) + + inputParameters, err := p.NewInputParameters(pr.parameterFields, nvargs) + if err != nil { + return nil, err + } + if err := s.pwr.Write(ctx, p.MtExecute, !s.inTx.Load(), p.StatementID(pr.stmtID), inputParameters); err != nil { + return nil, err + } + + var ids []p.LocatorID + lobReply := &p.WriteLobReply{} + + numRow, err := s.prd.IterateParts(ctx, offset, func(kind p.PartKind, attrs p.PartAttributes) error { + switch kind { + case p.PkWriteLobReply: + if err := s.prd.ReadPart(ctx, lobReply, nil); err != nil { + return err + } + ids = lobReply.IDs + return nil + default: + return p.ErrSkipped + } + }) + if err != nil { + return nil, err + } + fc := s.prd.FunctionCode() + + if len(ids) != 0 { + /* + writeLobParameters: + - chunkReaders + - nil (no callResult, exec does not have output parameters) + */ + + /* + write lob data only for the last record as lob streaming is only available for the last one + */ + startLastRec := len(nvargs) - len(pr.parameterFields) + if err := s.writeLobs(ctx, nil, ids, pr.parameterFields, nvargs[startLastRec:]); err != nil { + return nil, err + } + } + if s.sqlTracer != nil { + s.sqlTracer.log(ctx, t, traceExec, query, nvargs...) + } + if fc == p.FcDDL { + return driver.ResultNoRows, nil + } + return driver.RowsAffected(numRow), nil +} + +func (s *session) execCall(ctx context.Context, query string, pr *prepareResult, nvargs []driver.NamedValue) (*callResult, *callArgs, int64, error) { + t := time.Now() + defer metricsAddSQLTimeValue(s.metrics, time.Now(), sqlTimeCall) + + callArgs, err := convertCallArgs(pr.parameterFields, nvargs, s.attrs.cesu8Encoder, s.attrs.lobChunkSize) + if err != nil { + return nil, nil, 0, err + } + inputParameters, err := p.NewInputParameters(callArgs.inFields, callArgs.inArgs) + if err != nil { + return nil, nil, 0, err + } + + if err := s.pwr.Write(ctx, p.MtExecute, !s.inTx.Load(), (*p.StatementID)(&pr.stmtID), inputParameters); err != nil { + return nil, nil, 0, err + } + + cr := &callResult{session: s, outFields: callArgs.outFields} + + var qr *queryResult + var ids []p.LocatorID + outPrms := &p.OutputParameters{} + meta := &p.ResultMetadata{} + resSet := &p.Resultset{} + lobReply := &p.WriteLobReply{} + tableRowIdx := 0 + + numRow, err := s.prd.IterateParts(ctx, 0, func(kind p.PartKind, attrs p.PartAttributes) error { + switch kind { + case p.PkOutputParameters: + outPrms.OutputFields = cr.outFields + if err := s.prd.ReadPart(ctx, outPrms, cr); err != nil { + return err + } + cr.fieldValues = outPrms.FieldValues + cr.decodeErrors = outPrms.DecodeErrors + return nil + case p.PkResultMetadata: + /* + procedure call with table parameters does return metadata for each table + sequence: metadata, resultsetID, resultset + but: + - resultset might not be provided for all tables + - so, 'additional' query result is detected by new metadata part + */ + qr = &queryResult{session: s} + cr.outFields = append(cr.outFields, p.NewTableRowsParameterField(tableRowIdx)) + cr.fieldValues = append(cr.fieldValues, qr) + tableRowIdx++ + if err := s.prd.ReadPart(ctx, meta, nil); err != nil { + return err + } + qr.fields = meta.ResultFields + return nil + case p.PkResultset: + resSet.ResultFields = qr.fields + if err := s.prd.ReadPart(ctx, resSet, qr); err != nil { + return err + } + qr.fieldValues = resSet.FieldValues + qr.decodeErrors = resSet.DecodeErrors + qr.attrs = attrs + return nil + case p.PkResultsetID: + return s.prd.ReadPart(ctx, (*p.ResultsetID)(&qr.rsID), nil) + case p.PkWriteLobReply: + if err := s.prd.ReadPart(ctx, lobReply, nil); err != nil { + return err + } + ids = lobReply.IDs + return nil + default: + return p.ErrSkipped + } + }) + if err != nil { + return nil, nil, 0, err + } + + if len(ids) != 0 { + /* + writeLobParameters: + - chunkReaders + - cr (callResult output parameters are set after all lob input parameters are written) + */ + if err := s.writeLobs(ctx, cr, ids, callArgs.inFields, callArgs.inArgs); err != nil { + return nil, nil, 0, err + } + } + if s.sqlTracer != nil { + s.sqlTracer.log(ctx, t, traceExecCall, query, nvargs...) + } + return cr, callArgs, numRow, nil +} + +func (s *session) fetchNext(ctx context.Context, qr *queryResult) error { + defer metricsAddSQLTimeValue(s.metrics, time.Now(), sqlTimeFetch) + + if err := s.pwr.Write(ctx, p.MtFetchNext, false, p.ResultsetID(qr.rsID), p.Fetchsize(s.attrs.fetchSize)); err != nil { //nolint: gosec + return err + } + + resSet := &p.Resultset{ResultFields: qr.fields, FieldValues: qr.fieldValues} // reuse field values + + _, err := s.prd.IterateParts(ctx, 0, func(kind p.PartKind, attrs p.PartAttributes) error { + switch kind { + case p.PkResultset: + if err := s.prd.ReadPart(ctx, resSet, qr); err != nil { + return err + } + qr.fieldValues = resSet.FieldValues + qr.decodeErrors = resSet.DecodeErrors + qr.attrs = attrs + return nil + default: + return p.ErrSkipped + } + }) + return err +} + +func (s *session) dropStatementID(ctx context.Context, id uint64) error { + if err := s.pwr.Write(ctx, p.MtDropStatementID, false, p.StatementID(id)); err != nil { + return err + } + return s.prd.SkipParts(ctx) +} + +func (s *session) closeResultsetID(ctx context.Context, id uint64) error { + if err := s.pwr.Write(ctx, p.MtCloseResultset, false, p.ResultsetID(id)); err != nil { + return err + } + return s.prd.SkipParts(ctx) +} + +func (s *session) commit(ctx context.Context) error { + defer metricsAddSQLTimeValue(s.metrics, time.Now(), sqlTimeCommit) + + if err := s.pwr.Write(ctx, p.MtCommit, false); err != nil { + return err + } + if err := s.prd.SkipParts(ctx); err != nil { + return err + } + return nil +} + +func (s *session) rollback(ctx context.Context) error { + defer metricsAddSQLTimeValue(s.metrics, time.Now(), sqlTimeRollback) + + if err := s.pwr.Write(ctx, p.MtRollback, false); err != nil { + return err + } + if err := s.prd.SkipParts(ctx); err != nil { + return err + } + return nil +} + +func (s *session) disconnect(ctx context.Context) error { + if err := s.pwr.Write(ctx, p.MtDisconnect, false); err != nil { + return err + } + /* + Do not read server reply as on slow connections the TCP/IP connection is closed (by Server) + before the reply can be read completely. + + // if err := s.pr.readSkip(); err != nil { + // return err + // } + + */ + return nil +} + +/* +readLob reads output lob or result lob parameters from db. + +read lob reply + - seems like readLobreply returns only a result for one lob - even if more then one is requested + --> read single lobs +*/ +func (s *session) readLob(ctx context.Context, request *p.ReadLobRequest, reply *p.ReadLobReply) error { + defer metricsAddSQLTimeValue(s.metrics, time.Now(), sqlTimeFetchLob) + + var err error + for err != io.EOF { //nolint: errorlint + if err = s.pwr.Write(ctx, p.MtWriteLob, false, request); err != nil { + return err + } + + if _, err = s.prd.IterateParts(ctx, 0, func(kind p.PartKind, attrs p.PartAttributes) error { + if kind == p.PkReadLobReply { + return s.prd.ReadPart(ctx, reply, nil) + } + return p.ErrSkipped + }); err != nil { + return err + } + + _, err = reply.Write() + if err != nil && err != io.EOF { //nolint: errorlint + return err + } + } + return nil +} + +// writeLobs writes input lob parameters to db. +func (s *session) writeLobs(ctx context.Context, cr *callResult, ids []p.LocatorID, inPrmFields []*p.ParameterField, nvargs []driver.NamedValue) error { + if len(inPrmFields) != len(nvargs) { + panic("lob streaming can only be done for one (the last) record") + } + descrs := make([]*p.WriteLobDescr, 0, len(ids)) + j := 0 + for i, f := range inPrmFields { + if f.IsLob() && nvargs[i].Value != nil { + lobInDescr, ok := nvargs[i].Value.(*p.LobInDescr) + if !ok { + return fmt.Errorf("protocol error: invalid lob parameter %[1]T %[1]v - lobInDescr expected", nvargs[i]) + } + if j > len(ids) { + return fmt.Errorf("protocol error: invalid number of lob parameter ids %d", len(ids)) + } + if !lobInDescr.IsLastData() { + descrs = append(descrs, &p.WriteLobDescr{LobInDescr: lobInDescr, ID: ids[j]}) + j++ + } + } + } + + writeLobRequest := &p.WriteLobRequest{} + for len(descrs) != 0 { + + if len(descrs) != len(ids) { + return fmt.Errorf("protocol error: invalid number of lob parameter ids %d - expected %d", len(descrs), len(ids)) + } + for i, descr := range descrs { // check if ids and descrs are in sync + if descr.ID != ids[i] { + return fmt.Errorf("protocol error: lob parameter id mismatch %d - expected %d", descr.ID, ids[i]) + } + } + + // TODO check total size limit + for _, descr := range descrs { + if err := descr.FetchNext(s.attrs.lobChunkSize); err != nil { + return err + } + } + + writeLobRequest.Descrs = descrs + + if err := s.pwr.Write(ctx, p.MtReadLob, false, writeLobRequest); err != nil { + return err + } + + lobReply := &p.WriteLobReply{} + outPrms := &p.OutputParameters{} + + if _, err := s.prd.IterateParts(ctx, 0, func(kind p.PartKind, attrs p.PartAttributes) error { + switch kind { + case p.PkOutputParameters: + outPrms.OutputFields = cr.outFields + if err := s.prd.ReadPart(ctx, outPrms, nil); err != nil { + return err + } + cr.fieldValues = outPrms.FieldValues + cr.decodeErrors = outPrms.DecodeErrors + return nil + case p.PkWriteLobReply: + if err := s.prd.ReadPart(ctx, lobReply, nil); err != nil { + return err + } + ids = lobReply.IDs + return nil + default: + return p.ErrSkipped + } + }); err != nil { + return err + } + + // remove done descr + j := 0 + for _, descr := range descrs { + if !descr.IsLastData() { + descrs[j] = descr + j++ + } + } + descrs = descrs[:j] + } + return nil +} diff --git a/vendor/github.com/SAP/go-hdb/driver/sniffer.go b/vendor/github.com/SAP/go-hdb/driver/sniffer.go new file mode 100644 index 00000000..ceab5b13 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/sniffer.go @@ -0,0 +1,102 @@ +package driver + +import ( + "context" + "errors" + "io" + "log" + "log/slog" + "net" + "sync" + + p "github.com/SAP/go-hdb/driver/internal/protocol" + "github.com/SAP/go-hdb/driver/internal/protocol/encoding" + "github.com/SAP/go-hdb/driver/unicode/cesu8" + "github.com/SAP/go-hdb/driver/wgroup" +) + +// A Sniffer is a simple proxy for logging hdb protocol requests and responses. +type Sniffer struct { + logger *slog.Logger + conn net.Conn + dbConn net.Conn +} + +// NewSniffer creates a new sniffer instance. The conn parameter is the net.Conn connection, where the Sniffer +// is listening for hdb protocol calls. The dbAddr is the hdb host port address in "host:port" format. +func NewSniffer(conn net.Conn, dbConn net.Conn) *Sniffer { + return &Sniffer{ + logger: slog.Default().With(slog.String("conn", conn.RemoteAddr().String())), + conn: conn, + dbConn: dbConn, + } +} + +func pipeData(wg *sync.WaitGroup, conn net.Conn, dbConn net.Conn, wr io.Writer) { + defer wg.Done() + + mwr := io.MultiWriter(dbConn, wr) + trd := io.TeeReader(conn, mwr) + buf := make([]byte, 1000) + + var err error + for err == nil { + _, err = trd.Read(buf) + } +} + +func readMsg(ctx context.Context, prd *p.Reader) error { + // TODO complete for non generic parts, see internal/protocol/parts/newGenPartReader for details + _, err := prd.IterateParts(ctx, 0, nil) + // _, err := prd.IterateParts(ctx, 0, func(kind p.PartKind, attrs p.PartAttributes, read func(part p.Part)) {}) + return err +} + +func logData(ctx context.Context, wg *sync.WaitGroup, prd *p.Reader) { + defer wg.Done() + + if err := prd.ReadProlog(ctx); err != nil { + panic(err) + } + + var err error + for !errors.Is(err, io.EOF) { + err = readMsg(ctx, prd) + } +} + +// Run starts the protocol request and response logging. +func (s *Sniffer) Run() error { + clientRd, clientWr := io.Pipe() + dbRd, dbWr := io.Pipe() + + ctx := context.Background() + wg := &sync.WaitGroup{} + + wgroup.Go(wg, func() { + pipeData(wg, s.conn, s.dbConn, clientWr) + }) + wgroup.Go(wg, func() { + pipeData(wg, s.dbConn, s.conn, dbWr) + }) + + defaultDecoder := cesu8.DefaultDecoder() + + clientDec := encoding.NewDecoder(clientRd, defaultDecoder, false) + dbDec := encoding.NewDecoder(dbRd, defaultDecoder, false) + + pClientRd := p.NewClientReader(clientDec, defaultDecoder, true, s.logger, defaultLobChunkSize) + pDBRd := p.NewDBReader(dbDec, defaultDecoder, true, s.logger, defaultLobChunkSize) + + wgroup.Go(wg, func() { + logData(ctx, wg, pClientRd) + }) + wgroup.Go(wg, func() { + logData(ctx, wg, pDBRd) + }) + + wg.Wait() + log.Println("end run") + + return nil +} diff --git a/vendor/github.com/SAP/go-hdb/driver/stats.go b/vendor/github.com/SAP/go-hdb/driver/stats.go new file mode 100644 index 00000000..15a0d1c1 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/stats.go @@ -0,0 +1,30 @@ +package driver + +// StatsHistogram represents statistic data in a histogram structure. +type StatsHistogram struct { + // Count holds the number of measurements + Count uint64 + // Sum holds the sum of the measurements. + Sum float64 + // Buckets contains the count of measurements belonging to a bucket where the + // value of the measurement is less or equal the bucket map key. + Buckets map[float64]uint64 +} + +// Stats contains driver statistics. +type Stats struct { + // Gauges + OpenConnections int // The number of current established driver connections. + OpenTransactions int // The number of current open driver transactions. + OpenStatements int // The number of current open driver database statements. + // Counters + ReadBytes uint64 // Total bytes read by client connection. + WrittenBytes uint64 // Total bytes written by client connection. + SessionConnects uint64 // Total number of session connects (switch users). + // Time histograms (Sum and upper bounds in Unit) + TimeUnit string // Time unit + ReadTime *StatsHistogram // Time spent on reading from connection. + WriteTime *StatsHistogram // Time spent on writing to connection. + AuthTime *StatsHistogram // Time spent on authentication. + SQLTimes map[string]*StatsHistogram // Time spent on different SQL statements. +} diff --git a/vendor/github.com/SAP/go-hdb/driver/stats.tmpl b/vendor/github.com/SAP/go-hdb/driver/stats.tmpl new file mode 100644 index 00000000..e90f33cf --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/stats.tmpl @@ -0,0 +1,19 @@ +{{define "time" -}} +{{printf "%10d" .Count}} {{printf "%12.1f" .Sum}}{{range .Buckets}}{{printf "%10d" .}}{{end -}} +{{end -}} +{{define "bounds" -}}{{range $k, $v := . -}}{{printf "%10.1f" $k}}{{end}}{{end -}} +openConnections {{.OpenConnections}} +openTransactions {{.OpenTransactions}} +openStatements {{.OpenStatements}} +readBytes {{.ReadBytes}} +writtenBytes {{.WrittenBytes}} +sessionConnects {{.SessionConnects}} +timeUnit {{.TimeUnit}} +{{printf "%-12s" ""}}{{printf "%10s" "Count"}} {{printf "%12s" "Sum"}}{{template "bounds" .ReadTime.Buckets}} +{{printf "%-12s" "readTime"}}{{template "time" .ReadTime}} +{{printf "%-12s" "writeTime"}}{{template "time" .WriteTime}} +{{printf "%-12s" "authTime"}}{{template "time" .AuthTime}} +sqlTimes: +{{range $k, $v := .SQLTimes -}} +{{printf " %-10s" $k}}{{template "time" $v}} +{{end}} diff --git a/vendor/github.com/SAP/go-hdb/driver/statscfg.go b/vendor/github.com/SAP/go-hdb/driver/statscfg.go new file mode 100644 index 00000000..ad6ac930 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/statscfg.go @@ -0,0 +1,54 @@ +package driver + +import ( + _ "embed" // embed stats configuration + "encoding/json" + "fmt" + "slices" + "time" +) + +//go:embed statscfg.json +var statsCfgRaw []byte + +var statsCfg struct { + TimeUnit string `json:"timeUnit"` + SQLTimeTexts []string `json:"sqlTimeTexts"` + TimeUpperBounds []float64 `json:"timeUpperBounds"` +} + +// time unit map (see go package time format.go). +var timeUnitMap = map[string]uint64{ + "ns": uint64(time.Nanosecond), + "us": uint64(time.Microsecond), + "µs": uint64(time.Microsecond), // U+00B5 = micro symbol + "μs": uint64(time.Microsecond), // U+03BC = Greek letter mu + "ms": uint64(time.Millisecond), + "s": uint64(time.Second), + "m": uint64(time.Minute), + "h": uint64(time.Hour), +} + +func loadStatsCfg() error { + + if err := json.Unmarshal(statsCfgRaw, &statsCfg); err != nil { + return fmt.Errorf("invalid statscfg.json file: %w", err) + } + + if len(statsCfg.SQLTimeTexts) != int(numSQLTime) { + return fmt.Errorf("invalid number of statscfg.json sqlTimeTexts %d - expected %d", len(statsCfg.SQLTimeTexts), numSQLTime) + } + if len(statsCfg.TimeUpperBounds) == 0 { + return fmt.Errorf("number of statscfg.json timeUpperBounds needs to be greater than %d", 0) + } + + if _, ok := timeUnitMap[statsCfg.TimeUnit]; !ok { + return fmt.Errorf("invalid time unit in statscfg.json %s", statsCfg.TimeUnit) + } + + // sort and dedup timeBuckets + slices.Sort(statsCfg.TimeUpperBounds) + statsCfg.TimeUpperBounds = slices.Compact(statsCfg.TimeUpperBounds) + + return nil +} diff --git a/vendor/github.com/SAP/go-hdb/driver/statscfg.json b/vendor/github.com/SAP/go-hdb/driver/statscfg.json new file mode 100644 index 00000000..c661937b --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/statscfg.json @@ -0,0 +1,5 @@ +{ + "timeUnit": "ms", + "sqlTimeTexts":["query", "prepare", "exec", "call", "fetch", "fetchlob", "rollback", "commit"], + "timeUpperBounds": [1.0, 10.0, 100.0, 1000.0, 10000.0, 100000.0] +} diff --git a/vendor/github.com/SAP/go-hdb/driver/stmt.go b/vendor/github.com/SAP/go-hdb/driver/stmt.go new file mode 100644 index 00000000..254083ca --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/stmt.go @@ -0,0 +1,398 @@ +package driver + +import ( + "context" + "database/sql" + "database/sql/driver" + "errors" + "fmt" + "iter" + "slices" + "sync" + + "github.com/SAP/go-hdb/driver/wgroup" +) + +// check if statements implements all required interfaces. +var ( + _ driver.Stmt = (*stmt)(nil) + _ driver.StmtExecContext = (*stmt)(nil) + _ driver.StmtQueryContext = (*stmt)(nil) + _ driver.NamedValueChecker = (*stmt)(nil) +) + +type stmt struct { + session *session + wg *sync.WaitGroup // from conn + attrs *connAttrs + metrics *metrics + query string + pr *prepareResult + + // rows: stored procedures with table output parameters + rows *sql.Rows +} + +type totalRowsAffected int64 + +func (t *totalRowsAffected) add(r driver.Result) { + if r == nil { + return + } + rows, err := r.RowsAffected() + if err != nil { + return + } + *t += totalRowsAffected(rows) +} + +func newStmt(session *session, wg *sync.WaitGroup, attrs *connAttrs, metrics *metrics, query string, pr *prepareResult) *stmt { + metrics.msgCh <- gaugeMsg{idx: gaugeStmt, v: 1} // increment number of statements. + return &stmt{session: session, wg: wg, attrs: attrs, metrics: metrics, query: query, pr: pr} +} + +/* +NumInput differs dependent on statement (check is done in QueryContext and ExecContext): +- #args == #param (only in params): query, exec, exec bulk (non control query) +- #args == #param (in and out params): exec call +- #args == 0: exec bulk (control query) +- #args == #input param: query call. +*/ +func (s *stmt) NumInput() int { return -1 } + +func (s *stmt) Close() error { + s.metrics.msgCh <- gaugeMsg{idx: gaugeStmt, v: -1} // decrement number of statements. + + if s.rows != nil { + s.rows.Close() + } + + if s.session.isBad() { + return driver.ErrBadConn + } + return s.session.dropStatementID(context.Background(), s.pr.stmtID) +} + +// CheckNamedValue implements NamedValueChecker interface. +func (s *stmt) CheckNamedValue(nv *driver.NamedValue) error { + // conversion is happening as part of the exec, query call + return nil +} + +func (s *stmt) QueryContext(ctx context.Context, nvargs []driver.NamedValue) (driver.Rows, error) { + if s.pr.isProcedureCall() { + return nil, fmt.Errorf("invalid procedure call %s - please use Exec instead", s.query) + } + if err := s.session.preventSwitchUser(ctx); err != nil { + return nil, err + } + + var sqlErr error + var rows driver.Rows + done := make(chan struct{}) + wgroup.Go(s.wg, func() { + defer close(done) + rows, sqlErr = s.session.query(ctx, s.query, s.pr, nvargs) + }) + + select { + case <-ctx.Done(): + s.session.cancel() + return nil, ctx.Err() + case <-done: + return rows, sqlErr + } +} + +func (s *stmt) ExecContext(ctx context.Context, nvargs []driver.NamedValue) (driver.Result, error) { + if hookFn, ok := ctx.Value(connHookCtxKey).(connHookFn); ok { + hookFn(choStmtExec) + } + if err := s.session.preventSwitchUser(ctx); err != nil { + return nil, err + } + + var sqlErr error + var result driver.Result + var rows *sql.Rows // needed to avoid data race in case if context get cancelled. + done := make(chan struct{}) + wgroup.Go(s.wg, func() { + defer close(done) + if s.pr.isProcedureCall() { + result, s.rows, sqlErr = s.execCall(ctx, s.pr, nvargs) + } else { + result, sqlErr = s.execDefault(ctx, nvargs) + } + }) + + select { + case <-ctx.Done(): + s.session.cancel() + return nil, ctx.Err() + case <-done: + s.rows = rows + return result, sqlErr + } +} + +func (s *stmt) execCall(ctx context.Context, pr *prepareResult, nvargs []driver.NamedValue) (driver.Result, *sql.Rows, error) { + /* + call without lob input parameters: + --> callResult output parameter values are set after read call + call with lob output parameters: + --> callResult output parameter values are set after last lob input write + */ + + cr, callArgs, numRow, err := s.session.execCall(ctx, s.query, pr, nvargs) + if err != nil { + return nil, nil, err + } + + numOutArgs := len(callArgs.outArgs) + // no output args -> done + if numOutArgs == 0 { + return driver.RowsAffected(numRow), nil, nil + } + + numOutputField := len(cr.outFields) + scanArgs := make([]any, numOutputField) + for i := range numOutArgs { + scanArgs[i] = callArgs.outArgs[i].Value.(sql.Out).Dest + } + // acccount for table output fields without call arguments. + for i := numOutArgs; i < numOutputField; i++ { + scanArgs[i] = new(sql.Rows) + } + + // no table output parameters -> QueryRow + if len(callArgs.outFields) == numOutArgs { + if err := stdConnTracker.callDB().QueryRow("", cr).Scan(scanArgs...); err != nil { + return nil, nil, err + } + return driver.RowsAffected(numRow), nil, nil + } + + // table output parameters -> Query (needs to kept open) + rows, err := stdConnTracker.callDB().Query("", cr) + if err != nil { + return nil, rows, err + } + if !rows.Next() { + return nil, rows, rows.Err() + } + if err := rows.Scan(scanArgs...); err != nil { + return nil, rows, err + } + return driver.RowsAffected(numRow), rows, nil +} + +func (s *stmt) execDefault(ctx context.Context, nvargs []driver.NamedValue) (driver.Result, error) { + numNVArg, numField := len(nvargs), s.pr.numField() + + if numNVArg == 0 { + if numField != 0 { + return nil, fmt.Errorf("invalid number of arguments %d - expected %d", numNVArg, numField) + } + return s.session.exec(ctx, s.query, s.pr, nvargs, 0) + } + if numNVArg == 1 { + switch nvargs[0].Value.(type) { + case func(args []any) error: + return s.execFct(ctx, nvargs) + case iter.Seq[[]any]: + return s.execSeq(ctx, nvargs) + } + } + if numNVArg == numField { + return s.exec(ctx, s.pr, nvargs, 0) + } + if numNVArg%numField != 0 { + return nil, fmt.Errorf("invalid number of arguments %d - multiple of %d expected", numNVArg, numField) + } + return s.execMany(ctx, nvargs) +} + +// ErrEndOfRows is the error to be returned using a function based bulk exec to indicate +// the end of rows. +var ErrEndOfRows = errors.New("end of rows") + +/* +Non 'atomic' (transactional) operation due to the split in packages (bulkSize), +execMany data might only be written partially to the database in case of hdb stmt errors. +*/ +func (s *stmt) execFct(ctx context.Context, nvargs []driver.NamedValue) (driver.Result, error) { + bulkSize := s.attrs.bulkSize + + totalRowsAffected := totalRowsAffected(0) + args := make([]driver.NamedValue, 0, s.pr.numField()) + scanArgs := make([]any, s.pr.numField()) + + fct, ok := nvargs[0].Value.(func(args []any) error) + if !ok { + panic("invalid argument") // should never happen + } + + done := false + batch := 0 + for !done { + args = args[:0] + for range bulkSize { + err := fct(scanArgs) + if errors.Is(err, ErrEndOfRows) { + done = true + break + } + if err != nil { + return driver.RowsAffected(totalRowsAffected), err + } + + args = slices.Grow(args, len(scanArgs)) + for i, scanArg := range scanArgs { + nv := driver.NamedValue{Ordinal: i + 1} + if t, ok := scanArg.(sql.NamedArg); ok { + nv.Name = t.Name + nv.Value = t.Value + } else { + nv.Name = "" + nv.Value = scanArg + } + args = append(args, nv) + } + } + + r, err := s.exec(ctx, s.pr, args, batch*bulkSize) + totalRowsAffected.add(r) + if err != nil { + return driver.RowsAffected(totalRowsAffected), err + } + batch++ + } + return driver.RowsAffected(totalRowsAffected), nil +} + +func (s *stmt) execSeq(ctx context.Context, nvargs []driver.NamedValue) (driver.Result, error) { + bulkSize := s.attrs.bulkSize + + totalRowsAffected := totalRowsAffected(0) + args := make([]driver.NamedValue, 0, s.pr.numField()) + + seq, ok := nvargs[0].Value.(iter.Seq[[]any]) + if !ok { + panic("invalid argument") // should never happen + } + + batch, n := 0, 0 + for scanArgs := range seq { + if len(scanArgs) != s.pr.numField() { + return driver.RowsAffected(totalRowsAffected), fmt.Errorf("invalid number of args %d - expected %d", len(scanArgs), s.pr.numField()) + } + + args = slices.Grow(args, len(scanArgs)) + for i, scanArg := range scanArgs { + nv := driver.NamedValue{Ordinal: i + 1} + if t, ok := scanArg.(sql.NamedArg); ok { + nv.Name = t.Name + nv.Value = t.Value + } else { + nv.Name = "" + nv.Value = scanArg + } + args = append(args, nv) + } + + n++ + if n >= bulkSize { + r, err := s.exec(ctx, s.pr, args, batch*bulkSize) + totalRowsAffected.add(r) + if err != nil { + return driver.RowsAffected(totalRowsAffected), err + } + args = args[:0] + batch++ + } + } + + if n > 0 { + r, err := s.exec(ctx, s.pr, args, batch*bulkSize) + totalRowsAffected.add(r) + if err != nil { + return driver.RowsAffected(totalRowsAffected), err + } + } + + return driver.RowsAffected(totalRowsAffected), nil +} + +/* +Non 'atomic' (transactional) operation due to the split in packages (bulkSize), +execMany data might only be written partially to the database in case of hdb stmt errors. +*/ +func (s *stmt) execMany(ctx context.Context, nvargs []driver.NamedValue) (driver.Result, error) { + bulkSize := s.attrs.bulkSize + + totalRowsAffected := totalRowsAffected(0) + numField := s.pr.numField() + numNVArg := len(nvargs) + numRec := numNVArg / numField + numBatch := numRec / bulkSize + if numRec%bulkSize != 0 { + numBatch++ + } + + for i := range numBatch { + from := i * numField * bulkSize + to := (i + 1) * numField * bulkSize + if to > numNVArg { + to = numNVArg + } + r, err := s.exec(ctx, s.pr, nvargs[from:to], i*bulkSize) + totalRowsAffected.add(r) + if err != nil { + return driver.RowsAffected(totalRowsAffected), err + } + } + return driver.RowsAffected(totalRowsAffected), nil +} + +/* +exec executes a sql statement. + +Bulk insert containing LOBs: + - Precondition: + .Sending more than one row with partial LOB data. + - Observations: + .In hdb version 1 and 2 'piecewise' LOB writing does work. + .Same does not work in case of geo fields which are LOBs en,- decoded as well. + .In hana version 4 'piecewise' LOB writing seems not to work anymore at all. + - Server implementation (not documented): + .'piecewise' LOB writing is only supported for the last row of a 'bulk insert'. + - Current implementation: + One server call in case of + .'non bulk' execs or + .'bulk' execs without LOBs + else potential several server calls (split into packages). + - Package invariant: + .for all packages except the last one, the last row contains 'incomplete' LOB data ('piecewise' writing) +*/ +func (s *stmt) exec(ctx context.Context, pr *prepareResult, nvargs []driver.NamedValue, ofs int) (driver.Result, error) { + addLobDataRecs, err := convertExecArgs(pr.parameterFields, nvargs, s.attrs.cesu8Encoder, s.attrs.lobChunkSize) + if err != nil { + return driver.ResultNoRows, err + } + + // piecewise LOB handling + numColumn := len(pr.parameterFields) + totalRowsAffected := totalRowsAffected(0) + from := 0 + for _, row := range addLobDataRecs { + to := (row + 1) * numColumn + + r, err := s.session.exec(ctx, s.query, pr, nvargs[from:to], ofs) + totalRowsAffected.add(r) + if err != nil { + return driver.RowsAffected(totalRowsAffected), err + } + from = to + } + return driver.RowsAffected(totalRowsAffected), nil +} diff --git a/vendor/github.com/SAP/go-hdb/driver/trace.go b/vendor/github.com/SAP/go-hdb/driver/trace.go new file mode 100644 index 00000000..c5537847 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/trace.go @@ -0,0 +1,89 @@ +package driver + +import ( + "context" + "database/sql/driver" + "flag" + "fmt" + "log/slog" + "strconv" + "sync/atomic" + "time" +) + +var ( + protTrace atomic.Bool + sqlTrace atomic.Bool +) + +func setTrace(b *atomic.Bool, s string) error { + v, err := strconv.ParseBool(s) + if err == nil { + b.Store(v) + } + return err +} + +func init() { + flag.BoolFunc("hdb.protTrace", "enabling hdb protocol trace", func(s string) error { return setTrace(&protTrace, s) }) + flag.BoolFunc("hdb.sqlTrace", "enabling hdb sql trace", func(s string) error { return setTrace(&sqlTrace, s) }) +} + +// SQLTrace returns true if sql tracing output is active, false otherwise. +func SQLTrace() bool { return sqlTrace.Load() } + +// SetSQLTrace sets sql tracing output active or inactive. +func SetSQLTrace(on bool) { sqlTrace.Store(on) } + +const ( + tracePing = "ping" + tracePrepare = "prepare" + traceQuery = "query" + traceExec = "exec" + traceExecCall = "call" +) + +type sqlTracer struct { + logger *slog.Logger + maxArg int +} + +const defSQLTracerMaxArg = 5 // default limit of number of arguments + +func newSQLTracer(logger *slog.Logger, maxArg int) *sqlTracer { + if maxArg <= 0 { + maxArg = defSQLTracerMaxArg + } + return &sqlTracer{logger: logger, maxArg: maxArg} +} + +func (t *sqlTracer) log(ctx context.Context, startTime time.Time, traceKind string, query string, nvargs ...driver.NamedValue) { + duration := time.Since(startTime).Milliseconds() + l := len(nvargs) + + attrs := []slog.Attr{ + slog.String(traceKind, query), + slog.Int64("ms", duration), + } + + if l == 0 { + t.logger.LogAttrs(ctx, slog.LevelInfo, "SQL", attrs...) + return + } + + numArg := min(l, t.maxArg) + argAttrs := make([]slog.Attr, 0, numArg) + for i := range numArg { + name := nvargs[i].Name + if name == "" { + name = strconv.Itoa(nvargs[i].Ordinal) + } + argAttrs = append(argAttrs, slog.String(name, fmt.Sprintf("%v", nvargs[i].Value))) + } + if l > t.maxArg { + argAttrs = append(argAttrs, slog.Int("numArgSkip", l-t.maxArg)) + } + attrs = append(attrs, slog.Any("arg", slog.GroupValue(argAttrs...))) + + t.logger.LogAttrs(ctx, slog.LevelInfo, "SQL", attrs...) +} diff --git a/vendor/github.com/SAP/go-hdb/driver/unicode/cesu8/cesu8.go b/vendor/github.com/SAP/go-hdb/driver/unicode/cesu8/cesu8.go new file mode 100644 index 00000000..6b205989 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/unicode/cesu8/cesu8.go @@ -0,0 +1,142 @@ +// Package cesu8 implements functions and constants to support text encoded in CESU-8. +// It implements functions comparable to the unicode/utf8 package for UTF-8 de- and encoding. +package cesu8 + +import ( + "unicode/utf16" + "unicode/utf8" +) + +const ( + // CESUMax is the maximum amount of bytes used by an CESU-8 codepoint encoding. + CESUMax = 6 +) + +// Copied from unicode utf8. +const ( + tx = 0b10000000 + t3 = 0b11100000 + + maskx = 0b00111111 + mask3 = 0b00001111 + + rune1Max = 1<<7 - 1 + rune2Max = 1<<11 - 1 + rune3Max = 1<<16 - 1 +) + +// Size returns the amount of bytes needed to encode an UTF-8 byte slice to CESU-8. +func Size(p []byte) int { + n := 0 + for len(p) > 0 { + r, size := DecodeRune(p) + n += RuneLen(r) + p = p[size:] + } + return n +} + +// StringSize is like Size with a string as parameter. +func StringSize(s string) int { + n := 0 + for _, r := range s { + n += RuneLen(r) + } + return n +} + +// EncodeRune writes into p (which must be large enough) the CESU-8 encoding of the rune. It returns the number of bytes written. +func EncodeRune(p []byte, r rune) int { + if r <= rune3Max { + return utf8.EncodeRune(p, r) + } + high, low := utf16.EncodeRune(r) + _ = p[5] // eliminate bounds checks + p[0] = t3 | byte(high>>12) + p[1] = tx | byte(high>>6)&maskx + p[2] = tx | byte(high)&maskx + p[3] = t3 | byte(low>>12) + p[4] = tx | byte(low>>6)&maskx + p[5] = tx | byte(low)&maskx + return CESUMax +} + +// FullRune reports whether the bytes in p begin with a full CESU-8 encoding of a rune. +func FullRune(p []byte) bool { + if isSurrogate(p) { + return isSurrogate(p[3:]) + } + return utf8.FullRune(p) +} + +func decodeSurrogates(p []byte) (rune, int) { + high := decodeCheckedSurrogate(p) + low, ok := decodeSurrogate(p[3:]) + if !ok { + return utf8.RuneError, 3 + } + return utf16.DecodeRune(high, low), CESUMax +} + +// DecodeRune unpacks the first CESU-8 encoding in p and returns the rune and its width in bytes. +func DecodeRune(p []byte) (rune, int) { + if !isSurrogate(p) { + return utf8.DecodeRune(p) + } + return decodeSurrogates(p) +} + +// RuneLen returns the number of bytes required to encode the rune. +func RuneLen(r rune) int { + switch { + case r < 0: + return -1 + case r <= rune1Max: + return 1 + case r <= rune2Max: + return 2 + case r <= rune3Max: + return 3 + case r <= utf8.MaxRune: + return CESUMax + default: + return -1 + } +} + +const ( + sp0 = 0xed + sb1Min = 0xa0 + sb1Max = 0xbf +) + +func decodeSurrogate(p []byte) (rune, bool) { + if len(p) < 3 { + return utf8.RuneError, false + } + p0 := p[0] + if p0 != sp0 { + return utf8.RuneError, false + } + b1 := p[1] + if b1 < sb1Min || b1 > sb1Max { + return utf8.RuneError, false + } + b2 := p[2] + return rune(p0&mask3)<<12 | rune(b1&maskx)<<6 | rune(b2&maskx), true +} + +func decodeCheckedSurrogate(p []byte) rune { + return rune(p[0]&mask3)<<12 | rune(p[1]&maskx)<<6 | rune(p[2]&maskx) +} + +func isSurrogate(p []byte) bool { + if len(p) < 3 { + return false + } + b1 := p[1] + if p[0] != sp0 || b1 < sb1Min || b1 > sb1Max { + return false + } + return true +} diff --git a/vendor/github.com/SAP/go-hdb/driver/unicode/cesu8/encoding.go b/vendor/github.com/SAP/go-hdb/driver/unicode/cesu8/encoding.go new file mode 100644 index 00000000..fe3a83c1 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/unicode/cesu8/encoding.go @@ -0,0 +1,197 @@ +// Package cesu8 implements functions and constants to support text encoded in CESU-8. +// It implements functions comparable to the unicode/utf8 package for UTF-8 de- and encoding. +package cesu8 + +import ( + "fmt" + "unicode" + "unicode/utf8" + + "golang.org/x/text/transform" +) + +// Encoding constants. +const ( + UTF8 = "UTF-8" + CESU8 = "CESU-8" +) + +// DecodeError is raised when a transformer detects invalid encoded data. +type DecodeError struct { + enc string // encoding + p int // position of error in value + v []byte // value +} + +func newDecodeError(enc string, p int, v []byte) *DecodeError { + // copy value + cv := make([]byte, len(v)) + copy(cv, v) + return &DecodeError{enc: enc, p: p, v: cv} +} + +func (e *DecodeError) Error() string { + return fmt.Sprintf("invalid %s: %x at position %d", e.enc, e.v, e.p) +} + +// Enc returns the expected encoding of the erroneous data. +func (e *DecodeError) Enc() string { return e.enc } + +// Pos returns the position of the invalid rune. +func (e *DecodeError) Pos() int { return e.p } + +// Value returns the value which should be decoded. +func (e *DecodeError) Value() []byte { return e.v } + +// Encoder supports encoding of UTF-8 encoded data into CESU-8. +type Encoder struct { + transform.NopResetter + errorHandler func(err *DecodeError) (rune, error) +} + +// NewEncoder creates a new encoder instance. With parameter errorHandler a custom error handling function could be used in case +// the encoder would detect invalid UTF-8 encoded characters. +func NewEncoder(errorHandler func(err *DecodeError) (rune, error)) *Encoder { + return &Encoder{errorHandler: errorHandler} +} + +// Transform implements the transform.Transformer interface. +func (e *Encoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + i, j := 0, 0 + for i < len(src) { + if src[i] < utf8.RuneSelf { + if j >= len(dst) { + return j, i, transform.ErrShortDst + } + dst[j] = src[i] + i++ + j++ + continue + } + // check if additional bytes needed (ErrShortSrc) only + // - if further bytes are potentially available (!atEOF) and + // - remaining buffer smaller than max size for an encoded UTF-8 rune + if !atEOF && len(src[i:]) < utf8.UTFMax { + if !utf8.FullRune(src[i:]) { + return j, i, transform.ErrShortSrc + } + } + r, n := utf8.DecodeRune(src[i:]) + // invalid UTF-8 cases: + // - if p is empty it returns (RuneError, 0) + // - otherwise, if the encoding is invalid, it returns (RuneError, 1) + if (n == 0 || n == 1) && r == utf8.RuneError { + decodeErr := newDecodeError(UTF8, i, src) + if e.errorHandler == nil { + return j, i, decodeErr + } + r, err = e.errorHandler(decodeErr) + if err != nil { + return j, i, err + } + } + m := RuneLen(r) + switch { + case m == -1: + panic("internal UTF-8 to CESU-8 transformation error") + case j+m > len(dst): + return j, i, transform.ErrShortDst + } + EncodeRune(dst[j:], r) + i += n + j += m + } + return j, i, nil +} + +// Decoder supports decoding of CESU-8 encoded data into UTF-8. +type Decoder struct { + transform.NopResetter + errorHandler func(err *DecodeError) (rune, error) +} + +// NewDecoder creates a new decoder instance. With parameter errorHandler a custom error handling function could be used in case +// the decoder would detect invalid CESU-8 encoded characters. +func NewDecoder(errorHandler func(err *DecodeError) (rune, error)) *Decoder { + return &Decoder{errorHandler: errorHandler} +} + +func (d *Decoder) handleDecodeError(r rune, i int, src []byte) (rune, error) { + decodeErr := newDecodeError(CESU8, i, src) + if d.errorHandler == nil { + return r, decodeErr + } + return d.errorHandler(decodeErr) +} + +// Transform implements the transform.Transformer interface. +func (d *Decoder) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { + i, j := 0, 0 + for i < len(src) { + if src[i] < utf8.RuneSelf { + if j >= len(dst) { + return j, i, transform.ErrShortDst + } + dst[j] = src[i] + i++ + j++ + continue + } + p := src[i:] + // check if additional bytes needed (ErrShortSrc) only + // - if further bytes are potentially available (!atEOF) and + // - remaining buffer smaller than max size for an encoded CESU-8 rune + if !atEOF && len(p) < CESUMax { + if !FullRune(p) { + return j, i, transform.ErrShortSrc + } + } + /* + cannot use DecodeRune as we cannot distinguish betweeen + .unicode replacement character and + .invalid surrogate + r, n := DecodeRune(src[i:]) + */ + var r rune + var n int + if !isSurrogate(p) { + if r, n = utf8.DecodeRune(p); r == utf8.RuneError && (n == 0 || n == 1) { + if r, err = d.handleDecodeError(r, i, src); err != nil { + return j, i, err + } + } + } else { + if r, n = decodeSurrogates(p); r == utf8.RuneError { + if r, err = d.handleDecodeError(r, i, src); err != nil { + return j, i, err + } + } + } + m := utf8.RuneLen(r) + switch { + case m == -1: + panic("internal CESU-8 to UTF-8 transformation error") + case j+m > len(dst): + return j, i, transform.ErrShortDst + } + utf8.EncodeRune(dst[j:], r) + i += n + j += m + } + return j, i, nil +} + +var ( + defaultDecoder = NewDecoder(nil) + defaultEncoder = NewEncoder(nil) +) + +// DefaultDecoder returns the default CESU-8 to UTF-8 decoder. +func DefaultDecoder() transform.Transformer { return defaultDecoder } + +// DefaultEncoder returns the default UTF-8 to CESU-8 encoder. +func DefaultEncoder() transform.Transformer { return defaultEncoder } + +// ReplaceErrorHandler is a decoding error handling function replacing invalid CESU-8 data with the +// unicode replacement character '\uFFFD'. +func ReplaceErrorHandler(err *DecodeError) (rune, error) { return unicode.ReplacementChar, nil } diff --git a/vendor/github.com/SAP/go-hdb/driver/version.go b/vendor/github.com/SAP/go-hdb/driver/version.go new file mode 100644 index 00000000..25325a33 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/version.go @@ -0,0 +1,173 @@ +package driver + +import ( + "fmt" + "strconv" + "strings" +) + +const ( + versionMajor = iota + versionMinor + versionRevision + versionPatch + versionBuildID + versionCount +) + +/* +versionNumber holds the information of a hdb semantic version. + +u.vv.wwx.yy.zzzzzzzzzz + +u.vv: hdb version (major.minor) +ww: SPS number +wwx: revision number +yy: patch number +zzzzzzzzzz: build id + +Example: 2.00.045.00.1575639312 + + hdb version: 2.00 + SPS number: 04 + revision number: 045 + patch number: 0 + build id: 1575639312 +*/ +type versionNumber []uint64 // assumption: all fields are numeric + +func (vn versionNumber) String() string { + s := fmt.Sprintf("%d.%s.%s.%s", + vn[versionMajor], + formatUint64(vn[versionMinor], 2), + formatUint64(vn[versionRevision], 3), + formatUint64(vn[versionPatch], 2), + ) + if vn[versionBuildID] != 0 { + return fmt.Sprintf("%s.%d", s, vn[versionBuildID]) + } + return s +} + +func parseVersionNumber(s string) versionNumber { + vn := make([]uint64, versionCount) + + parts := strings.SplitN(s, ".", versionCount) + for i, part := range parts { + vn[i], _ = strconv.ParseUint(part, 10, 64) + } + return vn +} + +func formatUint64(i uint64, digits int) string { + s := strings.Repeat("0", digits) + strconv.FormatUint(i, 10) + return s[len(s)-digits:] +} + +func (vn versionNumber) isZero() bool { + for _, n := range vn { + if n != 0 { + return false + } + } + return true +} + +func compareUint64(u1, u2 uint64) int { + switch { + case u1 == u2: + return 0 + case u1 > u2: + return 1 + default: + return -1 + } +} + +// Compare compares the version number with a second version number vn2. The result will be +// +// 0 in case the two versions are equal, +// +// -1 in case version v has lower precedence than c2, +// +// 1 in case version v has higher precedence than c2. +func (vn versionNumber) compare(vn2 versionNumber) int { + for i := range versionCount - 1 { // ignore buildID - might not be ordered} + if r := compareUint64(vn[i], vn2[i]); r != 0 { + return r + } + } + return 0 +} + +// hdbVersionNumberOne - if HANA version 1 assume version 1.00 SPS 12. +var versionNumberOne = parseVersionNumber("1.00.120") + +// HDBVersion feature flags. +const ( + hdbfNone uint64 = 1 << iota + hdbfServerVersion // HANA reports server version in connect options + hdbfConnectClientInfo // HANA accepts ClientInfo as part of the connection process +) + +var hdbFeatureAvailability = map[uint64]versionNumber{ + hdbfServerVersion: parseVersionNumber("2.00.000"), + hdbfConnectClientInfo: parseVersionNumber("2.00.042"), +} + +// Version is representing a hdb version. +type Version struct { + vn versionNumber + feature uint64 +} + +func (v *Version) String() string { return v.vn.String() } + +// Major returns the major field of a hdbVersionNumber. +func (v *Version) Major() uint64 { return v.vn[versionMajor] } + +// Minor returns the minor field of a HDBVersionNumber. +func (v *Version) Minor() uint64 { return v.vn[versionMinor] } + +// SPS returns the sps field of a HDBVersionNumber. +func (v *Version) SPS() uint64 { return v.vn[versionRevision] / 10 } + +// Revision returns the revision field of a HDBVersionNumber. +func (v *Version) Revision() uint64 { return v.vn[versionRevision] } + +// Patch returns the patch field of a HDBVersionNumber. +func (v *Version) Patch() uint64 { return v.vn[versionPatch] } + +// BuildID returns the build id field of a HDBVersionNumber. +func (v *Version) BuildID() uint64 { return v.vn[versionBuildID] } + +// parseVersion parses a semantic hdb version string field. +func parseVersion(s string) *Version { + vn := parseVersionNumber(s) + if vn.isZero() { // hdb 1.00 does not report version + vn = versionNumberOne + } + + var feature uint64 + // detect features + for f, cv := range hdbFeatureAvailability { + if vn.compare(cv) >= 0 { // v is equal or greater than cv + feature |= f // add feature + } + } + return &Version{vn: vn, feature: feature} +} + +// compare compares the version with a second version v2. The result will be +// +// 0 in case the two versions are equal, +// +// -1 in case version v has lower precedence than c2, +// +// 1 in case version v has higher precedence than c2. +func (v *Version) compare(v2 *Version) int { + return v.vn.compare(v2.vn) +} + +// hasFeature returns true if HDBVersion does support feature - false otherwise. +func (v *Version) hasFeature(feature uint64) bool { return v.feature&feature != 0 } diff --git a/vendor/github.com/SAP/go-hdb/driver/wgroup/wgroup1.24.go b/vendor/github.com/SAP/go-hdb/driver/wgroup/wgroup1.24.go new file mode 100644 index 00000000..f433f1a8 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/wgroup/wgroup1.24.go @@ -0,0 +1,15 @@ +//go:build !go1.25 + +// Package wgroup wraps WaitGroup Go until anly go versions >= 1.25 are going to be supported. +package wgroup + +import "sync" + +// Go is a wrapper for sync.WaitGroup.Go and will be deleted if only go versions >= 1.25 are supported. +func Go(wg *sync.WaitGroup, f func()) { + wg.Add(1) + go func() { + defer wg.Done() + f() + }() +} diff --git a/vendor/github.com/SAP/go-hdb/driver/wgroup/wgroup1.25.go b/vendor/github.com/SAP/go-hdb/driver/wgroup/wgroup1.25.go new file mode 100644 index 00000000..7f391abe --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/wgroup/wgroup1.25.go @@ -0,0 +1,11 @@ +//go:build go1.25 + +// Package wgroup provides compatibility on go1.24 and go1.25. +package wgroup + +import "sync" + +// Go is a wrapper for sync.WaitGroup.Go and will be deleted if only go versions >= 1.25 are supported. +func Go(wg *sync.WaitGroup, f func()) { + wg.Go(f) +} diff --git a/vendor/github.com/SAP/go-hdb/driver/x_bstring_test.py b/vendor/github.com/SAP/go-hdb/driver/x_bstring_test.py new file mode 100644 index 00000000..217c43f3 --- /dev/null +++ b/vendor/github.com/SAP/go-hdb/driver/x_bstring_test.py @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +from hdbcli import dbapi +import hashlib +import argparse + +parser = argparse.ArgumentParser(description="bstring test script") +parser.add_argument("address", help="address") +parser.add_argument('port', type=int, help='port: 3xxxx') +parser.add_argument('user', help='user') +parser.add_argument('password', help='password') +args = parser.parse_args() + +try: + conn = dbapi.connect(address=args.address, port=args.port,user=args.user, password=args.password) + try: + cursor = conn.cursor() + try: + hash = hashlib.sha256() + hash.update(b"TEST") + cursor.execute("SELECT 'FOOBAR' FROM DUMMY WHERE HASH_SHA256('TEST') = :id", {"id": hash.digest()}) + except Exception as err: + print("error: {}".format(err)) + finally: + cursor.close() + except Exceptiopn as err: + print("error: {}".format(err)) + finally: + conn.close() +except Exception as err: + print("error: {}".format(err)) diff --git a/vendor/modules.txt b/vendor/modules.txt index 811e01e9..7719b47b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -15,6 +15,19 @@ filippo.io/edwards25519/field # github.com/Masterminds/semver/v3 v3.4.0 ## explicit; go 1.21 github.com/Masterminds/semver/v3 +# github.com/SAP/go-hdb v1.14.5 +## explicit; go 1.24.0 +github.com/SAP/go-hdb/driver +github.com/SAP/go-hdb/driver/dial +github.com/SAP/go-hdb/driver/internal/protocol +github.com/SAP/go-hdb/driver/internal/protocol/auth +github.com/SAP/go-hdb/driver/internal/protocol/encoding +github.com/SAP/go-hdb/driver/internal/protocol/julian +github.com/SAP/go-hdb/driver/internal/protocol/levenshtein +github.com/SAP/go-hdb/driver/internal/rand/alphanum +github.com/SAP/go-hdb/driver/internal/unsafe +github.com/SAP/go-hdb/driver/unicode/cesu8 +github.com/SAP/go-hdb/driver/wgroup # github.com/antlr4-go/antlr/v4 v4.13.1 ## explicit; go 1.22 github.com/antlr4-go/antlr/v4 @@ -678,8 +691,8 @@ golang.org/x/oauth2/clientcredentials golang.org/x/oauth2/internal golang.org/x/oauth2/jws golang.org/x/oauth2/jwt -# golang.org/x/sync v0.13.0 -## explicit; go 1.23.0 +# golang.org/x/sync v0.17.0 +## explicit; go 1.24.0 golang.org/x/sync/semaphore golang.org/x/sync/singleflight # golang.org/x/sys v0.35.0 @@ -696,8 +709,8 @@ golang.org/x/sys/windows/svc/mgr # golang.org/x/term v0.31.0 ## explicit; go 1.23.0 golang.org/x/term -# golang.org/x/text v0.24.0 -## explicit; go 1.23.0 +# golang.org/x/text v0.29.0 +## explicit; go 1.24.0 golang.org/x/text/cases golang.org/x/text/encoding golang.org/x/text/encoding/internal From 78c0a619d8681a7b8f228dc314dccaa4da8f4598 Mon Sep 17 00:00:00 2001 From: Geoff Greer Date: Wed, 1 Oct 2025 17:39:34 -0700 Subject: [PATCH 2/4] Add example yaml and docker compose config for SAP HANA. Error if we can't ping the database. --- docker-compose-hanaexpress-test.yml | 32 ++++++++++++++++++ examples/sap-hana-test.yml | 51 +++++++++++++++++++++++++++++ pkg/connector/connector.go | 5 ++- test/hanaexpress/password.json | 3 ++ 4 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 docker-compose-hanaexpress-test.yml create mode 100644 examples/sap-hana-test.yml create mode 100644 test/hanaexpress/password.json diff --git a/docker-compose-hanaexpress-test.yml b/docker-compose-hanaexpress-test.yml new file mode 100644 index 00000000..4ab1578f --- /dev/null +++ b/docker-compose-hanaexpress-test.yml @@ -0,0 +1,32 @@ +services: + # HANA Express Database for testing + hanaexpress: + image: saplabs/hanaexpress:latest + container_name: baton-hanaexpress-test + hostname: hanaexpress + ulimits: + nofile: + soft: 1048576 + hard: 1048576 + sysctls: + kernel.shmmax: 1073741824 + net.ipv4.ip_local_port_range: 40000 60999 + kernel.shmall: 8388608 + ports: + - "39013:39013" + - "39017:39017" + - "39041:39041" + - "39042:39042" + - "39043:39043" + - "39044:39044" + - "39045:39045" + - "1128:1128" + - "1129:1129" + - "59013:59013" + - "59014:59014" + command: > + --passwords-url file:///hana/mounts/password.json + --agree-to-sap-license + + volumes: + - ./test/hanaexpress:/hana/mounts diff --git a/examples/sap-hana-test.yml b/examples/sap-hana-test.yml new file mode 100644 index 00000000..c048b9d8 --- /dev/null +++ b/examples/sap-hana-test.yml @@ -0,0 +1,51 @@ +--- + app_name: HANA Express Test + + connect: + dsn: "hdb://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_DATABASE}" + + resource_types: + user: + name: "User" + description: "A user within the SAP HANA system" + list: + query: | + SELECT + USER_ID, + USER_NAME, + CASE + WHEN USER_DEACTIVATED = 'TRUE' THEN 'inactive' + ELSE 'active' + END as STATUS, + CREATE_TIME, + LAST_SUCCESSFUL_CONNECT, + COMMENTS + FROM + "SYS"."USERS" + ORDER BY USER_ID + LIMIT ? OFFSET ? + # Pagination configuration + pagination: + strategy: "offset" + primary_key: "USER_ID" + # Mapping of query results to resource fields + map: + id: ".USER_ID" + display_name: ".USER_NAME" + description: ".USER_NAME" + # Extra attributes (traits) for the user resource + traits: + user: + status: .STATUS + login: .USER_NAME + # Email addresses + # emails: + # - ".email" + # account_type: ".account_type" + last_login: .LAST_SUCCESSFUL_CONNECT + created_at: .CREATE_TIME + profile: + comments: .COMMENTS + user_id: .USER_ID + created_at: .CREATE_TIME + last_login: .LAST_SUCCESSFUL_CONNECT diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index 06a30972..c44aab6a 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -76,7 +76,10 @@ func (c *Connector) Metadata(ctx context.Context) (*v2.ConnectorMetadata, error) // Validate is called to ensure that the connector is properly configured. It should exercise any API credentials // to be sure that they are valid. func (c *Connector) Validate(ctx context.Context) (annotations.Annotations, error) { - // TODO: Validate SQL connection. + err := c.db.Ping() + if err != nil { + return nil, err + } return nil, nil } diff --git a/test/hanaexpress/password.json b/test/hanaexpress/password.json new file mode 100644 index 00000000..6473ed2c --- /dev/null +++ b/test/hanaexpress/password.json @@ -0,0 +1,3 @@ +{ + "master_password" : "HXEHana1" +} From 4b1f9a0ef9071089300659131e8ad310db77a021 Mon Sep 17 00:00:00 2001 From: Geoff Greer Date: Thu, 2 Oct 2025 09:20:33 -0700 Subject: [PATCH 3/4] Fix lint error --- pkg/connector/connector.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index c44aab6a..6a0a877b 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -76,7 +76,7 @@ func (c *Connector) Metadata(ctx context.Context) (*v2.ConnectorMetadata, error) // Validate is called to ensure that the connector is properly configured. It should exercise any API credentials // to be sure that they are valid. func (c *Connector) Validate(ctx context.Context) (annotations.Annotations, error) { - err := c.db.Ping() + err := c.db.PingContext(ctx) if err != nil { return nil, err } From 09a5e68c8f8c024ee0826a3dff9206cc310b3c0b Mon Sep 17 00:00:00 2001 From: Geoff Greer Date: Thu, 2 Oct 2025 09:25:22 -0700 Subject: [PATCH 4/4] Don't override default max conns or conn lifetime in DB drivers. --- pkg/database/hdb/hdb.go | 11 ----------- pkg/database/mysql/mysql.go | 11 ----------- 2 files changed, 22 deletions(-) diff --git a/pkg/database/hdb/hdb.go b/pkg/database/hdb/hdb.go index f9be6401..8b98f376 100644 --- a/pkg/database/hdb/hdb.go +++ b/pkg/database/hdb/hdb.go @@ -3,26 +3,15 @@ package hdb import ( "context" "database/sql" - "time" _ "github.com/SAP/go-hdb/driver" ) -const ( - MaxIdleConns = 10 - MaxOpenConns = 10 - MaxConnLifetime = 5 * time.Minute -) - func Connect(ctx context.Context, dsn string) (*sql.DB, error) { db, err := sql.Open("hdb", dsn) if err != nil { return nil, err } - db.SetMaxOpenConns(MaxOpenConns) - db.SetMaxIdleConns(MaxIdleConns) - db.SetConnMaxLifetime(MaxConnLifetime) - return db, nil } diff --git a/pkg/database/mysql/mysql.go b/pkg/database/mysql/mysql.go index 43c9614a..1fc65e9f 100644 --- a/pkg/database/mysql/mysql.go +++ b/pkg/database/mysql/mysql.go @@ -6,17 +6,10 @@ import ( "fmt" "net/url" "strings" - "time" _ "github.com/go-sql-driver/mysql" ) -const ( - MaxIdleConns = 10 - MaxOpenConns = 10 - MaxConnLifetime = 5 * time.Minute -) - func convertURItoDSN(uri string) (string, error) { parsedURI, err := url.Parse(uri) if err != nil { @@ -62,9 +55,5 @@ func Connect(ctx context.Context, dsn string) (*sql.DB, error) { return nil, err } - db.SetMaxOpenConns(MaxOpenConns) - db.SetMaxIdleConns(MaxIdleConns) - db.SetConnMaxLifetime(MaxConnLifetime) - return db, nil }