├── cmd
├── bulkbench
│ ├── bulkbench.png
│ ├── templates
│ │ ├── dbresult.html
│ │ ├── testresult.html
│ │ └── index.html
│ ├── url.go
│ ├── prm.go
│ ├── bulkbench.go
│ ├── benchmark_test.go
│ ├── flag.go
│ └── index.go
└── sniffer
│ └── sniffer.go
├── driver
├── internal
│ ├── protocol
│ │ ├── encoding
│ │ │ ├── doc.go
│ │ │ ├── datetime.go
│ │ │ └── field.go
│ │ ├── doc.go
│ │ ├── resizeslice.go
│ │ ├── x_generator.go
│ │ ├── resizeslice_test.go
│ │ ├── parts1.24.go
│ │ ├── parts1.25.go
│ │ ├── rowsaffected.go
│ │ ├── decodeerror.go
│ │ ├── keyvaluesparts.go
│ │ ├── auth
│ │ │ ├── list.go
│ │ │ ├── jwt.go
│ │ │ ├── scram.go
│ │ │ ├── sessioncookie.go
│ │ │ ├── x509.go
│ │ │ ├── scram_test.go
│ │ │ ├── scramsha256.go
│ │ │ └── scrampbkdf2sha256.go
│ │ ├── dfv.go
│ │ ├── julian
│ │ │ ├── julian.go
│ │ │ └── julian_test.go
│ │ ├── functioncode.go
│ │ ├── levenshtein
│ │ │ └── levenshtein.go
│ │ ├── fieldnames.go
│ │ ├── messagetype.go
│ │ ├── simpleparts.go
│ │ ├── datatype.go
│ │ ├── partkind.go
│ │ ├── init.go
│ │ ├── auth_test.go
│ │ └── decode.go
│ ├── unsafe
│ │ └── unsafe.go
│ └── rand
│ │ └── alphanum
│ │ └── rand.go
├── statscfg.json
├── doc.go
├── wgroup
│ ├── wgroup1.25.go
│ └── wgroup1.24.go
├── dbconnectinfo.go
├── example_test.go
├── identifier_test.go
├── bytes.go
├── example_dsn_test.go
├── deprecated.go
├── ping_test.go
├── example_error_test.go
├── example_db_test.go
├── stats.tmpl
├── identifier.go
├── x_bstring_test.py
├── dial
│ └── dialer.go
├── conn_test.go
├── example_decimal_test.go
├── stats.go
├── example_userswitch_test.go
├── metadata.go
├── example_conn_test.go
├── decimal.go
├── statscfg.go
├── error.go
├── example_scanlobstring_test.go
├── example_scanlobbytes_test.go
├── example_metadata_test.go
├── version_test.go
├── calldriver.go
├── spatial
│ ├── example_spatial_test.go
│ ├── geojson.go
│ └── wkt.go
├── example_scanlobwriter_test.go
├── emptydate_test.go
├── trace.go
├── example_structscanner_test.go
├── scanner_test.go
├── docstore_test.go
├── sniffer.go
├── unicode
│ └── cesu8
│ │ ├── cesu8_test.go
│ │ └── cesu8.go
├── tx_test.go
├── example_bulk_test.go
├── dbconn.go
└── session_test.go
├── go.mod
├── testdata
└── x509
│ ├── ed25519.key
│ ├── ec_p256.ec.key
│ ├── ec_p256.pkcs8.key
│ ├── ec_p384.ec.key
│ ├── ec_p384.pkcs8.key
│ ├── ec_p521.ec.key
│ ├── ec_p521.pkcs8.key
│ ├── ed25519.crt
│ ├── ec_p256.crt
│ ├── ec_p384.crt
│ ├── ec_p521.crt
│ ├── rsa.crt
│ ├── rsa.pkcs1.key
│ ├── rsa.pkcs8.key
│ ├── rootCA.crt
│ ├── README.md
│ └── rootCA.key
├── go.sum
├── sqlscript
├── doc.go
├── example_test.go
└── scan_test.go
├── .github
├── workflows
│ ├── reuse.yml
│ └── build.yml
└── dependabot.yml
├── prometheus
├── Makefile
├── .gitignore
├── go.mod
├── collectors
│ └── example_test.go
└── go.sum
├── .gitignore
├── .golangci.yaml
├── certs
└── DigiCertGlobalRootCA.crt.pem
├── Makefile
├── REUSE.toml
└── README.md
/cmd/bulkbench/bulkbench.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SAP/go-hdb/HEAD/cmd/bulkbench/bulkbench.png
--------------------------------------------------------------------------------
/driver/internal/protocol/encoding/doc.go:
--------------------------------------------------------------------------------
1 | // Package encoding implements hdb field type en,- and decodings.
2 | package encoding
3 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/SAP/go-hdb
2 |
3 | go 1.24.0
4 |
5 | toolchain go1.25.5
6 |
7 | require golang.org/x/text v0.31.0
8 |
--------------------------------------------------------------------------------
/testdata/x509/ed25519.key:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | MC4CAQAwBQYDK2VwBCIEIMk76nF8pqvbwlCY6+6XBCuC2GKgN+CwOIbS8ipcO7Ns
3 | -----END PRIVATE KEY-----
4 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
2 | golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
3 |
--------------------------------------------------------------------------------
/driver/internal/protocol/doc.go:
--------------------------------------------------------------------------------
1 | // Package protocol implements the hdb command network protocol.
2 | //
3 | // http://help.sap.com/hana/SAP_HANA_SQL_Command_Network_Protocol_Reference_en.pdf
4 | package protocol
5 |
--------------------------------------------------------------------------------
/cmd/bulkbench/templates/dbresult.html:
--------------------------------------------------------------------------------
1 | {{define "dbresult"}}
2 |
3 | {{.Command}}
4 | {{.NumRow}}
5 | {{.Err}}
6 |
7 | {{end}}
8 |
9 | {{template "dbresult" .}}
10 |
--------------------------------------------------------------------------------
/driver/statscfg.json:
--------------------------------------------------------------------------------
1 | {
2 | "timeUnit": "ms",
3 | "sqlTimeTexts":["query", "prepare", "exec", "call", "fetch", "fetchlob", "rollback", "commit"],
4 | "timeUpperBounds": [1.0, 10.0, 100.0, 1000.0, 10000.0, 100000.0]
5 | }
6 |
--------------------------------------------------------------------------------
/sqlscript/doc.go:
--------------------------------------------------------------------------------
1 | // Package sqlscript provides functions to scan HDBSQL scripts with the help of bufio.Scanner.
2 | // This package is currently experimental and its public interface might be changed
3 | // in an incompatible way at any time.
4 | package sqlscript
5 |
--------------------------------------------------------------------------------
/testdata/x509/ec_p256.ec.key:
--------------------------------------------------------------------------------
1 | -----BEGIN EC PRIVATE KEY-----
2 | MHcCAQEEIFQ5tBNR1c8ixA/wgkR8BJAu4srFd4Q+jFiWaZOxzWewoAoGCCqGSM49
3 | AwEHoUQDQgAETnoiW85RM0TQNrG4K4jzQ7QFDZ+G1Ej4QGuP3pAXJ7BNQLSuwF79
4 | UeWoVZW8SzlZzZ/v3WTKa5BmfxM25pEI7g==
5 | -----END EC PRIVATE KEY-----
6 |
--------------------------------------------------------------------------------
/driver/internal/protocol/resizeslice.go:
--------------------------------------------------------------------------------
1 | package protocol
2 |
3 | func resizeSlice[S ~[]E, E any](s S, n int) S {
4 | switch {
5 | case s == nil:
6 | s = make(S, n)
7 | case n > cap(s):
8 | s = append(s, make(S, n-len(s))...)
9 | }
10 | return s[:n]
11 | }
12 |
--------------------------------------------------------------------------------
/testdata/x509/ec_p256.pkcs8.key:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgVDm0E1HVzyLED/CC
3 | RHwEkC7iysV3hD6MWJZpk7HNZ7ChRANCAAROeiJbzlEzRNA2sbgriPNDtAUNn4bU
4 | SPhAa4/ekBcnsE1AtK7AXv1R5ahVlbxLOVnNn+/dZMprkGZ/EzbmkQju
5 | -----END PRIVATE KEY-----
6 |
--------------------------------------------------------------------------------
/driver/doc.go:
--------------------------------------------------------------------------------
1 | // Package driver is a native Go SAP HANA driver implementation for the database/sql package.
2 | // For the SAP HANA SQL Command Network Protocol Reference please see:
3 | // https://help.sap.com/viewer/7e4aba181371442d9e4395e7ff71b777/2.0.03/en-US/9b9d8c894343424fac157c96dcb0a592.html
4 | package driver
5 |
--------------------------------------------------------------------------------
/cmd/bulkbench/templates/testresult.html:
--------------------------------------------------------------------------------
1 | {{define "testresult"}}
2 |
3 | {{.Sequential}}
4 | {{.BatchCount}}
5 | {{.BatchSize}}
6 | {{.BulkSize}}
7 | {{.Duration}}
8 | {{.Err}}
9 |
10 | {{end}}
11 |
12 | {{template "testresult" .}}
13 |
--------------------------------------------------------------------------------
/driver/internal/protocol/x_generator.go:
--------------------------------------------------------------------------------
1 | package protocol
2 |
3 | //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
4 |
--------------------------------------------------------------------------------
/testdata/x509/ec_p384.ec.key:
--------------------------------------------------------------------------------
1 | -----BEGIN EC PRIVATE KEY-----
2 | MIGkAgEBBDCdxPY/YCwnRUXZBPqvVJ7ky+ddocKYbwCYFZ7A6bgIdBWzz7YNKHt2
3 | IW7lZx6VviigBwYFK4EEACKhZANiAATZUKscHPml0iISfD7gRwMWVE/zOyIvta2T
4 | VqPPP0+FNwEXOrpuslVcyj8uxhegTaV1WdPwWOw2HIWz2Dvpc7qd4RPEKlV3MR0v
5 | R3MwX4qU+wZyUCPlYg22DEt1bpAsm+k=
6 | -----END EC PRIVATE KEY-----
7 |
--------------------------------------------------------------------------------
/driver/wgroup/wgroup1.25.go:
--------------------------------------------------------------------------------
1 | //go:build go1.25
2 |
3 | // Package wgroup provides compatibility on go1.24 and go1.25.
4 | package wgroup
5 |
6 | import "sync"
7 |
8 | // Go is a wrapper for sync.WaitGroup.Go and will be deleted if only go versions >= 1.25 are supported.
9 | func Go(wg *sync.WaitGroup, f func()) {
10 | wg.Go(f)
11 | }
12 |
--------------------------------------------------------------------------------
/.github/workflows/reuse.yml:
--------------------------------------------------------------------------------
1 | name: REUSE Compliance Check
2 | permissions:
3 | contents: read
4 | pull-requests: write
5 |
6 | on: [push, pull_request]
7 |
8 | jobs:
9 | test:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v4
13 | - name: REUSE Compliance Check
14 | uses: fsfe/reuse-action@v4
--------------------------------------------------------------------------------
/testdata/x509/ec_p384.pkcs8.key:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCdxPY/YCwnRUXZBPqv
3 | VJ7ky+ddocKYbwCYFZ7A6bgIdBWzz7YNKHt2IW7lZx6VviihZANiAATZUKscHPml
4 | 0iISfD7gRwMWVE/zOyIvta2TVqPPP0+FNwEXOrpuslVcyj8uxhegTaV1WdPwWOw2
5 | HIWz2Dvpc7qd4RPEKlV3MR0vR3MwX4qU+wZyUCPlYg22DEt1bpAsm+k=
6 | -----END PRIVATE KEY-----
7 |
--------------------------------------------------------------------------------
/testdata/x509/ec_p521.ec.key:
--------------------------------------------------------------------------------
1 | -----BEGIN EC PRIVATE KEY-----
2 | MIHcAgEBBEIA5dYmxFu/FdOVP8ssqF6IyYnThEHriL+tSs6JrPhih/MeupuHWKpy
3 | jFFGSbObITcuyMtdy4+95ybeGxKk6fEy2rWgBwYFK4EEACOhgYkDgYYABABuzvtv
4 | VJNLL3h9znFVmaIUUIsoN9MT20ppMcuCMHF3J2bnXImhKwLJfm968ECIAfizcA2y
5 | NMYZbqbiGIUybZWY2gFlhMDTZMy7U+vZ/sGwqdcJlRvY9nUXT86cm+WdP8N+YAom
6 | h6k7EG2y24LK6nXjvuBwlKDHg9O/XW68nW24+BBMsQ==
7 | -----END EC PRIVATE KEY-----
8 |
--------------------------------------------------------------------------------
/driver/wgroup/wgroup1.24.go:
--------------------------------------------------------------------------------
1 | //go:build !go1.25
2 |
3 | // Package wgroup wraps WaitGroup Go until anly go versions >= 1.25 are going to be supported.
4 | package wgroup
5 |
6 | import "sync"
7 |
8 | // Go is a wrapper for sync.WaitGroup.Go and will be deleted if only go versions >= 1.25 are supported.
9 | func Go(wg *sync.WaitGroup, f func()) {
10 | wg.Add(1)
11 | go func() {
12 | defer wg.Done()
13 | f()
14 | }()
15 | }
16 |
--------------------------------------------------------------------------------
/testdata/x509/ec_p521.pkcs8.key:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIA5dYmxFu/FdOVP8ss
3 | qF6IyYnThEHriL+tSs6JrPhih/MeupuHWKpyjFFGSbObITcuyMtdy4+95ybeGxKk
4 | 6fEy2rWhgYkDgYYABABuzvtvVJNLL3h9znFVmaIUUIsoN9MT20ppMcuCMHF3J2bn
5 | XImhKwLJfm968ECIAfizcA2yNMYZbqbiGIUybZWY2gFlhMDTZMy7U+vZ/sGwqdcJ
6 | lRvY9nUXT86cm+WdP8N+YAomh6k7EG2y24LK6nXjvuBwlKDHg9O/XW68nW24+BBM
7 | sQ==
8 | -----END PRIVATE KEY-----
9 |
--------------------------------------------------------------------------------
/driver/internal/protocol/resizeslice_test.go:
--------------------------------------------------------------------------------
1 | package protocol
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestResizeSlice(t *testing.T) {
8 | testSlices := [][]bool{
9 | nil,
10 | make([]bool, 0, 2),
11 | }
12 | n := 3
13 |
14 | for _, s := range testSlices {
15 | s = resizeSlice(s, n)
16 | if cap(s) < n || len(s) < n {
17 | t.Fatalf("resize error: cap %d len %d n %d", cap(s), len(s), n)
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/driver/dbconnectinfo.go:
--------------------------------------------------------------------------------
1 | package driver
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | // DBConnectInfo represents the connection information attributes returned by hdb.
8 | type DBConnectInfo struct {
9 | DatabaseName string
10 | Host string
11 | Port int
12 | IsConnected bool
13 | }
14 |
15 | func (ci *DBConnectInfo) String() string {
16 | return fmt.Sprintf("Database Name: %s Host: %s Port: %d connected: %t", ci.DatabaseName, ci.Host, ci.Port, ci.IsConnected)
17 | }
18 |
--------------------------------------------------------------------------------
/driver/example_test.go:
--------------------------------------------------------------------------------
1 | package driver_test
2 |
3 | import (
4 | "database/sql"
5 | "log"
6 |
7 | // Register hdb driver.
8 | _ "github.com/SAP/go-hdb/driver"
9 | )
10 |
11 | const (
12 | driverName = "hdb"
13 | hdbDsn = "hdb://user:password@host:port"
14 | )
15 |
16 | func Example() {
17 | db, err := sql.Open(driverName, hdbDsn)
18 | if err != nil {
19 | log.Fatal(err)
20 | }
21 | defer db.Close()
22 |
23 | if err := db.Ping(); err != nil {
24 | log.Fatal(err)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/prometheus/Makefile:
--------------------------------------------------------------------------------
1 | # builds and tests project via go tools
2 | all:
3 | @echo "update dependencies"
4 | go get -u ./...
5 | go mod tidy
6 | @echo "build and test"
7 | go build -v ./...
8 | go vet ./...
9 | golint -set_exit_status=true ./...
10 | staticcheck -checks all -fail none ./...
11 | golangci-lint run ./...
12 | @echo execute tests on latest go version
13 | go test ./...
14 | # @echo execute tests on older supported go versions
15 | GOTOOLCHAIN=go1.24.11 go1.24.11 test ./...
16 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "gomod" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "weekly"
12 |
--------------------------------------------------------------------------------
/driver/internal/unsafe/unsafe.go:
--------------------------------------------------------------------------------
1 | // Package unsafe provides wrapper functions for 'unsafe' type conversions.
2 | package unsafe
3 |
4 | import "unsafe"
5 |
6 | // String2ByteSlice converts a string to a byte slice.
7 | func String2ByteSlice(str string) []byte {
8 | if str == "" {
9 | return nil
10 | }
11 | return unsafe.Slice(unsafe.StringData(str), len(str))
12 | }
13 |
14 | // ByteSlice2String converts a byte slice to a string.
15 | func ByteSlice2String(bs []byte) string {
16 | if len(bs) == 0 {
17 | return ""
18 | }
19 | return unsafe.String(unsafe.SliceData(bs), len(bs))
20 | }
21 |
--------------------------------------------------------------------------------
/prometheus/.gitignore:
--------------------------------------------------------------------------------
1 | # If you prefer the allow list template instead of the deny list, see community template:
2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
3 | #
4 | # Binaries for programs and plugins
5 | *.exe
6 | *.exe~
7 | *.dll
8 | *.so
9 | *.dylib
10 |
11 | # Test binary, built with `go test -c`
12 | *.test
13 |
14 | # Output of the go coverage tool, specifically when used with LiteIDE
15 | *.out
16 |
17 | # Dependency directories (remove the comment below to include it)
18 | # vendor/
19 |
20 | # Go workspace file
21 | go.work
22 |
23 | # Other
24 | *.DS_Store
25 | .vscode
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # If you prefer the allow list template instead of the deny list, see community template:
2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
3 | #
4 | # Binaries for programs and plugins
5 | *.exe
6 | *.exe~
7 | *.dll
8 | *.so
9 | *.dylib
10 |
11 | # Test binary, built with `go test -c`
12 | *.test
13 |
14 | # Output of the go coverage tool, specifically when used with LiteIDE
15 | *.out
16 |
17 | # Dependency directories (remove the comment below to include it)
18 | # vendor/
19 |
20 | # Go workspace file
21 | go.work
22 |
23 | # Other
24 | *.DS_Store
25 | .vscode
26 | cmd/bulkbench/bulkbench
27 | cmd/sniffer/sniffer
28 |
--------------------------------------------------------------------------------
/driver/identifier_test.go:
--------------------------------------------------------------------------------
1 | package driver
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | type testIdentifier struct {
8 | id Identifier
9 | s string
10 | }
11 |
12 | var testIdentifierData = []*testIdentifier{
13 | {"_", "_"},
14 | {"_A", "_A"},
15 | {"A#$_", "A#$_"},
16 | {"1", `"1"`},
17 | {"a", `"a"`},
18 | {"$", `"$"`},
19 | {"日本語", `"日本語"`},
20 | {"testTransaction", `"testTransaction"`},
21 | {"a.b.c", `"a.b.c"`},
22 | {"AAA.BBB.CCC", `"AAA.BBB.CCC"`},
23 | }
24 |
25 | func TestIdentifierStringer(t *testing.T) {
26 | for i, d := range testIdentifierData {
27 | if d.id.String() != d.s {
28 | t.Fatalf("%d id %s - expected %s", i, d.id, d.s)
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/driver/bytes.go:
--------------------------------------------------------------------------------
1 | package driver
2 |
3 | import (
4 | "database/sql/driver"
5 | )
6 |
7 | // NullBytes represents an []byte that may be null.
8 | // NullBytes implements the Scanner interface so
9 | // it can be used as a scan destination, similar to NullString.
10 | type NullBytes struct {
11 | Bytes []byte
12 | Valid bool // Valid is true if Bytes is not NULL
13 | }
14 |
15 | // Scan implements the Scanner interface.
16 | func (n *NullBytes) Scan(value any) error {
17 | n.Bytes, n.Valid = value.([]byte)
18 | return nil
19 | }
20 |
21 | // Value implements the driver Valuer interface.
22 | func (n NullBytes) Value() (driver.Value, error) {
23 | if !n.Valid {
24 | return nil, nil
25 | }
26 | return n.Bytes, nil
27 | }
28 |
--------------------------------------------------------------------------------
/driver/example_dsn_test.go:
--------------------------------------------------------------------------------
1 | package driver_test
2 |
3 | import (
4 | "database/sql"
5 | "log"
6 | "net/url"
7 |
8 | "github.com/SAP/go-hdb/driver"
9 | )
10 |
11 | // dsn creates data source name with the help of the net/url package.
12 | func dsn() string {
13 | dsn := &url.URL{
14 | Scheme: driver.DriverName,
15 | User: url.UserPassword("user", "password"),
16 | Host: "host:port",
17 | }
18 | return dsn.String()
19 | }
20 |
21 | // ExampleDSN shows how to construct a DSN (data source name) as url.
22 | func ExampleDSN() {
23 | db, err := sql.Open(driver.DriverName, dsn())
24 | if err != nil {
25 | log.Fatal(err)
26 | }
27 | defer db.Close()
28 |
29 | if err := db.Ping(); err != nil {
30 | log.Fatal(err)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/driver/deprecated.go:
--------------------------------------------------------------------------------
1 | package driver
2 |
3 | import (
4 | "database/sql/driver"
5 | )
6 |
7 | // deprecated driver interface methods.
8 | func (*conn) Prepare(query string) (driver.Stmt, error) { panic("deprecated") }
9 | func (*conn) Begin() (driver.Tx, error) { panic("deprecated") }
10 | func (*conn) Exec(query string, args []driver.Value) (driver.Result, error) { panic("deprecated") }
11 | func (*conn) Query(query string, args []driver.Value) (driver.Rows, error) { panic("deprecated") }
12 | func (*stmt) Exec(args []driver.Value) (driver.Result, error) { panic("deprecated") }
13 | func (*stmt) Query(args []driver.Value) (rows driver.Rows, err error) { panic("deprecated") }
14 |
--------------------------------------------------------------------------------
/driver/internal/protocol/parts1.24.go:
--------------------------------------------------------------------------------
1 | //go:build !go1.25
2 |
3 | package protocol
4 |
5 | import "reflect"
6 |
7 | // newGenPartReader returns a generic part reader.
8 | func newGenPartReader(kind PartKind) Part {
9 | if kind == PkAuthentication {
10 | return nil // cannot instantiate generically
11 | }
12 | pt, ok := genPartTypeMap[kind]
13 | if !ok {
14 | // whether part cannot be instantiated generically or
15 | // part is not (yet) known to the driver
16 | return nil
17 | }
18 | // create instance
19 | part, ok := reflect.New(pt).Interface().(Part)
20 | if !ok {
21 | panic("part kind does not implement part reader interface") // should never happen
22 | }
23 | if part, ok := part.(initer); ok {
24 | part.init()
25 | }
26 | return part
27 | }
28 |
--------------------------------------------------------------------------------
/driver/internal/protocol/parts1.25.go:
--------------------------------------------------------------------------------
1 | //go:build go1.25
2 |
3 | package protocol
4 |
5 | import "reflect"
6 |
7 | // newGenPartReader returns a generic part reader.
8 | func newGenPartReader(kind PartKind) Part {
9 | if kind == PkAuthentication {
10 | return nil // cannot instantiate generically
11 | }
12 | pt, ok := genPartTypeMap[kind]
13 | if !ok {
14 | // whether part cannot be instantiated generically or
15 | // part is not (yet) known to the driver
16 | return nil
17 | }
18 | // create instance
19 | part, ok := reflect.TypeAssert[Part](reflect.New(pt))
20 | if !ok {
21 | panic("part kind does not implement part reader interface") // should never happen
22 | }
23 | if part, ok := part.(initer); ok {
24 | part.init()
25 | }
26 | return part
27 | }
28 |
--------------------------------------------------------------------------------
/driver/ping_test.go:
--------------------------------------------------------------------------------
1 | //go:build !unit
2 |
3 | package driver_test
4 |
5 | import (
6 | "testing"
7 |
8 | "github.com/SAP/go-hdb/driver"
9 | )
10 |
11 | func benchmarkPing(b *testing.B) {
12 | db := driver.MT.DB()
13 | if err := db.Ping(); err != nil {
14 | b.Fatal(err)
15 | }
16 | }
17 |
18 | func benchmarkPingSeq(b *testing.B) {
19 | for b.Loop() {
20 | benchmarkPing(b)
21 | }
22 | }
23 |
24 | func benchmarkPingPar(b *testing.B, pb *testing.PB) {
25 | for pb.Next() {
26 | benchmarkPing(b)
27 | }
28 | }
29 |
30 | func BenchmarkPing(b *testing.B) {
31 | b.Run("Ping sequentially", func(b *testing.B) {
32 | benchmarkPingSeq(b)
33 | })
34 | b.Run("Ping parallel", func(b *testing.B) {
35 | b.RunParallel(func(pb *testing.PB) { benchmarkPingPar(b, pb) })
36 | })
37 | }
38 |
--------------------------------------------------------------------------------
/prometheus/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/SAP/go-hdb/prometheus
2 |
3 | go 1.24.0
4 |
5 | toolchain go1.25.5
6 |
7 | replace github.com/SAP/go-hdb => ..
8 |
9 | require (
10 | github.com/SAP/go-hdb v1.14.14
11 | github.com/prometheus/client_golang v1.23.2
12 | )
13 |
14 | require (
15 | github.com/beorn7/perks v1.0.1 // indirect
16 | github.com/cespare/xxhash/v2 v2.3.0 // indirect
17 | github.com/kr/text v0.2.0 // indirect
18 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
19 | github.com/prometheus/client_model v0.6.2 // indirect
20 | github.com/prometheus/common v0.67.4 // indirect
21 | github.com/prometheus/procfs v0.19.2 // indirect
22 | go.yaml.in/yaml/v2 v2.4.3 // indirect
23 | golang.org/x/sys v0.38.0 // indirect
24 | golang.org/x/text v0.31.0 // indirect
25 | google.golang.org/protobuf v1.36.10 // indirect
26 | )
27 |
--------------------------------------------------------------------------------
/driver/example_error_test.go:
--------------------------------------------------------------------------------
1 | //go:build !unit
2 |
3 | package driver_test
4 |
5 | import (
6 | "database/sql"
7 | "errors"
8 | "fmt"
9 | "log"
10 |
11 | "github.com/SAP/go-hdb/driver"
12 | )
13 |
14 | const (
15 | errCodeInvalidTableName = 259
16 | )
17 |
18 | func ExampleError() {
19 | db := sql.OpenDB(driver.MT.Connector())
20 | defer db.Close()
21 |
22 | invalidTableName := driver.RandomIdentifier("table_")
23 | stmt, err := db.Query(fmt.Sprintf("select * from %s", invalidTableName))
24 | if err == nil {
25 | defer stmt.Close()
26 | }
27 |
28 | var dbError driver.Error
29 | if err != nil {
30 | // Check if error is driver.Error.
31 | if errors.As(err, &dbError) {
32 | switch dbError.Code() {
33 | case errCodeInvalidTableName:
34 | fmt.Print("invalid table name")
35 | default:
36 | log.Fatalf("code %d text %s", dbError.Code(), dbError.Text())
37 | }
38 | }
39 | }
40 | // output: invalid table name
41 | }
42 |
--------------------------------------------------------------------------------
/driver/example_db_test.go:
--------------------------------------------------------------------------------
1 | //go:build !unit
2 |
3 | package driver_test
4 |
5 | import (
6 | "database/sql"
7 | "log"
8 |
9 | "github.com/SAP/go-hdb/driver"
10 | )
11 |
12 | // ExampleDB shows hot to print extended database statistics with the help of
13 | // function driver.OpenDB and a driver.DB object.
14 | func ExampleDB() {
15 | // print default sql database statistics.
16 | db1 := sql.OpenDB(driver.MT.Connector())
17 | log.Printf("waitDuration: %d", db1.Stats().WaitDuration) // print field waitDuration of default database statistics.
18 | db1.Close()
19 |
20 | // print extended go-hdb driver db statistics.
21 | db2 := driver.OpenDB(driver.MT.Connector())
22 | log.Printf("waitDuration: %d", db2.Stats().WaitDuration) // print field waitDuration of default database statistics.
23 | log.Printf("bytesWritten: %d", db2.ExStats().WrittenBytes) // print field bytesWritten of extended driver database statistics.
24 | db2.Close()
25 | // output:
26 | }
27 |
--------------------------------------------------------------------------------
/driver/internal/protocol/rowsaffected.go:
--------------------------------------------------------------------------------
1 | package protocol
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/SAP/go-hdb/driver/internal/protocol/encoding"
7 | )
8 |
9 | // rows affected.
10 | const (
11 | raSuccessNoInfo = -2
12 | raExecutionFailed = -3
13 | )
14 |
15 | // rowsAffected represents a rows affected part.
16 | type rowsAffected struct {
17 | rows []int32
18 | }
19 |
20 | func (r rowsAffected) String() string {
21 | return fmt.Sprintf("%v", r.rows)
22 | }
23 |
24 | func (r *rowsAffected) decodeNumArg(dec *encoding.Decoder, numArg int) error {
25 | r.rows = resizeSlice(r.rows, numArg)
26 |
27 | for i := range numArg {
28 | r.rows[i] = dec.Int32()
29 | }
30 | return dec.Error()
31 | }
32 |
33 | // Total return the total number of all affected rows.
34 | func (r rowsAffected) Total() int64 {
35 | total := int64(0)
36 | for _, rows := range r.rows {
37 | if rows > 0 {
38 | total += int64(rows)
39 | }
40 | }
41 | return total
42 | }
43 |
--------------------------------------------------------------------------------
/driver/stats.tmpl:
--------------------------------------------------------------------------------
1 | {{define "time" -}}
2 | {{printf "%10d" .Count}} {{printf "%12.1f" .Sum}}{{range .Buckets}}{{printf "%10d" .}}{{end -}}
3 | {{end -}}
4 | {{define "bounds" -}}{{range $k, $v := . -}}{{printf "%10.1f" $k}}{{end}}{{end -}}
5 | openConnections {{.OpenConnections}}
6 | openTransactions {{.OpenTransactions}}
7 | openStatements {{.OpenStatements}}
8 | readBytes {{.ReadBytes}}
9 | writtenBytes {{.WrittenBytes}}
10 | sessionConnects {{.SessionConnects}}
11 | timeUnit {{.TimeUnit}}
12 | {{printf "%-12s" ""}}{{printf "%10s" "Count"}} {{printf "%12s" "Sum"}}{{template "bounds" .ReadTime.Buckets}}
13 | {{printf "%-12s" "readTime"}}{{template "time" .ReadTime}}
14 | {{printf "%-12s" "writeTime"}}{{template "time" .WriteTime}}
15 | {{printf "%-12s" "authTime"}}{{template "time" .AuthTime}}
16 | sqlTimes:
17 | {{range $k, $v := .SQLTimes -}}
18 | {{printf " %-10s" $k}}{{template "time" $v}}
19 | {{end}}
20 |
--------------------------------------------------------------------------------
/driver/identifier.go:
--------------------------------------------------------------------------------
1 | package driver
2 |
3 | import (
4 | "strconv"
5 |
6 | "github.com/SAP/go-hdb/driver/internal/rand/alphanum"
7 | )
8 |
9 | // Identifier in hdb SQL statements like schema or table name.
10 | type Identifier string
11 |
12 | // RandomIdentifier returns a random Identifier prefixed by the prefix parameter.
13 | // This function is used to generate database objects with random names for test and example code.
14 | func RandomIdentifier(prefix string) Identifier {
15 | return Identifier(prefix + alphanum.ReadString(16))
16 | }
17 |
18 | func (i Identifier) isSimple() bool {
19 | // var reSimple = regexp.MustCompile("^[_A-Z][_#$A-Z0-9]*$")
20 | for i, r := range i {
21 | switch {
22 | case r == '_' || ('A' <= r && r <= 'Z'): // valid char
23 | case i != 0 && (r == '#' || r == '$' || ('0' <= r && r <= '9')): // valid char for non first char
24 | default:
25 | return false
26 | }
27 | }
28 | return true
29 | }
30 | func (i Identifier) String() string {
31 | if i.isSimple() {
32 | return string(i)
33 | }
34 | return strconv.Quote(string(i))
35 | }
36 |
--------------------------------------------------------------------------------
/.golangci.yaml:
--------------------------------------------------------------------------------
1 | linters-settings:
2 | gocritic:
3 | disabled-checks:
4 | - exitAfterDefer
5 | gosec:
6 | excludes:
7 | - G113
8 | gomoddirectives:
9 | # Allow local `replace` directives.
10 | # Default: false
11 | replace-local: true
12 |
13 | linters:
14 | enable-all: true
15 | disable:
16 | - tenv
17 | - wsl
18 | - varnamelen
19 | - wrapcheck
20 | - testpackage
21 | - whitespace
22 | - thelper
23 | - testableexamples
24 | - recvcheck
25 | - paralleltest
26 | - nonamedreturns
27 | - nlreturn
28 | - nilnil
29 | - nilerr
30 | - nestif
31 | - mnd
32 | - lll
33 | - ireturn
34 | - gofumpt
35 | - godox
36 | - gochecknoinits
37 | - gochecknoglobals
38 | - exhaustruct
39 | - exhaustive
40 | - forcetypeassert
41 | - err113
42 | - depguard
43 | - errname
44 | - funlen
45 | - cyclop
46 | - contextcheck
47 | - gocognit
48 | - maintidx
49 | - revive
50 | - dupl
51 | - dupword
52 | - rowserrcheck
53 | - perfsprint
54 |
--------------------------------------------------------------------------------
/testdata/x509/ed25519.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIC0DCBuQIUREIcwgAcBS61spMmwnyzIIQaVFkwDQYJKoZIhvcNAQELBQAwJDEi
3 | MCAGA1UEAwwZR28tSERCIFguNTA5IFRlc3RzIFJvb3RDQTAeFw0yMzEwMDIxMDQ2
4 | MTFaFw0zMzA5MjgxMDQ2MTFaMCAxHjAcBgNVBAMMFUdvSERCVGVzdFVzZXJfZWQy
5 | NTUxOTAqMAUGAytlcAMhAP28erLkXC2y9aRY4PL3obc2STT8R0Ii8pDq7HhzWoKf
6 | MA0GCSqGSIb3DQEBCwUAA4ICAQC7VlGWbH6E4XGKb+cN1ejWqg8lmdQgJFBvKDnU
7 | 9d3EAS2xpT927yTcUWoDxP/ZIOdEBOjHAj7VVaqLIKphjL7jHBrHPAcPfdyzTymW
8 | I1Pl3T4JDkMd6rgNF4tUMz1ryFrKmKyMZeLzCQwFpo5Z9n5gFR9oQ2LnXB6EyEnn
9 | +VFw+EOcEj6QRmL6GUMTqPMLO83Zy09FhmMFlYNpYN5X5ZlVyX6d5wAOXeY0vRIw
10 | JgYDsLQZbF9aPNEohnk+HhhQtUu0nhqT622EIkg+7GJ1+PdFKxdRV17EpdZRWwP8
11 | GG7ccPzlMU+Mww9vihq+TNtB13FeGsYfKYvRAqJv28pR9NieE9jHCa4P/iXrgRCc
12 | hXwciNLV/85u87YMYkE8M3mZpB6M4KEb1iBnGf6Id6vLIRQVcIj5o65FpJDoLrTt
13 | OqHjAt/JLpHffAF7MOBFRqGWUyT8S0L3/yIAaBm/TSWT/MPzPO5wd9fVpLGKmRFN
14 | GfehJvZLAC5xvK3yYj7cYmdmalG1ps4X0broO2fDSC6B2UwtiyiCoY+SJls6wcXe
15 | lF4wycOLQDthHeo+i+i+zIEfBxzHHT5eKsPwjWypd0JIe9sagajbeZceuUDBr8rW
16 | bF7YiB2tt4+4G384YvujTK2CtegUfr+EGzp/E9/ouhuz9+bjGgWoBOwrUYQrNb7c
17 | XRSwNQ==
18 | -----END CERTIFICATE-----
19 |
--------------------------------------------------------------------------------
/driver/x_bstring_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | from hdbcli import dbapi
3 | import hashlib
4 | import argparse
5 |
6 | parser = argparse.ArgumentParser(description="bstring test script")
7 | parser.add_argument("address", help="address")
8 | parser.add_argument('port', type=int, help='port: 3xxxx')
9 | parser.add_argument('user', help='user')
10 | parser.add_argument('password', help='password')
11 | args = parser.parse_args()
12 |
13 | try:
14 | conn = dbapi.connect(address=args.address, port=args.port,user=args.user, password=args.password)
15 | try:
16 | cursor = conn.cursor()
17 | try:
18 | hash = hashlib.sha256()
19 | hash.update(b"TEST")
20 | cursor.execute("SELECT 'FOOBAR' FROM DUMMY WHERE HASH_SHA256('TEST') = :id", {"id": hash.digest()})
21 | except Exception as err:
22 | print("error: {}".format(err))
23 | finally:
24 | cursor.close()
25 | except Exceptiopn as err:
26 | print("error: {}".format(err))
27 | finally:
28 | conn.close()
29 | except Exception as err:
30 | print("error: {}".format(err))
31 |
--------------------------------------------------------------------------------
/driver/internal/rand/alphanum/rand.go:
--------------------------------------------------------------------------------
1 | // Package alphanum implements functions for randomized alphanum content.
2 | package alphanum
3 |
4 | import (
5 | "crypto/rand"
6 |
7 | "github.com/SAP/go-hdb/driver/internal/unsafe"
8 | )
9 |
10 | const csAlphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" // alphanumeric character set.
11 | var numAlphanum = byte(len(csAlphanum)) // len character sets <= max(byte)
12 |
13 | // Read fills p with random alphanumeric characters and returns the number of read bytes. It never returns an error, and always fills b entirely.
14 | func Read(p []byte) (n int, err error) {
15 | // starting with go1.24 rand.Read is never returning a error.
16 | rand.Read(p) //nolint: errcheck
17 | for i, b := range p {
18 | p[i] = csAlphanum[b%numAlphanum]
19 | }
20 | return n, nil
21 | }
22 |
23 | // ReadString returns a random string of alphanumeric characters and panics if crypto random reader returns an error.
24 | func ReadString(n int) string {
25 | b := make([]byte, n)
26 | Read(b) //nolint: errcheck
27 | return unsafe.ByteSlice2String(b)
28 | }
29 |
--------------------------------------------------------------------------------
/driver/internal/protocol/decodeerror.go:
--------------------------------------------------------------------------------
1 | package protocol
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | )
7 |
8 | // DecodeError represents a decoding error.
9 | type DecodeError struct {
10 | row int
11 | fieldName string
12 | err error
13 | }
14 |
15 | func (e *DecodeError) Unwrap() error { return e.err }
16 |
17 | func (e *DecodeError) Error() string {
18 | return fmt.Sprintf("decode error: %s row: %d fieldname: %s", e.err, e.row, e.fieldName)
19 | }
20 |
21 | // DecodeErrors represents a list of decoding errors.
22 | type DecodeErrors []*DecodeError
23 |
24 | func (errs DecodeErrors) rowErrors(row int) error {
25 | var rowErrs []error
26 | for _, err := range errs {
27 | if err.row == row {
28 | rowErrs = append(rowErrs, err)
29 | }
30 | }
31 | switch len(rowErrs) {
32 | case 0:
33 | return nil
34 | case 1:
35 | return rowErrs[0]
36 | default:
37 | return errors.Join(rowErrs...)
38 | }
39 | }
40 |
41 | // RowErrors returns errors if they were assigned to a row, nil otherwise.
42 | func (errs DecodeErrors) RowErrors(row int) error {
43 | if len(errs) == 0 {
44 | return nil
45 | }
46 | return errs.rowErrors(row)
47 | }
48 |
--------------------------------------------------------------------------------
/driver/dial/dialer.go:
--------------------------------------------------------------------------------
1 | // Package dial provides types to implement go-hdb custom dialers.
2 | package dial
3 |
4 | import (
5 | "context"
6 | "net"
7 | "time"
8 | )
9 |
10 | // DialerOptions contains optional parameters that might be used by a Dialer.
11 | type DialerOptions struct {
12 | Timeout, TCPKeepAlive time.Duration
13 | TCPKeepAliveConfig net.KeepAliveConfig
14 | }
15 |
16 | // The Dialer interface needs to be implemented by custom Dialers. A Dialer for providing a custom driver connection
17 | // to the database can be set in the driver.Connector object.
18 | type Dialer interface {
19 | DialContext(ctx context.Context, address string, options DialerOptions) (net.Conn, error)
20 | }
21 |
22 | // DefaultDialer is the default driver Dialer implementation.
23 | var DefaultDialer Dialer = &dialer{}
24 |
25 | // default dialer implementation.
26 | type dialer struct{}
27 |
28 | func (d *dialer) DialContext(ctx context.Context, address string, options DialerOptions) (net.Conn, error) {
29 | dialer := net.Dialer{Timeout: options.Timeout, KeepAlive: options.TCPKeepAlive, KeepAliveConfig: options.TCPKeepAliveConfig}
30 | return dialer.DialContext(ctx, "tcp", address)
31 | }
32 |
--------------------------------------------------------------------------------
/testdata/x509/ec_p256.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIC/zCB6AIUREIcwgAcBS61spMmwnyzIIQaVFYwDQYJKoZIhvcNAQELBQAwJDEi
3 | MCAGA1UEAwwZR28tSERCIFguNTA5IFRlc3RzIFJvb3RDQTAeFw0yMzEwMDIxMDQ0
4 | MjFaFw0zMzA5MjgxMDQ0MjFaMCAxHjAcBgNVBAMMFUdvSERCVGVzdFVzZXJfZWNf
5 | cDI1NjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABE56IlvOUTNE0DaxuCuI80O0
6 | BQ2fhtRI+EBrj96QFyewTUC0rsBe/VHlqFWVvEs5Wc2f791kymuQZn8TNuaRCO4w
7 | DQYJKoZIhvcNAQELBQADggIBAGoTvl7ljerYt0g0CrsqyB+a5W5SVefeTp+Q/Ah2
8 | sBCQ3V5ZB0BrLED0yMptf2DS3mpsfkVGHQ74eOGjiOZ4/Zwa9uC26Oj6qtgMkc3m
9 | lII7nAjLezyvgeBEd0mZTwe8XK0V9CgHKzJR+qI8cH9p23B77hQM4J5IyZ44xSVy
10 | iQ94VIflj33jc3HrOymsrzBNC956dMuHyozYCyzIIndsiHE0J+aSNjzTkc3owwvl
11 | vN0Hyg3wTIJRqnrbXTxVDGSEyHRMG5Z0UeAzj/ozZv1DZXFo85/r1v8st6ixpB/O
12 | iHyeD5SMlKBOvqeE1v0jGYIZ1QceHT7jfQY7ol0qJ+f26YNgUhnJPxgC72It26Lc
13 | ip1ZmwcETENrBR007fR+5m+ztCXh89KW2yHkBkB0cypDcvq0pMKpyFP/p50scpZs
14 | DTt1Pfr7k9ufQC2coR+4FFV4m0/2uLMEsY74ckNspvSlxv+DEtBDikj25dAYOWQC
15 | X4ymJjWiJub0r8O+OsvkT+RzDto51fcMm5DQR7N7bqytRBf4/ZrzLNc1HhQk0KGK
16 | N6OCAR90STj4o8lmeXMW2CgHvft8ZJJ+vG3oRJTKHjrl9MwbRuHqUQqLiN0VXqoG
17 | lqwcn0ZApFqSeh4D7fPnLxd9OpRB4VYJ1gVpwYNyzZFZfcHHcQU5VKIQoLBgasZR
18 | igy0
19 | -----END CERTIFICATE-----
20 |
--------------------------------------------------------------------------------
/testdata/x509/ec_p384.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDHTCCAQUCFERCHMIAHAUutbKTJsJ8syCEGlRXMA0GCSqGSIb3DQEBCwUAMCQx
3 | IjAgBgNVBAMMGUdvLUhEQiBYLjUwOSBUZXN0cyBSb290Q0EwHhcNMjMxMDAyMTA0
4 | NTAzWhcNMzMwOTI4MTA0NTAzWjAgMR4wHAYDVQQDDBVHb0hEQlRlc3RVc2VyX2Vj
5 | X3AzODQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATZUKscHPml0iISfD7gRwMWVE/z
6 | OyIvta2TVqPPP0+FNwEXOrpuslVcyj8uxhegTaV1WdPwWOw2HIWz2Dvpc7qd4RPE
7 | KlV3MR0vR3MwX4qU+wZyUCPlYg22DEt1bpAsm+kwDQYJKoZIhvcNAQELBQADggIB
8 | AIl3FfH+jCnOWzQyXeHcl40FPvOynBPbOtpoozYNAqh+UyyC4ETyis4r5up5X/Py
9 | OyEfaSMWXbPegrk18d98uMlUzuq2hOiy3AxH0FprTjkZea55FXiseAPhMmz/kGoR
10 | N6iwyVLtOY4BYzHs+4+B8RxYS/HLNQdgGkSMxVbWCKSVzMzxeBOtCMl+8wHk92Z8
11 | 9syEk/u02l14FU6qFuyf3wwZEjIp3/xx+WaXG9ZtRdexbrAu4hz4CUg31Rqa8quw
12 | KvdQhLfvnuWPIKdCmJWD+YDspuMsUWH9nGvADrFK0z32tfS2zootn+p5btJzcUum
13 | Rx3XErbZP4pJ61CvSBYAfK3ja33yL2Dvuav3j3rQsLusA5LCsx/4TkovS1KD7gql
14 | O41FdSmmycivrSIdsjPVBTewcG8Brnivoeq0wTCH+TvDKhfboab+jYWhh/33i4oI
15 | v/pX2s/ZxEbMsmbh1TfxFAFXT9KwnDOKRw0nRavBkfEFbn6t2xUSURXQl1Ua1sY8
16 | hKYjJm+wcvV/a0N5EXbcbIul7jOr1TC9pNqijlZGIpP+6AOIadkosjchYnyz8TI2
17 | eJgK+0lUj6mSpZkaomcV1WYugdvfMZB2kEAol8CchpNi5CQJqW74GI7wQoGjaYZE
18 | KeUuo/REuQxdeTZ2DRb5d+/75lG2uqpI0pe8hFZLBZxc
19 | -----END CERTIFICATE-----
20 |
--------------------------------------------------------------------------------
/testdata/x509/ec_p521.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDQzCCASsCFERCHMIAHAUutbKTJsJ8syCEGlRYMA0GCSqGSIb3DQEBCwUAMCQx
3 | IjAgBgNVBAMMGUdvLUhEQiBYLjUwOSBUZXN0cyBSb290Q0EwHhcNMjMxMDAyMTA0
4 | NTQwWhcNMzMwOTI4MTA0NTQwWjAgMR4wHAYDVQQDDBVHb0hEQlRlc3RVc2VyX2Vj
5 | X3A1MjEwgZswEAYHKoZIzj0CAQYFK4EEACMDgYYABABuzvtvVJNLL3h9znFVmaIU
6 | UIsoN9MT20ppMcuCMHF3J2bnXImhKwLJfm968ECIAfizcA2yNMYZbqbiGIUybZWY
7 | 2gFlhMDTZMy7U+vZ/sGwqdcJlRvY9nUXT86cm+WdP8N+YAomh6k7EG2y24LK6nXj
8 | vuBwlKDHg9O/XW68nW24+BBMsTANBgkqhkiG9w0BAQsFAAOCAgEASkxdFL8YlICp
9 | FWSRyLwZoKWpn79SH1heXzLOahhtJ4agegq+vLK4Ou5EvuRhARdhXeJcJzIy3bPN
10 | AT5AZjjEatrTQtqXzycO24D8i5YqJp1PkzzWasdtZOJba6Ge+kE3QfMRY0+FGhVi
11 | foa2K6HLQ0OREzTxtanojiDak6UaJkUBQDANTNd6dtX5rAuUrMKzBBUBzUQlIBQh
12 | bvQfWah05C4ibLK5E/bMgZUhS4pBxKc7zj8uOHW62bGkLJkcl7qn7YyjRVBc/8fZ
13 | y3BEBlxzyTZmnjk9CbmQaK/DmWcayB5nAvLK4ItKk39Su9bP4PTkMNn4/1ir9qyf
14 | urSKPCk4FSockOC5b2IeHBgvyokuKxNjGrCshG5JK31aWoHZng5dVQrmaRY1CqSW
15 | HcTi31KBtW6J3naWlMOq16MMVBODoO1wKoHEpAXDsJ+UzvwfG6VIiZsvESiKkOHL
16 | c666zIRAtnA+5uYd0PTOCYMbecRybVinSvf1AHR6uwRfflljsyVCb/LH0991k01m
17 | rdP0urlpv7TRGsvEAxIRAMYXaDriDE6hlGMcqudglhAL66vwHX+bcG820qDlcff/
18 | vYIif6293GieNBp+JR/q/8Sc8j5iZ9vGenVBXtVTDCICHzNfdUERqlbhkFVEMGpJ
19 | uoAPuT2f6r9Pib26YFIiEThf/o0usAk=
20 | -----END CERTIFICATE-----
21 |
--------------------------------------------------------------------------------
/cmd/bulkbench/url.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "net/http"
5 | "net/url"
6 | "strconv"
7 | )
8 |
9 | const (
10 | urlQuerySequential = "sequential"
11 | urlQueryBatchCount = "batchcount"
12 | urlQueryBatchSize = "batchsize"
13 | urlQueryCommand = "command"
14 | )
15 |
16 | type urlQuery struct {
17 | values url.Values
18 | }
19 |
20 | func newURLQuery(r *http.Request) *urlQuery {
21 | return &urlQuery{values: r.URL.Query()}
22 | }
23 |
24 | func (q *urlQuery) get(name string) (string, bool) {
25 | v := q.values.Get(name)
26 | if v == "" {
27 | return v, false
28 | }
29 | return v, true
30 | }
31 |
32 | func (q *urlQuery) getString(name string, defValue string) string {
33 | s, ok := q.get(name)
34 | if !ok {
35 | return defValue
36 | }
37 | return s
38 | }
39 |
40 | func (q *urlQuery) getBool(name string, defValue bool) bool {
41 | s, ok := q.get(name)
42 | if !ok {
43 | return defValue
44 | }
45 | b, err := strconv.ParseBool(s)
46 | if err != nil {
47 | return defValue
48 | }
49 | return b
50 | }
51 |
52 | func (q *urlQuery) getInt(name string, defValue int) int {
53 | s, ok := q.get(name)
54 | if !ok {
55 | return defValue
56 | }
57 | i, err := strconv.Atoi(s)
58 | if err != nil {
59 | return defValue
60 | }
61 | return i
62 | }
63 |
--------------------------------------------------------------------------------
/driver/internal/protocol/keyvaluesparts.go:
--------------------------------------------------------------------------------
1 | package protocol
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/SAP/go-hdb/driver/internal/protocol/encoding"
7 | )
8 |
9 | type clientInfo map[string]string
10 |
11 | func (c clientInfo) String() string { return fmt.Sprintf("%v", map[string]string(c)) }
12 |
13 | func (c clientInfo) size() int {
14 | size := 0
15 | for k, v := range c {
16 | size += encoding.Cesu8FieldSize(k)
17 | size += encoding.Cesu8FieldSize(v)
18 | }
19 | return size
20 | }
21 |
22 | func (c clientInfo) numArg() int { return len(c) }
23 |
24 | func (c *clientInfo) decodeNumArg(dec *encoding.Decoder, numArg int) error {
25 | *c = clientInfo{} // no reuse of maps - create new one
26 |
27 | for range numArg {
28 | k, err := dec.Cesu8Field()
29 | if err != nil {
30 | return err
31 | }
32 | v, err := dec.Cesu8Field()
33 | if err != nil {
34 | return err
35 | }
36 | // set key value
37 | (*c)[string(k.([]byte))] = string(v.([]byte))
38 | }
39 | return dec.Error()
40 | }
41 |
42 | func (c clientInfo) encode(enc *encoding.Encoder) error {
43 | for k, v := range c {
44 | if err := enc.Cesu8Field(k); err != nil {
45 | return err
46 | }
47 | if err := enc.Cesu8Field(v); err != nil {
48 | return err
49 | }
50 | }
51 | return nil
52 | }
53 |
--------------------------------------------------------------------------------
/driver/internal/protocol/auth/list.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | import (
4 | "sync"
5 | )
6 |
7 | type comparer[E any] interface {
8 | Compare(e E) bool
9 | }
10 |
11 | type list[K comparer[K], V any] struct {
12 | valueFn func(k K) (V, error)
13 | mu sync.RWMutex
14 | idx int
15 | keys []K
16 | values []V
17 | }
18 |
19 | func newList[K comparer[K], V any](maxEntry int, valueFn func(k K) (V, error)) *list[K, V] {
20 | return &list[K, V]{
21 | valueFn: valueFn,
22 | keys: make([]K, 0, maxEntry),
23 | values: make([]V, 0, maxEntry),
24 | }
25 | }
26 |
27 | func (l *list[K, V]) find(k K) (v V, ok bool) {
28 | l.mu.RLock()
29 | defer l.mu.RUnlock()
30 | for i, k1 := range l.keys {
31 | if k1.Compare(k) {
32 | return l.values[i], true
33 | }
34 | }
35 | return
36 | }
37 |
38 | func (l *list[K, V]) Get(k K) (V, error) {
39 | if v, ok := l.find(k); ok {
40 | return v, nil
41 | }
42 | l.mu.Lock()
43 | defer l.mu.Unlock()
44 | v, err := l.valueFn(k)
45 | if err != nil {
46 | return v, err
47 | }
48 | if l.idx < len(l.keys) {
49 | l.keys[l.idx], l.values[l.idx] = k, v
50 | } else {
51 | l.keys = append(l.keys, k)
52 | l.values = append(l.values, v)
53 | }
54 | l.idx++
55 | if l.idx >= cap(l.keys) {
56 | l.idx = 0
57 | }
58 | return v, nil
59 | }
60 |
--------------------------------------------------------------------------------
/cmd/bulkbench/prm.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "strconv"
7 | "strings"
8 | )
9 |
10 | // prm represents a test parameter consisting of BatchCount and BatchSize.
11 | type prm struct {
12 | BatchCount, BatchSize int
13 | }
14 |
15 | // prmsValue represents a flag value for parameters.
16 | type prmsValue []prm
17 |
18 | // String implements the flag.Value interface.
19 | func (v prmsValue) String() string {
20 | b := new(bytes.Buffer)
21 | last := len(v) - 1
22 | for i, prm := range v {
23 | b.WriteString(strconv.Itoa(prm.BatchCount))
24 | b.WriteString("x")
25 | b.WriteString(strconv.Itoa(prm.BatchSize))
26 | if i != last {
27 | b.WriteString(" ")
28 | }
29 | }
30 | return b.String()
31 | }
32 |
33 | // Set implements the flag.Value interface.
34 | func (v *prmsValue) Set(s string) error {
35 | *v = nil // clear slice
36 | for _, ts := range strings.Split(s, " ") {
37 | t := strings.Split(ts, "x")
38 | if len(t) != 2 {
39 | return fmt.Errorf("invalid value: %s", s)
40 | }
41 | var err error
42 | var prm prm
43 | prm.BatchCount, err = strconv.Atoi(t[0])
44 | if err != nil {
45 | return err
46 | }
47 | prm.BatchSize, err = strconv.Atoi(t[1])
48 | if err != nil {
49 | return err
50 | }
51 | *v = append(*v, prm)
52 | }
53 | return nil
54 | }
55 |
--------------------------------------------------------------------------------
/driver/internal/protocol/dfv.go:
--------------------------------------------------------------------------------
1 | package protocol
2 |
3 | // Data format version values.
4 | const (
5 | DfvLevel0 int = 0 // base data format
6 | DfvLevel1 int = 1 // eval types support all data types
7 | DfvLevel2 int = 2 // reserved, broken, do not use
8 | DfvLevel3 int = 3 // additional types Longdate, Secondate, Daydate, Secondtime supported for NGAP
9 | DfvLevel4 int = 4 // generic support for new date/time types
10 | DfvLevel5 int = 5 // spatial types in ODBC on request
11 | DfvLevel6 int = 6 // BINTEXT
12 | DfvLevel7 int = 7 // with boolean support
13 | DfvLevel8 int = 8 // with FIXED8/12/16 support
14 | )
15 |
16 | var (
17 | defaultDfv = DfvLevel8
18 | supportedDfvs = []int{DfvLevel1, DfvLevel4, DfvLevel6, DfvLevel8}
19 | )
20 |
21 | // SupportedDfvs returns a slice of data format versions supported by the driver.
22 | // If parameter defaultOnly is set only the default dfv is returned, otherwise
23 | // all supported dfv values are returned.
24 | func SupportedDfvs(defaultOnly bool) []int {
25 | if defaultOnly {
26 | return []int{defaultDfv}
27 | }
28 | return supportedDfvs
29 | }
30 |
31 | // IsSupportedDfv returns true if the data format version dfv is supported by the driver, false otherwise.
32 | func IsSupportedDfv(dfv int) bool {
33 | return dfv == DfvLevel1 || dfv == DfvLevel4 || dfv == DfvLevel6 || dfv == DfvLevel8
34 | }
35 |
--------------------------------------------------------------------------------
/driver/conn_test.go:
--------------------------------------------------------------------------------
1 | //go:build !unit
2 |
3 | package driver
4 |
5 | import (
6 | "context"
7 | "database/sql"
8 | "errors"
9 | "testing"
10 | )
11 |
12 | func testCancelContext(t *testing.T, db *sql.DB) {
13 | stmt, err := db.Prepare("select * from dummy")
14 | if err != nil {
15 | t.Fatal(err)
16 | }
17 | defer stmt.Close()
18 |
19 | // create cancel context
20 | ctx, cancel := context.WithCancel(t.Context())
21 |
22 | // callback function to cancel context
23 | cancelCtx := func(op int) {
24 | if op == choStmtExec {
25 | cancel()
26 | }
27 | }
28 | // set hook context.
29 | hookCtx := withConnHook(ctx, cancelCtx)
30 | // exec - should return with error context.Cancelled.
31 | if _, err := stmt.ExecContext(hookCtx); !errors.Is(err, context.Canceled) {
32 | t.Fatal(err)
33 | }
34 |
35 | // use statement again - should work even first stmt.Exec got cancelled.
36 | for range 5 {
37 | if _, err := stmt.Exec(); err != nil {
38 | t.Fatal(err)
39 | }
40 | }
41 | }
42 |
43 | func TestConnection(t *testing.T) {
44 | t.Parallel()
45 |
46 | tests := []struct {
47 | name string
48 | fct func(t *testing.T, db *sql.DB)
49 | }{
50 | {"cancelContext", testCancelContext},
51 | }
52 |
53 | db := MT.DB()
54 | for _, test := range tests {
55 | t.Run(test.name, func(t *testing.T) {
56 | t.Parallel()
57 | test.fct(t, db)
58 | })
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/driver/example_decimal_test.go:
--------------------------------------------------------------------------------
1 | //go:build !unit
2 |
3 | package driver_test
4 |
5 | import (
6 | "database/sql"
7 | "fmt"
8 | "log"
9 | "math/big"
10 |
11 | "github.com/SAP/go-hdb/driver"
12 | )
13 |
14 | /*
15 | ExampleDecimal creates a table with a single decimal attribute, insert a record into it and select the entry afterwards.
16 | This demonstrates the usage of the type Decimal to write and scan decimal database attributes.
17 | */
18 | func ExampleDecimal() {
19 | db := sql.OpenDB(driver.MT.Connector())
20 | defer db.Close()
21 |
22 | tableName := driver.RandomIdentifier("table_")
23 |
24 | if _, err := db.Exec(fmt.Sprintf("create table %s (x decimal)", tableName)); err != nil { // Create table with decimal attribute.
25 | log.Fatal(err)
26 | }
27 |
28 | // Decimal values are represented in Go as big.Rat.
29 | in := (*driver.Decimal)(big.NewRat(1, 1)) // Create *big.Rat and cast to Decimal.
30 |
31 | if _, err := db.Exec(fmt.Sprintf("insert into %s values(?)", tableName), in); err != nil { // Insert record.
32 | log.Fatal(err)
33 | }
34 |
35 | var out driver.Decimal // Declare scan variable.
36 |
37 | if err := db.QueryRow(fmt.Sprintf("select * from %s", tableName)).Scan(&out); err != nil {
38 | log.Fatal(err)
39 | }
40 |
41 | fmt.Printf("Decimal value: %s", (*big.Rat)(&out).String()) // Cast scan variable to *big.Rat to use *big.Rat methods.
42 |
43 | // output: Decimal value: 1/1
44 | }
45 |
--------------------------------------------------------------------------------
/certs/DigiCertGlobalRootCA.crt.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
3 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
4 | d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
5 | QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
6 | MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
7 | b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
8 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
9 | CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
10 | nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
11 | 43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
12 | T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
13 | gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
14 | BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
15 | TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
16 | DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
17 | hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
18 | 06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
19 | PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
20 | YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
21 | CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
22 | -----END CERTIFICATE-----
23 |
--------------------------------------------------------------------------------
/driver/stats.go:
--------------------------------------------------------------------------------
1 | package driver
2 |
3 | // StatsHistogram represents statistic data in a histogram structure.
4 | type StatsHistogram struct {
5 | // Count holds the number of measurements
6 | Count uint64
7 | // Sum holds the sum of the measurements.
8 | Sum float64
9 | // Buckets contains the count of measurements belonging to a bucket where the
10 | // value of the measurement is less or equal the bucket map key.
11 | Buckets map[float64]uint64
12 | }
13 |
14 | // Stats contains driver statistics.
15 | type Stats struct {
16 | // Gauges
17 | OpenConnections int // The number of current established driver connections.
18 | OpenTransactions int // The number of current open driver transactions.
19 | OpenStatements int // The number of current open driver database statements.
20 | // Counters
21 | ReadBytes uint64 // Total bytes read by client connection.
22 | WrittenBytes uint64 // Total bytes written by client connection.
23 | SessionConnects uint64 // Total number of session connects (switch users).
24 | // Time histograms (Sum and upper bounds in Unit)
25 | TimeUnit string // Time unit
26 | ReadTime *StatsHistogram // Time spent on reading from connection.
27 | WriteTime *StatsHistogram // Time spent on writing to connection.
28 | AuthTime *StatsHistogram // Time spent on authentication.
29 | SQLTimes map[string]*StatsHistogram // Time spent on different SQL statements.
30 | }
31 |
--------------------------------------------------------------------------------
/testdata/x509/rsa.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDxzCCAa8CFERCHMIAHAUutbKTJsJ8syCEGlRVMA0GCSqGSIb3DQEBCwUAMCQx
3 | IjAgBgNVBAMMGUdvLUhEQiBYLjUwOSBUZXN0cyBSb290Q0EwHhcNMjMxMDAyMTA0
4 | MjU4WhcNMzMwOTI4MTA0MjU4WjAcMRowGAYDVQQDDBFHb0hEQlRlc3RVc2VyX3Jz
5 | YTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOlXXvE8v+DsK6IOlEvf
6 | gvq0TJcKZEPOXoPVZR9rB9bjMAu3xgs/D42iQ/s4kM+8b3QKUuPE/EgGAFt4GaeJ
7 | ro5t+QkocXrl8zOUplOT1XmVAybxs5qR+ca60+c1NshST42AqiL1Fo2aWjlNmcgM
8 | 2UIHxtscOblH9k74LDeeuYwCUiIuxVqQ7yVLDHJ4WAV3ka22Pg9MH+VwxaQ3I6BE
9 | cgyGPU8RJUjBSpOnxEY5EPSmkU7fNXuIyGrVLxTUwGDJ4qlbs9RdqJtTSO8dprTs
10 | peExqDCuU20VHuidgloUr30OxZEw+uU1uI/Clb/Xueup0tKOOA5BoEVn5HLnaVN8
11 | AokCAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAAewZeuaz9tAtfqrbxNAi7p+2dCU9
12 | JlgNQm91wFZNqrkYdVvzDmPa5SKyq41qhyqPuD9U8Fsr/np0KH098XJVrwmEgFbQ
13 | UN+5w3KS2BjICbwMp2EJ4SwHZiqmVVMIjFXrkkg4Ffs7Xk+PAwphrf9FBKjWaVqA
14 | Gl6xv/YRENQWp+CkJ+CyuYpvFHBNnsVVlM+Wrwq3VONTlbpU3JGZcAfuvNrDCQK2
15 | jevBbvvh9/8EgPrVFYuOo+1qrOs7IUz0wa6ic0X+3RbUNlPDP0rBc8GWbklPIgsk
16 | lJlsNvUftYL/PA+sEq/53RETJr0DU0ncR3w+ySizBUgNuOnSKb/XfsDXH+1M/yEj
17 | DDOinKPz6nX9qAm/db+ozSFq4mk4ldhzbo8VCwdq0W1Zu1mJ78Iofff4pC/+JDQg
18 | R2fiHinEnJyNeeX7+mzdQCgDcrhBXZwmeDwzArEyddIPb5t/qrqPanrkcM9zzv3e
19 | ODQVnqmWHLJsf8KEc8rdhJ0eqBccFRyjwcSusiSlSp02ulNTJ9KxXt+zdZJbEL50
20 | X3cIUcCoHNsOXeZCUWH/UAgi6Dy8qX6qbs+9bt5mQlizmw92T6aT9ktIQJC8UM5x
21 | PcW7fMVZs5sdV8CGHow38ZqBvInujSZx2ww0H5HnTDNx2WlX4HN7qhS2/QlS3euy
22 | AUxhVq0QnDw/Vl0=
23 | -----END CERTIFICATE-----
24 |
--------------------------------------------------------------------------------
/sqlscript/example_test.go:
--------------------------------------------------------------------------------
1 | //go:build !unit
2 |
3 | package sqlscript_test
4 |
5 | import (
6 | "bufio"
7 | "database/sql"
8 | "log"
9 | "os"
10 | "strings"
11 |
12 | "github.com/SAP/go-hdb/driver"
13 | "github.com/SAP/go-hdb/sqlscript"
14 | )
15 |
16 | // Example demonstrates the usage of go-hdb sqlscript scanning functions.
17 | func Example() {
18 | ddlScript := `
19 | -- create local temporary table
20 | CREATE LOCAL TEMPORARY TABLE #my_local_temp_table (
21 | Column1 INTEGER,
22 | Column2 VARCHAR(10)
23 | );
24 | --- insert some records
25 | INSERT INTO #MY_LOCAL_TEMP_TABLE VALUES (1,'A');
26 | INSERT INTO #MY_LOCAL_TEMP_TABLE VALUES (2,'B');
27 | --- and drop the table
28 | DROP TABLE #my_local_temp_table
29 | `
30 |
31 | const envDSN = "GOHDBDSN"
32 |
33 | dsn := os.Getenv(envDSN)
34 | // exit if dsn is missing.
35 | if dsn == "" {
36 | return
37 | }
38 |
39 | connector, err := driver.NewDSNConnector(dsn)
40 | if err != nil {
41 | log.Fatal(err)
42 | }
43 |
44 | db := sql.OpenDB(connector)
45 | defer db.Close()
46 |
47 | scanner := bufio.NewScanner(strings.NewReader(ddlScript))
48 | // Include comments as part of the sql statements.
49 | scanner.Split(sqlscript.ScanFunc(sqlscript.DefaultSeparator, true))
50 |
51 | for scanner.Scan() {
52 | if _, err := db.Exec(scanner.Text()); err != nil {
53 | log.Fatal(err)
54 | }
55 | }
56 | if err := scanner.Err(); err != nil {
57 | log.Fatal(err)
58 | }
59 |
60 | // output:
61 | }
62 |
--------------------------------------------------------------------------------
/driver/example_userswitch_test.go:
--------------------------------------------------------------------------------
1 | //go:build !unit
2 |
3 | package driver_test
4 |
5 | import (
6 | "context"
7 | "database/sql"
8 | "fmt"
9 | "log"
10 |
11 | "github.com/SAP/go-hdb/driver"
12 | )
13 |
14 | // ExampleWithUserSwitch demonstrates switching users on new or existing connections.
15 | func ExampleWithUserSwitch() {
16 | ctr := driver.MT.Connector()
17 | db := sql.OpenDB(ctr)
18 | defer db.Close()
19 |
20 | tableName := driver.RandomIdentifier("table_")
21 |
22 | sessionUser := &driver.SessionUser{Username: ctr.Username(), Password: ctr.Password()}
23 | ctx := driver.WithUserSwitch(context.Background(), sessionUser)
24 |
25 | // Create table.
26 | if _, err := db.ExecContext(context.Background(), fmt.Sprintf("create table %s (i integer)", tableName)); err != nil {
27 | log.Fatal(err)
28 | }
29 |
30 | // Switch user via context.
31 | stmt, err := db.PrepareContext(ctx, fmt.Sprintf("insert into %s values (?)", tableName))
32 | if err != nil {
33 | log.Fatal(err)
34 | }
35 | defer stmt.Close()
36 | // Switch to different user not possible in context of statement and transactions, but
37 | // former context can be used as long as the session user data are not changed.
38 | if _, err := stmt.ExecContext(ctx, 42); err != nil {
39 | log.Fatal(err)
40 | }
41 |
42 | // Drop table.
43 | if _, err := db.ExecContext(context.Background(), fmt.Sprintf("drop table %s", tableName)); err != nil {
44 | log.Fatal(err)
45 | }
46 |
47 | // output:
48 | }
49 |
--------------------------------------------------------------------------------
/driver/metadata.go:
--------------------------------------------------------------------------------
1 | package driver
2 |
3 | import (
4 | "context"
5 | "reflect"
6 |
7 | p "github.com/SAP/go-hdb/driver/internal/protocol"
8 | )
9 |
10 | // ColumnType equals sql.ColumnType.
11 | type ColumnType interface {
12 | DatabaseTypeName() string
13 | DecimalSize() (precision, scale int64, ok bool)
14 | Length() (length int64, ok bool)
15 | Name() string
16 | Nullable() (nullable bool, ok bool)
17 | ScanType() reflect.Type
18 | }
19 |
20 | // ParameterType extends ColumnType with stored procedure metadata.
21 | type ParameterType interface {
22 | ColumnType
23 | In() bool
24 | Out() bool
25 | InOut() bool
26 | }
27 |
28 | // StmtMetadata provides access to the parameter and result metadata of a prepared statement.
29 | type StmtMetadata interface {
30 | ParameterTypes() []ParameterType
31 | ColumnTypes() []ColumnType
32 | }
33 |
34 | // use unexported type to avoid key collisions.
35 | type stmtMetadataCtxKeyType struct{}
36 |
37 | var stmtMetadataCtxKey stmtMetadataCtxKeyType
38 |
39 | // WithStmtMetadata can be used to add a statement metadata reference to the context used for a Prepare call.
40 | // The Prepare call will set the stmtMetadata reference on successful preparation.
41 | func WithStmtMetadata(ctx context.Context, stmtMetadata *StmtMetadata) context.Context {
42 | return context.WithValue(ctx, stmtMetadataCtxKey, stmtMetadata)
43 | }
44 |
45 | var (
46 | _ StmtMetadata = (*prepareResult)(nil)
47 | _ ParameterType = (*p.ParameterField)(nil)
48 | _ ColumnType = (*p.ResultField)(nil)
49 | )
50 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # builds and tests project via go tools
2 | all:
3 | @echo "update dependencies"
4 | go get -u ./...
5 | go mod tidy
6 | @echo "build and test"
7 | go build -v ./...
8 | go vet ./...
9 | golint -set_exit_status=true ./...
10 | staticcheck -checks all -fail none ./...
11 | golangci-lint run ./...
12 | @echo execute tests on latest go version
13 | go test ./...
14 | go test ./... -race
15 | # @echo execute tests on older supported go versions
16 | GOTOOLCHAIN=go1.24.11 go1.24.11 test ./...
17 | GOTOOLCHAIN=go1.24.11 go1.24.11 test ./... -race
18 |
19 | #see fsfe reuse tool (https://git.fsfe.org/reuse/tool)
20 | #on linux: if pipx uses outdated packages, delete ~/.local/pipx/cache entries
21 | @echo "reuse (license) check"
22 | pipx run reuse lint
23 |
24 | #go generate
25 | generate:
26 | @echo "generate"
27 | go generate ./...
28 |
29 | #install additional tools
30 | tools:
31 | #install stringer
32 | @echo "install latest stringer version"
33 | go install golang.org/x/tools/cmd/stringer@latest
34 | #install linter
35 | @echo "install latest go linter version"
36 | go install golang.org/x/lint/golint@latest
37 | #install staticcheck
38 | @echo "install latest staticcheck version"
39 | go install honnef.co/go/tools/cmd/staticcheck@latest
40 | #install golangci-lint
41 | @echo "install latest golangci-lint version"
42 | go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
43 |
44 | #install additional go versions
45 | go:
46 | go install golang.org/dl/go1.24.11@latest
47 | go1.24.11 download
48 |
--------------------------------------------------------------------------------
/driver/internal/protocol/julian/julian.go:
--------------------------------------------------------------------------------
1 | // Package julian provided julian time conversion functions.
2 | package julian
3 |
4 | import (
5 | "time"
6 | )
7 |
8 | const gregorianDay = 2299161 // Start date of Gregorian Calendar as Julian Day Number
9 | var gregorianDate = DayToTime(gregorianDay) // Start date of Gregorian Calendar (1582-10-15)
10 |
11 | // TimeToDay returns the Julian Date Number of time's date components.
12 | // The algorithm is taken from https://en.wikipedia.org/wiki/Julian_day.
13 | func TimeToDay(t time.Time) int {
14 | t = t.UTC()
15 |
16 | month := int(t.Month())
17 |
18 | a := (14 - month) / 12
19 | y := t.Year() + 4800 - a
20 | m := month + (12 * a) - 3
21 |
22 | if t.Before(gregorianDate) { // Julian Calendar
23 | return t.Day() + (153*m+2)/5 + 365*y + y/4 - 32083
24 | }
25 | // Gregorian Calendar
26 | return t.Day() + (153*m+2)/5 + 365*y + y/4 - y/100 + y/400 - 32045
27 | }
28 |
29 | // DayToTime returns the correcponding UTC date for a Julian Day Number.
30 | // The algorithm is taken from https://en.wikipedia.org/wiki/Julian_day.
31 | func DayToTime(jd int) time.Time {
32 | var f int
33 |
34 | if jd < gregorianDay {
35 | f = jd + 1401
36 | } else {
37 | f = jd + 1401 + (((4*jd+274277)/146097)*3)/4 - 38
38 | }
39 |
40 | e := 4*f + 3
41 | g := (e % 1461) / 4
42 | h := 5*g + 2
43 | day := (h%153)/5 + 1
44 | month := (h/153+2)%12 + 1
45 | year := (e / 1461) - 4716 + (12+2-month)/12
46 |
47 | return time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC)
48 | }
49 |
--------------------------------------------------------------------------------
/driver/example_conn_test.go:
--------------------------------------------------------------------------------
1 | //go:build !unit
2 |
3 | package driver_test
4 |
5 | import (
6 | "context"
7 | "database/sql"
8 | "log"
9 |
10 | "github.com/SAP/go-hdb/driver"
11 | )
12 |
13 | // ExampleConn-HDBVersion shows how to retrieve hdb server info with the help of sql.Conn.Raw().
14 | func ExampleConn_HDBVersion() {
15 | db := sql.OpenDB(driver.MT.Connector())
16 | defer db.Close()
17 |
18 | // Grab connection.
19 | conn, err := db.Conn(context.Background())
20 | if err != nil {
21 | log.Fatal(err)
22 | }
23 | defer conn.Close()
24 |
25 | if err := conn.Raw(func(driverConn any) error {
26 | // Access driver.Conn methods.
27 | log.Printf("hdb version: %s", driverConn.(driver.Conn).HDBVersion())
28 | return nil
29 | }); err != nil {
30 | log.Fatal(err)
31 | }
32 | // output:
33 | }
34 |
35 | // ExampleConn-DBConnectInfo shows how to retrieve hdb DBConnectInfo with the help of sql.Conn.Raw().
36 | func ExampleConn_DBConnectInfo() {
37 | db := sql.OpenDB(driver.MT.Connector())
38 | defer db.Close()
39 |
40 | // Grab connection.
41 | conn, err := db.Conn(context.Background())
42 | if err != nil {
43 | log.Fatal(err)
44 | }
45 | defer conn.Close()
46 |
47 | if err := conn.Raw(func(driverConn any) error {
48 | // Access driver.Conn methods.
49 | ci, err := driverConn.(driver.Conn).DBConnectInfo(context.Background(), driverConn.(driver.Conn).DatabaseName())
50 | if err != nil {
51 | return err
52 | }
53 | log.Printf("db connect info: %s", ci)
54 | return nil
55 | }); err != nil {
56 | log.Fatal(err)
57 | }
58 | // output:
59 | }
60 |
--------------------------------------------------------------------------------
/driver/internal/protocol/functioncode.go:
--------------------------------------------------------------------------------
1 | package protocol
2 |
3 | // FunctionCode represents a function code.
4 | type FunctionCode int16
5 |
6 | // FunctionCode constants.
7 | const (
8 | fcNil FunctionCode = 0
9 | FcDDL FunctionCode = 1
10 | fcInsert FunctionCode = 2
11 | fcUpdate FunctionCode = 3
12 | fcDelete FunctionCode = 4
13 | fcSelect FunctionCode = 5
14 | fcSelectForUpdate FunctionCode = 6
15 | fcExplain FunctionCode = 7
16 | fcDBProcedureCall FunctionCode = 8
17 | fcDBProcedureCallWithResult FunctionCode = 9
18 | fcFetch FunctionCode = 10
19 | fcCommit FunctionCode = 11
20 | fcRollback FunctionCode = 12
21 | fcSavepoint FunctionCode = 13
22 | fcConnect FunctionCode = 14
23 | fcWriteLob FunctionCode = 15
24 | fcReadLob FunctionCode = 16
25 | fcPing FunctionCode = 17 //reserved: do not use
26 | fcDisconnect FunctionCode = 18
27 | fcCloseCursor FunctionCode = 19
28 | fcFindLob FunctionCode = 20
29 | fcAbapStream FunctionCode = 21
30 | fcXAStart FunctionCode = 22
31 | fcXAJoin FunctionCode = 23
32 | )
33 |
34 | // IsProcedureCall returns true if the function code is a procedure call, false otherwise.
35 | func (fc FunctionCode) IsProcedureCall() bool {
36 | return fc == fcDBProcedureCall
37 | }
38 |
--------------------------------------------------------------------------------
/driver/internal/protocol/levenshtein/levenshtein.go:
--------------------------------------------------------------------------------
1 | // Package levenshtein includes the levenshtein distance algorithm plus additional helper functions.
2 | // The algorithm is taken from https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#Go.
3 | package levenshtein
4 |
5 | import (
6 | "math"
7 | "strings"
8 | "unicode/utf8"
9 | )
10 |
11 | // Distance returns the Lewenshtein distance.
12 | func Distance(a, b string, caseSensitive bool) int {
13 | if caseSensitive {
14 | return distance(a, b)
15 | }
16 | return distance(strings.ToLower(a), strings.ToLower(b))
17 | }
18 |
19 | // MinString returns the string attribute determined by fn out of x with the minimal Lewenshtein distance to s.
20 | func MinString[S ~[]E, E any](x S, fn func(a E) string, s string, caseSensitive bool) (rv string) {
21 | minInt := math.MaxInt
22 | for _, e := range x {
23 | xs := fn(e)
24 | if d := Distance(xs, s, caseSensitive); d < minInt {
25 | rv = xs
26 | minInt = d
27 | }
28 | }
29 | return
30 | }
31 |
32 | func distance(a, b string) int {
33 | f := make([]int, utf8.RuneCountInString(b)+1)
34 |
35 | for j := range f {
36 | f[j] = j
37 | }
38 |
39 | for _, ca := range a {
40 | j := 1
41 | fj1 := f[0] // fj1 is the value of f[j - 1] in last iteration
42 | f[0]++
43 | for _, cb := range b {
44 | mn := min(f[j]+1, f[j-1]+1) // delete & insert
45 | if cb != ca {
46 | mn = min(mn, fj1+1) // change
47 | } else {
48 | mn = min(mn, fj1) // matched
49 | }
50 |
51 | fj1, f[j] = f[j], mn // save f[j] to fj1(j is about to increase), update f[j] to mn
52 | j++
53 | }
54 | }
55 |
56 | return f[len(f)-1]
57 | }
58 |
--------------------------------------------------------------------------------
/driver/decimal.go:
--------------------------------------------------------------------------------
1 | package driver
2 |
3 | import (
4 | "database/sql/driver"
5 | "fmt"
6 | "math/big"
7 | )
8 |
9 | // A Decimal is the driver representation of a database decimal field value as big.Rat.
10 | type Decimal big.Rat
11 |
12 | // Scan implements the database/sql/Scanner interface.
13 | func (d *Decimal) Scan(src any) error {
14 | r, ok := src.(*big.Rat)
15 | if !ok {
16 | return fmt.Errorf("decimal: invalid data type %T", src)
17 | }
18 | (*big.Rat)(d).Set(r)
19 | return nil
20 | }
21 |
22 | // Value implements the database/sql/Valuer interface.
23 | func (d Decimal) Value() (driver.Value, error) {
24 | return (*big.Rat)(&d), nil
25 | }
26 |
27 | // NullDecimal represents an Decimal that may be null.
28 | // NullDecimal implements the Scanner interface so
29 | // it can be used as a scan destination, similar to NullString.
30 | type NullDecimal struct {
31 | Decimal *Decimal
32 | Valid bool // Valid is true if Decimal is not NULL
33 | }
34 |
35 | // Scan implements the Scanner interface.
36 | func (n *NullDecimal) Scan(value any) error {
37 | if value == nil {
38 | n.Valid = false
39 | return nil
40 | }
41 | r, ok := value.(*big.Rat)
42 | if !ok {
43 | return fmt.Errorf("decimal: invalid data type %T", value)
44 | }
45 | n.Valid = true
46 | if n.Decimal == nil {
47 | n.Decimal = &Decimal{}
48 | }
49 | (*big.Rat)(n.Decimal).Set(r)
50 | return nil
51 | }
52 |
53 | // Value implements the driver Valuer interface.
54 | func (n NullDecimal) Value() (driver.Value, error) {
55 | if !n.Valid {
56 | return nil, nil
57 | }
58 | if n.Decimal == nil {
59 | return nil, fmt.Errorf("invalid decimal value %v", n.Decimal)
60 | }
61 | return (*big.Rat)(n.Decimal), nil
62 | }
63 |
--------------------------------------------------------------------------------
/driver/statscfg.go:
--------------------------------------------------------------------------------
1 | package driver
2 |
3 | import (
4 | _ "embed" // embed stats configuration
5 | "encoding/json"
6 | "fmt"
7 | "slices"
8 | "time"
9 | )
10 |
11 | //go:embed statscfg.json
12 | var statsCfgRaw []byte
13 |
14 | var statsCfg struct {
15 | TimeUnit string `json:"timeUnit"`
16 | SQLTimeTexts []string `json:"sqlTimeTexts"`
17 | TimeUpperBounds []float64 `json:"timeUpperBounds"`
18 | }
19 |
20 | // time unit map (see go package time format.go).
21 | var timeUnitMap = map[string]uint64{
22 | "ns": uint64(time.Nanosecond),
23 | "us": uint64(time.Microsecond),
24 | "µs": uint64(time.Microsecond), // U+00B5 = micro symbol
25 | "μs": uint64(time.Microsecond), // U+03BC = Greek letter mu
26 | "ms": uint64(time.Millisecond),
27 | "s": uint64(time.Second),
28 | "m": uint64(time.Minute),
29 | "h": uint64(time.Hour),
30 | }
31 |
32 | func loadStatsCfg() error {
33 |
34 | if err := json.Unmarshal(statsCfgRaw, &statsCfg); err != nil {
35 | return fmt.Errorf("invalid statscfg.json file: %w", err)
36 | }
37 |
38 | if len(statsCfg.SQLTimeTexts) != int(numSQLTime) {
39 | return fmt.Errorf("invalid number of statscfg.json sqlTimeTexts %d - expected %d", len(statsCfg.SQLTimeTexts), numSQLTime)
40 | }
41 | if len(statsCfg.TimeUpperBounds) == 0 {
42 | return fmt.Errorf("number of statscfg.json timeUpperBounds needs to be greater than %d", 0)
43 | }
44 |
45 | if _, ok := timeUnitMap[statsCfg.TimeUnit]; !ok {
46 | return fmt.Errorf("invalid time unit in statscfg.json %s", statsCfg.TimeUnit)
47 | }
48 |
49 | // sort and dedup timeBuckets
50 | slices.Sort(statsCfg.TimeUpperBounds)
51 | statsCfg.TimeUpperBounds = slices.Compact(statsCfg.TimeUpperBounds)
52 |
53 | return nil
54 | }
55 |
--------------------------------------------------------------------------------
/driver/internal/protocol/auth/jwt.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | // JWT implements JWT authentication.
8 | type JWT struct {
9 | token string
10 | logonname string
11 | _cookie []byte
12 | }
13 |
14 | // NewJWT creates a new authJWT instance.
15 | func NewJWT(token string) *JWT { return &JWT{token: token} }
16 |
17 | func (a *JWT) String() string { return fmt.Sprintf("method type %s token %s", a.Typ(), a.token) }
18 |
19 | // Cookie implements the AuthCookieGetter interface.
20 | func (a *JWT) Cookie() (string, []byte) { return a.logonname, a._cookie }
21 |
22 | // Typ implements the Method interface.
23 | func (a *JWT) Typ() string { return MtJWT }
24 |
25 | // Order implements the Method interface.
26 | func (a *JWT) Order() byte { return MoJWT }
27 |
28 | // PrepareInitReq implements the Method interface.
29 | func (a *JWT) PrepareInitReq(prms *Prms) error {
30 | prms.addString(a.Typ())
31 | prms.addString(a.token)
32 | return nil
33 | }
34 |
35 | // InitRepDecode implements the Method interface.
36 | func (a *JWT) InitRepDecode(d *Decoder) error {
37 | a.logonname = d.String()
38 | return nil
39 | }
40 |
41 | // PrepareFinalReq implements the Method interface.
42 | func (a *JWT) PrepareFinalReq(prms *Prms) error {
43 | prms.AddCESU8String(a.logonname)
44 | prms.addString(a.Typ())
45 | prms.addEmpty() // empty parameter
46 | return nil
47 | }
48 |
49 | // FinalRepDecode implements the Method interface.
50 | func (a *JWT) FinalRepDecode(d *Decoder) error {
51 | if err := d.NumPrm(2); err != nil {
52 | return err
53 | }
54 | mt := d.String()
55 | if err := checkAuthMethodType(mt, a.Typ()); err != nil {
56 | return err
57 | }
58 | a._cookie = d.bytes()
59 | return nil
60 | }
61 |
--------------------------------------------------------------------------------
/testdata/x509/rsa.pkcs1.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEoAIBAAKCAQEA6Vde8Ty/4Owrog6US9+C+rRMlwpkQ85eg9VlH2sH1uMwC7fG
3 | Cz8PjaJD+ziQz7xvdApS48T8SAYAW3gZp4mujm35CShxeuXzM5SmU5PVeZUDJvGz
4 | mpH5xrrT5zU2yFJPjYCqIvUWjZpaOU2ZyAzZQgfG2xw5uUf2TvgsN565jAJSIi7F
5 | WpDvJUsMcnhYBXeRrbY+D0wf5XDFpDcjoERyDIY9TxElSMFKk6fERjkQ9KaRTt81
6 | e4jIatUvFNTAYMniqVuz1F2om1NI7x2mtOyl4TGoMK5TbRUe6J2CWhSvfQ7FkTD6
7 | 5TW4j8KVv9e566nS0o44DkGgRWfkcudpU3wCiQIDAQABAoIBACDlH05U6Rv1T2Vm
8 | NsPHe7iKKG32mhHxCuP89meKeC10E7bjLnkxZ6jo4jqNS+TtRK0QM4VGpOYmanB6
9 | qusyfrg3iq8e03im6DUyVIxdv++G8U3RLyewQo0gk3T5zIJLEEn7hICvGoHV67g+
10 | V1qbbRlg7UjAXube3TtewyUILFIvUKhPzy6RRc+kfj+lzIefaLHbM4LrMzJThrtj
11 | CWnWBEtJ2bRZAemZKHzmtUVAvRVu3VTkEtglVo0xW4rTvs92cVDjBjrY1i0Kz7ho
12 | B6Wy5UB571JM76+oD+zrafNrGCqGs2EdiZdSH7uRSiNCfpQz3VwUImxdSxSwx9in
13 | yULyVLECgYEA91Y1e60cbNw8wJAINGstSE9OhRQ5zwWhIhjbUe5oe39sYVVZzGnR
14 | 6TRns+L+OFyXX+yd3er9jDp1L2I+6ZlkxMNKSUCEFGaR0WdP/MhpTDhQf5HvZwgM
15 | cEe9q51Ou+8gLcoE9JRKvUNmB042AM8Q8rT/dTUovCibiWb45eB4uAUCgYEA8YOr
16 | RjszKVfQ7MHErFVFCnZ8oeVpUVvCa5NKLJw8DZGO330shQyf6N2YnbPjsW/bBipF
17 | yGNYl5XDbLnWRYZFz+rhQWf1JfwJWYQY6z2Z6AtEhwRRh7Z5/opkrXTI9JcUXMOE
18 | SOdiNdrr/AQqRpKYjhl4hl+9xdPRTPVjFgHx+7UCf2dgoPoQYNEfPEU/7i36nfKb
19 | MzZsPb1JnQ+jVAfqncEAgUwtYFsP36yZOr71IUxtZtexDkqy7UavKEulxKXedz/G
20 | YEY9sPbV6ByeEdCV82zoHL01QdrOgmRrl9si5MWoLMr2pRuU36g7g2mKRwgPhAXt
21 | oNhcX6pFTr0fTOjH6a0CgYBKKuNIOBnoEl3It2DMg4smQWjY1vjW0VUNcDgRJxGS
22 | ISc9HtWzLkNVYefiVkIQVYdvhz/S4B9uvcE7/H3+79M0nK78RQ6V0aaVZOm6JDrF
23 | deBfjkXzZWhlcN6mHf0wj8zjB+/x64zPeSGLTUQmVBKhYNUQfwpqXDgkaB87vPEx
24 | GQKBgDBZSgNxiucU/erh9y/nySGIUKsGJdlBPBmAEsfW1XPejAFaeqzweA5XlAr0
25 | gFrfbbSSsv2WSDibRWgjGBS3PjiWmfuDApIGDsh5AG6WhAJT8XeuDMd3VgdNddd/
26 | WdHC9xSnzxhBjDZfbMZikQ89TZIbQQ4zVmrnOYYdYs13hQ0s
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/driver/internal/protocol/julian/julian_test.go:
--------------------------------------------------------------------------------
1 | package julian
2 |
3 | import (
4 | "testing"
5 | "time"
6 | )
7 |
8 | type testJulianDay struct {
9 | jd int
10 | time time.Time
11 | }
12 |
13 | var testJulianDayData = []testJulianDay{
14 | {1721424, time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC)},
15 | {1842713, time.Date(333, time.January, 27, 0, 0, 0, 0, time.UTC)},
16 | {2299160, time.Date(1582, time.October, 4, 0, 0, 0, 0, time.UTC)},
17 | {2299161, time.Date(1582, time.October, 15, 0, 0, 0, 0, time.UTC)},
18 | {2415021, time.Date(1900, time.January, 1, 0, 0, 0, 0, time.UTC)},
19 | {2447893, time.Date(1990, time.January, 1, 0, 0, 0, 0, time.UTC)},
20 | {2451545, time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC)},
21 | {2453750, time.Date(2006, time.January, 14, 0, 0, 0, 0, time.UTC)},
22 | {2455281, time.Date(2010, time.March, 25, 0, 0, 0, 0, time.UTC)},
23 | {2457188, time.Date(2015, time.June, 14, 0, 0, 0, 0, time.UTC)},
24 |
25 | {2440587, time.Date(1969, time.December, 31, 0, 0, 0, 0, time.UTC)},
26 | {2440588, time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC)},
27 | {5373484, time.Date(9999, time.December, 31, 0, 0, 0, 0, time.UTC)},
28 | {2457202, time.Date(2015, time.June, 28, 0, 0, 0, 0, time.UTC)},
29 | }
30 |
31 | func TestTimeToJulianDay(t *testing.T) {
32 | for i, d := range testJulianDayData {
33 | jd := TimeToDay(d.time)
34 | if jd != d.jd {
35 | t.Fatalf("Julian Day Number %d value %d - expected %d (date %s)", i, jd, d.jd, d.time)
36 | }
37 | }
38 | }
39 |
40 | func TestJulianDayToTime(t *testing.T) {
41 | for i, d := range testJulianDayData {
42 | time := DayToTime(d.jd)
43 | if !time.Equal(d.time) {
44 | t.Fatalf("Time %d value %s - expected %s (Julian Day Number %d)", i, time, d.time, d.jd)
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/driver/internal/protocol/encoding/datetime.go:
--------------------------------------------------------------------------------
1 | package encoding
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/SAP/go-hdb/driver/internal/protocol/julian"
7 | )
8 |
9 | // Longdate.
10 | func convertLongdateToTime(longdate int64) time.Time {
11 | const dayfactor = 10000000 * 24 * 60 * 60
12 | longdate--
13 | d := (longdate % dayfactor) * 100
14 | t := convertDaydateToTime((longdate / dayfactor) + 1)
15 | return t.Add(time.Duration(d))
16 | }
17 |
18 | // nanosecond: HDB - 7 digits precision (not 9 digits).
19 | func convertTimeToLongdate(t time.Time) int64 {
20 | return (((((((convertTimeToDayDate(t)-1)*24)+int64(t.Hour()))*60)+int64(t.Minute()))*60)+int64(t.Second()))*1e7 + int64(t.Nanosecond()/1e2) + 1
21 | }
22 |
23 | // Seconddate.
24 | func convertSeconddateToTime(seconddate int64) time.Time {
25 | const dayfactor = 24 * 60 * 60
26 | seconddate--
27 | d := (seconddate % dayfactor) * 1e9
28 | t := convertDaydateToTime((seconddate / dayfactor) + 1)
29 | return t.Add(time.Duration(d))
30 | }
31 | func convertTimeToSeconddate(t time.Time) int64 {
32 | return (((((convertTimeToDayDate(t)-1)*24)+int64(t.Hour()))*60)+int64(t.Minute()))*60 + int64(t.Second()) + 1
33 | }
34 |
35 | const julianHdb = 1721423 // 1 January 0001 00:00:00 (1721424) - 1
36 |
37 | // Daydate.
38 | func convertDaydateToTime(daydate int64) time.Time {
39 | return julian.DayToTime(int(daydate) + julianHdb)
40 | }
41 | func convertTimeToDayDate(t time.Time) int64 {
42 | return int64(julian.TimeToDay(t) - julianHdb)
43 | }
44 |
45 | // Secondtime.
46 | func convertSecondtimeToTime(secondtime int) time.Time {
47 | return time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC).Add(time.Duration(int64(secondtime-1) * 1e9))
48 | }
49 | func convertTimeToSecondtime(t time.Time) int {
50 | return (t.Hour()*60+t.Minute())*60 + t.Second() + 1
51 | }
52 |
--------------------------------------------------------------------------------
/testdata/x509/rsa.pkcs8.key:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | MIIEugIBADANBgkqhkiG9w0BAQEFAASCBKQwggSgAgEAAoIBAQDpV17xPL/g7Cui
3 | DpRL34L6tEyXCmRDzl6D1WUfawfW4zALt8YLPw+NokP7OJDPvG90ClLjxPxIBgBb
4 | eBmnia6ObfkJKHF65fMzlKZTk9V5lQMm8bOakfnGutPnNTbIUk+NgKoi9RaNmlo5
5 | TZnIDNlCB8bbHDm5R/ZO+Cw3nrmMAlIiLsVakO8lSwxyeFgFd5Gttj4PTB/lcMWk
6 | NyOgRHIMhj1PESVIwUqTp8RGORD0ppFO3zV7iMhq1S8U1MBgyeKpW7PUXaibU0jv
7 | Haa07KXhMagwrlNtFR7onYJaFK99DsWRMPrlNbiPwpW/17nrqdLSjjgOQaBFZ+Ry
8 | 52lTfAKJAgMBAAECggEAIOUfTlTpG/VPZWY2w8d7uIoobfaaEfEK4/z2Z4p4LXQT
9 | tuMueTFnqOjiOo1L5O1ErRAzhUak5iZqcHqq6zJ+uDeKrx7TeKboNTJUjF2/74bx
10 | TdEvJ7BCjSCTdPnMgksQSfuEgK8agdXruD5XWpttGWDtSMBe5t7dO17DJQgsUi9Q
11 | qE/PLpFFz6R+P6XMh59osdszguszMlOGu2MJadYES0nZtFkB6ZkofOa1RUC9FW7d
12 | VOQS2CVWjTFbitO+z3ZxUOMGOtjWLQrPuGgHpbLlQHnvUkzvr6gP7Otp82sYKoaz
13 | YR2Jl1Ifu5FKI0J+lDPdXBQibF1LFLDH2KfJQvJUsQKBgQD3VjV7rRxs3DzAkAg0
14 | ay1IT06FFDnPBaEiGNtR7mh7f2xhVVnMadHpNGez4v44XJdf7J3d6v2MOnUvYj7p
15 | mWTEw0pJQIQUZpHRZ0/8yGlMOFB/ke9nCAxwR72rnU677yAtygT0lEq9Q2YHTjYA
16 | zxDytP91NSi8KJuJZvjl4Hi4BQKBgQDxg6tGOzMpV9DswcSsVUUKdnyh5WlRW8Jr
17 | k0osnDwNkY7ffSyFDJ/o3Zids+Oxb9sGKkXIY1iXlcNsudZFhkXP6uFBZ/Ul/AlZ
18 | hBjrPZnoC0SHBFGHtnn+imStdMj0lxRcw4RI52I12uv8BCpGkpiOGXiGX73F09FM
19 | 9WMWAfH7tQJ/Z2Cg+hBg0R88RT/uLfqd8pszNmw9vUmdD6NUB+qdwQCBTC1gWw/f
20 | rJk6vvUhTG1m17EOSrLtRq8oS6XEpd53P8ZgRj2w9tXoHJ4R0JXzbOgcvTVB2s6C
21 | ZGuX2yLkxagsyvalG5TfqDuDaYpHCA+EBe2g2FxfqkVOvR9M6MfprQKBgEoq40g4
22 | GegSXci3YMyDiyZBaNjW+NbRVQ1wOBEnEZIhJz0e1bMuQ1Vh5+JWQhBVh2+HP9Lg
23 | H269wTv8ff7v0zScrvxFDpXRppVk6bokOsV14F+ORfNlaGVw3qYd/TCPzOMH7/Hr
24 | jM95IYtNRCZUEqFg1RB/CmpcOCRoHzu88TEZAoGAMFlKA3GK5xT96uH3L+fJIYhQ
25 | qwYl2UE8GYASx9bVc96MAVp6rPB4DleUCvSAWt9ttJKy/ZZIOJtFaCMYFLc+OJaZ
26 | +4MCkgYOyHkAbpaEAlPxd64Mx3dWB011139Z0cL3FKfPGEGMNl9sxmKRDz1NkhtB
27 | DjNWauc5hh1izXeFDSw=
28 | -----END PRIVATE KEY-----
29 |
--------------------------------------------------------------------------------
/driver/error.go:
--------------------------------------------------------------------------------
1 | package driver
2 |
3 | import (
4 | p "github.com/SAP/go-hdb/driver/internal/protocol"
5 | )
6 |
7 | // HDB error levels.
8 | const (
9 | HdbWarning = 0
10 | HdbError = 1
11 | HdbFatalError = 2
12 | )
13 |
14 | // DBError represents a single error returned by the database server.
15 | type DBError interface {
16 | Error() string // Implements the golang error interface.
17 | StmtNo() int // Returns the statement number of the error in multi statement contexts (e.g. bulk insert).
18 | Code() int // Code return the database error code.
19 | Position() int // Position returns the start position of erroneous sql statements sent to the database server.
20 | Level() int // Level return one of the database server predefined error levels.
21 | Text() string // Text return the error description sent from database server.
22 | IsWarning() bool // IsWarning returns true if the HDB error level equals 0.
23 | IsError() bool // IsError returns true if the HDB error level equals 1.
24 | IsFatal() bool // IsFatal returns true if the HDB error level equals 2.
25 | }
26 |
27 | // Error represents errors (an error collection) send by the database server.
28 | type Error interface {
29 | Error() string // Implements the golang error interface.
30 | NumError() int // NumError returns the number of errors.
31 | Unwrap() []error // Unwrap implements the standard error Unwrap function for errors wrapping multiple errors.
32 | SetIdx(idx int) // SetIdx sets the error index in case number of errors are greater 1 in the range of 0 <= index < NumError().
33 | DBError // DBError functions for error in case of single error, for error set by SetIdx in case of error collection.
34 | }
35 |
36 | var (
37 | _ DBError = (*p.HdbError)(nil)
38 | _ Error = (*p.HdbErrors)(nil)
39 | )
40 |
--------------------------------------------------------------------------------
/driver/internal/protocol/auth/scram.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | import (
4 | "crypto/hmac"
5 | "crypto/rand"
6 | "crypto/sha256"
7 | "fmt"
8 | )
9 |
10 | const (
11 | clientChallengeSize = 64
12 | serverChallengeSize = 48
13 | saltSize = 16
14 | clientProofSize = 32
15 | )
16 |
17 | func checkSalt(salt []byte) error {
18 | if len(salt) != saltSize {
19 | return fmt.Errorf("invalid salt size %d - expected %d", len(salt), saltSize)
20 | }
21 | return nil
22 | }
23 |
24 | func checkServerChallenge(serverChallenge []byte) error {
25 | if len(serverChallenge) != serverChallengeSize {
26 | return fmt.Errorf("invalid server challenge size %d - expected %d", len(serverChallenge), serverChallengeSize)
27 | }
28 | return nil
29 | }
30 |
31 | func clientChallenge() []byte {
32 | r := make([]byte, clientChallengeSize)
33 | // does not return err starting with go1.24
34 | rand.Read(r) //nolint: errcheck
35 | return r
36 | }
37 |
38 | func clientProof(key, salt, serverChallenge, clientChallenge []byte) ([]byte, error) {
39 | if len(key) != clientProofSize {
40 | return nil, fmt.Errorf("invalid key size %d - expected %d", len(key), clientProofSize)
41 | }
42 | sig := _hmac(_sha256(key), salt, serverChallenge, clientChallenge)
43 | if len(sig) != clientProofSize {
44 | return nil, fmt.Errorf("invalid sig size %d - expected %d", len(key), clientProofSize)
45 | }
46 | // xor sig and key into sig (inline: no further allocation).
47 | for i, v := range key {
48 | sig[i] ^= v
49 | }
50 | return sig, nil
51 | }
52 |
53 | func _sha256(p []byte) []byte {
54 | hash := sha256.New()
55 | hash.Write(p)
56 | return hash.Sum(nil)
57 | }
58 |
59 | func _hmac(key []byte, prms ...[]byte) []byte {
60 | hash := hmac.New(sha256.New, key)
61 | for _, p := range prms {
62 | hash.Write(p)
63 | }
64 | return hash.Sum(nil)
65 | }
66 |
--------------------------------------------------------------------------------
/driver/example_scanlobstring_test.go:
--------------------------------------------------------------------------------
1 | //go:build !unit
2 |
3 | package driver_test
4 |
5 | import (
6 | "database/sql"
7 | "fmt"
8 | "log"
9 |
10 | "github.com/SAP/go-hdb/driver"
11 | )
12 |
13 | // StringLob defines a string based data type for scanning Lobs.
14 | type StringLob string
15 |
16 | // Scan implements the database.sql.Scanner interface.
17 | func (s *StringLob) Scan(arg any) error { return driver.ScanLobString(arg, (*string)(s)) }
18 |
19 | // ExampleScanLobString demontrates how to read Lob data using a string based data type.
20 | func ExampleScanLobString() {
21 | // Open Test database.
22 | db := sql.OpenDB(driver.MT.Connector())
23 | defer db.Close()
24 |
25 | table := driver.RandomIdentifier("lob_")
26 |
27 | if _, err := db.Exec(fmt.Sprintf("create table %s (n1 nclob, n2 nclob)", table)); err != nil {
28 | log.Fatalf("create table failed: %s", err)
29 | }
30 |
31 | tx, err := db.Begin() // Start Transaction to avoid database error: SQL Error 596 - LOB streaming is not permitted in auto-commit mode.
32 | if err != nil {
33 | log.Fatal(err)
34 | }
35 |
36 | // Lob content can be written using a string.
37 | content := "scan lob string"
38 | _, err = tx.Exec(fmt.Sprintf("insert into %s values (?, ?)", table), content, content)
39 | if err != nil {
40 | log.Fatal(err)
41 | }
42 |
43 | if err := tx.Commit(); err != nil {
44 | log.Fatal(err)
45 | }
46 |
47 | // Select.
48 | stmt, err := db.Prepare(fmt.Sprintf("select * from %s", table))
49 | if err != nil {
50 | log.Fatal(err)
51 | }
52 | defer stmt.Close()
53 |
54 | // Scan into StringLob and sql.Null[StringLob].
55 | var s StringLob
56 | var ns sql.Null[StringLob]
57 | if err := stmt.QueryRow().Scan(&s, &ns); err != nil {
58 | log.Fatal(err)
59 | }
60 | fmt.Println(s)
61 | fmt.Println(ns.V)
62 |
63 | // output: scan lob string
64 | // scan lob string
65 | }
66 |
--------------------------------------------------------------------------------
/driver/example_scanlobbytes_test.go:
--------------------------------------------------------------------------------
1 | //go:build !unit
2 |
3 | package driver_test
4 |
5 | import (
6 | "database/sql"
7 | "fmt"
8 | "log"
9 |
10 | "github.com/SAP/go-hdb/driver"
11 | )
12 |
13 | // BytesLob defines a []byte based data type for scanning Lobs.
14 | type BytesLob []byte
15 |
16 | // Scan implements the database.sql.Scanner interface.
17 | func (b *BytesLob) Scan(arg any) error { return driver.ScanLobBytes(arg, (*[]byte)(b)) }
18 |
19 | // ExampleScanLobBytes demontrates how to read Lob data using a []byte based data type.
20 | func ExampleScanLobBytes() {
21 | // Open Test database.
22 | db := sql.OpenDB(driver.MT.Connector())
23 | defer db.Close()
24 |
25 | table := driver.RandomIdentifier("lob_")
26 |
27 | if _, err := db.Exec(fmt.Sprintf("create table %s (b1 blob, b2 blob)", table)); err != nil {
28 | log.Fatalf("create table failed: %s", err)
29 | }
30 |
31 | tx, err := db.Begin() // Start Transaction to avoid database error: SQL Error 596 - LOB streaming is not permitted in auto-commit mode.
32 | if err != nil {
33 | log.Fatal(err)
34 | }
35 |
36 | // Lob content can be written using a byte slice.
37 | content := []byte("scan lob bytes")
38 | _, err = tx.Exec(fmt.Sprintf("insert into %s values (?, ?)", table), content, content)
39 | if err != nil {
40 | log.Fatal(err)
41 | }
42 |
43 | if err := tx.Commit(); err != nil {
44 | log.Fatal(err)
45 | }
46 |
47 | // Select.
48 | stmt, err := db.Prepare(fmt.Sprintf("select * from %s", table))
49 | if err != nil {
50 | log.Fatal(err)
51 | }
52 | defer stmt.Close()
53 |
54 | // Scan into BytesLob and sql.Null[BytesLob].
55 | var b BytesLob
56 | var nb sql.Null[BytesLob]
57 | if err := stmt.QueryRow().Scan(&b, &nb); err != nil {
58 | log.Fatal(err)
59 | }
60 | fmt.Println(string(b))
61 | fmt.Println(string(nb.V))
62 |
63 | // output: scan lob bytes
64 | // scan lob bytes
65 | }
66 |
--------------------------------------------------------------------------------
/driver/internal/protocol/auth/sessioncookie.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | // SessionCookie implements session cookie authentication.
8 | type SessionCookie struct {
9 | cookie []byte
10 | logonname string
11 | clientID string
12 | }
13 |
14 | // NewSessionCookie creates a new authSessionCookie instance.
15 | func NewSessionCookie(cookie []byte, logonname, clientID string) *SessionCookie {
16 | return &SessionCookie{cookie: cookie, logonname: logonname, clientID: clientID}
17 | }
18 |
19 | func (a *SessionCookie) String() string {
20 | return fmt.Sprintf("method type %s cookie %v", a.Typ(), a.cookie)
21 | }
22 |
23 | // Typ implements the Mthod interface.
24 | func (a *SessionCookie) Typ() string { return MtSessionCookie }
25 |
26 | // Order implements the Method interface.
27 | func (a *SessionCookie) Order() byte { return MoSessionCookie }
28 |
29 | // PrepareInitReq implements the Method interface.
30 | func (a *SessionCookie) PrepareInitReq(prms *Prms) error {
31 | prms.addString(a.Typ())
32 | prms.addBytes(append(a.cookie, a.clientID...)) // cookie + clientID !!!
33 | return nil
34 | }
35 |
36 | // InitRepDecode implements the Method interface.
37 | func (a *SessionCookie) InitRepDecode(d *Decoder) error {
38 | return nil
39 | }
40 |
41 | // PrepareFinalReq implements the Method interface.
42 | func (a *SessionCookie) PrepareFinalReq(prms *Prms) error {
43 | prms.AddCESU8String(a.logonname)
44 | prms.addString(a.Typ())
45 | prms.addEmpty() // empty parameter
46 | return nil
47 | }
48 |
49 | // FinalRepDecode implements the Method interface.
50 | func (a *SessionCookie) FinalRepDecode(d *Decoder) error {
51 | if err := d.NumPrm(2); err != nil {
52 | return err
53 | }
54 | mt := d.String()
55 | if err := checkAuthMethodType(mt, a.Typ()); err != nil {
56 | return err
57 | }
58 | d.bytes() // second parameter seems to be empty
59 | return nil
60 | }
61 |
--------------------------------------------------------------------------------
/REUSE.toml:
--------------------------------------------------------------------------------
1 | version = 1
2 | SPDX-PackageName = "go-hdb"
3 | SPDX-PackageSupplier = "stefan.miller_at_sap.com"
4 | SPDX-PackageDownloadLocation = "https://github.com/SAP/go-hdb"
5 | SPDX-PackageComment = "The code in this project may include calls to APIs (“API Calls”) of\n SAP or third-party products or services developed outside of this project\n (“External Products”).\n “APIs” means application programming interfaces, as well as their respective\n specifications and implementing code that allows software to communicate with\n other software.\n API Calls to External Products are not licensed under the open source license\n that governs this project. The use of such API Calls and related External\n Products are subject to applicable additional agreements with the relevant\n provider of the External Products. In no event shall the open source license\n that governs this project grant any rights in or to any External Products,or\n alter, expand or supersede any terms of the applicable additional agreements.\n If you have a valid license agreement with SAP for the use of a particular SAP\n External Product, then you may make use of any API Calls included in this\n project’s code for that SAP External Product, subject to the terms of such\n license agreement. If you do not have a valid license agreement for the use of\n a particular SAP External Product, then you may only make use of any API Calls\n in this project for that SAP External Product for your internal, non-productive\n and non-commercial test and evaluation of such API Calls. Nothing herein grants\n you any rights to use or access any SAP External Product, or provide any third\n parties the right to use of access any SAP External Product, through API Calls."
6 |
7 | [[annotations]]
8 | path = "**"
9 | precedence = "aggregate"
10 | SPDX-FileCopyrightText = "2014-2024 SAP SE or an SAP affiliate company and go-hdb contributors"
11 | SPDX-License-Identifier = "Apache-2.0"
12 |
--------------------------------------------------------------------------------
/driver/example_metadata_test.go:
--------------------------------------------------------------------------------
1 | //go:build !unit
2 |
3 | package driver_test
4 |
5 | import (
6 | "context"
7 | "database/sql"
8 | "fmt"
9 | "log"
10 | "reflect"
11 |
12 | "github.com/SAP/go-hdb/driver"
13 | )
14 |
15 | // ExampleWithStmtMetadata demonstrates the use of statement metadata provided by PrepareContext.
16 | func ExampleWithStmtMetadata() {
17 | const procOut = `create procedure %s (out message nvarchar(1024))
18 | language SQLSCRIPT as
19 | begin
20 | message := 'Hello World!';
21 | end
22 | `
23 |
24 | db := sql.OpenDB(driver.MT.Connector())
25 | defer db.Close()
26 |
27 | procedure := driver.RandomIdentifier("procOut_")
28 |
29 | if _, err := db.Exec(fmt.Sprintf(procOut, procedure)); err != nil { // Create stored procedure.
30 | log.Fatal(err)
31 | }
32 |
33 | // Call PrepareContext with statement metadata context value.
34 | var stmtMetadata driver.StmtMetadata
35 | ctx := driver.WithStmtMetadata(context.Background(), &stmtMetadata)
36 |
37 | stmt, err := db.PrepareContext(ctx, fmt.Sprintf("call %s(?)", procedure))
38 | if err != nil {
39 | log.Fatal(err)
40 | }
41 | defer stmt.Close()
42 |
43 | // Create Exec args based on statement metadata columns...
44 | columnTypes := stmtMetadata.ColumnTypes()
45 | numColumnType := len(columnTypes)
46 | args := make([]any, numColumnType)
47 |
48 | for i, columnType := range columnTypes {
49 | out := reflect.New(columnType.ScanType()).Interface()
50 | args[i] = sql.Named(columnType.Name(), sql.Out{Dest: out})
51 | }
52 |
53 | // .. and execute Exec.
54 | if _, err := stmt.Exec(args...); err != nil {
55 | log.Fatal(err)
56 | }
57 |
58 | // Finally print the values.
59 | for _, arg := range args {
60 | namedArg := arg.(sql.NamedArg)
61 | sqlOut := namedArg.Value.(sql.Out)
62 | dest := sqlOut.Dest.(*sql.NullString)
63 | fmt.Printf("%s: %s", namedArg.Name, dest.String)
64 | }
65 |
66 | // output: MESSAGE: Hello World!
67 | }
68 |
--------------------------------------------------------------------------------
/driver/version_test.go:
--------------------------------------------------------------------------------
1 | package driver
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func testVersionNumberParse(t *testing.T) {
8 | var tests = []struct {
9 | s string
10 | v versionNumber
11 | }{
12 | {"2.00.048.00", versionNumber{2, 0, 48, 0, 0}},
13 | {"2.00.045.00.15756393121", versionNumber{2, 0, 45, 0, 15756393121}},
14 | }
15 |
16 | for i, test := range tests {
17 | v := parseVersion(test.s)
18 | if v.String() != test.s {
19 | t.Fatalf("line: %d got: %s expected: %s", i, v, test.s)
20 | }
21 | }
22 | }
23 |
24 | func testVersionNumberCompare(t *testing.T) {
25 | var tests = []struct {
26 | s1, s2 string
27 | r int
28 | }{
29 | {"2.00.045.00.15756393121", "2.00.048.00", -1},
30 | {"2.00.045.00.15756393121", "2.00.045.00.15756393122", 0}, // should be equal as the buildID is not tested
31 | }
32 |
33 | for i, test := range tests {
34 | v1 := parseVersionNumber(test.s1)
35 | v2 := parseVersionNumber(test.s2)
36 | if v1.compare(v2) != test.r {
37 | t.Fatalf("line: %d expected: compare(%s,%s) = %d", i, v1, v2, test.r)
38 | }
39 | }
40 | }
41 |
42 | func testVersionFeature(t *testing.T) {
43 | for f, cv1 := range hdbFeatureAvailability {
44 | for _, cv2 := range hdbFeatureAvailability {
45 | v1 := parseVersion(cv1.String())
46 | v2 := parseVersion(cv2.String())
47 |
48 | hasFeature := v2.compare(v1) >= 0
49 |
50 | if v2.hasFeature(f) != hasFeature {
51 | t.Fatalf("Version %s has feature %d - got %t - expected %t", v2, f, v2.hasFeature(f), hasFeature)
52 | }
53 | }
54 | }
55 | }
56 |
57 | func TestVersion(t *testing.T) {
58 | tests := []struct {
59 | name string
60 | fct func(t *testing.T)
61 | }{
62 | {"parse", testVersionNumberParse},
63 | {"compare", testVersionNumberCompare},
64 | {"feature", testVersionFeature},
65 | }
66 |
67 | for _, test := range tests {
68 | t.Run(test.name, func(t *testing.T) {
69 | test.fct(t)
70 | })
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/testdata/x509/rootCA.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIFKTCCAxGgAwIBAgIUGuggDXnGLuG2+1/hJzhCQLyoje0wDQYJKoZIhvcNAQEL
3 | BQAwJDEiMCAGA1UEAwwZR28tSERCIFguNTA5IFRlc3RzIFJvb3RDQTAeFw0yMzEw
4 | MDIxMDM4MDRaFw0zMzA5MjkxMDM4MDRaMCQxIjAgBgNVBAMMGUdvLUhEQiBYLjUw
5 | OSBUZXN0cyBSb290Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDh
6 | wFDDaT7hf+3Q2NypyX7/2s0BexzvN6ITfiDQ5436gQrErAAHb7MjDidgz5BIbTwQ
7 | TqLVufOMwGW4fKFGVjncXLlSDpy1mb0PuSawhB8YJ644M1TIOFwPgpaDX2RZ+EzE
8 | Y6MHgY4Or6tGD5t/ov5uJuuAO4EJx0TxVpUjl6ns3gbdASXzlaabeeD5e2wCcI9g
9 | u1kgri/7Tf+WATnHTeRlbtTibXftmVkGzIqw0rD/CWACHxNjESLsUt0syUBC/xdr
10 | USj+ZwE5Ci3+rhfbdBvxGdB85Z3tQuW2HrlwrdpacrE3Dm7oWnv8Os1Q4Ij9k9XA
11 | kLGlXKkKjR1qKXTNxIy0gX6zLJw+MRZ03jJGRZTt0lwnSNv4uFiE4shZxoNBtmKk
12 | jkM6Z7FsyGfEE2vSSYCOKnavufa0MPM5vTFsWR5b7gckCrDtiX9QSw758hj/LQyx
13 | OvUw8q65lflSF3/fkG0E1AZ7N5ZODEr1REH91jywU/EzczZf1IZQ6IRFPM0RKuIv
14 | haNzMhGFjHwvuaGza4JwuUSrZ/NBEJqUGY6SSqmC25FGGwec3Cx0tlAVRiRSYgTw
15 | XVtuAyHgMNjNsiWtjOs+V9UUq6stO2v0biUqlFUIioBn9SQWkPOm0NCZBAEEKZjj
16 | 3W5KuzKKpdBNQqUhQsgwvDACVQLbu63e8DbjRNdZ+wIDAQABo1MwUTAdBgNVHQ4E
17 | FgQULKL13uEh6EMwVcFz9XV2VmSvDtEwHwYDVR0jBBgwFoAULKL13uEh6EMwVcFz
18 | 9XV2VmSvDtEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAd7dI
19 | ZpgfJ7lxzj4SAjCNgFtgUuBh1l/yq5/NI/LsTBKeKH2Ta4gAqODPNoP8/4OBbTgO
20 | o/nBnBd2D8kladb4imeHG7PJMcF4eYFqPmsjvUv556FPfP8LV2bMfet/N+i9EXGi
21 | FWkWG4b8mVrlxrQfl8ZftPh9P1XdE7urqHRnstiaWaIxQQZhoGcYbm724QI1+EtX
22 | /qQhO/+ohagkAadZC/QVPysHwZ/11zFIO6MWqx3p/WSUMN8mk0hhqvLg2RFgitaj
23 | NVlHo+CMtok+j27PIjeT5piX8m/SMKRQ9rpM8S5Vn7rLrLPPEq8khEnP71p1sLuu
24 | NrVNeanDkvnWvldVfMgsMUHd/PZozry0gnlouJa8J2euVmiYpISlQdQudSqH9QN5
25 | J0OIOo+a8n7KXiUDYVW4UlY+YEU5v2Viq+wkYHrDp3/51XY50lXfJaimqgHQ6kB6
26 | cUutexEhnNkxqCvvVjrX8iCE0P7VLvaUfjnOhRm/hTGrTDfRj4yK6eGCe92TRe4A
27 | zr+gLFjfurwYXKK8Pi5dvwN9Uvh9eggdVNcOKBXYmY9iVfTAvYscvaIvxwk3txRi
28 | vJUYTWykDUNz0kLRpRDmKCMFHAcIAjHcSWPC/nQUoMc9TZ9WDDrk1J9gb3OcCtTa
29 | 3gBPBgTfY8l2Nmurt26PYwDXsiQjPOP0EQtDXxo=
30 | -----END CERTIFICATE-----
31 |
--------------------------------------------------------------------------------
/driver/internal/protocol/fieldnames.go:
--------------------------------------------------------------------------------
1 | package protocol
2 |
3 | import (
4 | "cmp"
5 | "errors"
6 | "slices"
7 | "unique"
8 |
9 | "github.com/SAP/go-hdb/driver/internal/protocol/encoding"
10 | )
11 |
12 | const noFieldName uint32 = 0xFFFFFFFF
13 |
14 | type ofsHandle struct {
15 | ofs uint32
16 | handle unique.Handle[string]
17 | }
18 |
19 | type fieldNames struct { // use struct here to get a stable pointer
20 | ofsHandles []ofsHandle
21 | }
22 |
23 | func (fn *fieldNames) search(ofs uint32) (int, bool) {
24 | return slices.BinarySearchFunc(fn.ofsHandles, ofsHandle{ofs: ofs}, func(a, b ofsHandle) int {
25 | return cmp.Compare(a.ofs, b.ofs)
26 | })
27 | }
28 |
29 | func (fn *fieldNames) insertOfs(ofs uint32) {
30 | if ofs == noFieldName {
31 | return
32 | }
33 | i, found := fn.search(ofs)
34 | if found { // duplicate
35 | return
36 | }
37 |
38 | if i >= len(fn.ofsHandles) { // append
39 | fn.ofsHandles = append(fn.ofsHandles, ofsHandle{ofs: ofs})
40 | } else {
41 | fn.ofsHandles = append(fn.ofsHandles, ofsHandle{})
42 | copy(fn.ofsHandles[i+1:], fn.ofsHandles[i:])
43 | fn.ofsHandles[i] = ofsHandle{ofs: ofs}
44 | }
45 | }
46 |
47 | func (fn *fieldNames) name(ofs uint32) string {
48 | if i, found := fn.search(ofs); found {
49 | return fn.ofsHandles[i].handle.Value()
50 | }
51 | return ""
52 | }
53 |
54 | func (fn *fieldNames) decode(dec *encoding.Decoder) error {
55 | // TODO sniffer - python client texts are returned differently?
56 | // - double check offset calc (CESU8 issue?)
57 | var errs []error
58 |
59 | pos := uint32(0)
60 | for i, ofsHandle := range fn.ofsHandles {
61 | diff := int(ofsHandle.ofs - pos)
62 | if diff > 0 {
63 | dec.Skip(diff)
64 | }
65 | n, s, err := dec.CESU8LIString()
66 | if err != nil {
67 | errs = append(errs, err)
68 | }
69 | fn.ofsHandles[i].handle = unique.Make(s)
70 | // len byte + size + diff
71 | pos += uint32(n + diff) //nolint: gosec
72 | }
73 | return errors.Join(errs...)
74 | }
75 |
--------------------------------------------------------------------------------
/driver/calldriver.go:
--------------------------------------------------------------------------------
1 | package driver
2 |
3 | import (
4 | "context"
5 | "database/sql/driver"
6 | "fmt"
7 | )
8 |
9 | // Boilerplate to define a minimal sql driver implementation.
10 | // To be used for converting stored procedure output parameters
11 | // including sql.Rows output table parameters to guarantee
12 | // exactly the same conversion behavior as for sql.Query.
13 | var (
14 | _ driver.Driver = (*callDriver)(nil)
15 | _ driver.Connector = (*callConnector)(nil)
16 | _ driver.Conn = (*callConn)(nil)
17 | _ driver.NamedValueChecker = (*callConn)(nil)
18 | _ driver.QueryerContext = (*callConn)(nil)
19 | )
20 |
21 | type callDriver struct{}
22 |
23 | var (
24 | defCallDriver = &callDriver{}
25 | defCallConn = &callConn{}
26 | )
27 |
28 | func (d *callDriver) Open(name string) (driver.Conn, error) { return defCallConn, nil }
29 |
30 | type callConnector struct{}
31 |
32 | func (c *callConnector) Connect(context.Context) (driver.Conn, error) { return defCallConn, nil }
33 | func (c *callConnector) Driver() driver.Driver { return defCallDriver }
34 |
35 | type callConn struct{}
36 |
37 | func (c *callConn) Prepare(query string) (driver.Stmt, error) { panic("not implemented") }
38 | func (c *callConn) Close() error { return nil }
39 | func (c *callConn) Begin() (driver.Tx, error) { panic("not implemented") }
40 | func (c *callConn) CheckNamedValue(nv *driver.NamedValue) error { return nil }
41 |
42 | // QueryContext is used to convert the stored procedure output parameters.
43 | func (c *callConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
44 | if len(args) != 1 {
45 | return nil, fmt.Errorf("invalid argument length %d - expected 1", len(args))
46 | }
47 | cr, ok := args[0].Value.(*callResult)
48 | if !ok {
49 | return nil, fmt.Errorf("invalid argument type %T", args[0])
50 | }
51 | return cr, nil
52 | }
53 |
--------------------------------------------------------------------------------
/driver/spatial/example_spatial_test.go:
--------------------------------------------------------------------------------
1 | package spatial_test
2 |
3 | import (
4 | "fmt"
5 | "log"
6 |
7 | "github.com/SAP/go-hdb/driver/spatial"
8 | )
9 |
10 | /*
11 | ExampleGeometry demonstrates the conversion of a geospatial object to
12 | - 'well known binary' format
13 | - 'extended well known binary' format
14 | - 'well known text' format
15 | - 'extended known text' format
16 | - 'geoJSON' format
17 | */
18 | func ExampleGeometry() {
19 | // create geospatial object
20 | g := spatial.GeometryCollection{spatial.Point{X: 1, Y: 1}, spatial.LineString{{X: 1, Y: 1}, {X: 2, Y: 2}}}
21 |
22 | // 'well known binary' format
23 | wkb, err := spatial.EncodeWKB(g, false)
24 | if err != nil {
25 | log.Fatal(err)
26 | }
27 | fmt.Printf("%s\n", wkb)
28 |
29 | // 'extended well known binary' format
30 | ewkb, err := spatial.EncodeEWKB(g, false, 4711)
31 | if err != nil {
32 | log.Fatal(err)
33 | }
34 | fmt.Printf("%s\n", ewkb)
35 |
36 | // - 'well known text' format
37 | wkt, err := spatial.EncodeWKT(g)
38 | if err != nil {
39 | log.Fatal(err)
40 | }
41 | fmt.Printf("%s\n", wkt)
42 |
43 | // - 'extended known text' format
44 | ewkt, err := spatial.EncodeEWKT(g, 4711)
45 | if err != nil {
46 | log.Fatal(err)
47 | }
48 | fmt.Printf("%s\n", ewkt)
49 |
50 | // - 'geoJSON' format
51 | geoJSON, err := spatial.EncodeGeoJSON(g)
52 | if err != nil {
53 | log.Fatal(err)
54 | }
55 | fmt.Printf("%s\n", geoJSON)
56 |
57 | // output: 0107000000020000000101000000000000000000f03f000000000000f03f010200000002000000000000000000f03f000000000000f03f00000000000000400000000000000040
58 | // 010700002067120000020000000101000000000000000000f03f000000000000f03f010200000002000000000000000000f03f000000000000f03f00000000000000400000000000000040
59 | // GEOMETRYCOLLECTION (POINT (1 1),LINESTRING (1 1,2 2))
60 | // SRID=4711;GEOMETRYCOLLECTION (POINT (1 1),LINESTRING (1 1,2 2))
61 | // {"type":"GeometryCollection","geometries":[{"type":"Point","coordinates":[1,1]},{"type":"LineString","coordinates":[[1,1],[2,2]]}]}
62 | }
63 |
--------------------------------------------------------------------------------
/driver/example_scanlobwriter_test.go:
--------------------------------------------------------------------------------
1 | //go:build !unit
2 |
3 | package driver_test
4 |
5 | import (
6 | "database/sql"
7 | "fmt"
8 | "log"
9 |
10 | "github.com/SAP/go-hdb/driver"
11 | )
12 |
13 | // WriterLob defines a io.Writer based data type for scanning Lobs.
14 | type WriterLob []byte
15 |
16 | // Write implements the io.Writer interface.
17 | func (b *WriterLob) Write(p []byte) (n int, err error) {
18 | *b = append(*b, p...)
19 | return len(p), nil
20 | }
21 |
22 | // Scan implements the database.sql.Scanner interface.
23 | func (b *WriterLob) Scan(arg any) error { return driver.ScanLobWriter(arg, b) }
24 |
25 | // ExampleScanLobWriter demontrates how to read Lob data using a io.Writer based data type.
26 | func ExampleScanLobWriter() {
27 | // Open Test database.
28 | db := sql.OpenDB(driver.MT.Connector())
29 | defer db.Close()
30 |
31 | table := driver.RandomIdentifier("lob_")
32 |
33 | if _, err := db.Exec(fmt.Sprintf("create table %s (n1 nclob, n2 nclob)", table)); err != nil {
34 | log.Fatalf("create table failed: %s", err)
35 | }
36 |
37 | tx, err := db.Begin() // Start Transaction to avoid database error: SQL Error 596 - LOB streaming is not permitted in auto-commit mode.
38 | if err != nil {
39 | log.Fatal(err)
40 | }
41 |
42 | // Lob content can be written using a string.
43 | content := "scan lob writer"
44 | _, err = tx.Exec(fmt.Sprintf("insert into %s values (?, ?)", table), content, content)
45 | if err != nil {
46 | log.Fatal(err)
47 | }
48 |
49 | if err := tx.Commit(); err != nil {
50 | log.Fatal(err)
51 | }
52 |
53 | // Select.
54 | stmt, err := db.Prepare(fmt.Sprintf("select * from %s", table))
55 | if err != nil {
56 | log.Fatal(err)
57 | }
58 | defer stmt.Close()
59 |
60 | // Scan into WriterLob and sql.Null[WriterLob].
61 | var w WriterLob
62 | var nw sql.Null[WriterLob]
63 | if err := stmt.QueryRow().Scan(&w, &nw); err != nil {
64 | log.Fatal(err)
65 | }
66 | fmt.Println(string(w))
67 | fmt.Println(string(nw.V))
68 |
69 | // output: scan lob writer
70 | // scan lob writer
71 | }
72 |
--------------------------------------------------------------------------------
/testdata/x509/README.md:
--------------------------------------------------------------------------------
1 | # Test keys and certificates to test X.509 authentication
2 |
3 | ## Commands used to create the keys and certs
4 |
5 | ```bash
6 | openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out rootCA.key
7 | openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 3650 -out rootCA.crt -subj "/CN=Go-HDB X.509 Tests RootCA"
8 |
9 | openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out rsa.pkcs8.key
10 | openssl rsa -in rsa.pkcs8.key -out rsa.pkcs1.key -traditional
11 | openssl req -new -key rsa.pkcs8.key -out rsa.csr -subj "/CN=GoHDBTestUser_rsa"
12 | openssl x509 -req -in rsa.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out rsa.crt -days 3649 -sha256
13 |
14 | openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out ec_p256.pkcs8.key
15 | openssl ec -in ec_p256.pkcs8.key -out ec_p256.ec.key
16 | openssl req -new -key ec_p256.pkcs8.key -out ec_p256.csr -subj "/CN=GoHDBTestUser_ec_p256"
17 | openssl x509 -req -in ec_p256.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out ec_p256.crt -days 3649 -sha256
18 |
19 | openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-384 -out ec_p384.pkcs8.key
20 | openssl ec -in ec_p384.pkcs8.key -out ec_p384.ec.key
21 | openssl req -new -key ec_p384.pkcs8.key -out ec_p384.csr -subj "/CN=GoHDBTestUser_ec_p384"
22 | openssl x509 -req -in ec_p384.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out ec_p384.crt -days 3649 -sha256
23 |
24 | openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-521 -out ec_p521.pkcs8.key
25 | openssl ec -in ec_p521.pkcs8.key -out ec_p521.ec.key
26 | openssl req -new -key ec_p521.pkcs8.key -out ec_p521.csr -subj "/CN=GoHDBTestUser_ec_p521"
27 | openssl x509 -req -in ec_p521.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out ec_p521.crt -days 3649 -sha256
28 |
29 | openssl genpkey -algorithm ED25519 -out ed25519.key
30 | openssl req -new -key ed25519.key -out ed25519.csr -subj "/CN=GoHDBTestUser_ed25519"
31 | openssl x509 -req -in ed25519.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out ed25519.crt -days 3649 -sha256
32 | ```
33 |
--------------------------------------------------------------------------------
/driver/internal/protocol/messagetype.go:
--------------------------------------------------------------------------------
1 | package protocol
2 |
3 | // MessageType represents the message type.
4 | type MessageType int8
5 |
6 | // MessageType constants.
7 | const (
8 | mtNil MessageType = 0
9 | MtExecuteDirect MessageType = 2
10 | MtPrepare MessageType = 3
11 | mtAbapStream MessageType = 4
12 | mtXAStart MessageType = 5
13 | mtXAJoin MessageType = 6
14 | MtExecute MessageType = 13
15 | MtWriteLob MessageType = 16
16 | MtReadLob MessageType = 17
17 | mtFindLob MessageType = 18
18 | MtAuthenticate MessageType = 65
19 | MtConnect MessageType = 66
20 | MtCommit MessageType = 67
21 | MtRollback MessageType = 68
22 | MtCloseResultset MessageType = 69
23 | MtDropStatementID MessageType = 70
24 | MtFetchNext MessageType = 71
25 | mtFetchAbsolute MessageType = 72
26 | mtFetchRelative MessageType = 73
27 | mtFetchFirst MessageType = 74
28 | mtFetchLast MessageType = 75
29 | MtDisconnect MessageType = 77
30 | mtExecuteITab MessageType = 78
31 | mtFetchNextITab MessageType = 79
32 | mtInsertNextITab MessageType = 80
33 | mtBatchPrepare MessageType = 81
34 | MtDBConnectInfo MessageType = 82
35 | mtXopenXAStart MessageType = 83
36 | mtXopenXAEnd MessageType = 84
37 | mtXopenXAPrepare MessageType = 85
38 | mtXopenXACommit MessageType = 86
39 | mtXopenXARollback MessageType = 87
40 | mtXopenXARecover MessageType = 88
41 | mtXopenXAForget MessageType = 89
42 | )
43 |
44 | // ClientInfoSupported returns true if message does support client info, false otherwise.
45 | func (mt MessageType) ClientInfoSupported() bool {
46 | /*
47 | mtConnect is only supported since 2.00.042
48 | As server version is only available after connect we do not use it
49 | to support especially version 1.00.122 until maintenance
50 | will end in sommer 2021
51 |
52 | return mt == mtConnect || mt == mtPrepare || mt == mtExecuteDirect || mt == mtExecute
53 | */
54 | return mt == MtPrepare || mt == MtExecuteDirect || mt == MtExecute
55 | }
56 |
--------------------------------------------------------------------------------
/driver/internal/protocol/simpleparts.go:
--------------------------------------------------------------------------------
1 | package protocol
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/SAP/go-hdb/driver/internal/protocol/encoding"
7 | "github.com/SAP/go-hdb/driver/unicode/cesu8"
8 | )
9 |
10 | // ClientID represents a client id part.
11 | type ClientID []byte
12 |
13 | func (id ClientID) String() string { return string(id) }
14 | func (id ClientID) size() int { return len(id) }
15 | func (id *ClientID) decodeBufLen(dec *encoding.Decoder, bufLen int) error {
16 | *id = resizeSlice(*id, bufLen)
17 | dec.Bytes(*id)
18 | return dec.Error()
19 | }
20 | func (id ClientID) encode(enc *encoding.Encoder) error { enc.Bytes(id); return nil }
21 |
22 | // Command represents a command part with cesu8 content.
23 | type Command []byte
24 |
25 | func (c Command) String() string { return string(c) }
26 | func (c Command) size() int { return cesu8.Size(c) }
27 | func (c *Command) decodeBufLen(dec *encoding.Decoder, bufLen int) error {
28 | *c = resizeSlice(*c, bufLen)
29 | var err error
30 | *c, err = dec.CESU8Bytes(len(*c))
31 | if err != nil {
32 | return err
33 | }
34 | return dec.Error()
35 | }
36 | func (c Command) encode(enc *encoding.Encoder) error { _, err := enc.CESU8Bytes(c); return err }
37 |
38 | // Fetchsize represents a fetch size part.
39 | type Fetchsize int32
40 |
41 | func (s Fetchsize) String() string { return fmt.Sprintf("fetchsize %d", s) }
42 | func (s *Fetchsize) decode(dec *encoding.Decoder) error {
43 | *s = Fetchsize(dec.Int32())
44 | return dec.Error()
45 | }
46 | func (s Fetchsize) encode(enc *encoding.Encoder) error { enc.Int32(int32(s)); return nil }
47 |
48 | // StatementID represents the statement id part type.
49 | type StatementID uint64
50 |
51 | func (id StatementID) String() string { return fmt.Sprintf("%d", id) }
52 |
53 | // Decode implements the partDecoder interface.
54 | func (id *StatementID) decode(dec *encoding.Decoder) error {
55 | *id = StatementID(dec.Uint64())
56 | return dec.Error()
57 | }
58 |
59 | // Encode implements the partEncoder interface.
60 | func (id StatementID) encode(enc *encoding.Encoder) error { enc.Uint64(uint64(id)); return nil }
61 |
--------------------------------------------------------------------------------
/driver/internal/protocol/datatype.go:
--------------------------------------------------------------------------------
1 | package protocol
2 |
3 | import (
4 | "database/sql"
5 | "reflect"
6 | "time"
7 | )
8 |
9 | // DataType is the type definition for data types supported by this package.
10 | type DataType byte
11 |
12 | // Data type constants.
13 | const (
14 | DtUnknown DataType = iota // unknown data type
15 | DtBoolean
16 | DtTinyint
17 | DtSmallint
18 | DtInteger
19 | DtBigint
20 | DtReal
21 | DtDouble
22 | DtDecimal
23 | DtTime
24 | DtString
25 | DtBytes
26 | DtLob
27 | DtRows
28 | )
29 |
30 | // RegisterScanType registers driver owned datatype scantypes (e.g. Decimal, Lob).
31 | func RegisterScanType(dt DataType, scanType, scanNullType reflect.Type) bool {
32 | scanTypes[dt].scanType = scanType
33 | scanTypes[dt].scanNullType = scanNullType
34 | return true
35 | }
36 |
37 | var scanTypes = []struct {
38 | scanType reflect.Type
39 | scanNullType reflect.Type
40 | }{
41 | DtUnknown: {reflect.TypeFor[any](), reflect.TypeFor[any]()},
42 | DtBoolean: {reflect.TypeFor[bool](), reflect.TypeFor[sql.NullBool]()},
43 | DtTinyint: {reflect.TypeFor[uint8](), reflect.TypeFor[sql.NullByte]()},
44 | DtSmallint: {reflect.TypeFor[int16](), reflect.TypeFor[sql.NullInt16]()},
45 | DtInteger: {reflect.TypeFor[int32](), reflect.TypeFor[sql.NullInt32]()},
46 | DtBigint: {reflect.TypeFor[int64](), reflect.TypeFor[sql.NullInt64]()},
47 | DtReal: {reflect.TypeFor[float32](), reflect.TypeFor[sql.NullFloat64]()},
48 | DtDouble: {reflect.TypeFor[float64](), reflect.TypeFor[sql.NullFloat64]()},
49 | DtTime: {reflect.TypeFor[time.Time](), reflect.TypeFor[sql.NullTime]()},
50 | DtString: {reflect.TypeFor[string](), reflect.TypeFor[sql.NullString]()},
51 | DtBytes: {nil, nil}, // to be registered by driver
52 | DtDecimal: {nil, nil}, // to be registered by driver
53 | DtLob: {nil, nil}, // to be registered by driver
54 | DtRows: {reflect.TypeFor[sql.Rows](), reflect.TypeFor[sql.Rows]()},
55 | }
56 |
57 | // ScanType return the scan type (reflect.Type) of the corresponding data type.
58 | func (dt DataType) ScanType(nullable bool) reflect.Type {
59 | if nullable {
60 | return scanTypes[dt].scanNullType
61 | }
62 | return scanTypes[dt].scanType
63 | }
64 |
--------------------------------------------------------------------------------
/cmd/bulkbench/bulkbench.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "embed"
6 | "errors"
7 | "flag"
8 | "io/fs"
9 | "log"
10 | "net"
11 | "net/http"
12 | _ "net/http/pprof" //nolint: gosec
13 | "os"
14 | "os/signal"
15 | "runtime"
16 | "time"
17 | )
18 |
19 | //go:embed templates/*
20 | var rootFS embed.FS
21 |
22 | const (
23 | tmplIndex = "index.html"
24 | tmplDBResult = "dbresult.html"
25 | tmplTestResult = "testresult.html"
26 | )
27 |
28 | func main() {
29 | if !flag.Parsed() {
30 | flag.Parse()
31 | }
32 |
33 | // Print runtime info.
34 | log.Printf("Runtime Info - GOMAXPROCS: %d NumCPU: %d GOOS/GOARCH: %s/%s", runtime.GOMAXPROCS(0), runtime.NumCPU(), runtime.GOOS, runtime.GOARCH)
35 |
36 | if err := run(); err != nil {
37 | log.Fatal(err)
38 | }
39 | }
40 |
41 | func run() error {
42 | dba, err := newDBA(dsn)
43 | if err != nil {
44 | return err
45 | }
46 | defer dba.close()
47 |
48 | templateFS, err := fs.Sub(rootFS, "templates")
49 | if err != nil {
50 | return err
51 | }
52 | testHandler, err := newTestHandler(dba, templateFS)
53 | if err != nil {
54 | return err
55 | }
56 | dbHandler, err := newDBHandler(dba, templateFS)
57 | if err != nil {
58 | return err
59 | }
60 | indexHandler, err := newIndexHandler(dba, templateFS)
61 | if err != nil {
62 | return err
63 | }
64 |
65 | http.Handle("/test/", testHandler)
66 | http.Handle("/db/", dbHandler)
67 | http.Handle("/", indexHandler)
68 | http.HandleFunc("/favicon.ico", func(http.ResponseWriter, *http.Request) {}) // Avoid "/" handler call for browser favicon request.
69 | addr := net.JoinHostPort(host, port)
70 | srv := http.Server{Addr: addr, ReadHeaderTimeout: 30 * time.Second}
71 |
72 | done := make(chan struct{})
73 | go func() {
74 | sigint := make(chan os.Signal, 1)
75 | signal.Notify(sigint, os.Interrupt)
76 | <-sigint
77 |
78 | // shutdown server.
79 | log.Println("shutting down...")
80 | if err := srv.Shutdown(context.Background()); err != nil {
81 | log.Print(err)
82 | }
83 | close(done)
84 | }()
85 |
86 | log.Printf("listening on %s ...", addr)
87 | if err := srv.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
88 | log.Print(err)
89 | }
90 | <-done
91 | return nil
92 | }
93 |
--------------------------------------------------------------------------------
/driver/emptydate_test.go:
--------------------------------------------------------------------------------
1 | //go:build !unit
2 |
3 | package driver
4 |
5 | import (
6 | "database/sql"
7 | "fmt"
8 | "testing"
9 | "time"
10 |
11 | p "github.com/SAP/go-hdb/driver/internal/protocol"
12 | )
13 |
14 | func testEmptyDate(t *testing.T, tableName Identifier, dfv int, emptyDateAsNull bool) {
15 | var nt sql.NullTime
16 | var emptyDate = time.Date(0, time.December, 31, 0, 0, 0, 0, time.UTC)
17 |
18 | connector := MT.NewConnector()
19 | connector.SetDfv(dfv)
20 | connector.SetEmptyDateAsNull(emptyDateAsNull)
21 | db := sql.OpenDB(connector)
22 | defer db.Close()
23 |
24 | // Query db.
25 | rows, err := db.Query(fmt.Sprintf("select * from %s", tableName))
26 | if err != nil {
27 | t.Fatal(err)
28 | }
29 | defer rows.Close()
30 |
31 | if rows.Next() {
32 | if err := rows.Scan(&nt); err != nil {
33 | t.Fatal(err)
34 | }
35 | }
36 | if err := rows.Err(); err != nil {
37 | t.Fatal(err)
38 | }
39 |
40 | columnTypes, err := rows.ColumnTypes()
41 | if err != nil {
42 | t.Fatal(err)
43 | }
44 |
45 | // dfv == 1 -> empty date equals NULL, else depends on build tag
46 | if dfv == p.DfvLevel1 || emptyDateAsNull {
47 | if nt.Valid != false {
48 | t.Fatalf("dfv %d time %v columnType %v", dfv, nt, columnTypes[0].DatabaseTypeName())
49 | }
50 | } else {
51 | if !nt.Time.Equal(emptyDate) {
52 | t.Fatalf("dfv %d time %v columnType %v", dfv, nt, columnTypes[0].DatabaseTypeName())
53 | }
54 | }
55 | }
56 |
57 | func TestEmptyDate(t *testing.T) {
58 | t.Parallel()
59 |
60 | tableName := RandomIdentifier("table_")
61 |
62 | db := MT.DB()
63 |
64 | // Create table.
65 | if _, err := db.Exec(fmt.Sprintf("create table %s (d date)", tableName)); err != nil {
66 | t.Fatal(err)
67 | }
68 | // Insert empty date value.
69 | if _, err := db.Exec(fmt.Sprintf("insert into %s values ('0000-00-00')", tableName)); err != nil {
70 | t.Fatal(err)
71 | }
72 |
73 | for _, dfv := range p.SupportedDfvs(testing.Short()) {
74 | t.Run(fmt.Sprintf("dfv %d emptyDateAsNull %t", dfv, false), func(t *testing.T) {
75 | t.Parallel()
76 | testEmptyDate(t, tableName, dfv, false)
77 | })
78 | t.Run(fmt.Sprintf("dfv %d emptyDateAsNull %t", dfv, true), func(t *testing.T) {
79 | t.Parallel()
80 | testEmptyDate(t, tableName, dfv, true)
81 | })
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/driver/trace.go:
--------------------------------------------------------------------------------
1 | package driver
2 |
3 | import (
4 | "context"
5 | "database/sql/driver"
6 | "flag"
7 | "fmt"
8 | "log/slog"
9 | "strconv"
10 | "sync/atomic"
11 | "time"
12 | )
13 |
14 | var (
15 | protTrace atomic.Bool
16 | sqlTrace atomic.Bool
17 | )
18 |
19 | func setTrace(b *atomic.Bool, s string) error {
20 | v, err := strconv.ParseBool(s)
21 | if err == nil {
22 | b.Store(v)
23 | }
24 | return err
25 | }
26 |
27 | func init() {
28 | flag.BoolFunc("hdb.protTrace", "enabling hdb protocol trace", func(s string) error { return setTrace(&protTrace, s) })
29 | flag.BoolFunc("hdb.sqlTrace", "enabling hdb sql trace", func(s string) error { return setTrace(&sqlTrace, s) })
30 | }
31 |
32 | // SQLTrace returns true if sql tracing output is active, false otherwise.
33 | func SQLTrace() bool { return sqlTrace.Load() }
34 |
35 | // SetSQLTrace sets sql tracing output active or inactive.
36 | func SetSQLTrace(on bool) { sqlTrace.Store(on) }
37 |
38 | const (
39 | tracePing = "ping"
40 | tracePrepare = "prepare"
41 | traceQuery = "query"
42 | traceExec = "exec"
43 | traceExecCall = "call"
44 | )
45 |
46 | type sqlTracer struct {
47 | logger *slog.Logger
48 | maxArg int
49 | }
50 |
51 | const defSQLTracerMaxArg = 5 // default limit of number of arguments
52 |
53 | func newSQLTracer(logger *slog.Logger, maxArg int) *sqlTracer {
54 | if maxArg <= 0 {
55 | maxArg = defSQLTracerMaxArg
56 | }
57 | return &sqlTracer{logger: logger, maxArg: maxArg}
58 | }
59 |
60 | func (t *sqlTracer) log(ctx context.Context, startTime time.Time, traceKind string, query string, nvargs ...driver.NamedValue) {
61 | duration := time.Since(startTime).Milliseconds()
62 | l := len(nvargs)
63 |
64 | attrs := []slog.Attr{
65 | slog.String(traceKind, query),
66 | slog.Int64("ms", duration),
67 | }
68 |
69 | if l == 0 {
70 | t.logger.LogAttrs(ctx, slog.LevelInfo, "SQL", attrs...)
71 | return
72 | }
73 |
74 | numArg := min(l, t.maxArg)
75 | argAttrs := make([]slog.Attr, 0, numArg)
76 | for i := range numArg {
77 | name := nvargs[i].Name
78 | if name == "" {
79 | name = strconv.Itoa(nvargs[i].Ordinal)
80 | }
81 | argAttrs = append(argAttrs, slog.String(name, fmt.Sprintf("%v", nvargs[i].Value)))
82 | }
83 | if l > t.maxArg {
84 | argAttrs = append(argAttrs, slog.Int("numArgSkip", l-t.maxArg))
85 | }
86 | attrs = append(attrs, slog.Any("arg", slog.GroupValue(argAttrs...)))
87 |
88 | t.logger.LogAttrs(ctx, slog.LevelInfo, "SQL", attrs...)
89 | }
90 |
--------------------------------------------------------------------------------
/cmd/bulkbench/benchmark_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "os"
7 | "runtime"
8 | "sort"
9 | "testing"
10 | "time"
11 |
12 | "github.com/SAP/go-hdb/driver"
13 | )
14 |
15 | func Benchmark(b *testing.B) {
16 |
17 | dba, err := newDBA(dsn)
18 | if err != nil {
19 | b.Fatal(err)
20 | }
21 | ts := newTests(dba)
22 |
23 | f := func(b *testing.B, sequential bool, batchCount, batchSize int) {
24 | ds := make([]time.Duration, 0, b.N)
25 | var avgDuration, maxDuration time.Duration
26 | var minDuration time.Duration = 1<<63 - 1
27 |
28 | for b.Loop() {
29 | tr := ts.execute(sequential, batchCount, batchSize, drop)
30 | if tr.Err != nil {
31 | b.Fatal(tr.Err)
32 | }
33 |
34 | avgDuration += tr.Duration
35 | if tr.Duration < minDuration {
36 | minDuration = tr.Duration
37 | }
38 | if tr.Duration > maxDuration {
39 | maxDuration = tr.Duration
40 | }
41 | ds = append(ds, tr.Duration)
42 | }
43 |
44 | // Median.
45 | var medDuration time.Duration
46 | sort.Slice(ds, func(i, j int) bool { return ds[i] < ds[j] })
47 | l := len(ds)
48 | switch {
49 | case l == 0: // keep med == 0
50 | case l%2 != 0: // odd number
51 | medDuration = ds[l/2] // mid value
52 | default:
53 | medDuration = (ds[l/2] + ds[l/2-1]) / 2 // even number - return avg of the two mid numbers
54 | }
55 |
56 | // Add metrics.
57 | b.ReportMetric((avgDuration / time.Duration(b.N)).Seconds(), "avgsec/op")
58 | b.ReportMetric(minDuration.Seconds(), "minsec/op")
59 | b.ReportMetric(maxDuration.Seconds(), "maxsec/op")
60 | b.ReportMetric(medDuration.Seconds(), "medsec/op")
61 | }
62 |
63 | // Additional info.
64 | log.SetOutput(os.Stdout)
65 | log.Printf("Runtime Info - GOMAXPROCS: %d NumCPU: %d DriverVersion %s HDBVersion: %s",
66 | runtime.GOMAXPROCS(0),
67 | runtime.NumCPU(),
68 | driver.DriverVersion,
69 | dba.hdbVersion(),
70 | )
71 |
72 | b.Cleanup(func() {
73 | dba.close()
74 | })
75 |
76 | for _, prm := range parameters {
77 | // Use batchCount and batchCount flags.
78 | b.Run(fmt.Sprintf("sequential-%dx%d", prm.BatchCount, prm.BatchSize), func(b *testing.B) {
79 | f(b, true, prm.BatchCount, prm.BatchSize)
80 | })
81 | }
82 | for _, prm := range parameters {
83 | // Use batchCount and batchCount flags.
84 | b.Run(fmt.Sprintf("concurrent-%dx%d", prm.BatchCount, prm.BatchSize), func(b *testing.B) {
85 | f(b, false, prm.BatchCount, prm.BatchSize)
86 | })
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/driver/example_structscanner_test.go:
--------------------------------------------------------------------------------
1 | //go:build !unit
2 |
3 | package driver_test
4 |
5 | import (
6 | "database/sql"
7 | "fmt"
8 | "log"
9 |
10 | "github.com/SAP/go-hdb/driver"
11 | )
12 |
13 | // ExampleScanRow is used to showcase struct scanning.
14 | type ExampleScanRow struct {
15 | Afield string `sql:"A"` // database field name is "A"
16 | Bfield int `sql:"B"` // database field name is "B"
17 | C bool // database field name is "C"
18 | AsD string // database field name is "D"
19 | }
20 |
21 | // Tag implements the Tagger interface to define tags for ExampleScanRow dynamically.
22 | func (s *ExampleScanRow) Tag(fieldName string) (string, bool) {
23 | switch fieldName {
24 | case "AsD":
25 | return `sql:"D"`, true
26 | default:
27 | return "", false
28 | }
29 | }
30 |
31 | // ExampleStructScanner demonstrates how to read database rows into a go struct.
32 | func ExampleStructScanner() {
33 | // Open Test database.
34 | db := sql.OpenDB(driver.MT.Connector())
35 | defer db.Close()
36 |
37 | table := driver.RandomIdentifier("structscanner_")
38 |
39 | // Create table.
40 | if _, err := db.Exec(fmt.Sprintf("create table %s (a varchar(30), b integer, c boolean, d varchar(20))", table)); err != nil {
41 | log.Fatal(err)
42 | }
43 |
44 | // Insert test row data.
45 | if _, err := db.Exec(fmt.Sprintf("insert into %s values (?,?,?,?)", table), "test", 42, true, "I am D"); err != nil {
46 | log.Fatal(err)
47 | }
48 |
49 | // Create scanner.
50 | scanner, err := driver.NewStructScanner[ExampleScanRow]()
51 | if err != nil {
52 | log.Fatal(err)
53 | }
54 |
55 | // Scan target.
56 | row := new(ExampleScanRow)
57 |
58 | // Scan rows with the help of the struct scanner.
59 | if err = func() error {
60 | rows, err := db.Query(fmt.Sprintf("select * from %s", table))
61 | if err != nil {
62 | return err
63 | }
64 | defer rows.Close()
65 |
66 | for rows.Next() {
67 | if err := scanner.Scan(rows, row); err != nil {
68 | return err
69 | }
70 | }
71 | if rows.Err() != nil {
72 | return err
73 | }
74 | return rows.Close()
75 | }(); err != nil {
76 | log.Fatal(err)
77 | }
78 |
79 | // Scan a single row with the help of the struct scanner.
80 | if err = func() error {
81 | rows, err := db.Query(fmt.Sprintf("select * from %s", table))
82 | if err != nil {
83 | return err
84 | }
85 | // Rows will be closed by scanner.ScanRow.
86 | return scanner.ScanRow(rows, row)
87 | }(); err != nil {
88 | log.Fatal(err)
89 | }
90 |
91 | // output:
92 | }
93 |
--------------------------------------------------------------------------------
/driver/scanner_test.go:
--------------------------------------------------------------------------------
1 | //go:build !unit
2 |
3 | package driver
4 |
5 | import (
6 | "fmt"
7 | "testing"
8 | )
9 |
10 | type testScanRow struct {
11 | A string `sql:"s,varchar(30)"`
12 | B int `sql:"i,integer"`
13 | C bool
14 | Y string
15 | }
16 |
17 | func (ts *testScanRow) Tag(fieldName string) (string, bool) {
18 | switch fieldName {
19 | case "Y":
20 | return `sql:"x,varchar(30)"`, true
21 | default:
22 | return "", false
23 | }
24 | }
25 |
26 | func TestScanStruct(t *testing.T) {
27 | t.Parallel()
28 |
29 | db := MT.DB()
30 |
31 | testRow := testScanRow{A: "testRow", B: 42, C: true, Y: "I am a X"}
32 |
33 | scanner, err := NewStructScanner[testScanRow]()
34 | if err != nil {
35 | t.Fatal(err)
36 | }
37 |
38 | tableName := RandomIdentifier("scanStruct_")
39 | columnDefs, err := scanner.columnDefs()
40 | if err != nil {
41 | t.Fatal(err)
42 | }
43 | if _, err := db.Exec(fmt.Sprintf("create table %s %s", tableName, columnDefs)); err != nil {
44 | t.Fatal(err)
45 | }
46 |
47 | if _, err := db.Exec(fmt.Sprintf("insert into %s values %s", tableName, scanner.queryPlaceholders()), testRow.A, testRow.B, testRow.C, testRow.Y); err != nil {
48 | t.Fatal(err)
49 | }
50 |
51 | testScanStructRows := func() error {
52 | row := new(testScanRow)
53 |
54 | rows, err := db.Query(fmt.Sprintf("select * from %s", tableName))
55 | if err != nil {
56 | return err
57 | }
58 | defer rows.Close()
59 |
60 | for rows.Next() {
61 | if err := scanner.Scan(rows, row); err != nil {
62 | return err
63 | }
64 | if *row != testRow {
65 | return fmt.Errorf("row %v not equal to %v", row, testRow)
66 | }
67 | }
68 | return rows.Err()
69 | }
70 |
71 | testScanStructRow := func() error {
72 | row := new(testScanRow)
73 |
74 | rows, err := db.Query(fmt.Sprintf("select * from %s", tableName))
75 | if err != nil {
76 | return err
77 | }
78 |
79 | if err := scanner.ScanRow(rows, row); err != nil {
80 | return err
81 | }
82 | if *row != testRow {
83 | return fmt.Errorf("row %v not equal to %v", row, testRow)
84 | }
85 | return nil
86 | }
87 |
88 | tests := []struct {
89 | name string
90 | fn func() error
91 | }{
92 | {"testScanStructRows", testScanStructRows},
93 | {"testScanStructRow", testScanStructRow},
94 | }
95 |
96 | for _, test := range tests {
97 | t.Run(test.name, func(t *testing.T) {
98 | t.Parallel()
99 | if err := test.fn(); err != nil {
100 | t.Fatal(err)
101 | }
102 | })
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/driver/spatial/geojson.go:
--------------------------------------------------------------------------------
1 | package spatial
2 |
3 | import (
4 | "encoding/json"
5 | "math"
6 | "reflect"
7 | )
8 |
9 | func coordToSlice(fs ...float64) []*float64 {
10 | cs := make([]*float64, len(fs))
11 | for i, f := range fs {
12 | if !math.IsNaN(f) {
13 | cs[i] = &fs[i]
14 | }
15 | }
16 | return cs
17 | }
18 |
19 | func (c Coord) coordToSlice() []*float64 { return coordToSlice(c.X, c.Y) }
20 | func (c CoordZ) coordToSlice() []*float64 { return coordToSlice(c.X, c.Y, c.Z) }
21 | func (c CoordM) coordToSlice() []*float64 { return coordToSlice(c.X, c.Y, 0, c.M) }
22 | func (c CoordZM) coordToSlice() []*float64 { return coordToSlice(c.X, c.Y, c.Z, c.M) }
23 |
24 | func jsonCoord(v reflect.Value) []*float64 {
25 | switch {
26 | case v.Type().ConvertibleTo(coordType):
27 | return v.Convert(coordType).Interface().(Coord).coordToSlice()
28 | case v.Type().ConvertibleTo(coordZType):
29 | return v.Convert(coordZType).Interface().(CoordZ).coordToSlice()
30 | case v.Type().ConvertibleTo(coordMType):
31 | return v.Convert(coordMType).Interface().(CoordM).coordToSlice()
32 | case v.Type().ConvertibleTo(coordZMType):
33 | return v.Convert(coordZMType).Interface().(CoordZM).coordToSlice()
34 | default:
35 | panic("invalid coordinate type")
36 | }
37 | }
38 |
39 | func jsonConvert(rv reflect.Value) any {
40 | switch rv.Kind() {
41 | case reflect.Slice:
42 | size := rv.Len()
43 | s := make([]any, size)
44 | for i := range size {
45 | s[i] = jsonConvert(rv.Index(i))
46 | }
47 | return s
48 | case reflect.Interface:
49 | return jsonConvert(rv.Elem())
50 | default:
51 | return jsonCoord(rv)
52 | }
53 | }
54 |
55 | func jsonConvertGeometries(rv reflect.Value) any {
56 | size := rv.Len()
57 | s := make([]any, size)
58 | for i := range size {
59 | iv := rv.Index(i)
60 | s[i] = jsonType{Type: geoTypeName(iv.Interface().(Geometry)), Coordinates: jsonConvert(iv)}
61 | }
62 | return s
63 | }
64 |
65 | type jsonType struct {
66 | Type string `json:"type"`
67 | Coordinates any `json:"coordinates"`
68 | }
69 |
70 | type jsonTypeGeometries struct {
71 | Type string `json:"type"`
72 | Geometries any `json:"geometries"`
73 | }
74 |
75 | // EncodeGeoJSON encodes a geometry to the geoJSON format.
76 | func EncodeGeoJSON(g Geometry) ([]byte, error) {
77 | switch geoType(g) {
78 | case geoGeometryCollection:
79 | return json.Marshal(jsonTypeGeometries{Type: geoTypeName(g), Geometries: jsonConvertGeometries(reflect.ValueOf(g))})
80 | default:
81 | return json.Marshal(jsonType{Type: geoTypeName(g), Coordinates: jsonConvert(reflect.ValueOf(g))})
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/driver/docstore_test.go:
--------------------------------------------------------------------------------
1 | //go:build !unit
2 |
3 | package driver_test
4 |
5 | import (
6 | "bytes"
7 | "cmp"
8 | "database/sql"
9 | "encoding/json"
10 | "fmt"
11 | "reflect"
12 | "slices"
13 | "testing"
14 |
15 | "github.com/SAP/go-hdb/driver"
16 | )
17 |
18 | func TestDocstore(t *testing.T) {
19 | t.Parallel()
20 |
21 | type testData struct {
22 | ID int `json:"id"`
23 | Attr1 string `json:"attr1"`
24 | Attr2 bool `json:"attr2"`
25 | }
26 |
27 | testRecords := []testData{
28 | {ID: 1, Attr1: "test text1", Attr2: true},
29 | {ID: 2, Attr1: "test text2", Attr2: false},
30 | {ID: 3, Attr1: "test text3", Attr2: true},
31 | }
32 |
33 | collectionName := driver.RandomIdentifier("docstore_").String()
34 |
35 | db := sql.OpenDB(driver.MT.Connector())
36 | defer db.Close()
37 |
38 | if _, err := db.Exec("create collection " + collectionName); err != nil {
39 | t.Fatal(err)
40 | }
41 | defer func() {
42 | if _, err := db.Exec(fmt.Sprintf("drop collection %s cascade", collectionName)); err != nil {
43 | t.Fatal(err)
44 | }
45 | }()
46 |
47 | // marshall test data
48 | fields := make([]any, len(testRecords))
49 | for i, record := range testRecords {
50 | var err error
51 | fields[i], err = json.Marshal(record)
52 | if err != nil {
53 | t.Fatal(err)
54 | }
55 | }
56 |
57 | if _, err := db.Exec(fmt.Sprintf("insert into %s values(?)", collectionName), fields...); err != nil {
58 | t.Fatal(err)
59 | }
60 |
61 | rows, err := db.Query(fmt.Sprintf("select * from %s", collectionName))
62 | // TODO: follow up on error
63 | // rows, err := db.Query(fmt.Sprintf("select * from %s where \"id\" = %d", collectionName, 1))
64 | // rows, err := db.Query(fmt.Sprintf("select * from %s where \"id\" = ?", collectionName), 1)
65 | if err != nil {
66 | t.Fatal(err)
67 | }
68 | defer rows.Close()
69 |
70 | cmpRecords := []testData{}
71 |
72 | lob := driver.Lob{}
73 | for rows.Next() {
74 | b := new(bytes.Buffer)
75 | lob.SetWriter(b)
76 |
77 | if err := rows.Scan(&lob); err != nil {
78 | t.Fatal(err)
79 | }
80 |
81 | var field testData
82 | if err := json.Unmarshal(b.Bytes(), &field); err != nil {
83 | t.Fatal(err)
84 | }
85 | cmpRecords = append(cmpRecords, field)
86 | }
87 | if err := rows.Err(); err != nil {
88 | t.Fatal(err)
89 | }
90 |
91 | // compare
92 | cmpFn := func(a, b testData) int {
93 | return cmp.Compare(a.ID, b.ID)
94 | }
95 | slices.SortFunc(testRecords, cmpFn)
96 | slices.SortFunc(cmpRecords, cmpFn)
97 | if !reflect.DeepEqual(testRecords, cmpRecords) {
98 | t.Fatalf("got %v - expected %v", cmpRecords, testRecords)
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/sqlscript/scan_test.go:
--------------------------------------------------------------------------------
1 | package sqlscript
2 |
3 | import (
4 | "bufio"
5 | "strings"
6 | "testing"
7 | )
8 |
9 | func testScan(t *testing.T, separator rune, comments bool, script string, result []string) {
10 | scanner := bufio.NewScanner(strings.NewReader(script))
11 | if separator == DefaultSeparator && !comments {
12 | // if default values use Scan function directly
13 | scanner.Split(Scan)
14 | } else {
15 | // else use SplitFunc 'getter'
16 | scanner.Split(ScanFunc(separator, comments))
17 | }
18 |
19 | l := len(result)
20 | i := 0
21 | for scanner.Scan() {
22 | // t.Logf("statement %d\n%s", i, scanner.Bytes())
23 | if l <= i {
24 | t.Fatalf("for scan line %d result line is missing", i)
25 | }
26 | text := scanner.Text()
27 | if text != result[i] {
28 | t.Fatalf("line %d got text\n%s\nexpected\n%s", i, text, result[i])
29 | }
30 | i++
31 | }
32 | if err := scanner.Err(); err != nil {
33 | t.Fatal(err)
34 | }
35 | if i != l {
36 | t.Fatalf("got number of lines: %d - expected %d", i, l)
37 | }
38 | }
39 |
40 | func TestScript(t *testing.T) {
41 | testScript := `
42 | --Comment 0 followed by a newline
43 |
44 | --Comment 1
45 | --Comment 2
46 | STATEMENT;
47 |
48 | --Comment 3
49 | STATEMENT WITH PARAMETERS;
50 |
51 | --Comment 4
52 | STATEMENT WITH QUOTED LIST 'a,b,c';
53 | STATEMENT WITHOUT COMMENT;
54 |
55 | --COMMENT 5
56 | MULTI LINE STATEMENT WITH
57 | PARAMETERS A
58 | B AND C;
59 |
60 | --COMMENT 6
61 | MULTI LINE STATEMENT WITH SINGE QUOTED PARAMER '
62 | --A
63 | --B
64 | --C' LOOKING LIKE COMMENTS;
65 |
66 | --COMMENT 7
67 | MULTI LINE STATEMENT WITH DOUBLE QUOTED PARAMER "
68 | --A
69 | --B
70 | --C" LOOKING LIKE COMMENTS;
71 | `
72 | noCommentsResult := []string{
73 | "STATEMENT",
74 | "STATEMENT WITH PARAMETERS",
75 | "STATEMENT WITH QUOTED LIST 'a,b,c'",
76 | "STATEMENT WITHOUT COMMENT",
77 | "MULTI LINE STATEMENT WITH PARAMETERS A B AND C",
78 | "MULTI LINE STATEMENT WITH SINGE QUOTED PARAMER '--A--B--C' LOOKING LIKE COMMENTS",
79 | "MULTI LINE STATEMENT WITH DOUBLE QUOTED PARAMER \"--A--B--C\" LOOKING LIKE COMMENTS",
80 | }
81 |
82 | commentsResult := []string{
83 | "--Comment 0 followed by a newline\n--Comment 1\n--Comment 2\nSTATEMENT",
84 | "--Comment 3\nSTATEMENT WITH PARAMETERS",
85 | "--Comment 4\nSTATEMENT WITH QUOTED LIST 'a,b,c'",
86 | "STATEMENT WITHOUT COMMENT",
87 | "--COMMENT 5\nMULTI LINE STATEMENT WITH PARAMETERS A B AND C",
88 | "--COMMENT 6\nMULTI LINE STATEMENT WITH SINGE QUOTED PARAMER '--A--B--C' LOOKING LIKE COMMENTS",
89 | "--COMMENT 7\nMULTI LINE STATEMENT WITH DOUBLE QUOTED PARAMER \"--A--B--C\" LOOKING LIKE COMMENTS",
90 | }
91 |
92 | testScan(t, DefaultSeparator, false, testScript, noCommentsResult)
93 | testScan(t, DefaultSeparator, true, testScript, commentsResult)
94 | }
95 |
--------------------------------------------------------------------------------
/driver/internal/protocol/partkind.go:
--------------------------------------------------------------------------------
1 | package protocol
2 |
3 | // PartKind represents the part kind.
4 | type PartKind int8
5 |
6 | // PartKind constants.
7 | const (
8 | pkNil PartKind = 0
9 | PkCommand PartKind = 3
10 | PkResultset PartKind = 5
11 | pkError PartKind = 6
12 | PkStatementID PartKind = 10
13 | pkTransactionID PartKind = 11
14 | pkRowsAffected PartKind = 12
15 | PkResultsetID PartKind = 13
16 | PkTopologyInformation PartKind = 15
17 | pkTableLocation PartKind = 16
18 | PkReadLobRequest PartKind = 17
19 | PkReadLobReply PartKind = 18
20 | pkAbapIStream PartKind = 25
21 | pkAbapOStream PartKind = 26
22 | pkCommandInfo PartKind = 27
23 | PkWriteLobRequest PartKind = 28
24 | PkClientContext PartKind = 29
25 | PkWriteLobReply PartKind = 30
26 | PkParameters PartKind = 32
27 | PkAuthentication PartKind = 33
28 | pkSessionContext PartKind = 34
29 | PkClientID PartKind = 35
30 | pkProfile PartKind = 38
31 | PkStatementContext PartKind = 39
32 | pkPartitionInformation PartKind = 40
33 | PkOutputParameters PartKind = 41
34 | PkConnectOptions PartKind = 42
35 | pkCommitOptions PartKind = 43
36 | pkFetchOptions PartKind = 44
37 | PkFetchSize PartKind = 45
38 | PkParameterMetadata PartKind = 47
39 | PkResultMetadata PartKind = 48
40 | pkFindLobRequest PartKind = 49
41 | pkFindLobReply PartKind = 50
42 | pkItabSHM PartKind = 51
43 | pkItabChunkMetadata PartKind = 53
44 | pkItabMetadata PartKind = 55
45 | pkItabResultChunk PartKind = 56
46 | PkClientInfo PartKind = 57
47 | pkStreamData PartKind = 58
48 | pkOStreamResult PartKind = 59
49 | pkFDARequestMetadata PartKind = 60
50 | pkFDAReplyMetadata PartKind = 61
51 | pkBatchPrepare PartKind = 62 //Reserved: do not use
52 | pkBatchExecute PartKind = 63 //Reserved: do not use
53 | PkTransactionFlags PartKind = 64
54 | pkRowSlotImageParamMetadata PartKind = 65 //Reserved: do not use
55 | pkRowSlotImageResultset PartKind = 66 //Reserved: do not use
56 | PkDBConnectInfo PartKind = 67
57 | pkLobFlags PartKind = 68
58 | pkResultsetOptions PartKind = 69
59 | pkXATransactionInfo PartKind = 70
60 | pkSessionVariable PartKind = 71
61 | pkWorkLoadReplayContext PartKind = 72
62 | pkSQLReplyOptions PartKind = 73
63 | )
64 |
--------------------------------------------------------------------------------
/driver/internal/protocol/init.go:
--------------------------------------------------------------------------------
1 | package protocol
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/SAP/go-hdb/driver/internal/protocol/encoding"
7 | )
8 |
9 | type endianess int8
10 |
11 | const (
12 | bigEndian endianess = 0
13 | littleEndian endianess = 1
14 | )
15 |
16 | const (
17 | initRequestFillerSize = 4
18 | )
19 |
20 | var initRequestFiller uint32 = 0xffffffff
21 |
22 | type version struct {
23 | major int8
24 | minor int16
25 | }
26 |
27 | func (v version) String() string {
28 | return fmt.Sprintf("%d.%d", v.major, v.minor)
29 | }
30 |
31 | type initRequest struct {
32 | product version
33 | protocol version
34 | numOptions int8
35 | endianess endianess
36 | }
37 |
38 | func (r *initRequest) String() string {
39 | switch r.numOptions {
40 | default:
41 | return fmt.Sprintf("productVersion %s protocolVersion %s", r.product, r.protocol)
42 | case 1:
43 | return fmt.Sprintf("productVersion %s protocolVersion %s endianess %s", r.product, r.protocol, r.endianess)
44 | }
45 | }
46 |
47 | func (r *initRequest) decode(dec *encoding.Decoder) error {
48 | dec.Skip(initRequestFillerSize) // filler
49 | r.product.major = dec.Int8()
50 | r.product.minor = dec.Int16()
51 | r.protocol.major = dec.Int8()
52 | r.protocol.minor = dec.Int16()
53 | dec.Skip(1) // reserved filler
54 | r.numOptions = dec.Int8()
55 |
56 | switch r.numOptions {
57 | default:
58 | panic("invalid number of options")
59 |
60 | case 0:
61 | dec.Skip(2)
62 |
63 | case 1:
64 | cnt := dec.Int8()
65 | if cnt != 1 {
66 | panic("invalid number of options - 1 expected")
67 | }
68 | r.endianess = endianess(dec.Int8())
69 | }
70 | return dec.Error()
71 | }
72 |
73 | func (r *initRequest) encode(enc *encoding.Encoder) error {
74 | enc.Uint32(initRequestFiller)
75 | enc.Int8(r.product.major)
76 | enc.Int16(r.product.minor)
77 | enc.Int8(r.protocol.major)
78 | enc.Int16(r.protocol.minor)
79 |
80 | switch r.numOptions {
81 | default:
82 | panic("invalid number of options")
83 |
84 | case 0:
85 | enc.Zeroes(4)
86 |
87 | case 1:
88 | // reserved
89 | enc.Zeroes(1)
90 | enc.Int8(r.numOptions)
91 | enc.Int8(int8(littleEndian))
92 | enc.Int8(int8(r.endianess))
93 | }
94 | return nil
95 | }
96 |
97 | type initReply struct {
98 | product version
99 | protocol version
100 | }
101 |
102 | func (r *initReply) String() string {
103 | return fmt.Sprintf("productVersion %s protocolVersion %s", r.product, r.protocol)
104 | }
105 |
106 | func (r *initReply) decode(dec *encoding.Decoder) error {
107 | r.product.major = dec.Int8()
108 | r.product.minor = dec.Int16()
109 | r.protocol.major = dec.Int8()
110 | r.protocol.minor = dec.Int16()
111 | dec.Skip(2) // commitInitReplySize
112 | return dec.Error()
113 | }
114 |
--------------------------------------------------------------------------------
/cmd/sniffer/sniffer.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "errors"
5 | "flag"
6 | "fmt"
7 | "io"
8 | "log"
9 | "net"
10 | "os"
11 |
12 | "github.com/SAP/go-hdb/driver"
13 | )
14 |
15 | func main() {
16 | addr, dbAddr := cli()
17 |
18 | log.Printf("listening on %s (database address %s)", addr.String(), dbAddr.String())
19 |
20 | l, err := net.Listen(addr.Network(), addr.String())
21 | if err != nil {
22 | log.Fatal(err)
23 | }
24 | defer l.Close()
25 | for {
26 | conn, err := l.Accept()
27 | if err != nil {
28 | log.Fatal(err)
29 | }
30 |
31 | go handler(conn, dbAddr)
32 | }
33 | }
34 |
35 | func handler(conn net.Conn, dbAddr net.Addr) {
36 | dbConn, err := net.Dial(dbAddr.Network(), dbAddr.String())
37 | if err != nil {
38 | log.Printf("hdb connection error: %s", err)
39 | return
40 | }
41 |
42 | defer dbConn.Close()
43 |
44 | err = driver.NewSniffer(conn, dbConn).Run()
45 | switch {
46 | case err == nil:
47 | return
48 | case errors.Is(err, io.EOF):
49 | log.Printf("client connection closed - local address %s - remote address %s",
50 | conn.LocalAddr().String(),
51 | conn.RemoteAddr().String(),
52 | )
53 | default:
54 | log.Printf("sniffer protocol error: %s - close connection - local address %s - remote address %s",
55 | err,
56 | conn.LocalAddr().String(),
57 | conn.RemoteAddr().String(),
58 | )
59 | }
60 | }
61 |
62 | const (
63 | defaultAddr = "localhost:50000"
64 | defaultDBAddr = "localhost:39013"
65 | )
66 |
67 | type addrValue struct {
68 | addr string
69 | }
70 |
71 | func (v *addrValue) String() string { return v.addr }
72 | func (v *addrValue) Network() string { return "tcp" }
73 | func (v *addrValue) Set(s string) error {
74 | if _, _, err := net.SplitHostPort(s); err != nil {
75 | return err
76 | }
77 | v.addr = s
78 | return nil
79 | }
80 |
81 | func cli() (addr, dbAddr net.Addr) {
82 | const usageText = `
83 | %[1]s is a Hana Network Protocol analyzer. It lets you see whats happening
84 | on protocol level connecting a client to the database server.
85 | %[1]s is an early alpha-version, supporting mainly go-hdb based clients.
86 |
87 | Using with other clients might
88 | - completely fail or
89 | - provide incomplete output
90 |
91 | Usage of %[1]s:
92 | `
93 | a := &addrValue{addr: defaultAddr}
94 | dba := &addrValue{addr: defaultDBAddr}
95 |
96 | args := flag.NewFlagSet("", flag.ExitOnError)
97 | args.Usage = func() {
98 | fmt.Fprintf(args.Output(), usageText, os.Args[0])
99 | args.PrintDefaults()
100 | }
101 | args.Var(a, "s", ": Sniffer address to accept connections. (required)")
102 | args.Var(dba, "db", ": Database address to connect to. (required)")
103 |
104 | args.Parse(os.Args[1:]) //nolint:errcheck
105 |
106 | return a, dba
107 | }
108 |
--------------------------------------------------------------------------------
/driver/internal/protocol/auth/x509.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "time"
7 | )
8 |
9 | const (
10 | x509ServerNonceSize = 64
11 | )
12 |
13 | // X509 implements X509 authentication.
14 | type X509 struct {
15 | certKey *CertKey
16 | serverNonce []byte
17 | logonName string
18 | }
19 |
20 | // NewX509 creates a new authX509 instance.
21 | func NewX509(certKey *CertKey) *X509 { return &X509{certKey: certKey} }
22 |
23 | func (a *X509) String() string {
24 | return fmt.Sprintf("method type %s %s", a.Typ(), a.certKey)
25 | }
26 |
27 | // Typ implements the Method interface.
28 | func (a *X509) Typ() string { return MtX509 }
29 |
30 | // Order implements the Method interface.
31 | func (a *X509) Order() byte { return MoX509 }
32 |
33 | // PrepareInitReq implements the Method interface.
34 | func (a *X509) PrepareInitReq(prms *Prms) error {
35 | // prevent auth call to hdb with invalid certificate
36 | // as hbd only allows a limited number of unsuccessful authentications
37 | // - currently only validity period is checked
38 | if err := a.certKey.validate(time.Now()); err != nil {
39 | return err
40 | }
41 | prms.addString(a.Typ())
42 | prms.addEmpty()
43 | return nil
44 | }
45 |
46 | // InitRepDecode implements the Method interface.
47 | func (a *X509) InitRepDecode(d *Decoder) error {
48 | a.serverNonce = d.bytes()
49 | if len(a.serverNonce) != x509ServerNonceSize {
50 | return fmt.Errorf("invalid server nonce size %d - expected %d", len(a.serverNonce), x509ServerNonceSize)
51 | }
52 | return nil
53 | }
54 |
55 | // PrepareFinalReq implements the Method interface.
56 | func (a *X509) PrepareFinalReq(prms *Prms) error {
57 | prms.addEmpty() // empty username
58 | prms.addString(a.Typ())
59 |
60 | subPrms := prms.addPrms()
61 |
62 | certBlocks := a.certKey.certBlocks
63 |
64 | numBlocks := len(certBlocks)
65 |
66 | message := bytes.NewBuffer(certBlocks[0].Bytes)
67 |
68 | subPrms.addBytes(certBlocks[0].Bytes)
69 |
70 | if numBlocks == 1 {
71 | subPrms.addEmpty()
72 | } else {
73 | chainPrms := subPrms.addPrms()
74 | for _, block := range certBlocks[1:] {
75 | message.Write(block.Bytes)
76 | chainPrms.addBytes(block.Bytes)
77 | }
78 | }
79 |
80 | message.Write(a.serverNonce)
81 |
82 | signature, err := a.certKey.sign(message)
83 | if err != nil {
84 | return err
85 | }
86 | subPrms.addBytes(signature)
87 | return nil
88 | }
89 |
90 | // FinalRepDecode implements the Method interface.
91 | func (a *X509) FinalRepDecode(d *Decoder) error {
92 | if err := d.NumPrm(2); err != nil {
93 | return err
94 | }
95 | mt := d.String()
96 | if err := checkAuthMethodType(mt, a.Typ()); err != nil {
97 | return err
98 | }
99 | d.subSize()
100 | if err := d.NumPrm(1); err != nil {
101 | return err
102 | }
103 | var err error
104 | a.logonName, err = d.cesu8String()
105 | return err
106 | }
107 |
--------------------------------------------------------------------------------
/driver/sniffer.go:
--------------------------------------------------------------------------------
1 | package driver
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "io"
7 | "log"
8 | "log/slog"
9 | "net"
10 | "sync"
11 |
12 | p "github.com/SAP/go-hdb/driver/internal/protocol"
13 | "github.com/SAP/go-hdb/driver/internal/protocol/encoding"
14 | "github.com/SAP/go-hdb/driver/unicode/cesu8"
15 | "github.com/SAP/go-hdb/driver/wgroup"
16 | )
17 |
18 | // A Sniffer is a simple proxy for logging hdb protocol requests and responses.
19 | type Sniffer struct {
20 | logger *slog.Logger
21 | conn net.Conn
22 | dbConn net.Conn
23 | }
24 |
25 | // NewSniffer creates a new sniffer instance. The conn parameter is the net.Conn connection, where the Sniffer
26 | // is listening for hdb protocol calls. The dbAddr is the hdb host port address in "host:port" format.
27 | func NewSniffer(conn net.Conn, dbConn net.Conn) *Sniffer {
28 | return &Sniffer{
29 | logger: slog.Default().With(slog.String("conn", conn.RemoteAddr().String())),
30 | conn: conn,
31 | dbConn: dbConn,
32 | }
33 | }
34 |
35 | func pipeData(wg *sync.WaitGroup, conn net.Conn, dbConn net.Conn, wr io.Writer) {
36 | defer wg.Done()
37 |
38 | mwr := io.MultiWriter(dbConn, wr)
39 | trd := io.TeeReader(conn, mwr)
40 | buf := make([]byte, 1000)
41 |
42 | var err error
43 | for err == nil {
44 | _, err = trd.Read(buf)
45 | }
46 | }
47 |
48 | func readMsg(ctx context.Context, prd *p.Reader) error {
49 | // TODO complete for non generic parts, see internal/protocol/parts/newGenPartReader for details
50 | _, err := prd.IterateParts(ctx, 0, nil)
51 | // _, err := prd.IterateParts(ctx, 0, func(kind p.PartKind, attrs p.PartAttributes, read func(part p.Part)) {})
52 | return err
53 | }
54 |
55 | func logData(ctx context.Context, wg *sync.WaitGroup, prd *p.Reader) {
56 | defer wg.Done()
57 |
58 | if err := prd.ReadProlog(ctx); err != nil {
59 | panic(err)
60 | }
61 |
62 | var err error
63 | for !errors.Is(err, io.EOF) {
64 | err = readMsg(ctx, prd)
65 | }
66 | }
67 |
68 | // Run starts the protocol request and response logging.
69 | func (s *Sniffer) Run() error {
70 | clientRd, clientWr := io.Pipe()
71 | dbRd, dbWr := io.Pipe()
72 |
73 | ctx := context.Background()
74 | wg := &sync.WaitGroup{}
75 |
76 | wgroup.Go(wg, func() {
77 | pipeData(wg, s.conn, s.dbConn, clientWr)
78 | })
79 | wgroup.Go(wg, func() {
80 | pipeData(wg, s.dbConn, s.conn, dbWr)
81 | })
82 |
83 | defaultDecoder := cesu8.DefaultDecoder()
84 |
85 | clientDec := encoding.NewDecoder(clientRd, defaultDecoder, false)
86 | dbDec := encoding.NewDecoder(dbRd, defaultDecoder, false)
87 |
88 | pClientRd := p.NewClientReader(clientDec, defaultDecoder, true, s.logger, defaultLobChunkSize)
89 | pDBRd := p.NewDBReader(dbDec, defaultDecoder, true, s.logger, defaultLobChunkSize)
90 |
91 | wgroup.Go(wg, func() {
92 | logData(ctx, wg, pClientRd)
93 | })
94 | wgroup.Go(wg, func() {
95 | logData(ctx, wg, pDBRd)
96 | })
97 |
98 | wg.Wait()
99 | log.Println("end run")
100 |
101 | return nil
102 | }
103 |
--------------------------------------------------------------------------------
/driver/internal/protocol/encoding/field.go:
--------------------------------------------------------------------------------
1 | package encoding
2 |
3 | import (
4 | "math"
5 |
6 | "github.com/SAP/go-hdb/driver/unicode/cesu8"
7 | )
8 |
9 | const (
10 | booleanFalseValue byte = 0
11 | booleanNullValue byte = 1
12 | booleanTrueValue byte = 2
13 | )
14 |
15 | const (
16 | realNullValue uint32 = ^uint32(0)
17 | doubleNullValue uint64 = ^uint64(0)
18 | )
19 |
20 | const (
21 | longdateNullValue int64 = 3155380704000000001
22 | seconddateNullValue int64 = 315538070401
23 | daydateNullValue int32 = 3652062
24 | secondtimeNullValue int32 = 86402
25 | )
26 |
27 | // Field size constants.
28 | const (
29 | BooleanFieldSize = 1
30 | TinyintFieldSize = 1
31 | SmallintFieldSize = 2
32 | IntegerFieldSize = 4
33 | BigintFieldSize = 8
34 | RealFieldSize = 4
35 | DoubleFieldSize = 8
36 | DateFieldSize = 4
37 | TimeFieldSize = 4
38 | TimestampFieldSize = DateFieldSize + TimeFieldSize
39 | LongdateFieldSize = 8
40 | SeconddateFieldSize = 8
41 | DaydateFieldSize = 4
42 | SecondtimeFieldSize = 4
43 | DecimalFieldSize = 16
44 | Fixed8FieldSize = 8
45 | Fixed12FieldSize = 12
46 | Fixed16FieldSize = 16
47 | LobInputParametersSize = 9
48 | )
49 |
50 | // string / binary length indicators.
51 | const (
52 | bytesLenIndNullValue byte = 255
53 | bytesLenIndSmall byte = 245
54 | bytesLenIndMedium byte = 246
55 | bytesLenIndBig byte = 247
56 | )
57 |
58 | // VarFieldSize returns the size of a varible field variable ([]byte, string and unicode variants).
59 | func varSize(size int) int {
60 | switch {
61 | default:
62 | return -1
63 | case size <= int(bytesLenIndSmall):
64 | return size + 1
65 | case size <= math.MaxInt16:
66 | return size + 3
67 | case size <= math.MaxInt32:
68 | return size + 5
69 | }
70 | }
71 |
72 | // Cesu8FieldSize returns the size of a cesu8 field.
73 | func Cesu8FieldSize(v any) int {
74 | switch v := v.(type) {
75 | case []byte:
76 | return varSize(cesu8.Size(v))
77 | case string:
78 | return varSize(cesu8.StringSize(v))
79 | default:
80 | panic("invalid type for cesu8 field") // should never happen
81 | }
82 | }
83 |
84 | // VarFieldSize returns the size of a var field.
85 | func VarFieldSize(v any) int {
86 | switch v := v.(type) {
87 | case []byte:
88 | return varSize(len(v))
89 | case string:
90 | return varSize(len(v))
91 | default:
92 | panic("invalid type for var field") // should never happen
93 | }
94 | }
95 |
96 | // HexFieldSize returns the size of a hex field.
97 | func HexFieldSize(v any) int {
98 | switch v := v.(type) {
99 | case []byte:
100 | l := len(v)
101 | if l%2 != 0 {
102 | panic("even hex field length required")
103 | }
104 | return varSize(l / 2)
105 | case string:
106 | l := len(v)
107 | if l%2 != 0 {
108 | panic("even hex field length required")
109 | }
110 | return varSize(l / 2)
111 | default:
112 | panic("invalid hex field type")
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/cmd/bulkbench/flag.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "os"
7 | "strconv"
8 |
9 | "github.com/SAP/go-hdb/driver"
10 | )
11 |
12 | // Flag name constants.
13 | const (
14 | fnDSN = "dsn"
15 | fnHost = "host"
16 | fnPort = "port"
17 | fnBufferSize = "bufferSize"
18 | fnParameters = "parameters"
19 | fnDrop = "drop"
20 | fnWait = "wait"
21 | )
22 |
23 | var flagNames = []string{fnDSN, fnHost, fnPort, fnBufferSize, fnParameters, fnDrop, fnWait}
24 |
25 | // Environment constants.
26 | const (
27 | envDSN = "GOHDBDSN"
28 | envHost = "HOST"
29 | envPort = "PORT"
30 | envBufferSize = "BUFFERSIZE"
31 | envParameters = "PARAMETERS"
32 | envDrop = "DROP"
33 | envWait = "WAIT"
34 | )
35 |
36 | var (
37 | dsn, host, port string
38 | bufferSize int
39 | // when using too many concurrent connections (approx 1000), hdb 'resets connection -> limit number of concurrent connections to 100.
40 | parameters = prmsValue{{1, 100000}, {10, 10000}, {100, 1000}, {1, 1000000}, {10, 100000}, {100, 10000}}
41 | drop bool
42 | wait int
43 | )
44 |
45 | func init() {
46 | defaultBufferSize := driver.NewConnector().BufferSize()
47 |
48 | flag.StringVar(&dsn, fnDSN, getStringEnv(envDSN, "hdb://MyUser:MyPassword@localhost:39013"), fmt.Sprintf("DNS (environment variable: %s)", envDSN))
49 | flag.StringVar(&host, fnHost, getStringEnv(envHost, "localhost"), fmt.Sprintf("HTTP host (environment variable: %s)", envHost))
50 | flag.StringVar(&port, fnPort, getStringEnv(envPort, "8080"), fmt.Sprintf("HTTP port (environment variable: %s)", envPort))
51 | flag.IntVar(&bufferSize, fnBufferSize, getIntEnv(envBufferSize, defaultBufferSize), fmt.Sprintf("Buffer size in bytes (environment variable: %s)", envBufferSize))
52 | flag.Var(¶meters, fnParameters, fmt.Sprintf("Parameters (environment variable: %s)", envParameters))
53 | flag.BoolVar(&drop, fnDrop, getBoolEnv(envDrop, true), fmt.Sprintf("Drop table before test (environment variable: %s)", envDrop))
54 | flag.IntVar(&wait, fnWait, getIntEnv(envWait, 0), fmt.Sprintf("Wait time before starting test in seconds (environment variable: %s)", envWait))
55 | }
56 |
57 | // flags returns a slice containing all command-line flags defined in this package.
58 | func flags() []*flag.Flag {
59 | flags := make([]*flag.Flag, 0)
60 | for _, name := range flagNames {
61 | if fl := flag.Lookup(name); fl != nil {
62 | flags = append(flags, fl)
63 | }
64 | }
65 | return flags
66 | }
67 |
68 | func getStringEnv(key, defValue string) string {
69 | value, ok := os.LookupEnv(key)
70 | if !ok {
71 | return defValue
72 | }
73 | return value
74 | }
75 |
76 | func getIntEnv(key string, defValue int) int {
77 | value, ok := os.LookupEnv(key)
78 | if !ok {
79 | return defValue
80 | }
81 | i, err := strconv.Atoi(value)
82 | if err != nil {
83 | return defValue
84 | }
85 | return i
86 | }
87 |
88 | func getBoolEnv(key string, defValue bool) bool {
89 | value, ok := os.LookupEnv(key)
90 | if !ok {
91 | return defValue
92 | }
93 | b, err := strconv.ParseBool(value)
94 | if err != nil {
95 | return defValue
96 | }
97 | return b
98 | }
99 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: build
2 | permissions:
3 | contents: read
4 | pull-requests: write
5 | on: [push]
6 | jobs:
7 |
8 | vetlint:
9 | runs-on: ubuntu-latest
10 | name: Go vet lint
11 |
12 | steps:
13 |
14 | - uses: actions/checkout@v4
15 |
16 | - name: Setup go
17 | uses: actions/setup-go@v5
18 | with:
19 | go-version: '1.25'
20 |
21 | - name: Vet
22 | run: |
23 | go vet ./...
24 |
25 | - name: Lint
26 | run: |
27 | go install golang.org/x/lint/golint@latest
28 | golint ./...
29 |
30 | build-linux:
31 | runs-on: ubuntu-latest
32 | strategy:
33 | matrix:
34 | goos: [linux]
35 | goarch: [amd64, arm, arm64, s390x]
36 | go: ['1.25.5', '1.24.11']
37 | fail-fast: false
38 |
39 | name: Go ${{ matrix.go }} ${{ matrix.goos }} ${{ matrix.goarch }} build
40 |
41 | env:
42 | GOOS: ${{ matrix.goos }}
43 | GOARCH: ${{ matrix.goarch }}
44 | GOARM: 6
45 | GOTOOLCHAIN: go${{ matrix.go }}
46 |
47 | steps:
48 |
49 | - name: Install qemu-user
50 | run: |
51 | sudo apt-get update
52 | sudo apt-get -y install qemu-user
53 |
54 | - uses: actions/checkout@v4
55 |
56 | - name: Setup go
57 | uses: actions/setup-go@v5
58 | with:
59 | go-version: ${{ matrix.go }}
60 |
61 | - name: Get dependencies
62 | run: |
63 | go get -v -t -d ./...
64 |
65 | - name: Build
66 | run: |
67 | go build -v ./...
68 |
69 | - name: Test
70 | run: |
71 | go test -v --tags unit ./...
72 |
73 | build-macos:
74 | runs-on: macos-latest
75 | strategy:
76 | matrix:
77 | go: ['1.25.5', '1.24.11']
78 | fail-fast: false
79 |
80 | name: Go ${{ matrix.go }} macOS
81 |
82 | env:
83 | GOTOOLCHAIN: go${{ matrix.go }}
84 |
85 | steps:
86 |
87 | - uses: actions/checkout@v4
88 |
89 | - name: Setup go
90 | uses: actions/setup-go@v5
91 | with:
92 | go-version: ${{ matrix.go }}
93 |
94 | - name: Get dependencies
95 | run: |
96 | go get -v -t -d ./...
97 |
98 | - name: Build
99 | run: |
100 | go build -v ./...
101 |
102 | - name: Test
103 | run: |
104 | go test -v --tags unit ./...
105 |
106 | build-windows:
107 | runs-on: windows-latest
108 | strategy:
109 | matrix:
110 | go: ['1.25.5', '1.24.11']
111 | fail-fast: false
112 |
113 | name: Go ${{ matrix.go }} Windows
114 |
115 | env:
116 | GOTOOLCHAIN: go${{ matrix.go }}
117 |
118 | steps:
119 |
120 | - uses: actions/checkout@v4
121 |
122 | - name: Setup go
123 | uses: actions/setup-go@v5
124 | with:
125 | go-version: ${{ matrix.go }}
126 |
127 | - name: Get dependencies
128 | run: |
129 | go get -v -t -d ./...
130 |
131 | - name: Build
132 | run: |
133 | go build -v ./...
134 |
135 | - name: Test
136 | run: |
137 | go test -v --tags unit ./...
138 |
--------------------------------------------------------------------------------
/driver/internal/protocol/auth/scram_test.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestSCRAM(t *testing.T) {
8 | testData := []struct {
9 | method string
10 | salt []byte
11 | serverChallenge []byte
12 | rounds int
13 | clientChallenge []byte
14 | password []byte
15 | clientProof []byte
16 | serverProof []byte
17 | }{
18 | {
19 | method: MtSCRAMSHA256,
20 | salt: []byte{214, 199, 255, 118, 92, 174, 94, 190, 197, 225, 57, 154, 157, 109, 119, 245},
21 | serverChallenge: []byte{224, 22, 242, 18, 237, 99, 6, 28, 162, 248, 96, 7, 115, 152, 134, 65, 141, 65, 168, 126, 168, 86, 87, 72, 16, 119, 12, 91, 227, 123, 51, 194, 203, 168, 56, 133, 70, 236, 230, 214, 89, 167, 130, 123, 132, 178, 211, 186},
22 | clientChallenge: []byte{219, 141, 27, 200, 255, 90, 182, 125, 133, 151, 127, 36, 26, 106, 213, 31, 57, 89, 50, 201, 237, 11, 158, 110, 8, 13, 2, 71, 9, 235, 213, 27, 64, 43, 181, 181, 147, 140, 10, 63, 156, 133, 133, 165, 171, 67, 187, 250, 41, 145, 176, 164, 137, 54, 72, 42, 47, 112, 252, 77, 102, 152, 220, 223},
23 | password: []byte{65, 100, 109, 105, 110, 49, 50, 51, 52},
24 | clientProof: []byte{23, 243, 209, 70, 117, 54, 25, 92, 21, 173, 194, 108, 63, 25, 188, 185, 230, 61, 124, 190, 73, 80, 225, 126, 191, 119, 32, 112, 231, 72, 184, 199},
25 | },
26 | {
27 | method: MtSCRAMPBKDF2SHA256,
28 | salt: []byte{51, 178, 213, 213, 92, 82, 194, 40, 80, 120, 197, 91, 166, 67, 23, 63},
29 | serverChallenge: []byte{32, 91, 165, 18, 158, 77, 134, 69, 128, 157, 69, 209, 47, 33, 171, 164, 56, 172, 229, 0, 153, 3, 65, 29, 239, 210, 186, 134, 81, 32, 29, 137, 239, 167, 39, 1, 171, 117, 85, 138, 109, 38, 42, 77, 43, 42, 82, 70},
30 | rounds: 15000,
31 | clientChallenge: []byte{137, 156, 182, 60, 158, 138, 93, 103, 80, 202, 54, 191, 210, 78, 142, 207, 210, 176, 157, 129, 128, 19, 135, 0, 127, 26, 58, 197, 188, 216, 121, 26, 120, 196, 34, 138, 5, 8, 58, 32, 36, 240, 199, 126, 164, 112, 64, 35, 46, 102, 255, 249, 126, 250, 24, 103, 198, 152, 33, 75, 6, 179, 187, 230},
32 | password: []byte{84, 111, 111, 114, 49, 50, 51, 52},
33 | clientProof: []byte{253, 181, 101, 0, 214, 222, 25, 99, 98, 253, 141, 106, 38, 255, 16, 153, 34, 74, 211, 70, 21, 91, 71, 223, 170, 36, 249, 124, 1, 135, 176, 37},
34 | serverProof: []byte{228, 2, 183, 82, 29, 218, 234, 242, 40, 50, 142, 158, 142, 153, 185, 189, 130, 51, 176, 155, 23, 179, 58, 19, 126, 144, 139, 229, 116, 3, 242, 197},
35 | },
36 | }
37 |
38 | for _, r := range testData {
39 | var key []byte
40 | var err error
41 | switch r.method {
42 | case MtSCRAMSHA256:
43 | key, err = scramsha256Key(r.password, r.salt)
44 | case MtSCRAMPBKDF2SHA256:
45 | key, err = scrampbkdf2sha256Key(string(r.password), r.salt, r.rounds)
46 | default:
47 | t.Fatalf("unknown authentication method %s", r.method)
48 | }
49 | if err != nil {
50 | t.Fatal(err)
51 | }
52 | clientProof, err := clientProof(key, r.salt, r.serverChallenge, r.clientChallenge)
53 | if err != nil {
54 | t.Fatal(err)
55 | }
56 | for i, v := range clientProof {
57 | if v != r.clientProof[i] {
58 | t.Fatalf("diff index % d - got %v - expected %v", i, clientProof, r.clientProof)
59 | }
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/driver/internal/protocol/auth_test.go:
--------------------------------------------------------------------------------
1 | package protocol
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/SAP/go-hdb/driver/internal/protocol/auth"
8 | "github.com/SAP/go-hdb/driver/internal/protocol/encoding"
9 | "github.com/SAP/go-hdb/driver/unicode/cesu8"
10 | )
11 |
12 | func authEncodeStep(t *testing.T, part PartEncoder) []byte {
13 | buf := bytes.Buffer{}
14 | enc := encoding.NewEncoder(&buf, cesu8.DefaultEncoder())
15 |
16 | if err := part.encode(enc); err != nil {
17 | t.Fatal(err)
18 | }
19 |
20 | return buf.Bytes()
21 | }
22 |
23 | func authDecodeStep(t *testing.T, part partDecoder, data []byte) {
24 | dec := encoding.NewDecoder(bytes.NewBuffer(data), cesu8.DefaultDecoder(), false)
25 |
26 | if err := part.decode(dec); err != nil {
27 | t.Fatal(err)
28 | }
29 | }
30 |
31 | func testJWTAuth(t *testing.T) {
32 | a := NewAuthHnd("")
33 | a.AddJWT("dummy token")
34 |
35 | successful := t.Run("init request", func(t *testing.T) {
36 | initRequest, err := a.InitRequest()
37 | if err != nil {
38 | t.Fatal(err)
39 | }
40 |
41 | actual := authEncodeStep(t, initRequest)
42 | expected := []byte("\x03\x00\x00\x03JWT\x0Bdummy token")
43 |
44 | if !bytes.Equal(expected, actual) {
45 | t.Fatalf("expected %q, got %q", string(expected), string(actual))
46 | }
47 | })
48 |
49 | if successful {
50 | successful = t.Run("init reply", func(t *testing.T) {
51 | initReply, err := a.InitReply()
52 | if err != nil {
53 | t.Fatal(err)
54 | }
55 |
56 | authDecodeStep(t, initReply, []byte("\x02\x00\x03JWT\x07USER123"))
57 |
58 | authJWT := a.Selected().(*auth.JWT)
59 |
60 | logonname, _ := authJWT.Cookie()
61 | if logonname != "USER123" {
62 | t.Fatalf("expected USER123, got %s", logonname)
63 | }
64 | })
65 | }
66 |
67 | if successful {
68 | successful = t.Run("final request", func(t *testing.T) {
69 | finalRequest, err := a.FinalRequest()
70 | if err != nil {
71 | t.Fatal(err)
72 | }
73 |
74 | actual := authEncodeStep(t, finalRequest)
75 | expected := []byte("\x03\x00\x07USER123\x03JWT\x00")
76 |
77 | if !bytes.Equal(expected, actual) {
78 | t.Fatalf("expected %q, got %q", string(expected), string(actual))
79 | }
80 | })
81 | }
82 |
83 | if successful {
84 | t.Run("final reply", func(t *testing.T) {
85 | finalReply, err := a.FinalReply()
86 | if err != nil {
87 | t.Fatal(err)
88 | }
89 |
90 | authDecodeStep(t, finalReply, []byte("\x02\x00\x03JWT\x205be8f43e064e0589ce07ba9de6fce107"))
91 |
92 | const expectedCookie = "5be8f43e064e0589ce07ba9de6fce107"
93 |
94 | authJWT := a.Selected().(*auth.JWT)
95 | _, cookie := authJWT.Cookie()
96 | if string(cookie) != expectedCookie {
97 | t.Fatalf("expected %q, got %q", expectedCookie, string(cookie))
98 | }
99 | })
100 | }
101 | }
102 |
103 | func TestAuth(t *testing.T) {
104 | tests := []struct {
105 | name string
106 | fct func(t *testing.T)
107 | }{
108 | {"testJWTAuth", testJWTAuth},
109 | }
110 |
111 | for _, test := range tests {
112 | func(name string, fct func(t *testing.T)) {
113 | t.Run(name, func(t *testing.T) {
114 | t.Parallel()
115 | fct(t)
116 | })
117 | }(test.name, test.fct)
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/driver/internal/protocol/auth/scramsha256.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | // Salted Challenge Response Authentication Mechanism (SCRAM)
4 |
5 | import (
6 | "bytes"
7 | "fmt"
8 | )
9 |
10 | func scramsha256Key(password, salt []byte) ([]byte, error) {
11 | return _sha256(_hmac(password, salt)), nil
12 | }
13 |
14 | // use cache as key calculation is expensive.
15 | var scramKeyCache = newList(3, func(k *SCRAMSHA256) ([]byte, error) {
16 | return scramsha256Key([]byte(k.password), k.salt)
17 | })
18 |
19 | // SCRAMSHA256 implements SCRAMSHA256 authentication.
20 | type SCRAMSHA256 struct {
21 | username, password string
22 | clientChallenge []byte
23 | salt, serverChallenge []byte
24 | serverProof []byte
25 | }
26 |
27 | // NewSCRAMSHA256 creates a new authSCRAMSHA256 instance.
28 | func NewSCRAMSHA256(username, password string) *SCRAMSHA256 {
29 | return &SCRAMSHA256{username: username, password: password, clientChallenge: clientChallenge()}
30 | }
31 |
32 | func (a *SCRAMSHA256) String() string {
33 | return fmt.Sprintf("method type %s clientChallenge %v", a.Typ(), a.clientChallenge)
34 | }
35 |
36 | // Compare implements cache.Compare interface.
37 | func (a *SCRAMSHA256) Compare(a1 *SCRAMSHA256) bool {
38 | return a.password == a1.password && bytes.Equal(a.salt, a1.salt)
39 | }
40 |
41 | // Typ implements the Method interface.
42 | func (a *SCRAMSHA256) Typ() string { return MtSCRAMSHA256 }
43 |
44 | // Order implements the Method interface.
45 | func (a *SCRAMSHA256) Order() byte { return MoSCRAMSHA256 }
46 |
47 | // PrepareInitReq implements the Method interface.
48 | func (a *SCRAMSHA256) PrepareInitReq(prms *Prms) error {
49 | prms.addString(a.Typ())
50 | prms.addBytes(a.clientChallenge)
51 | return nil
52 | }
53 |
54 | // InitRepDecode implements the Method interface.
55 | func (a *SCRAMSHA256) InitRepDecode(d *Decoder) error {
56 | d.subSize() // sub parameters
57 | if err := d.NumPrm(2); err != nil {
58 | return err
59 | }
60 | a.salt = d.bytes()
61 | a.serverChallenge = d.bytes()
62 | if err := checkSalt(a.salt); err != nil {
63 | return err
64 | }
65 | if err := checkServerChallenge(a.serverChallenge); err != nil {
66 | return err
67 | }
68 | return nil
69 | }
70 |
71 | // PrepareFinalReq implements the Method interface.
72 | func (a *SCRAMSHA256) PrepareFinalReq(prms *Prms) error {
73 | key, err := scramKeyCache.Get(a)
74 | if err != nil {
75 | return err
76 | }
77 | clientProof, err := clientProof(key, a.salt, a.serverChallenge, a.clientChallenge)
78 | if err != nil {
79 | return err
80 | }
81 |
82 | prms.AddCESU8String(a.username)
83 | prms.addString(a.Typ())
84 | subPrms := prms.addPrms()
85 | subPrms.addBytes(clientProof)
86 |
87 | return nil
88 | }
89 |
90 | // FinalRepDecode implements the Method interface.
91 | func (a *SCRAMSHA256) FinalRepDecode(d *Decoder) error {
92 | if err := d.NumPrm(2); err != nil {
93 | return err
94 | }
95 | mt := d.String()
96 | if err := checkAuthMethodType(mt, a.Typ()); err != nil {
97 | return err
98 | }
99 | if d.subSize() == 0 { // mnSCRAMSHA256: server does not return server proof parameter
100 | return nil
101 | }
102 | if err := d.NumPrm(1); err != nil {
103 | return err
104 | }
105 | a.serverProof = d.bytes()
106 | return nil
107 | }
108 |
--------------------------------------------------------------------------------
/cmd/bulkbench/index.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "html/template"
7 | "io"
8 | "io/fs"
9 | "log"
10 | "net/http"
11 | "runtime"
12 |
13 | "github.com/SAP/go-hdb/driver"
14 | )
15 |
16 | type indexTestDef struct {
17 | Descr string
18 | SequentialLink string
19 | ConcurrentLink string
20 | }
21 |
22 | type indexCommandDef struct {
23 | Command string
24 | Link string
25 | }
26 |
27 | type indexData struct {
28 | Flags []*flag.Flag
29 | TestDefs []*indexTestDef
30 | SchemaName string
31 | TableName string
32 | SchemaCommands []*indexCommandDef
33 | TableCommands []*indexCommandDef
34 | }
35 |
36 | // indexHandler implements the http.Handler interface for the html index page.
37 | type indexHandler struct {
38 | tmpl *template.Template
39 | data *indexData
40 | }
41 |
42 | func newIndexTestDef(batchCount, batchSize int) *indexTestDef {
43 | return &indexTestDef{
44 | Descr: fmt.Sprintf("%d x %d", batchCount, batchSize),
45 | SequentialLink: fmt.Sprintf("test?sequential=t&batchcount=%d&batchsize=%d", batchCount, batchSize),
46 | ConcurrentLink: fmt.Sprintf("test?sequential=f&batchcount=%d&batchsize=%d", batchCount, batchSize),
47 | }
48 | }
49 |
50 | // newIndexHandler returns a new IndexHandler instance.
51 | func newIndexHandler(dba *dba, templateFS fs.FS) (*indexHandler, error) {
52 | funcMap := template.FuncMap{
53 | "gomaxprocs": func() int { return runtime.GOMAXPROCS(0) },
54 | "numcpu": runtime.NumCPU,
55 | "driverversion": func() string { return driver.DriverVersion },
56 | "hdbversion": dba.hdbVersion,
57 | "goos": func() string { return runtime.GOOS },
58 | "goarch": func() string { return runtime.GOARCH },
59 | }
60 |
61 | tmpl, err := template.New(tmplIndex).Funcs(funcMap).ParseFS(templateFS, tmplIndex)
62 | if err != nil {
63 | return nil, err
64 | }
65 |
66 | indexTestDefs := []*indexTestDef{}
67 | for _, prm := range parameters {
68 | indexTestDefs = append(indexTestDefs, newIndexTestDef(prm.BatchCount, prm.BatchSize))
69 | }
70 |
71 | const dbCommand = "db?command="
72 |
73 | tableCommands := []*indexCommandDef{
74 | {Command: "create", Link: dbCommand + cmdCreateTable},
75 | {Command: "drop", Link: dbCommand + cmdDropTable},
76 | {Command: "deleteRows", Link: dbCommand + cmdDeleteRows},
77 | {Command: "countRows", Link: dbCommand + cmdCountRows},
78 | }
79 |
80 | schemaCommands := []*indexCommandDef{
81 | {Command: "create", Link: dbCommand + cmdCreateSchema},
82 | {Command: "drop", Link: dbCommand + cmdDropSchema},
83 | }
84 |
85 | data := &indexData{
86 | Flags: flags(),
87 | TestDefs: indexTestDefs,
88 | SchemaName: string(dba.schemaName),
89 | TableName: string(dba.tableName),
90 | SchemaCommands: schemaCommands,
91 | TableCommands: tableCommands,
92 | }
93 |
94 | // test if data and template definition does match
95 | if err := tmpl.Execute(io.Discard, data); err != nil {
96 | return nil, err
97 | }
98 |
99 | return &indexHandler{tmpl: tmpl, data: data}, nil
100 | }
101 |
102 | func (h *indexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
103 | err := h.tmpl.Execute(w, h.data)
104 | if err != nil {
105 | log.Printf("template execute error: %s", err)
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/prometheus/collectors/example_test.go:
--------------------------------------------------------------------------------
1 | //go:build !unit
2 |
3 | package collectors_test
4 |
5 | import (
6 | "log"
7 | "net"
8 | "net/http"
9 | "os"
10 | "os/signal"
11 | "sync"
12 | "time"
13 |
14 | "github.com/SAP/go-hdb/driver"
15 | "github.com/SAP/go-hdb/driver/wgroup"
16 | drivercollectors "github.com/SAP/go-hdb/prometheus/collectors"
17 | "github.com/prometheus/client_golang/prometheus"
18 | "github.com/prometheus/client_golang/prometheus/collectors"
19 | "github.com/prometheus/client_golang/prometheus/promhttp"
20 | )
21 |
22 | func formatHTTPAddr(addr string) string {
23 | host, port, err := net.SplitHostPort(addr)
24 | if err != nil {
25 | return addr
26 | }
27 | if host == "" {
28 | host = "localhost"
29 | }
30 | if port == "" {
31 | port = "80"
32 | }
33 | return net.JoinHostPort(host, port)
34 | }
35 |
36 | // Example demonstrates the usage of go-hdb prometheus metrics.
37 | func Example() {
38 | const (
39 | envDSN = "GOHDBDSN"
40 | envHTTP = "GOHDBHTTP"
41 | )
42 |
43 | dsn := os.Getenv(envDSN)
44 | addr := os.Getenv(envHTTP)
45 |
46 | // exit if dsn or http address is missing.
47 | if dsn == "" {
48 | log.Printf("to start the go-hdb prometheus metrics example please set environment variable %s", envDSN)
49 | return
50 | }
51 | if addr == "" {
52 | log.Printf("to start the go-hdb prometheus metrics example please set environment variable %s", envHTTP)
53 | return
54 | }
55 |
56 | connector, err := driver.NewDSNConnector(dsn)
57 | if err != nil {
58 | log.Fatal(err)
59 | }
60 | // use driver.OpenDB instead of sql.OpenDB to collect driver.DB specific statistics.
61 | db := driver.OpenDB(connector)
62 | defer db.Close()
63 |
64 | // use dbName as label.
65 | const dbName = "myDatabase"
66 |
67 | // register collector for sql.DB stats.
68 | sqlDBStatsCollector := collectors.NewDBStatsCollector(db.DB, dbName)
69 | if err := prometheus.Register(sqlDBStatsCollector); err != nil {
70 | log.Fatal(err)
71 | }
72 |
73 | // register collector for go-hdb driver stats.
74 | driverCollector := drivercollectors.NewDriverStatsCollector(connector.NativeDriver(), dbName)
75 | if err := prometheus.Register(driverCollector); err != nil {
76 | log.Fatal(err)
77 | }
78 |
79 | // register collector for extended go-hdb db stats.
80 | driverDBExStatsCollector := drivercollectors.NewDBExStatsCollector(db, dbName)
81 | if err := prometheus.Register(driverDBExStatsCollector); err != nil {
82 | log.Fatal(err)
83 | }
84 |
85 | wg := new(sync.WaitGroup)
86 | done := make(chan struct{})
87 |
88 | // do some database stuff...
89 | wgroup.Go(wg, func() {
90 | for {
91 | select {
92 | case <-done:
93 | return
94 | default:
95 | if err := db.Ping(); err != nil {
96 | log.Fatal(err)
97 | }
98 | }
99 | }
100 | })
101 |
102 | // register prometheus HTTP handler and start HTTP server.
103 | http.Handle("/metrics", promhttp.Handler())
104 | go func() {
105 | server := &http.Server{Addr: addr, ReadHeaderTimeout: 30 * time.Second}
106 | log.Fatal(server.ListenAndServe())
107 | }()
108 |
109 | log.Printf("access the metrics at http://%s/metrics", formatHTTPAddr(addr))
110 |
111 | sigint := make(chan os.Signal, 1)
112 | signal.Notify(sigint, os.Interrupt)
113 | <-sigint
114 |
115 | close(done)
116 | wg.Wait()
117 |
118 | // output:
119 | }
120 |
--------------------------------------------------------------------------------
/testdata/x509/rootCA.key:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDhwFDDaT7hf+3Q
3 | 2NypyX7/2s0BexzvN6ITfiDQ5436gQrErAAHb7MjDidgz5BIbTwQTqLVufOMwGW4
4 | fKFGVjncXLlSDpy1mb0PuSawhB8YJ644M1TIOFwPgpaDX2RZ+EzEY6MHgY4Or6tG
5 | D5t/ov5uJuuAO4EJx0TxVpUjl6ns3gbdASXzlaabeeD5e2wCcI9gu1kgri/7Tf+W
6 | ATnHTeRlbtTibXftmVkGzIqw0rD/CWACHxNjESLsUt0syUBC/xdrUSj+ZwE5Ci3+
7 | rhfbdBvxGdB85Z3tQuW2HrlwrdpacrE3Dm7oWnv8Os1Q4Ij9k9XAkLGlXKkKjR1q
8 | KXTNxIy0gX6zLJw+MRZ03jJGRZTt0lwnSNv4uFiE4shZxoNBtmKkjkM6Z7FsyGfE
9 | E2vSSYCOKnavufa0MPM5vTFsWR5b7gckCrDtiX9QSw758hj/LQyxOvUw8q65lflS
10 | F3/fkG0E1AZ7N5ZODEr1REH91jywU/EzczZf1IZQ6IRFPM0RKuIvhaNzMhGFjHwv
11 | uaGza4JwuUSrZ/NBEJqUGY6SSqmC25FGGwec3Cx0tlAVRiRSYgTwXVtuAyHgMNjN
12 | siWtjOs+V9UUq6stO2v0biUqlFUIioBn9SQWkPOm0NCZBAEEKZjj3W5KuzKKpdBN
13 | QqUhQsgwvDACVQLbu63e8DbjRNdZ+wIDAQABAoICABr/Bwl0mVxDvU+SJ203FzSx
14 | 2H3arZ6ebgeGYpaTd43jMZj29/frr9CQ3ZJSWq7hP+Neq/1YA9vV7t7Nf0cXLPXW
15 | XCOyc04KM6rg2X4WsVXk+jdmorcLelgxJcj8YYFFfqn7lPWNqiHFIyZj279l4VGQ
16 | iO1oRZRC8VJ/p/e+KB1sfbCfIF034lkfDZF4D3YJOjQh+KoTpSbtxb3iqU4ABBXs
17 | gju48ImyzWEmmQKakielhNwsFYLte9omnKQW8FQysifCEwoSjPm3vGwDgKncgYeR
18 | rotztVuNT5q5o4kUg+ijZOAwcyszwDZCXTwp5b1BQ7iyn5577WEWzVOH3/EdazU5
19 | 79ZflZLNf+v7PTmOzFSCUgby6npCP/MPUwBCrPNEERqUjC1RrjwMHHWMyIzLp18+
20 | GGLeNhRQzdINJxbsTSu5YU5RwwnG/QlfHJUC5F6kU46bc5ovGbdYbDKQitjkkJ4k
21 | 5BXq0Wf3ab+N9VoTV9cCWzWm7HL7u3mICVvdemlr31lSJbtgNhx3PUklb63S+Iwy
22 | kV9hENx17bqmRgLZQC97ZzeFRV53TeO8D1WJp2dwRgJphdsNJfbIlr2ldpGmrZ3X
23 | dXn94vk3U0bhn00HYxcDS1+8A3/RIOwPuVTvy9aIqrPbrfuyCUm/VuwFfgp7HrNd
24 | sxz5rHDE5MOxZooYkPypAoIBAQD2loJeepDDCEqAWP2MEQIuKlTFTw+gQubLauhf
25 | 3BCW0pDWREcah1aB+BlgW7zsNKiuWtCCjf0abMXi1NqRGD+9CaiVa48fX0mqLBPk
26 | GYt1+6TtsuOVsFVV/VmBOvB8vh+jVk+wyLWIjnkMQ/Xsm64wDcTLvJbe6uLzo/12
27 | E3Zsh8bWU4WHA5GZcGgt97OIBplth64zBbtzFSMmh2ykYvkOWkjy+S9kpmwEwzJr
28 | U/bN3nXJL+beShV6S0zAkxEWQpBsqVsdDO8OpIxMOit/a6lq23WCS6xbSGqaQkhe
29 | F2RdC3fPKxYTIGbhK/3sxg4B+AvXZGkq9oTbAx6oRXazVObjAoIBAQDqXjRA/9GO
30 | CxGCQgZxA4xkIzvuQhBtEPEZn1HeQQo1PuI5/PvlnXJJRbsRSWyOgCh8qPPj3tBq
31 | /18pINmkZPI/fgzTTpAMRi12TybFIydSaKQcibbMf52WrOf5ebCUuxgesBFBm71o
32 | 90F9zj24u35WJD/882/8OoVEm/LdI/FZwiFAl8pTnzlgxRzerTgtGSTgZ5apsQED
33 | gteAJrazP3FBq2BVNVAVqhKrO55DrOs0rZSAtDNiuHftfoexczHaiKwvV8ZngMrJ
34 | k3JQ+2k4U8wGz9RjxLI/cbeRNFQ3HB5nBxp1EGTlfgL5UFgN5zh0BSuomwO1Kki2
35 | QCV/pTj3IJQJAoIBAQDO9tiJL9oct/K/8vEsBIKN+N2ZlZgJ7N0FUr+i5XPPAwCp
36 | tjjvnTQQdgnmhFj27+O8I2Dqkv4ilbUpg3nHWlD0+wxSDSrdK/8KI2C2jHIvHnz5
37 | PLIjyxJ0z+W1v6BlMrYfQ6wzNuKWsO4MS2Y3pxr2HjhUv/7pLsA5uiRcXE2DHhrX
38 | fNaP0YD9BgmKu2ImA1P3SG//RwpjtxP/nJ6lAqUDVOfxdxA+LChxftvslVaKx/KY
39 | X+ooHIoWUOkkoMswocHSUfq1UUu8QIO43wHvOo/BrlzcdWM+YnOG8acHwh5ssdln
40 | OkSW3RU8XM4NUtYi9OwLZq2Wb75mjCHJHjRYpCP9AoIBAFu94IqJGUiXAJWSrt7S
41 | WsRgHneXmMJ43UgAW5W6s/o/0WKNqzUourH//8g2i64EcNTLdhz+/WrE6EU7PLbj
42 | ZQiBmRemKzECz4z+Utjcd+oEWClrpwjJ87AlovC/N+YgjsKEfVzHUA+kqhhLAVIs
43 | 4rlpmzqzOGTtq3k96oWyKinOmiGkyWiObXF592EbQWA19X96TTnDtfff4eeiP+ZO
44 | sVDZeu4f3md8ma45uiwXpkKKqBTxdSPxvdBTtV47D4rR8UTfASVG/xqFAzy8DWYl
45 | Nzp7ZFJhjrzTEN5gM48XDLebyjmE31oOR6+8SZu3pRuVYo+vjnX+RVRCK+uZi4EB
46 | WbECggEABtpPOe5Lt1ThKWuDFVxJK1mdcY3g329hdqFC+qijvYgzfWqm97oDWhHp
47 | LIhXYpAx+Q1HQlQ1Vn16TRqaTmc4wHdGo6Tl4V+0XIXkoInjPugo7ZiAOZ69Ckg1
48 | 3M3vs/hnOhJo48mT0KzMUKeXwWkMhEQymlfYVBMurMHkibOJgaGS5SZSYDBZkzpW
49 | oM3FYzOKtLcESrCNvEY7hlhIHLXkleVzEiKqZs01wIJ0xzaTIeYqH56aM3jRTKux
50 | FD/FDGXWSJAjgDib+i4XrrM8KJ5a8AWJVgyBSzltrIETXo0JETOP/z9Yk3bZh3Mx
51 | h8LRfqEj3aptxfT1exabQMhaIEfzHQ==
52 | -----END PRIVATE KEY-----
53 |
--------------------------------------------------------------------------------
/driver/unicode/cesu8/cesu8_test.go:
--------------------------------------------------------------------------------
1 | package cesu8
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 | "unicode/utf8"
7 | )
8 |
9 | func testCodeLen(t *testing.T) {
10 | b := make([]byte, CESUMax)
11 | for i := rune(0); i <= utf8.MaxRune; i++ {
12 | n := EncodeRune(b, i)
13 | if n != RuneLen(i) {
14 | t.Fatalf("rune length check error %d %d", n, RuneLen(i))
15 | }
16 | }
17 | }
18 |
19 | func testCP(t *testing.T) {
20 | // see http://en.wikipedia.org/wiki/CESU-8
21 | var testData = []*struct {
22 | cp rune
23 | utf8 []byte
24 | }{
25 | {0x45, []byte{0x45}},
26 | {0x205, []byte{0xc8, 0x85}},
27 | {0x10400, []byte{0xed, 0xa0, 0x81, 0xed, 0xb0, 0x80}},
28 | }
29 |
30 | b := make([]byte, CESUMax)
31 | for _, d := range testData {
32 | n1 := EncodeRune(b, d.cp)
33 | if !bytes.Equal(b[:n1], d.utf8) {
34 | t.Fatalf("encode code point %x char %c cesu-8 %x - expected %x", d.cp, d.cp, b[:n1], d.utf8)
35 | } else {
36 | t.Logf("encode code point %x char %c cesu-8 %x", d.cp, d.cp, b[:n1])
37 | }
38 |
39 | cp, n2 := DecodeRune(b[:n1])
40 | if cp != d.cp || n2 != n1 {
41 | t.Fatalf("decode code point %x size %d - expected %x size %d", cp, n2, d.cp, n1)
42 | } else {
43 | t.Logf("decode code point %x size %d", cp, n2)
44 | }
45 | }
46 | }
47 |
48 | func testString(t *testing.T) {
49 | // took from https://golang.org/src/unicode/utf8/utf8_test.go
50 | var testData = []string{
51 | "",
52 | "abcd",
53 | "☺☻☹",
54 | "日a本b語ç日ð本Ê語þ日¥本¼語i日©",
55 | "日a本b語ç日ð本Ê語þ日¥本¼語i日©日a本b語ç日ð本Ê語þ日¥本¼語i日©日a本b語ç日ð本Ê語þ日¥本¼語i日©",
56 | "\x80\x80\x80\x80",
57 | }
58 |
59 | b := make([]byte, CESUMax)
60 | for i, s := range testData {
61 | n := 0
62 | for _, r := range s {
63 | n += utf8.EncodeRune(b, r)
64 | if r >= 0xFFFF { // CESU-8: 6 Bytes
65 | n += 2
66 | }
67 | }
68 |
69 | // 1. Test: cesu string size
70 | m := StringSize(s)
71 | if m != n {
72 | t.Fatalf("%d invalid string size %d - expected %d", i, m, n)
73 | }
74 | // 2. Test: cesu slice len
75 | m = Size([]byte(s))
76 | if m != n {
77 | t.Fatalf("%d invalid slice size %d - expected %d", i, m, n)
78 | }
79 | // 3. Test: convert len
80 | m = 0
81 | for _, r := range s {
82 | m += EncodeRune(b, r)
83 | }
84 | if m != n {
85 | t.Fatalf("%d invalid encoder size %d - expected %d", i, m, n)
86 | }
87 | }
88 | }
89 |
90 | func testReplacementChar(t *testing.T) {
91 | // https://github.com/SAP/go-hdb/issues/145
92 | // https://github.com/SAP/go-hdb/issues/147
93 |
94 | if !utf8.ValidRune(utf8.RuneError) {
95 | t.Fatalf("%c is not a valid utf8 rune", utf8.RuneError)
96 | }
97 | p := make([]byte, utf8.RuneLen(utf8.RuneError))
98 | utf8.EncodeRune(p, utf8.RuneError)
99 |
100 | encoder := NewEncoder(nil)
101 | b := make([]byte, Size(p))
102 | _, _, err := encoder.Transform(b, p, true)
103 | if err != nil {
104 | t.Fatal(err)
105 | }
106 |
107 | decoder := NewDecoder(nil)
108 | _, _, err = decoder.Transform(p, b, true)
109 | if err != nil {
110 | t.Fatal(err)
111 | }
112 | }
113 |
114 | func TestCESU8(t *testing.T) {
115 | tests := []struct {
116 | name string
117 | fct func(t *testing.T)
118 | }{
119 | {"testCodeLen", testCodeLen},
120 | {"testCP", testCP},
121 | {"testString", testString},
122 | {"testReplacementChar", testReplacementChar},
123 | }
124 |
125 | for _, test := range tests {
126 | t.Run(test.name, func(t *testing.T) {
127 | test.fct(t)
128 | })
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/driver/tx_test.go:
--------------------------------------------------------------------------------
1 | //go:build !unit
2 |
3 | package driver_test
4 |
5 | import (
6 | "database/sql"
7 | "fmt"
8 | "testing"
9 |
10 | "github.com/SAP/go-hdb/driver"
11 | )
12 |
13 | func testTransactionCommit(t *testing.T, db *sql.DB) {
14 | table := driver.RandomIdentifier("testTxCommit_")
15 | if _, err := db.Exec(fmt.Sprintf("create table %s (i tinyint)", table)); err != nil {
16 | t.Fatal(err)
17 | }
18 |
19 | tx1, err := db.Begin()
20 | if err != nil {
21 | t.Fatal(err)
22 | }
23 |
24 | tx2, err := db.Begin()
25 | if err != nil {
26 | t.Fatal(err)
27 | }
28 | defer tx2.Rollback() //nolint:errcheck
29 |
30 | // insert record in transaction 1
31 | if _, err := tx1.Exec(fmt.Sprintf("insert into %s values(42)", table)); err != nil {
32 | t.Fatal(err)
33 | }
34 |
35 | // count records in transaction 1
36 | i := 0
37 | if err := tx1.QueryRow(fmt.Sprintf("select count(*) from %s", table)).Scan(&i); err != nil {
38 | t.Fatal(err)
39 | }
40 | if i != 1 {
41 | t.Fatal(fmt.Errorf("tx1: invalid number of records %d - 1 expected", i))
42 | }
43 |
44 | // count records in transaction 2 - isolation level 'read committed'' (default) expected, so no record should be there
45 | if err := tx2.QueryRow(fmt.Sprintf("select count(*) from %s", table)).Scan(&i); err != nil {
46 | t.Fatal(err)
47 | }
48 | if i != 0 {
49 | t.Fatal(fmt.Errorf("tx2: invalid number of records %d - 0 expected", i))
50 | }
51 |
52 | // commit insert
53 | if err := tx1.Commit(); err != nil {
54 | t.Fatal(err)
55 | }
56 |
57 | // in isolation level 'read commited' (default) record should be visible now
58 | if err := tx2.QueryRow(fmt.Sprintf("select count(*) from %s", table)).Scan(&i); err != nil {
59 | t.Fatal(err)
60 | }
61 | if i != 1 {
62 | t.Fatal(fmt.Errorf("tx2: invalid number of records %d - 1 expected", i))
63 | }
64 | }
65 |
66 | func testTransactionRollback(t *testing.T, db *sql.DB) {
67 | table := driver.RandomIdentifier("testTxRollback_")
68 | if _, err := db.Exec(fmt.Sprintf("create table %s (i tinyint)", table)); err != nil {
69 | t.Fatal(err)
70 | }
71 |
72 | tx, err := db.Begin()
73 | if err != nil {
74 | t.Fatal(err)
75 | }
76 |
77 | // insert record
78 | if _, err := tx.Exec(fmt.Sprintf("insert into %s values(42)", table)); err != nil {
79 | t.Fatal(err)
80 | }
81 |
82 | // count records
83 | i := 0
84 | if err := tx.QueryRow(fmt.Sprintf("select count(*) from %s", table)).Scan(&i); err != nil {
85 | t.Fatal(err)
86 | }
87 | if i != 1 {
88 | t.Fatal(fmt.Errorf("tx: invalid number of records %d - 1 expected", i))
89 | }
90 |
91 | // rollback insert
92 | if err := tx.Rollback(); err != nil {
93 | t.Fatal(err)
94 | }
95 |
96 | // new transaction
97 | tx, err = db.Begin()
98 | if err != nil {
99 | t.Fatal(err)
100 | }
101 | defer tx.Rollback() //nolint:errcheck
102 |
103 | // rollback - no record expected
104 | if err := tx.QueryRow(fmt.Sprintf("select count(*) from %s", table)).Scan(&i); err != nil {
105 | t.Fatal(err)
106 | }
107 | if i != 0 {
108 | t.Fatal(fmt.Errorf("tx: invalid number of records %d - 0 expected", i))
109 | }
110 | }
111 |
112 | func TestTransaction(t *testing.T) {
113 | tests := []struct {
114 | name string
115 | fct func(t *testing.T, db *sql.DB)
116 | }{
117 | {"transactionCommit", testTransactionCommit},
118 | {"transactionRollback", testTransactionRollback},
119 | }
120 |
121 | db := driver.MT.DB()
122 | for _, test := range tests {
123 | t.Run(test.name, func(t *testing.T) {
124 | test.fct(t, db)
125 | })
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/driver/unicode/cesu8/cesu8.go:
--------------------------------------------------------------------------------
1 | // Package cesu8 implements functions and constants to support text encoded in CESU-8.
2 | // It implements functions comparable to the unicode/utf8 package for UTF-8 de- and encoding.
3 | package cesu8
4 |
5 | import (
6 | "unicode/utf16"
7 | "unicode/utf8"
8 | )
9 |
10 | const (
11 | // CESUMax is the maximum amount of bytes used by an CESU-8 codepoint encoding.
12 | CESUMax = 6
13 | )
14 |
15 | // Copied from unicode utf8.
16 | const (
17 | tx = 0b10000000
18 | t3 = 0b11100000
19 |
20 | maskx = 0b00111111
21 | mask3 = 0b00001111
22 |
23 | rune1Max = 1<<7 - 1
24 | rune2Max = 1<<11 - 1
25 | rune3Max = 1<<16 - 1
26 | )
27 |
28 | // Size returns the amount of bytes needed to encode an UTF-8 byte slice to CESU-8.
29 | func Size(p []byte) int {
30 | n := 0
31 | for len(p) > 0 {
32 | r, size := DecodeRune(p)
33 | n += RuneLen(r)
34 | p = p[size:]
35 | }
36 | return n
37 | }
38 |
39 | // StringSize is like Size with a string as parameter.
40 | func StringSize(s string) int {
41 | n := 0
42 | for _, r := range s {
43 | n += RuneLen(r)
44 | }
45 | return n
46 | }
47 |
48 | // EncodeRune writes into p (which must be large enough) the CESU-8 encoding of the rune. It returns the number of bytes written.
49 | func EncodeRune(p []byte, r rune) int {
50 | if r <= rune3Max {
51 | return utf8.EncodeRune(p, r)
52 | }
53 | high, low := utf16.EncodeRune(r)
54 | _ = p[5] // eliminate bounds checks
55 | p[0] = t3 | byte(high>>12)
56 | p[1] = tx | byte(high>>6)&maskx
57 | p[2] = tx | byte(high)&maskx
58 | p[3] = t3 | byte(low>>12)
59 | p[4] = tx | byte(low>>6)&maskx
60 | p[5] = tx | byte(low)&maskx
61 | return CESUMax
62 | }
63 |
64 | // FullRune reports whether the bytes in p begin with a full CESU-8 encoding of a rune.
65 | func FullRune(p []byte) bool {
66 | if isSurrogate(p) {
67 | return isSurrogate(p[3:])
68 | }
69 | return utf8.FullRune(p)
70 | }
71 |
72 | func decodeSurrogates(p []byte) (rune, int) {
73 | high := decodeCheckedSurrogate(p)
74 | low, ok := decodeSurrogate(p[3:])
75 | if !ok {
76 | return utf8.RuneError, 3
77 | }
78 | return utf16.DecodeRune(high, low), CESUMax
79 | }
80 |
81 | // DecodeRune unpacks the first CESU-8 encoding in p and returns the rune and its width in bytes.
82 | func DecodeRune(p []byte) (rune, int) {
83 | if !isSurrogate(p) {
84 | return utf8.DecodeRune(p)
85 | }
86 | return decodeSurrogates(p)
87 | }
88 |
89 | // RuneLen returns the number of bytes required to encode the rune.
90 | func RuneLen(r rune) int {
91 | switch {
92 | case r < 0:
93 | return -1
94 | case r <= rune1Max:
95 | return 1
96 | case r <= rune2Max:
97 | return 2
98 | case r <= rune3Max:
99 | return 3
100 | case r <= utf8.MaxRune:
101 | return CESUMax
102 | default:
103 | return -1
104 | }
105 | }
106 |
107 | const (
108 | sp0 = 0xed
109 | sb1Min = 0xa0
110 | sb1Max = 0xbf
111 | )
112 |
113 | func decodeSurrogate(p []byte) (rune, bool) {
114 | if len(p) < 3 {
115 | return utf8.RuneError, false
116 | }
117 | p0 := p[0]
118 | if p0 != sp0 {
119 | return utf8.RuneError, false
120 | }
121 | b1 := p[1]
122 | if b1 < sb1Min || b1 > sb1Max {
123 | return utf8.RuneError, false
124 | }
125 | b2 := p[2]
126 | return rune(p0&mask3)<<12 | rune(b1&maskx)<<6 | rune(b2&maskx), true
127 | }
128 |
129 | func decodeCheckedSurrogate(p []byte) rune {
130 | return rune(p[0]&mask3)<<12 | rune(p[1]&maskx)<<6 | rune(p[2]&maskx)
131 | }
132 |
133 | func isSurrogate(p []byte) bool {
134 | if len(p) < 3 {
135 | return false
136 | }
137 | b1 := p[1]
138 | if p[0] != sp0 || b1 < sb1Min || b1 > sb1Max {
139 | return false
140 | }
141 | return true
142 | }
143 |
--------------------------------------------------------------------------------
/driver/internal/protocol/auth/scrampbkdf2sha256.go:
--------------------------------------------------------------------------------
1 | package auth
2 |
3 | // Salted Challenge Response Authentication Mechanism (SCRAM)
4 |
5 | import (
6 | "bytes"
7 | "crypto/pbkdf2"
8 | "crypto/sha256"
9 | "fmt"
10 | )
11 |
12 | /*
13 |
14 | return _sha256(pbkdf2.Key(password, salt, rounds, clientProofSize, sha256.New))
15 | }
16 | */
17 |
18 | // use cache as key calculation is expensive.
19 | var scrampbkdf2KeyCache = newList(3, func(k *SCRAMPBKDF2SHA256) ([]byte, error) {
20 | return scrampbkdf2sha256Key(k.password, k.salt, int(k.rounds))
21 | })
22 |
23 | // SCRAMPBKDF2SHA256 implements SCRAMPBKDF2SHA256 authentication.
24 | type SCRAMPBKDF2SHA256 struct {
25 | username, password string
26 | clientChallenge []byte
27 | salt, serverChallenge []byte
28 | serverProof []byte
29 | rounds uint32
30 | }
31 |
32 | // NewSCRAMPBKDF2SHA256 creates a new authSCRAMPBKDF2SHA256 instance.
33 | func NewSCRAMPBKDF2SHA256(username, password string) *SCRAMPBKDF2SHA256 {
34 | return &SCRAMPBKDF2SHA256{username: username, password: password, clientChallenge: clientChallenge()}
35 | }
36 |
37 | func (a *SCRAMPBKDF2SHA256) String() string {
38 | return fmt.Sprintf("method type %s clientChallenge %v", a.Typ(), a.clientChallenge)
39 | }
40 |
41 | // Compare implements cache.Compare interface.
42 | func (a *SCRAMPBKDF2SHA256) Compare(a1 *SCRAMPBKDF2SHA256) bool {
43 | return a.password == a1.password && bytes.Equal(a.salt, a1.salt) && a.rounds == a1.rounds
44 | }
45 |
46 | // Typ implements the Method interface.
47 | func (a *SCRAMPBKDF2SHA256) Typ() string { return MtSCRAMPBKDF2SHA256 }
48 |
49 | // Order implements the Method interface.
50 | func (a *SCRAMPBKDF2SHA256) Order() byte { return MoSCRAMPBKDF2SHA256 }
51 |
52 | // PrepareInitReq implements the Method interface.
53 | func (a *SCRAMPBKDF2SHA256) PrepareInitReq(prms *Prms) error {
54 | prms.addString(a.Typ())
55 | prms.addBytes(a.clientChallenge)
56 | return nil
57 | }
58 |
59 | // InitRepDecode implements the Method interface.
60 | func (a *SCRAMPBKDF2SHA256) InitRepDecode(d *Decoder) error {
61 | d.subSize() // sub parameters
62 | if err := d.NumPrm(3); err != nil {
63 | return err
64 | }
65 | a.salt = d.bytes()
66 | a.serverChallenge = d.bytes()
67 | if err := checkSalt(a.salt); err != nil {
68 | return err
69 | }
70 | if err := checkServerChallenge(a.serverChallenge); err != nil {
71 | return err
72 | }
73 | var err error
74 | if a.rounds, err = d.bigUint32(); err != nil {
75 | return err
76 | }
77 | return nil
78 | }
79 |
80 | // PrepareFinalReq implements the Method interface.
81 | func (a *SCRAMPBKDF2SHA256) PrepareFinalReq(prms *Prms) error {
82 | key, err := scrampbkdf2KeyCache.Get(a)
83 | if err != nil {
84 | return err
85 | }
86 | clientProof, err := clientProof(key, a.salt, a.serverChallenge, a.clientChallenge)
87 | if err != nil {
88 | return err
89 | }
90 |
91 | prms.AddCESU8String(a.username)
92 | prms.addString(a.Typ())
93 | subPrms := prms.addPrms()
94 | subPrms.addBytes(clientProof)
95 |
96 | return nil
97 | }
98 |
99 | // FinalRepDecode implements the Method interface.
100 | func (a *SCRAMPBKDF2SHA256) FinalRepDecode(d *Decoder) error {
101 | if err := d.NumPrm(2); err != nil {
102 | return err
103 | }
104 | mt := d.String()
105 | if err := checkAuthMethodType(mt, a.Typ()); err != nil {
106 | return err
107 | }
108 | d.subSize()
109 | if err := d.NumPrm(1); err != nil {
110 | return err
111 | }
112 | a.serverProof = d.bytes()
113 | return nil
114 | }
115 |
116 | func scrampbkdf2sha256Key(password string, salt []byte, rounds int) ([]byte, error) {
117 | b, err := pbkdf2.Key(sha256.New, password, salt, rounds, clientProofSize)
118 | if err != nil {
119 | return nil, err
120 | }
121 | return _sha256(b), nil
122 | }
123 |
--------------------------------------------------------------------------------
/driver/example_bulk_test.go:
--------------------------------------------------------------------------------
1 | //go:build !unit
2 |
3 | package driver_test
4 |
5 | import (
6 | "context"
7 | "database/sql"
8 | "fmt"
9 | "iter"
10 | "log"
11 | "slices"
12 |
13 | "github.com/SAP/go-hdb/driver"
14 | )
15 |
16 | /*
17 | ExampleBulkInsert inserts 2000 rows into a database table:
18 |
19 | 1000 rows are inserted via an extended argument list and
20 | 1000 rows are inserted with the help of a argument function
21 | */
22 | func Example_bulkInsert() {
23 | const numRow = 1000 // Number of rows to be inserted into table.
24 |
25 | db := sql.OpenDB(driver.MT.Connector())
26 | defer db.Close()
27 |
28 | tableName := driver.RandomIdentifier("table_")
29 |
30 | // Create table.
31 | if _, err := db.Exec(fmt.Sprintf("create table %s (i integer, f double)", tableName)); err != nil {
32 | log.Fatal(err)
33 | }
34 |
35 | // Prepare statement.
36 | stmt, err := db.PrepareContext(context.Background(), fmt.Sprintf("insert into %s values (?, ?)", tableName))
37 | if err != nil {
38 | log.Fatal(err)
39 | }
40 | defer stmt.Close()
41 |
42 | // Bulk insert via 'extended' argument list.
43 | args := make([]any, numRow*2)
44 | for i := range numRow {
45 | args[i*2], args[i*2+1] = i, float64(i)
46 | }
47 | if _, err := stmt.Exec(args...); err != nil {
48 | log.Fatal(err)
49 | }
50 |
51 | // Bulk insert via function.
52 | i := 0
53 | if _, err := stmt.Exec(func(args []any) error {
54 | if i >= numRow {
55 | return driver.ErrEndOfRows
56 | }
57 | args[0], args[1] = i, float64(i)
58 | i++
59 | return nil
60 | }); err != nil {
61 | log.Fatal(err)
62 | }
63 |
64 | // Select number of inserted rows.
65 | var count int
66 | if err := db.QueryRow(fmt.Sprintf("select count(*) from %s", tableName)).Scan(&count); err != nil {
67 | log.Fatal(err)
68 | }
69 | fmt.Print(count)
70 |
71 | // Drop table.
72 | if _, err := db.Exec(fmt.Sprintf("drop table %s", tableName)); err != nil {
73 | log.Fatal(err)
74 | }
75 |
76 | // output: 2000
77 | }
78 |
79 | /*
80 | ExampleBulkInsert inserts 3000 rows into a database table:
81 |
82 | 1000 rows are inserted via a slices chunc iterator
83 | 1000 rows are inserted via a custom iterator
84 | */
85 | func Example_bulkInsertViaIterator() {
86 | const numRow = 1000 // Number of rows to be inserted into table.
87 |
88 | db := sql.OpenDB(driver.MT.Connector())
89 | defer db.Close()
90 |
91 | tableName := driver.RandomIdentifier("table_")
92 |
93 | // Create table.
94 | if _, err := db.Exec(fmt.Sprintf("create table %s (i integer, f double)", tableName)); err != nil {
95 | log.Fatal(err)
96 | }
97 |
98 | // Prepare statement.
99 | stmt, err := db.PrepareContext(context.Background(), fmt.Sprintf("insert into %s values (?, ?)", tableName))
100 | if err != nil {
101 | log.Fatal(err)
102 | }
103 | defer stmt.Close()
104 |
105 | // Bulk insert via slices chunc iterator.
106 | args := make([]any, numRow*2)
107 | for i := range numRow {
108 | args[i*2], args[i*2+1] = i, float64(i)
109 | }
110 |
111 | if _, err := stmt.Exec(slices.Chunk(args, 2)); err != nil {
112 | log.Fatal(err)
113 | }
114 |
115 | // Bulk insert via custom iterator.
116 | var myIter iter.Seq[[]any] = func(yield func([]any) bool) {
117 | for i := range numRow {
118 | if !yield([]any{i, float64(i)}) {
119 | return
120 | }
121 | }
122 | }
123 |
124 | if _, err := stmt.Exec(myIter); err != nil {
125 | log.Fatal(err)
126 | }
127 |
128 | // Select number of inserted rows.
129 | var count int
130 | if err := db.QueryRow(fmt.Sprintf("select count(*) from %s", tableName)).Scan(&count); err != nil {
131 | log.Fatal(err)
132 | }
133 | fmt.Print(count)
134 |
135 | // Drop table.
136 | if _, err := db.Exec(fmt.Sprintf("drop table %s", tableName)); err != nil {
137 | log.Fatal(err)
138 | }
139 |
140 | // output: 2000
141 | }
142 |
--------------------------------------------------------------------------------
/driver/dbconn.go:
--------------------------------------------------------------------------------
1 | package driver
2 |
3 | import (
4 | "context"
5 | "crypto/tls"
6 | "database/sql/driver"
7 | "fmt"
8 | "io"
9 | "log/slog"
10 | "net"
11 | "runtime/pprof"
12 | "time"
13 | )
14 |
15 | var (
16 | cpuProfile = false
17 | )
18 |
19 | type dbConn interface {
20 | io.ReadWriteCloser
21 | lastRead() time.Time
22 | lastWrite() time.Time
23 | }
24 |
25 | type profileDBConn struct {
26 | dbConn
27 | }
28 |
29 | func (c *profileDBConn) Read(b []byte) (n int, err error) {
30 | pprof.Do(context.Background(), pprof.Labels("db", "read"), func(ctx context.Context) {
31 | n, err = c.dbConn.Read(b)
32 | })
33 | return
34 | }
35 |
36 | func (c *profileDBConn) Write(b []byte) (n int, err error) {
37 | pprof.Do(context.Background(), pprof.Labels("db", "write"), func(ctx context.Context) {
38 | n, err = c.dbConn.Write(b)
39 | })
40 | return
41 | }
42 |
43 | // stdDBConn wraps the database tcp connection. It sets timeouts and handles driver ErrBadConn behavior.
44 | type stdDBConn struct {
45 | metrics *metrics
46 | conn net.Conn
47 | timeout time.Duration
48 | logger *slog.Logger
49 | _lastRead time.Time
50 | _lastWrite time.Time
51 | }
52 |
53 | func newDBConn(ctx context.Context, logger *slog.Logger, host string, metrics *metrics, attrs *connAttrs) (dbConn, error) {
54 | conn, err := attrs.dialContext(ctx, host)
55 | if err != nil {
56 | return nil, err
57 | }
58 | // is TLS connection requested?
59 | if attrs.tlsConfig != nil {
60 | conn = tls.Client(conn, attrs.tlsConfig)
61 | }
62 |
63 | dbConn := &stdDBConn{metrics: metrics, conn: conn, timeout: attrs.timeout, logger: logger}
64 | if cpuProfile {
65 | return &profileDBConn{dbConn: dbConn}, nil
66 | }
67 | return dbConn, nil
68 | }
69 |
70 | func (c *stdDBConn) lastRead() time.Time { return c._lastRead }
71 | func (c *stdDBConn) lastWrite() time.Time { return c._lastWrite }
72 |
73 | func (c *stdDBConn) deadline() (deadline time.Time) {
74 | if c.timeout == 0 {
75 | return
76 | }
77 | return time.Now().Add(c.timeout)
78 | }
79 |
80 | func (c *stdDBConn) Close() error { return c.conn.Close() }
81 |
82 | // Read implements the io.Reader interface.
83 | func (c *stdDBConn) Read(b []byte) (int, error) {
84 | // set timeout
85 | if err := c.conn.SetReadDeadline(c.deadline()); err != nil {
86 | return 0, fmt.Errorf("%w: %w", driver.ErrBadConn, err)
87 | }
88 | c._lastRead = time.Now()
89 | n, err := c.conn.Read(b)
90 | c.metrics.msgCh <- timeMsg{idx: timeRead, d: time.Since(c._lastRead)}
91 | c.metrics.msgCh <- counterMsg{idx: counterBytesRead, v: uint64(n)} //nolint:gosec
92 | if err != nil {
93 | 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()))
94 | // wrap error in driver.ErrBadConn
95 | return n, fmt.Errorf("%w: %w", driver.ErrBadConn, err)
96 | }
97 | return n, nil
98 | }
99 |
100 | // Write implements the io.Writer interface.
101 | func (c *stdDBConn) Write(b []byte) (int, error) {
102 | // set timeout
103 | if err := c.conn.SetWriteDeadline(c.deadline()); err != nil {
104 | return 0, fmt.Errorf("%w: %w", driver.ErrBadConn, err)
105 | }
106 | c._lastWrite = time.Now()
107 | n, err := c.conn.Write(b)
108 | c.metrics.msgCh <- timeMsg{idx: timeWrite, d: time.Since(c._lastWrite)}
109 | c.metrics.msgCh <- counterMsg{idx: counterBytesWritten, v: uint64(n)} //nolint:gosec
110 | if err != nil {
111 | 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()))
112 | // wrap error in driver.ErrBadConn
113 | return n, fmt.Errorf("%w: %w", driver.ErrBadConn, err)
114 | }
115 | return n, nil
116 | }
117 |
--------------------------------------------------------------------------------
/driver/session_test.go:
--------------------------------------------------------------------------------
1 | //go:build !unit
2 |
3 | package driver_test
4 |
5 | import (
6 | "database/sql"
7 | "fmt"
8 | "testing"
9 |
10 | "github.com/SAP/go-hdb/driver"
11 | )
12 |
13 | func TestUserSwitch(t *testing.T) {
14 | t.Parallel()
15 |
16 | ctr := driver.MT.Connector()
17 | tableName := driver.RandomIdentifier("table_")
18 |
19 | sessionUser := &driver.SessionUser{Username: ctr.Username(), Password: ctr.Password()}
20 | ctx := driver.WithUserSwitch(t.Context(), sessionUser)
21 |
22 | secondSessionUser := &driver.SessionUser{Username: "secondUser", Password: "secondPassword"}
23 | secondCtx := driver.WithUserSwitch(t.Context(), secondSessionUser)
24 |
25 | createTable := func() {
26 | db := sql.OpenDB(ctr)
27 | defer db.Close()
28 |
29 | // Create table.
30 | if _, err := db.Exec(fmt.Sprintf("create table %s (i integer)", tableName)); err != nil {
31 | t.Fatal(err)
32 | }
33 | // Insert record.
34 | if _, err := db.Exec(fmt.Sprintf("insert into %s values (?)", tableName), 42); err != nil {
35 | t.Fatal(err)
36 | }
37 | }
38 |
39 | testUserSwitchOnNew := func() {
40 | db := sql.OpenDB(ctr)
41 | defer db.Close()
42 |
43 | i := 0
44 | // switch user on new connection.
45 | conn, err := db.Conn(ctx)
46 | if err != nil {
47 | t.Fatal(err)
48 | }
49 | defer conn.Close()
50 | if err := conn.QueryRowContext(ctx, fmt.Sprintf("select * from %s", tableName)).Scan(&i); err != nil {
51 | t.Fatal(err)
52 | }
53 | }
54 |
55 | testUserSwitchOnExisting := func() {
56 | db := sql.OpenDB(ctr)
57 | defer db.Close()
58 |
59 | i := 0
60 | conn, err := db.Conn(t.Context())
61 | if err != nil {
62 | t.Fatal(err)
63 | }
64 | defer conn.Close()
65 | // switch user on existing connection.
66 | if err := conn.QueryRowContext(ctx, fmt.Sprintf("select * from %s", tableName)).Scan(&i); err != nil {
67 | t.Fatal(err)
68 | }
69 | }
70 |
71 | testUserSwitchOnStmt := func() {
72 | db := sql.OpenDB(ctr)
73 | defer db.Close()
74 |
75 | conn, err := db.Conn(t.Context())
76 | if err != nil {
77 | t.Fatal(err)
78 | }
79 | defer conn.Close()
80 | // switch user.
81 | stmt, err := conn.PrepareContext(ctx, fmt.Sprintf("select * from %s", tableName))
82 | if err != nil {
83 | t.Fatal(err)
84 | }
85 | defer stmt.Close()
86 | // switch user on statement context should throw an error.
87 | _, err = stmt.QueryContext(secondCtx) //nolint:sqlclosecheck
88 | switch err {
89 | // expected error.
90 | case driver.ErrSwitchUser: //nolint:errorlint
91 | case nil:
92 | t.Fatalf("expected error %s", driver.ErrSwitchUser)
93 | default:
94 | t.Fatalf("expected error %s - got %s", driver.ErrSwitchUser, err)
95 | }
96 | }
97 |
98 | testUserSwitchOnTx := func() {
99 | db := sql.OpenDB(ctr)
100 | defer db.Close()
101 |
102 | conn, err := db.Conn(t.Context())
103 | if err != nil {
104 | t.Fatal(err)
105 | }
106 | defer conn.Close()
107 | // switch user.
108 | tx, err := conn.BeginTx(ctx, nil)
109 | if err != nil {
110 | t.Fatal(err)
111 | }
112 | defer tx.Rollback() //nolint:errcheck
113 | // switch user on transaction context should throw an error.
114 | _, err = tx.PrepareContext(secondCtx, fmt.Sprintf("select * from %s", tableName)) //nolint:sqlclosecheck
115 | switch err {
116 | // expected error.
117 | case driver.ErrSwitchUser: //nolint:errorlint
118 | case nil:
119 | t.Fatalf("expected error %s", driver.ErrSwitchUser)
120 | default:
121 | t.Fatalf("expected error %s - got %s", driver.ErrSwitchUser, err)
122 | }
123 | }
124 |
125 | tests := []struct {
126 | name string
127 | fn func()
128 | }{
129 | {"testUserSwitchOnNew", testUserSwitchOnNew},
130 | {"testUserSwitchOnExisting", testUserSwitchOnExisting},
131 | {"testUserSwitchOnStmt", testUserSwitchOnStmt},
132 | {"testUserSwitchOnTx", testUserSwitchOnTx},
133 | }
134 |
135 | createTable()
136 |
137 | for _, test := range tests {
138 | t.Run(test.name, func(t *testing.T) {
139 | t.Parallel()
140 | test.fn()
141 | })
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # go-hdb
2 | [](https://pkg.go.dev/github.com/SAP/go-hdb/driver)
3 | [](https://goreportcard.com/report/github.com/SAP/go-hdb)
4 | [](https://api.reuse.software/info/github.com/SAP/go-hdb)
5 | 
6 |
7 | Go-hdb is a native Go (golang) HANA database driver for Go's sql package. It implements the SAP HANA SQL command network protocol.
8 |
9 | For the official SAP HANA client Go support (not this database driver) please see [SAP Help Portal](https://help.sap.com/docs/SAP_HANA_CLIENT).
10 |
11 | ## Installation
12 |
13 | ```
14 | go get -u github.com/SAP/go-hdb/driver
15 | ```
16 |
17 | ## Building
18 |
19 | To build go-hdb you need to have a working Go environment of the [latest or second latest Go version](https://golang.org/dl/).
20 |
21 | ## Documentation
22 |
23 | API documentation and documented examples can be found at .
24 |
25 | ## HANA cloud connection
26 |
27 | HANA cloud connection proxy is using SNI which does require a TLS connection.
28 | As default one can rely on the root certificate set provided by the host, which already comes with the nessecary
29 | DigiCert certificates (CA, G5).
30 | For more information on [Go](https://go.dev/) tls certificate handling, please see https://pkg.go.dev/crypto/tls#Config.
31 |
32 | Assuming the HANA cloud 'endpoint' is "something.hanacloud.ondemand.com:443". Then the dsn should look as follows:
33 |
34 | ```
35 | "hdb://:@something.hanacloud.ondemand.com:443?TLSServerName=something.hanacloud.ondemand.com"
36 | ```
37 |
38 | with:
39 | - TLSServerName same as 'host'
40 |
41 | ## Specific root certificate
42 | In case a specific root certificate (e.g. self-signed) would be needed, the TLSRootCAFile DSN parameter needs to
43 | point to the location in the filesystem where a root certificate file in 'pem-format' is stored.
44 |
45 | ## Tests
46 |
47 | To run the driver integration tests a HANA Database server is required. The test user must have privileges to create database schemas.
48 |
49 | Set environment variable GOHDBDSN:
50 |
51 | ```
52 | #linux example
53 | export GOHDBDSN="hdb://user:password@host:port"
54 | go test
55 | ```
56 |
57 | Using the Go build tag 'unit' only the driver unit tests will be executed (no HANA Database server required):
58 |
59 | ```
60 | go test --tags unit
61 | ```
62 |
63 | ## Features
64 |
65 | * Native Go implementation (no C libraries, CGO).
66 | * Go package compliant.
67 | * Support of database/sql/driver Execer and Queryer interface for parameter free statements and queries.
68 | * Support of 'bulk' query execution.
69 | * Support of UTF-8 to / from CESU-8 encodings for HANA Unicode types.
70 | * Built-in support of HANA decimals as Go rational numbers .
71 | * Support of Large Object streaming.
72 | * Support of Stored Procedures with table output parameters.
73 | * Support of TLS TCP connections.
74 | * Support of little-endian (e.g. amd64) and big-endian architectures (e.g. s390x).
75 | * Support of [driver connector](https://golang.org/pkg/database/sql/driver/#Connector).
76 | * Support of [PBKDF2](https://tools.ietf.org/html/rfc2898) authentication as default and standard user / password as fallback.
77 | * Support of client certificate (X509) and JWT (JSON Web Token) authentication.
78 | * [Prometheus](https://prometheus.io) collectors for driver and extended database statistics.
79 | * Support of [scanning database rows into go structs](https://pkg.go.dev/github.com/SAP/go-hdb/driver#StructScanner).
80 |
81 | ## Dependencies
82 |
83 | * Please see [go.mod](https://github.com/SAP/go-hdb/blob/main/go.mod).
84 |
85 | ## Licensing
86 |
87 | SAP SE or an SAP affiliate company and go-hdb contributors. Please see our [LICENSE](LICENSE.md) for copyright and license information. Detailed information including third-party components and their licensing/copyright information is available [via the REUSE tool](https://api.reuse.software/info/github.com/SAP/go-hdb).
88 |
--------------------------------------------------------------------------------
/prometheus/go.sum:
--------------------------------------------------------------------------------
1 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
2 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
3 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
4 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
5 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
8 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
9 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
10 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
11 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
12 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
13 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
14 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
15 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
16 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
17 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
18 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
19 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
20 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
21 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
22 | github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
23 | github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
24 | github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
25 | github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
26 | github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc=
27 | github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
28 | github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
29 | github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
30 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
31 | github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
32 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
33 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
34 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
35 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
36 | go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
37 | go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
38 | golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
39 | golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
40 | golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
41 | golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
42 | google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
43 | google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
44 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
45 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
46 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
47 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
48 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
49 |
--------------------------------------------------------------------------------
/driver/internal/protocol/decode.go:
--------------------------------------------------------------------------------
1 | package protocol
2 |
3 | import (
4 | "github.com/SAP/go-hdb/driver/internal/protocol/encoding"
5 | "golang.org/x/text/transform"
6 | )
7 |
8 | func decodeResult(tc typeCode, d *encoding.Decoder, tr transform.Transformer, lobReader LobReader, lobChunkSize, scale int) (any, error) { //nolint: gocyclo
9 | switch tc {
10 | case tcBoolean:
11 | return d.BooleanField()
12 | case tcTinyint:
13 | if !d.Bool() { // null value
14 | return nil, nil
15 | }
16 | return int64(d.Byte()), nil
17 | case tcSmallint:
18 | if !d.Bool() { // null value
19 | return nil, nil
20 | }
21 | return int64(d.Int16()), nil
22 | case tcInteger:
23 | if !d.Bool() { // null value
24 | return nil, nil
25 | }
26 | return int64(d.Int32()), nil
27 | case tcBigint:
28 | if !d.Bool() { // null value
29 | return nil, nil
30 | }
31 | return d.Int64(), nil
32 | case tcReal:
33 | return d.RealField()
34 | case tcDouble:
35 | return d.DoubleField()
36 | case tcDate:
37 | return d.DateField()
38 | case tcTime:
39 | return d.TimeField()
40 | case tcTimestamp:
41 | return d.TimestampField()
42 | case tcLongdate:
43 | return d.LongdateField()
44 | case tcSeconddate:
45 | return d.SeconddateField()
46 | case tcDaydate:
47 | return d.DaydateField()
48 | case tcSecondtime:
49 | return d.SecondtimeField()
50 | case tcDecimal:
51 | return d.DecimalField()
52 | case tcFixed8:
53 | return d.Fixed8Field(scale)
54 | case tcFixed12:
55 | return d.Fixed12Field(scale)
56 | case tcFixed16:
57 | return d.Fixed16Field(scale)
58 | case tcChar, tcVarchar, tcString, tcBstring, tcBinary, tcVarbinary:
59 | return d.VarField()
60 | case tcAlphanum:
61 | return d.AlphanumField()
62 | case tcNchar, tcNvarchar, tcNstring, tcShorttext:
63 | return d.Cesu8Field()
64 | case tcStPoint, tcStGeometry:
65 | return d.HexField()
66 | case tcBlob, tcClob, tcLocator, tcBintext:
67 | descr := newLobOutDescr(nil, lobReader, lobChunkSize)
68 | if descr.decode(d) {
69 | return nil, nil
70 | }
71 | return descr, nil
72 | case tcText, tcNclob, tcNlocator:
73 | descr := newLobOutDescr(tr, lobReader, lobChunkSize)
74 | if descr.decode(d) {
75 | return nil, nil
76 | }
77 | return descr, nil
78 | default:
79 | panic("invalid type code")
80 | }
81 | }
82 |
83 | func decodeLobParameter(d *encoding.Decoder) (any, error) {
84 | // real decoding (sniffer) not yet supported
85 | // descr := &LobInDescr{}
86 | // descr.Opt = LobOptions(d.Byte())
87 | // descr._size = int(d.Int32())
88 | // descr.pos = int(d.Int32())
89 | d.Byte()
90 | d.Int32()
91 | d.Int32()
92 | return nil, nil
93 | }
94 |
95 | func decodeParameter(tc typeCode, d *encoding.Decoder, scale int) (any, error) {
96 | switch tc {
97 | case tcBoolean:
98 | return d.BooleanField()
99 | case tcTinyint:
100 | return int64(d.Byte()), nil
101 | case tcSmallint:
102 | return int64(d.Int16()), nil
103 | case tcInteger:
104 | return int64(d.Int32()), nil
105 | case tcBigint:
106 | return d.Int64(), nil
107 | case tcReal:
108 | return d.RealField()
109 | case tcDouble:
110 | return d.DoubleField()
111 | case tcDate:
112 | return d.DateField()
113 | case tcTime:
114 | return d.TimeField()
115 | case tcTimestamp:
116 | return d.TimestampField()
117 | case tcLongdate:
118 | return d.LongdateField()
119 | case tcSeconddate:
120 | return d.SeconddateField()
121 | case tcDaydate:
122 | return d.DaydateField()
123 | case tcSecondtime:
124 | return d.SecondtimeField()
125 | case tcDecimal:
126 | return d.DecimalField()
127 | case tcFixed8:
128 | return d.Fixed8Field(scale)
129 | case tcFixed12:
130 | return d.Fixed12Field(scale)
131 | case tcFixed16:
132 | return d.Fixed16Field(scale)
133 | case tcChar, tcVarchar, tcString, tcBstring, tcBinary, tcVarbinary:
134 | return d.VarField()
135 | case tcAlphanum:
136 | return d.AlphanumField()
137 | case tcNchar, tcNvarchar, tcNstring, tcShorttext:
138 | return d.Cesu8Field()
139 | case tcStPoint, tcStGeometry:
140 | return d.HexField()
141 | case tcBlob, tcClob, tcLocator, tcBintext:
142 | return decodeLobParameter(d)
143 | case tcText, tcNclob, tcNlocator:
144 | return decodeLobParameter(d)
145 | default:
146 | panic("invalid type code")
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/driver/spatial/wkt.go:
--------------------------------------------------------------------------------
1 | package spatial
2 |
3 | import (
4 | "bytes"
5 | "math"
6 | "reflect"
7 | "strconv"
8 | "strings"
9 | )
10 |
11 | func wktTypeName(g Geometry) string {
12 | name := reflect.TypeOf(g).Name()
13 | size := len(name)
14 | switch {
15 | case name[size-2:] == "ZM":
16 | return strings.ToUpper(name[:size-2]) + " ZM"
17 | case name[size-1:] == "M":
18 | return strings.ToUpper(name[:size-1]) + " M"
19 | case name[size-1:] == "Z":
20 | return strings.ToUpper(name[:size-1]) + " Z"
21 | default:
22 | return strings.ToUpper(name)
23 | }
24 | }
25 |
26 | func wktShortTypeName(g Geometry) string {
27 | return strings.ToUpper(geoTypeName(g))
28 | }
29 |
30 | func formatFloat(f float64) string {
31 | if math.IsNaN(f) {
32 | return "NULL"
33 | }
34 | return strconv.FormatFloat(f, 'f', -1, 64)
35 | }
36 |
37 | type wktBuffer struct {
38 | bytes.Buffer
39 | }
40 |
41 | func (b *wktBuffer) writeCoord(fs ...float64) {
42 | b.WriteString(formatFloat(fs[0]))
43 | for _, f := range fs[1:] {
44 | b.WriteString(" ")
45 | b.WriteString(formatFloat(f))
46 | }
47 | }
48 |
49 | func (b *wktBuffer) withBrackets(fn func()) {
50 | b.WriteByte('(')
51 | fn()
52 | b.WriteByte(')')
53 | }
54 |
55 | func (b *wktBuffer) writeList(size int, fn func(i int)) {
56 | if size == 0 {
57 | b.WriteString("EMPTY")
58 | return
59 | }
60 | b.WriteByte('(')
61 | fn(0)
62 | for i := 1; i < size; i++ {
63 | b.WriteByte(',')
64 | fn(i)
65 | }
66 | b.WriteByte(')')
67 | }
68 |
69 | func (b *wktBuffer) writeStrings(strs ...string) {
70 | for _, s := range strs {
71 | b.WriteString(s)
72 | }
73 | }
74 |
75 | func (c Coord) encodeWKT(b *wktBuffer) { b.writeCoord(c.X, c.Y) }
76 | func (c CoordZ) encodeWKT(b *wktBuffer) { b.writeCoord(c.X, c.Y, c.Z) }
77 | func (c CoordM) encodeWKT(b *wktBuffer) { b.writeCoord(c.X, c.Y, c.M) }
78 | func (c CoordZM) encodeWKT(b *wktBuffer) { b.writeCoord(c.X, c.Y, c.Z, c.M) }
79 |
80 | func encodeWKTCoord(b *wktBuffer, c any) {
81 | cv := reflect.ValueOf(c)
82 | switch {
83 | case cv.Type().ConvertibleTo(coordType):
84 | cv.Convert(coordType).Interface().(Coord).encodeWKT(b)
85 | case cv.Type().ConvertibleTo(coordZType):
86 | cv.Convert(coordZType).Interface().(CoordZ).encodeWKT(b)
87 | case cv.Type().ConvertibleTo(coordMType):
88 | cv.Convert(coordMType).Interface().(CoordM).encodeWKT(b)
89 | case cv.Type().ConvertibleTo(coordZMType):
90 | cv.Convert(coordZMType).Interface().(CoordZM).encodeWKT(b)
91 | default:
92 | panic("invalid coordinate type")
93 | }
94 | }
95 |
96 | const (
97 | typeFull byte = iota
98 | typeShort
99 | typeNone
100 | )
101 |
102 | func encodeWKT(b *wktBuffer, typeFlag byte, g Geometry) {
103 | switch typeFlag {
104 | case typeFull:
105 | b.writeStrings(wktTypeName(g), " ")
106 | case typeShort:
107 | b.writeStrings(wktShortTypeName(g), " ")
108 | }
109 |
110 | switch geoType(g) {
111 | case geoPoint:
112 | b.withBrackets(func() {
113 | encodeWKTCoord(b, g)
114 | })
115 | case geoLineString, geoCircularString:
116 | gv := reflect.ValueOf(g)
117 | b.writeList(gv.Len(), func(i int) {
118 | encodeWKTCoord(b, gv.Index(i).Interface())
119 | })
120 | case geoPolygon:
121 | gv := reflect.ValueOf(g)
122 | b.writeList(gv.Len(), func(i int) {
123 | ringv := gv.Index(i)
124 | b.writeList(ringv.Len(), func(i int) {
125 | encodeWKTCoord(b, ringv.Index(i).Interface())
126 | })
127 | })
128 | case geoMultiPoint, geoMultiLineString, geoMultiPolygon:
129 | gv := reflect.ValueOf(g)
130 | b.writeList(gv.Len(), func(i int) {
131 | encodeWKT(b, typeNone, gv.Index(i).Interface().(Geometry))
132 | })
133 | case geoGeometryCollection:
134 | gv := reflect.ValueOf(g)
135 | b.writeList(gv.Len(), func(i int) {
136 | encodeWKT(b, typeShort, gv.Index(i).Interface().(Geometry))
137 | })
138 | }
139 | }
140 |
141 | // EncodeWKT encodes a geometry to the "well known text" format.
142 | func EncodeWKT(g Geometry) ([]byte, error) {
143 | b := new(wktBuffer)
144 | encodeWKT(b, typeFull, g)
145 | return b.Bytes(), nil
146 | }
147 |
148 | // EncodeEWKT encodes a geometry to the "well known text" format.
149 | func EncodeEWKT(g Geometry, srid int32) ([]byte, error) {
150 | b := new(wktBuffer)
151 | b.writeStrings("SRID=", strconv.Itoa(int(srid)), ";")
152 | encodeWKT(b, typeFull, g)
153 | return b.Bytes(), nil
154 | }
155 |
--------------------------------------------------------------------------------
/cmd/bulkbench/templates/index.html:
--------------------------------------------------------------------------------
1 | {{define "index"}}
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | bulkbench
10 |
15 |
16 |
17 |
18 |
19 | Runtime information
20 |
21 |
22 | GOMAXPROCS {{gomaxprocs}}
23 | NumCPU {{numcpu}}
24 | Driver Version {{driverversion}}
25 | HANA Version {{hdbversion}}
26 | goos/goarch {{goos}}/{{goarch}}
27 |
28 |
29 |
30 |
31 | Benchmark parameter
32 |
33 |
34 |
35 | Flag
36 | Value
37 | Usage
38 |
39 |
40 |
41 | {{- range .Flags}}
42 |
43 | {{.Name}}
44 | {{.Value}}
45 | {{.Usage}}
46 |
47 | {{- end}}
48 |
49 |
50 |
51 |
52 | Benchmarks
53 |
54 |
55 |
56 | BatchCount x BatchSize
57 | Sequential
58 | Concurrent
59 |
60 |
61 |
62 | {{- range .TestDefs}}
63 |
64 | {{.Descr}}
65 | start
66 | start
67 |
68 | {{- end}}
69 |
70 |
71 |
72 |
73 |
74 | Sequential
75 | BatchCount
76 | BatchSize
77 | BulkSize
78 | Duration
79 | Error
80 |
81 |
82 |
83 |
84 |
85 |
86 | Database commands
87 |
88 |
89 |
90 | Table: {{.TableName}}
91 | {{- range .TableCommands}}
92 | {{.Command}}
93 | {{- end}}
94 |
95 |
96 | Schema: {{.SchemaName}}
97 | {{- range .SchemaCommands}}
98 | {{.Command}}
99 | {{- end}}
100 |
101 |
102 |
103 |
104 |
105 |
106 | Command
107 | Rows
108 | Error
109 |
110 |
111 |
112 |
113 |
114 |
117 |
118 | "Rule 2. Measure. Don't tune for speed until you've measured,
119 | and even then don't unless one part of the code overwhelms the rest."
120 |
123 |
124 |
125 |
132 |
133 |
134 | {{end}}
135 |
136 | {{template "index" .}}
137 |
--------------------------------------------------------------------------------