Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: Experimental sql package #9

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions x/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## x

Experimental packages.
5 changes: 5 additions & 0 deletions x/postgres/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# postgres

Experimenting with predicates that use Go database/sql to query relational databases.

Currently just supports Postgres but should be applicable to other DBs as well.
32 changes: 32 additions & 0 deletions x/postgres/conn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package postgres

import (
"database/sql"
"sync"
"sync/atomic"
)

var (
currentID = new(int64)
connections sync.Map // connection id → *sql.DB
)

func nextID() int64 {
return atomic.AddInt64(currentID, 1)
}

func getConn(id int64) *sql.DB {
db, ok := connections.Load(id)
if !ok {
return nil
}
return db.(*sql.DB)
}

func setConn(id int64, db *sql.DB) {
connections.Store(id, db)
}

func deleteConn(id int64) {
connections.Delete(id)
}
13 changes: 13 additions & 0 deletions x/postgres/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module github.com/trealla-prolog/go/x/postgres

go 1.20

require (
github.com/lib/pq v1.10.9
github.com/trealla-prolog/go v0.13.9
)

require (
github.com/bytecodealliance/wasmtime-go/v8 v8.0.0 // indirect
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 // indirect
)
12 changes: 12 additions & 0 deletions x/postgres/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
github.com/bytecodealliance/wasmtime-go/v8 v8.0.0 h1:jP4sqm2PHgm3+eQ50zCoCdIyQFkIL/Rtkw6TT8OYPFI=
github.com/bytecodealliance/wasmtime-go/v8 v8.0.0/go.mod h1:tgazNLU7xSC2gfRAM8L4WyE+dgs5yp9FF5/tGebEQyM=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/trealla-prolog/go v0.13.9 h1:ZZesdRT+RjEXWEaDI4extKFIAbmExUUOVvw5NvXxZAI=
github.com/trealla-prolog/go v0.13.9/go.mod h1:PnMv/imG6iuoon0QUP/gSAqtHWeA55iV05W2dtOLn9U=
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o=
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
26 changes: 26 additions & 0 deletions x/postgres/library.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package postgres

import (
"context"
"fmt"

"github.com/trealla-prolog/go/trealla"
)

var predicates = []struct {
name string
arity int
proc trealla.Predicate
}{
{"postgres_open_url", 2, open_url_2},
{"postgres_execute", 4, execute_4},
}

func Register(ctx context.Context, pl trealla.Prolog) error {
for _, pred := range predicates {
if err := pl.Register(ctx, pred.name, pred.arity, pred.proc); err != nil {
return fmt.Errorf("failed to register predicate %s/%d: %w", pred.name, pred.arity, err)
}
}
return nil
}
74 changes: 74 additions & 0 deletions x/postgres/postgres.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package postgres

import (
"database/sql"

_ "github.com/lib/pq"

"github.com/trealla-prolog/go/trealla"
"github.com/trealla-prolog/go/trealla/terms"
)

func open_url_2(pl trealla.Prolog, _ trealla.Subquery, goal trealla.Term) trealla.Term {
pi := terms.PI(goal)
g, ok := goal.(trealla.Compound)
if !ok {
return terms.Throw(terms.TypeError("compound", goal, pi))
}

connStr, ok := g.Args[0].(string)
if !ok {
return terms.Throw(terms.TypeError("chars", g.Args[0], pi))
}

_, ok = g.Args[1].(trealla.Variable)
if !ok {
return terms.Throw(trealla.Atom("error").Of(trealla.Atom("uninstantiation_error").Of(g.Args[1]), pi))
}

db, err := sql.Open("postgres", connStr)
if err != nil {
return terms.Throw(dbError(err, pi))
}

id := nextID()
connections.Store(id, db)

g.Args[1] = trealla.Atom("pg").Of(id)
return g
}

func execute_4(pl trealla.Prolog, _ trealla.Subquery, goal trealla.Term) trealla.Term {
pi := terms.PI(goal)
g, ok := goal.(trealla.Compound)
if !ok {
return terms.Throw(terms.TypeError("compound", goal, pi))
}

handle, _ := g.Args[0].(trealla.Compound)
if handle.Functor != "pg" {
return terms.Throw(terms.TypeError("db_connection", g.Args[0], pi))
}

rawDB, ok := connections.Load(handle.Args[0].(int64))
if !ok {
return terms.Throw(terms.DomainError("db_connection", handle.Args[0], pi))
}
db := rawDB.(*sql.DB)

// TODO: apply query arguments

result, err := db.Exec(g.Args[1].(string))
_ = result
if err != nil {
return terms.Throw(dbError(err, pi))
}

// TODO: build result

return g
}

func dbError(err error, pi trealla.Term) trealla.Term {
return trealla.Atom("error").Of(trealla.Atom("db_error").Of(err.Error()), pi)
}
43 changes: 43 additions & 0 deletions x/postgres/postgres_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package postgres

import (
"context"
"reflect"
"testing"

"github.com/trealla-prolog/go/trealla"
)

func TestPG(t *testing.T) {
pl, err := trealla.New()
if err != nil {
t.Fatal(err)
}

ctx := context.Background()
if err := Register(ctx, pl); err != nil {
t.Fatal(err)
}

t.Run("open connection", func(t *testing.T) {
ctx := context.Background()
ans, err := pl.QueryOnce(ctx, `postgres_open_url("user=postgres password=password dbname=postgres sslmode=disable", Handle)`)
if err != nil {
t.Fatal(err)
}
want := trealla.Atom("pg").Of(int64(1))
if !reflect.DeepEqual(ans.Solution["Handle"], want) {
t.Error("bad handle. want:", want, "got:", ans.Solution["Handle"])
}
})

t.Run("execute query", func(t *testing.T) {
ctx := context.Background()
ans, err := pl.QueryOnce(ctx, `postgres_open_url("user=postgres password=password dbname=postgres sslmode=disable", Handle), postgres_execute(Handle, "CREATE TABLE IF NOT EXISTS guestbook (id serial primary key, time timestamp default CURRENT_TIMESTAMP, author text, msg text);", [], Results).`)
if err != nil {
t.Fatal(err)
}
_ = ans
// TODO
})
}