Skip to content

Commit

Permalink
feat(#6): add dsn builder
Browse files Browse the repository at this point in the history
  • Loading branch information
Nightapes committed Jun 23, 2021
1 parent ca7cf78 commit 36c8200
Show file tree
Hide file tree
Showing 10 changed files with 214 additions and 58 deletions.
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ This library uses the standard Golang [SQL driver interface](https://golang.org/

### Create Connection

#### with exasol dsn

```go
package main

Expand All @@ -23,6 +25,23 @@ func main() {
}
```

#### with exasol config

```go
package main

import (
"database/sql"

"github.com/exasol/exasol-driver-go"
)

func main() {
db, err := sql.Open("exasol", exasol.NewConfig("username", "password").Port(8563).String())
...
}
```

### Execute Statement

```go
Expand Down Expand Up @@ -98,6 +117,7 @@ Limitations: Only single ips or dns is supported
| clientversion | string | | Tell the server the version of the application. |
| compression | 0=off, 1=on | 0 | Switch data compression on or off. |
| encryption | 0=off, 1=on | 1 | Switch automatic encryption on or off. |
| insecure | 0=off, 1=on | 0 | Disable TLS/SSL verification. Use if you want to use a self-signed or invalid certificate (server side) |
| fetchsize | numeric, >0 | 128*1024 | Amount of data in kB which should be obtained by Exasol during a fetch. The JVM can run out of memory if the value is too high. |
| password | string | | Exasol password. |
| resultsetmaxrows | numeric | | Set the max amount of rows in the result set. |
Expand Down
6 changes: 1 addition & 5 deletions connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,8 @@ import (
"github.com/gorilla/websocket"
)

var (
defaultDialer = *websocket.DefaultDialer
)

type connection struct {
config *Config
config *config
websocket *websocket.Conn
ctx context.Context
isClosed bool
Expand Down
2 changes: 1 addition & 1 deletion connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
)

type connector struct {
config *Config
config *config
}

func (c *connector) Connect(ctx context.Context) (driver.Conn, error) {
Expand Down
9 changes: 5 additions & 4 deletions driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import (

type ExasolDriver struct{}

type Config struct {
type config struct {
User string
Password string
Host string
Port string
Port int
Params map[string]string // Connection parameters
ApiVersion int
ClientName string
Expand All @@ -25,14 +25,15 @@ type Config struct {
ResultSetMaxRows int
Timeout time.Time
Encryption bool
Insecure bool
}

func init() {
sql.Register("exasol", &ExasolDriver{})
}

func (e ExasolDriver) Open(dsn string) (driver.Conn, error) {
config, err := ParseDSN(dsn)
config, err := parseDSN(dsn)
if err != nil {
return nil, err
}
Expand All @@ -43,7 +44,7 @@ func (e ExasolDriver) Open(dsn string) (driver.Conn, error) {
}

func (e ExasolDriver) OpenConnector(dsn string) (driver.Connector, error) {
config, err := ParseDSN(dsn)
config, err := parseDSN(dsn)
if err != nil {
return nil, err
}
Expand Down
3 changes: 2 additions & 1 deletion driver_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package exasol

import (
"github.com/stretchr/testify/suite"
"strings"
"testing"

"github.com/stretchr/testify/suite"
)

type DriverTestSuite struct {
Expand Down
127 changes: 112 additions & 15 deletions dsn.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,108 @@ import (
"strings"
)

func ParseDSN(dsn string) (*Config, error) {
type DSNConfig struct {
host string
port int
user string
password string
autocommit *bool
encryption *bool
compression *bool
clientName string
clientVersion string
fetchSize int
insecure *bool
}

func NewConfig(user, password string) *DSNConfig {
return &DSNConfig{
host: "localhost",
port: 8563,
user: user,
password: password,
}
}

func (c *DSNConfig) Compression(enabled bool) *DSNConfig {
c.compression = &enabled
return c
}
func (c *DSNConfig) Encryption(enabled bool) *DSNConfig {
c.encryption = &enabled
return c
}
func (c *DSNConfig) Autocommit(enabled bool) *DSNConfig {
c.autocommit = &enabled
return c
}
func (c *DSNConfig) Insecure(enabled bool) *DSNConfig {
c.insecure = &enabled
return c
}
func (c *DSNConfig) FetchSize(size int) *DSNConfig {
c.fetchSize = size
return c
}
func (c *DSNConfig) ClientName(name string) *DSNConfig {
c.clientName = name
return c
}
func (c *DSNConfig) ClientVersion(version string) *DSNConfig {
c.clientVersion = version
return c
}
func (c *DSNConfig) Host(host string) *DSNConfig {
c.host = host
return c
}
func (c *DSNConfig) Port(port int) *DSNConfig {
c.port = port
return c
}

func (c *DSNConfig) String() string {
var sb strings.Builder
sb.WriteString(fmt.Sprintf("exa:%s:%d;user=%s;password=%s;", c.host, c.port, c.user, c.password))
if c.autocommit != nil {
sb.WriteString(fmt.Sprintf("autocommit=%d;", boolToInt(*c.autocommit)))
}
if c.compression != nil {
sb.WriteString(fmt.Sprintf("compression=%d;", boolToInt(*c.compression)))
}
if c.encryption != nil {
sb.WriteString(fmt.Sprintf("encryption=%d;", boolToInt(*c.encryption)))
}
if c.insecure != nil {
sb.WriteString(fmt.Sprintf("insecure=%d;", boolToInt(*c.insecure)))
}
if c.fetchSize != 0 {
sb.WriteString(fmt.Sprintf("fetchsize=%d;", c.fetchSize))
}
if c.clientName != "" {
sb.WriteString(fmt.Sprintf("clientname=%s;", c.clientName))
}
if c.clientVersion != "" {
sb.WriteString(fmt.Sprintf("clientversion=%s;", c.clientVersion))
}
return strings.TrimRight(sb.String(), ";")
}

func parseDSN(dsn string) (*config, error) {
if !strings.HasPrefix(dsn, "exa:") {
return nil, fmt.Errorf("invalid connection string, must start with 'exa:'")
}

splitDsn := splitIntoConnectionStringAndParameters(dsn)
hostPort := extractHostAndPort(splitDsn[0])

if len(hostPort) != 2 {
return nil, fmt.Errorf("invalid host or port, expected format: <host>:<port>")
host, port, err := extractHostAndPort(splitDsn[0])
if err != nil {
return nil, err
}

if len(splitDsn) < 2 {
return getBasicConfig(hostPort), nil
return getDefaultConfig(host, port), nil
} else {
return getConfigWithParameters(hostPort, splitDsn[1])
return getConfigWithParameters(host, port, splitDsn[1])
}
}

Expand All @@ -32,26 +118,35 @@ func splitIntoConnectionStringAndParameters(dsn string) []string {
return strings.SplitN(cleanDsn, ";", 2)
}

func extractHostAndPort(connectionString string) []string {
return strings.Split(connectionString, ":")
func extractHostAndPort(connectionString string) (string, int, error) {
hostPort := strings.Split(connectionString, ":")
if len(hostPort) != 2 {
return "", 0, fmt.Errorf("invalid host or port, expected format: <host>:<port>")
}
port, err := strconv.Atoi(hostPort[1])
if err != nil {
return "", 0, fmt.Errorf("invalid `port` value, numeric port expected")
}
return hostPort[0], port, nil
}

func getBasicConfig(hostPort []string) *Config {
return &Config{
Host: hostPort[0],
Port: hostPort[1],
func getDefaultConfig(host string, port int) *config {
return &config{
Host: host,
Port: port,
ApiVersion: 2,
Autocommit: true,
Encryption: true,
Compression: false,
Insecure: false,
ClientName: "Go client",
Params: map[string]string{},
FetchSize: 128 * 1024,
}
}

func getConfigWithParameters(hostPort []string, parametersString string) (*Config, error) {
config := getBasicConfig(hostPort)
func getConfigWithParameters(host string, port int, parametersString string) (*config, error) {
config := getDefaultConfig(host, port)
parameters := extractParameters(parametersString)
for _, parameter := range parameters {
parameter = strings.TrimRight(parameter, ";")
Expand All @@ -71,6 +166,8 @@ func getConfigWithParameters(hostPort []string, parametersString string) (*Confi
config.Autocommit = value == "1"
case "encryption":
config.Encryption = value == "1"
case "insecure":
config.Insecure = value == "1"
case "compression":
config.Compression = value == "1"
case "clientname":
Expand Down
42 changes: 33 additions & 9 deletions dsn_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package exasol

import (
"github.com/stretchr/testify/suite"
"testing"
"time"

"github.com/stretchr/testify/suite"
)

type DsnTestSuite struct {
Expand All @@ -15,7 +16,7 @@ func TestDsnSuite(t *testing.T) {
}

func (suite *DsnTestSuite) TestParseValidDsnWithoutParameters() {
dsn, err := ParseDSN("exa:localhost:1234")
dsn, err := parseDSN("exa:localhost:1234")
suite.NoError(err)
suite.Equal(dsn.User, "")
suite.Equal(dsn.Password, "")
Expand All @@ -35,7 +36,7 @@ func (suite *DsnTestSuite) TestParseValidDsnWithoutParameters() {
}

func (suite *DsnTestSuite) TestParseValidDsnWithParameters() {
dsn, err := ParseDSN(
dsn, err := parseDSN(
"exa:localhost:1234;user=sys;password=exasol;" +
"autocommit=0;" +
"encryption=0;" +
Expand Down Expand Up @@ -64,7 +65,7 @@ func (suite *DsnTestSuite) TestParseValidDsnWithParameters() {
}

func (suite *DsnTestSuite) TestParseValidDsnWithParameters2() {
dsn, err := ParseDSN(
dsn, err := parseDSN(
"exa:localhost:1234;user=sys;password=exasol;autocommit=1;encryption=1;compression=0")
suite.NoError(err)
suite.Equal(dsn.User, "sys")
Expand All @@ -77,31 +78,54 @@ func (suite *DsnTestSuite) TestParseValidDsnWithParameters2() {
}

func (suite *DsnTestSuite) TestInvalidPrefix() {
dsn, err := ParseDSN("exaa:localhost:1234")
dsn, err := parseDSN("exaa:localhost:1234")
suite.Nil(dsn)
suite.EqualError(err, "invalid connection string, must start with 'exa:'")
}

func (suite *DsnTestSuite) TestInvalidHostPortFormat() {
dsn, err := ParseDSN("exa:localhost")
dsn, err := parseDSN("exa:localhost")
suite.Nil(dsn)
suite.EqualError(err, "invalid host or port, expected format: <host>:<port>")
}

func (suite *DsnTestSuite) TestInvalidParameter() {
dsn, err := ParseDSN("exa:localhost:1234;user")
dsn, err := parseDSN("exa:localhost:1234;user")
suite.Nil(dsn)
suite.EqualError(err, "invalid parameter user, expected format <parameter>=<value>")
}

func (suite *DsnTestSuite) TestInvalidFetchsize() {
dsn, err := ParseDSN("exa:localhost:1234;fetchsize=size")
dsn, err := parseDSN("exa:localhost:1234;fetchsize=size")
suite.Nil(dsn)
suite.EqualError(err, "invalid `fetchsize` value, numeric expected")
}

func (suite *DsnTestSuite) TestInvalidResultsetmaxrows() {
dsn, err := ParseDSN("exa:localhost:1234;resultsetmaxrows=size")
dsn, err := parseDSN("exa:localhost:1234;resultsetmaxrows=size")
suite.Nil(dsn)
suite.EqualError(err, "invalid `resultsetmaxrows` value, numeric expected")
}

func (suite *DriverTestSuite) TestConfigToDsnDefaultValues() {
suite.Equal("exa:localhost:8563;user=sys;password=exasol", NewConfig("sys", "exasol").String())
dsn, err := parseDSN(
"exa:localhost:1234;user=sys;password=exasol;autocommit=1;encryption=1;compression=0")
suite.NoError(err)
suite.Equal(dsn.User, "sys")
suite.Equal(dsn.Password, "exasol")
suite.Equal(dsn.Host, "localhost")
suite.Equal(dsn.Port, 1234)
suite.Equal(dsn.Autocommit, true)
suite.Equal(dsn.Encryption, true)
suite.Equal(dsn.Compression, false)
}

func (suite *DriverTestSuite) TestConfigToDsn() {
config := NewConfig("sys", "exasol")
config.Compression(true)
config.Encryption(true)
config.Autocommit(true)
config.Insecure(true)
suite.Equal("exa:localhost:8563;user=sys;password=exasol;autocommit=1;compression=1;encryption=1;insecure=1", config.String())
}
7 changes: 7 additions & 0 deletions helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,10 @@ func namedValuesToValues(namedValues []driver.NamedValue) ([]driver.Value, error
}
return values, nil
}

func boolToInt(b bool) int {
if b {
return 1
}
return 0
}
Loading

0 comments on commit 36c8200

Please sign in to comment.