├── 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 | [![Go Reference](https://pkg.go.dev/badge/github.com/SAP/go-hdb/driver.svg)](https://pkg.go.dev/github.com/SAP/go-hdb/driver) 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/SAP/go-hdb)](https://goreportcard.com/report/github.com/SAP/go-hdb) 4 | [![REUSE status](https://api.reuse.software/badge/github.com/SAP/go-hdb)](https://api.reuse.software/info/github.com/SAP/go-hdb) 5 | ![](https://github.com/SAP/go-hdb/workflows/build/badge.svg) 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 | 23 | 24 | 25 | 26 | 27 | 28 |
GOMAXPROCS{{gomaxprocs}}
NumCPU{{numcpu}}
Driver Version{{driverversion}}
HANA Version{{hdbversion}}
goos/goarch{{goos}}/{{goarch}}
29 |
30 |
31 | Benchmark parameter 32 | 33 | 34 | 35 | 39 | 40 | 41 | {{- range .Flags}} 42 | 43 | 44 | 45 | 46 | 47 | {{- end}} 48 | 49 |
Flag 36 | Value 37 | Usage 38 |
{{.Name}}{{.Value}}{{.Usage}}
50 |
51 |
52 | Benchmarks 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | {{- range .TestDefs}} 63 | 64 | 65 | 66 | 67 | 68 | {{- end}} 69 | 70 |
BatchCount x BatchSizeSequentialConcurrent
{{.Descr}}startstart
71 | 72 | 73 | 74 | 81 | 82 | 83 |
Sequential 75 | BatchCount 76 | BatchSize 77 | BulkSize 78 | Duration 79 | Error 80 |
84 |
85 |
86 | Database commands 87 | 88 | 89 | 90 | 91 | {{- range .TableCommands}} 92 | 93 | {{- end}} 94 | 95 | 96 | 97 | {{- range .SchemaCommands}} 98 | 99 | {{- end}} 100 | 101 | 102 |
Table: {{.TableName}}{{.Command}}
Schema: {{.SchemaName}}{{.Command}}
103 | 104 | 105 | 106 | 110 | 111 | 112 |
Command 107 | Rows 108 | Error 109 |
113 |
114 |
115 | 116 |
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 | --------------------------------------------------------------------------------