Skip to content

Commit

Permalink
Merge pull request #46 from devalexandre/feat-adapter-from-db-pgx
Browse files Browse the repository at this point in the history
feat: add SQLAdapter for postgres
  • Loading branch information
VinGarcia authored Jan 22, 2024
2 parents fb8e814 + 9f12a36 commit 6356f35
Show file tree
Hide file tree
Showing 17 changed files with 488 additions and 13 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ test: setup go-mod-tidy
@( cd adapters/kpgx ; $(GOBIN)/richgo test $(path) $(args) -timeout=20s )
@( cd adapters/kpgx5 ; $(GOBIN)/richgo test $(path) $(args) -timeout=20s )
@( cd adapters/kmysql ; $(GOBIN)/richgo test $(path) $(args) -timeout=20s )
@( cd adapters/kpostgres ; $(GOBIN)/richgo test $(path) $(args) -timeout=20s )
@( cd adapters/ksqlserver ; $(GOBIN)/richgo test $(path) $(args) -timeout=20s )
@( cd adapters/ksqlite3 ; $(GOBIN)/richgo test $(path) $(args) -timeout=20s )
@( cd adapters/modernc-ksqlite ; $(GOBIN)/richgo test $(path) $(args) -timeout=20s )
Expand Down
22 changes: 22 additions & 0 deletions adapters/kpostgres/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module github.com/vingarcia/ksql/adapters/kpostgres

go 1.14

require (
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Microsoft/go-winio v0.5.2 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
github.com/containerd/continuity v0.2.2 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/gotestyourself/gotestyourself v2.2.0+incompatible // indirect
github.com/lib/pq v1.10.4
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/opencontainers/runc v1.1.0 // indirect
github.com/ory/dockertest v3.3.5+incompatible
github.com/vingarcia/ksql v1.11.1
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86 // indirect
gotest.tools v2.2.0+incompatible // indirect
)
230 changes: 230 additions & 0 deletions adapters/kpostgres/go.sum

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions adapters/kpostgres/kpostgres.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package kpostgres

import (
"database/sql"

"github.com/vingarcia/ksql"
"github.com/vingarcia/ksql/sqldialect"
)

// NewFromSQLDB builds a ksql.DB from a *sql.DB instance
func NewFromSQLDB(db *sql.DB) (ksql.DB, error) {
return ksql.NewWithAdapter(NewSQLAdapter(db), sqldialect.PostgresDialect{})
}
92 changes: 92 additions & 0 deletions adapters/kpostgres/kpostgres_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package kpostgres

import (
"context"
"database/sql"
"fmt"
"io"
"log"
"testing"
"time"

_ "github.com/lib/pq"
"github.com/ory/dockertest"
"github.com/ory/dockertest/docker"
"github.com/vingarcia/ksql"
"github.com/vingarcia/ksql/sqldialect"
)

func TestSQLAdapter(t *testing.T) {
ctx := context.Background()

postgresURL, closePostgres := startPostgresDB(ctx, "ksql")
defer closePostgres()

ksql.RunTestsForAdapter(t, "kpostgres", sqldialect.PostgresDialect{}, postgresURL, func(t *testing.T) (ksql.DBAdapter, io.Closer) {
sqldb, err := sql.Open("postgres", postgresURL)
if err != nil {
t.Fatal(err.Error())
}

return SQLAdapter{sqldb}, sqldb
})
}

func startPostgresDB(ctx context.Context, dbName string) (databaseURL string, closer func()) {
// uses a sensible default on windows (tcp/http) and linux/osx (socket)
dockerPool, err := dockertest.NewPool("")
if err != nil {
log.Fatalf("Could not connect to docker: %s", err)
}

// pulls an image, creates a container based on it and runs it
resource, err := dockerPool.RunWithOptions(
&dockertest.RunOptions{
Repository: "postgres",
Tag: "14.0",
Env: []string{
"POSTGRES_PASSWORD=postgres",
"POSTGRES_USER=postgres",
"POSTGRES_DB=" + dbName,
"listen_addresses = '*'",
},
},
func(config *docker.HostConfig) {
// set AutoRemove to true so that stopped container goes away by itself
config.AutoRemove = true
config.RestartPolicy = docker.RestartPolicy{Name: "no"}
},
)
if err != nil {
log.Fatalf("Could not start resource: %s", err)
}

hostAndPort := resource.GetHostPort("5432/tcp")
databaseUrl := fmt.Sprintf("postgres://postgres:postgres@%s/%s?sslmode=disable", hostAndPort, dbName)

fmt.Println("Connecting to postgres on url: ", databaseUrl)

resource.Expire(40) // Tell docker to hard kill the container in 40 seconds

var sqlDB *sql.DB
// exponential backoff-retry, because the application in the container might not be ready to accept connections yet
dockerPool.MaxWait = 10 * time.Second
dockerPool.Retry(func() error {
sqlDB, err = sql.Open("postgres", databaseUrl)
if err != nil {
return err
}

return sqlDB.Ping()
})
if err != nil {
log.Fatalf("Could not connect to docker: %s", err)
}
sqlDB.Close()

return databaseUrl, func() {
if err := dockerPool.Purge(resource); err != nil {
log.Fatalf("Could not purge resource: %s", err)
}
}
}
114 changes: 114 additions & 0 deletions adapters/kpostgres/sql_adapter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package kpostgres

import (
"context"
"database/sql"

"github.com/vingarcia/ksql"
"strconv"
"strings"
"unicode"
)

// SQLAdapter adapts the sql.DB type to be compatible with the `DBAdapter` interface
type SQLAdapter struct {
*sql.DB
}

var _ ksql.DBAdapter = SQLAdapter{}

// NewSQLAdapter returns a new instance of SQLAdapter with
// the provided database instance.
func NewSQLAdapter(db *sql.DB) SQLAdapter {
return SQLAdapter{
DB: db,
}
}

// ExecContext implements the DBAdapter interface
func (s SQLAdapter) ExecContext(ctx context.Context, query string, args ...interface{}) (ksql.Result, error) {
return s.DB.ExecContext(ctx, query, args...)
}

// QueryContext implements the DBAdapter interface
func (s SQLAdapter) QueryContext(ctx context.Context, query string, args ...interface{}) (ksql.Rows, error) {
rows, err := s.DB.QueryContext(ctx, query, args...)
return SQLRows{rows}, err
}

// BeginTx implements the Tx interface
func (s SQLAdapter) BeginTx(ctx context.Context) (ksql.Tx, error) {
tx, err := s.DB.BeginTx(ctx, nil)
return SQLTx{Tx: tx}, err
}

// Close implements the io.Closer interface
func (s SQLAdapter) Close() error {
return s.DB.Close()
}

// SQLTx is used to implement the DBAdapter interface and implements
// the Tx interface
type SQLTx struct {
*sql.Tx
}

// ExecContext implements the Tx interface
func (s SQLTx) ExecContext(ctx context.Context, query string, args ...interface{}) (ksql.Result, error) {
return s.Tx.ExecContext(ctx, query, args...)
}

// QueryContext implements the Tx interface
func (s SQLTx) QueryContext(ctx context.Context, query string, args ...interface{}) (ksql.Rows, error) {
rows, err := s.Tx.QueryContext(ctx, query, args...)
return SQLRows{rows}, err
}

// Rollback implements the Tx interface
func (s SQLTx) Rollback(ctx context.Context) error {
return s.Tx.Rollback()
}

// Commit implements the Tx interface
func (s SQLTx) Commit(ctx context.Context) error {
return s.Tx.Commit()
}

var _ ksql.Tx = SQLTx{}

// SQLRows implements the ksql.Rows interface and is used to help
// the SQLAdapter to implement the ksql.DBAdapter interface.
type SQLRows struct {
*sql.Rows
}

var _ ksql.Rows = SQLRows{}

// Scan implements the ksql.Rows interface
func (p SQLRows) Scan(args ...interface{}) error {
err := p.Rows.Scan(args...)
if err != nil {
// Since this is the error flow we decided it would be ok
// to spend a little bit more time parsing this error in order
// to produce better error messages.
//
// If the parsing fails we just return the error unchanged.
const scanErrPrefix = "sql: Scan error on column index "
var errMsg = err.Error()
if strings.HasPrefix(errMsg, scanErrPrefix) {
i := len(scanErrPrefix)
for unicode.IsDigit(rune(errMsg[i])) {
i++
}
colIndex, convErr := strconv.Atoi(errMsg[len(scanErrPrefix):i])
if convErr == nil {
return ksql.ScanArgError{
ColumnIndex: colIndex,
Err: err,
}
}
}
}

return err
}
2 changes: 1 addition & 1 deletion adapters/ksqlserver/ksqlserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func startSQLServerDB(dbName string) (databaseURL string, closer func()) {

return databaseUrl, func() {
if err := pool.Purge(resource); err != nil {
log.Fatalf("Could not purge resource: %s", err)
fmt.Printf("Could not purge resource: %s\n", err)
}
}
}
2 changes: 1 addition & 1 deletion benchmarks/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ sqlboilerfiles: $(GOBIN)/sqlboiler
sqlboiler psql -c sqlboiler.toml --wipe --no-tests

$(GOBIN)/sqlc:
go install github.com/kyleconroy/sqlc/cmd/sqlc@latest
go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest

$(GOBIN)/sqlboiler:
go install github.com/volatiletech/sqlboiler/v4@latest
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
github.com/go-sql-driver/mysql v1.6.0 // indirect
github.com/jackc/pgx/v4 v4.18.1
github.com/jmoiron/sqlx v1.3.4
github.com/lib/pq v1.10.4
github.com/lib/pq v1.10.9
github.com/mattn/go-sqlite3 v1.14.12 // indirect
github.com/vingarcia/ksql v1.11.1
github.com/vingarcia/ksql/adapters/kpgx v0.0.0-00010101000000-000000000000
Expand Down
4 changes: 2 additions & 2 deletions benchmarks/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -354,8 +354,8 @@ github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.1-0.20191011153232-f91d3411e481/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/sqlcgen/db.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion benchmarks/sqlcgen/models.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion benchmarks/sqlcgen/queries.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ go 1.14

require (
github.com/kr/pretty v0.2.1 // indirect
github.com/stretchr/testify v1.8.0
github.com/stretchr/testify v1.8.1
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
)
4 changes: 3 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
1 change: 1 addition & 0 deletions scripts/run-all-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export PATH=$PATH:$(pwd)/scripts
( cd adapters/kpgx5 ; run-with-replace.sh go test -coverprofile=coverage.txt -covermode=atomic -coverpkg=github.com/vingarcia/ksql ./... )
( cd adapters/ksqlite3 ; run-with-replace.sh go test -coverprofile=coverage.txt -covermode=atomic -coverpkg=github.com/vingarcia/ksql ./... )
( cd adapters/modernc-ksqlite ; run-with-replace.sh go test -coverprofile=coverage.txt -covermode=atomic -coverpkg=github.com/vingarcia/ksql ./... )
( cd adapters/kpostgres ; run-with-replace.sh go test -coverprofile=coverage.txt -covermode=atomic -coverpkg=github.com/vingarcia/ksql ./... )
( cd adapters/ksqlserver ; run-with-replace.sh go test -coverprofile=coverage.txt -covermode=atomic -coverpkg=github.com/vingarcia/ksql ./... )
( cd adapters/kmysql ; run-with-replace.sh go test -coverprofile=coverage.txt -covermode=atomic -coverpkg=github.com/vingarcia/ksql ./... )

Expand Down
6 changes: 3 additions & 3 deletions test_adapters.go
Original file line number Diff line number Diff line change
Expand Up @@ -2738,7 +2738,7 @@ func ModifiersTest(
tt.AssertNoErr(t, err)
tt.AssertEqual(t, taggedUser.ID, untaggedUser.ID)
tt.AssertEqual(t, taggedUser.Name, "Marta Ribeiro")
tt.AssertEqual(t, taggedUser.UpdatedAt, tt.ParseTime(t, "2000-08-05T14:00:00Z"))
tt.AssertEqual(t, taggedUser.UpdatedAt.UTC(), tt.ParseTime(t, "2000-08-05T14:00:00Z"))
})
})

Expand Down Expand Up @@ -2807,7 +2807,7 @@ func ModifiersTest(
var untaggedUser2 userWithNoTags
err = c.QueryOne(ctx, &untaggedUser2, "FROM users WHERE id = "+c.dialect.Placeholder(0), u.ID)
tt.AssertNoErr(t, err)
tt.AssertEqual(t, untaggedUser2.CreatedAt, tt.ParseTime(t, "2000-08-05T14:00:00Z"))
tt.AssertEqual(t, untaggedUser2.CreatedAt.UTC(), tt.ParseTime(t, "2000-08-05T14:00:00Z"))
})

t.Run("should not alter the value on queries", func(t *testing.T) {
Expand Down Expand Up @@ -2836,7 +2836,7 @@ func ModifiersTest(
tt.AssertNoErr(t, err)
tt.AssertEqual(t, taggedUser.ID, untaggedUser.ID)
tt.AssertEqual(t, taggedUser.Name, "Marta Ribeiro")
tt.AssertEqual(t, taggedUser.CreatedAt, tt.ParseTime(t, "2000-08-05T14:00:00Z"))
tt.AssertEqual(t, taggedUser.CreatedAt.UTC(), tt.ParseTime(t, "2000-08-05T14:00:00Z"))
})
})

Expand Down

0 comments on commit 6356f35

Please sign in to comment.