PostgreSQL driver
(import :std/db/postgresql)
Have a look at the postgresql-test.ss file to see more of how it is used with
the :std/db/dbi
.
postgresql-connect
The first way is simply to use the function.
(postgresql-connect host: "localhost" user: "foo" passwd: "bar")
;; => #<postgresql-connection #36>
But, often, we may want to close the connection when garbage collected so we,
the developer, don’t need to worry about hanging connections. Thus,
std/db/dbi#sql-connect
is often the better choice as it will
’s the
sql-close
into being.
(import :std/db/dbi)
(def pg (sql-connect postgresql-connect host: "localhost" user: "foo" passwd: "bar"))
;; => #<postgresql-connection #36>
defcatalog, Postgresql->Gerbil->Postgresql mapping
A catalog tells us what to do with what postgresql gives us.
Here’s the basic syntax.
((_ (name mixin ...) (oids serialize deserialize) ...)
A mixin is not always needed. Here is our first version.
(defcatalog my-default-catalog
;; BOOLOID
((16) (lambda _ "TRUE") (lambda _ 42))
;; INT8OID INT2OID INT4OID FLOAT4OID FLOAT8OID NUMERICOID
((20 21 23 700 701 1700) (lambda _ "42") (lambda _ 42)))
Try it out by parameterize
’ing the current-catalog
.
(parameterize ((current-catalog my-default-catalog))
(sql-eval-query pg "SELECT 1, FALSE WHERE $1" 'hey-you))
;; => (#(42 42))
defcatalog-default
By default there is a default-catalog
. If we want to declare a new type within
the default this is where to go.
For example, PostgreSQL has a JSON type.
(let (stmt (sql-prepare pg "SELECT typname, oid FROM pg_catalog.pg_type WHERE typname ILIKE '%json%'"))
(cons (sql-columns stmt) (sql-query stmt)))
;; => (("typname" "oid") #("json" "114") #("jsonb" "3802") #("jsonpath" "4072")
;; #("_json" "199") #("_jsonb" "3807") #("_jsonpath" "4073"))
And, Gerbil does as well! Actually, the :std/test/json
just turns it into a hash table.
First we see the oid
’s for postgres’ json types. Select them as JSON to see
that as well.
(import :std/text/json)
(def res
(let (stmt
(sql-prepare pg "SELECT json_build_object(typname, oid)
FROM pg_catalog.pg_type WHERE typname ILIKE '%json%'"))
(cons (sql-columns stmt) (sql-query stmt))))
;; => (("json_build_object") "{\"json\" : \"114\"}" "{\"jsonb\" : \"3802\"}"
;; "{\"jsonpath\" : \"4072\"}" "{\"_json\" : \"199\"}" "{\"_jsonb\" :
;; \"3807\"}" "{\"_jsonpath\" : \"4073\"}")
;;
(def json-res (map (cut call-with-input-string <> read-json) (cdr res)))
(map table->list json-res)
;; => (((json . "114")) ((jsonb . "3802")) ((jsonpath . "4072"))
;; ((_json . "199")) ((_jsonb . "3807")) ((_jsonpath . "4073")))
All we need is to (de)serialize them…
(def (serialize-json gerbil-json)
(call-with-output-string "" (cut write-json gerbil-json <>)))
(def (deserialize-json str)
(call-with-input-string str read-json))
… and add them to the default catalog.
(defcatalog-default ((114 3802) serialize-json deserialize-json))
Now our query return hash tables.
(let (stmt (sql-prepare pg "SELECT json_build_object(typname, oid) FROM pg_catalog.pg_type WHERE typname ILIKE '%json%'"))
(cons (sql-columns stmt) (sql-query stmt)))
;; => (("json_build_object") #<table #47> #<table #48> #<table #49> #<table #50>
;; #<table #51> #<table #52>)
Even better, we can pass them to queries!
(let (stmt (sql-prepare pg "SELECT * from json_each_text($1)"))
(cons (sql-columns stmt) (begin (sql-bind stmt (list->hash-table '(("foo" . 1) ("bar" . "baz"))))
(sql-query stmt))))
;; => (("key" "value") #("bar" "baz") #("foo" "1"))
default-catalog
What if we only want to change certain things from the default and not have them
be default, yet still have most of the default (de)serializers available?
default-catalog
and defcatalog
to the rescue.
Yes, mixin time!
First, note the difference. The default has a timestamp whereas ours does not.
(sql-eval-query pg "SELECT '2021-03-12'::timestamp, 1")
;; => (#(#<date #57 nanosecond: 0 second: 0 minute: 0 hour: 0 day: 12 month: 3
;; year: 2021 zone-offset: 0> 1))
(parameterize ((current-catalog my-default-catalog))
(sql-eval-query pg "SELECT '2021-03-12'::timestamp, 1"))
;;=> (#("2021-03-12 00:00:00" 42))
Make a new catalog with some mixins.
(defcatalog (united-default-catalog my-default-catalog default-catalog))
Which works as expected.
(parameterize ((current-catalog united-default-catalog))
(sql-eval-query pg "SELECT '2021-03-12'::timestamp, 1"))
;; => (#(#<date #58 nanosecond: 0 second: 0 minute: 0 hour: 0 day: 12 month: 3
;; year: 2021 zone-offset: 0> 42))
current-catalog
The current-catalog
parameter determines which catalog is used by default.
(eq? (current-catalog) default-catalog) ;; =? #t
We can parameterize
it.
[ (parameterize ((current-catalog my-default-catalog))
(sql-eval-query pg "SELECT '2021-03-12'::timestamp, 1"))
(parameterize ((current-catalog united-default-catalog))
(sql-eval-query pg "SELECT '2021-03-12'::timestamp, 1")) ]
;; => ( (#("2021-03-12 00:00:00" 42))
;; (#(#<date #62 nanosecond: 0 second: 0 minute: 0 hour: 0 day: 12 month: 3
; ; year: 2021 zone-offset: 0> 42)))
Use it to declare a global default.
(current-catalog my-default-catalog)
(sql-eval-query pg "SELECT 1") ;; => (42)
Don’t forget to set it back 😃.
(current-catalog default-catalog)
(sql-eval-query pg "SELECT 1") ;; => (1)