From 4bc4cc53a77dcdbdd1046a958cfaf04454a97043 Mon Sep 17 00:00:00 2001 From: Dimitri Fontaine Date: Fri, 22 Nov 2013 17:19:48 +0100 Subject: [PATCH] Add support for connecting to PostgreSQL with Unix Domain Sockets, fixing #15. --- pgloader.1.md | 11 ++++++- src/parser.lisp | 36 ++++++++++++++++++----- src/pgsql/queries.lisp | 67 +++++++++++++++++++++++------------------- test/sakila.load | 2 +- 4 files changed, 76 insertions(+), 40 deletions(-) diff --git a/pgloader.1.md b/pgloader.1.md index 4067bc6..c37695d 100644 --- a/pgloader.1.md +++ b/pgloader.1.md @@ -122,7 +122,16 @@ Where: - *netloc* - Can be either a hostname in dotted notation, or an ipv4. + Can be either a hostname in dotted notation, or an ipv4, or an unix + domain socket path. Empty is the default network location, under a + system providing *unix domain socket* that method is prefered, otherwise + the *netloc* default to `localhost`. + + It's possible to force the *unix domain socket* path by using the syntax + `unix:/path/to/where/the/socket/file/is`, so to force a non default + socket path and a non default port, you would have: + + postgresql://unix:/tmp:54321/dbname - *dbname* diff --git a/src/parser.lisp b/src/parser.lisp index a8ba7f7..0e3d160 100644 --- a/src/parser.lisp +++ b/src/parser.lisp @@ -244,12 +244,27 @@ (? (digit-char-p character)))) (defrule ipv4 (and ipv4-part "." ipv4-part "." ipv4-part "." ipv4-part) - (:text t)) + (:lambda (ipv4) + (list :ipv4 (text ipv4)))) -(defrule hostname (or ipv4 (and namestring (? (and "." hostname)))) - (:text t)) +;;; socket directory is unix only, so we can forbid ":" on the parsing +(defun socket-directory-character-p (char) + (or (member char #.(quote (coerce "/.-_" 'list))) + (alphanumericp char))) -(defrule dsn-hostname (and hostname (? dsn-port)) +(defrule socket-directory (and "unix:" (* (socket-directory-character-p character))) + (:destructure (unix socket-directory) + (declare (ignore unix)) + (list :unix (when socket-directory (text socket-directory))))) + +(defrule network-name (and namestring (? (and "." network-name))) + (:lambda (name) + (list :host (text name)))) + +(defrule hostname (or ipv4 socket-directory network-name) + (:identity t)) + +(defrule dsn-hostname (and (? hostname) (? dsn-port)) (:destructure (hostname &optional port) (append (list :host hostname) port))) @@ -303,9 +318,16 @@ (list :type type :user user :password password - :host (or host + :host (or (when host + (destructuring-bind (type &optional name) host + (ecase type + (:unix (if name (cons :unix name) :unix)) + (:ipv4 name) + (:host name)))) (case type - (:postgresql (getenv-default "PGHOST" "localhost")) + (:postgresql (getenv-default "PGHOST" + #+unix :unix + #-unix "localhost")) (:mysql (getenv-default "MYSQL_HOST" "localhost")))) :port (or port (parse-integer @@ -1919,7 +1941,7 @@ load database (*myconn-pass* ,password)) ,@body)) (:postgresql - `(let* ((*pgconn-host* ,host) + `(let* ((*pgconn-host* ,(if (consp host) (list 'quote host) host)) (*pgconn-port* ,port) (*pgconn-user* ,user) (*pgconn-pass* ,password)) diff --git a/src/pgsql/queries.lisp b/src/pgsql/queries.lisp index 5bb001e..589f528 100644 --- a/src/pgsql/queries.lisp +++ b/src/pgsql/queries.lisp @@ -31,27 +31,38 @@ (set-session-gucs *pg-settings* :transaction t) ,@forms))) ;; no database given, create a new database connection - `(pomo:with-connection (get-connection-spec ,dbname) - (log-message :debug "CONNECT") - (set-session-gucs *pg-settings*) - (handling-pgsql-notices () - (pomo:with-transaction () - (log-message :debug "BEGIN") - ,@forms))))) + `(let ((cl-postgres:*unix-socket-dir* + (if (and (consp *pgconn-host*) (eq :unix (car *pgconn-host*))) + (fad:pathname-as-directory (cdr *pgconn-host*)) + cl-postgres:*unix-socket-dir*))) + (pomo:with-connection (get-connection-spec ,dbname) + (log-message :debug "CONNECT") + (set-session-gucs *pg-settings*) + (handling-pgsql-notices () + (pomo:with-transaction () + (log-message :debug "BEGIN") + ,@forms)))))) (defmacro with-pgsql-connection ((dbname) &body forms) "Run FROMS within a PostgreSQL connection to DBNAME. To get the connection spec from the DBNAME, use `get-connection-spec'." - `(pomo:with-connection (get-connection-spec ,dbname) - (log-message :debug "CONNECT") - (set-session-gucs *pg-settings*) - (handling-pgsql-notices () - ,@forms))) + `(let ((cl-postgres:*unix-socket-dir* + (if (and (consp *pgconn-host*) (eq :unix (car *pgconn-host*))) + (fad:pathname-as-directory (cdr *pgconn-host*)) + cl-postgres:*unix-socket-dir*))) + (pomo:with-connection (get-connection-spec ,dbname) + (log-message :debug "CONNECT ~s" (get-connection-spec ,dbname)) + (set-session-gucs *pg-settings*) + (handling-pgsql-notices () + ,@forms)))) (defun get-connection-spec (dbname &key (with-port t)) "pomo:with-connection and cl-postgres:open-database and open-db-writer are not using the same connection spec format..." - (let ((conspec (list dbname *pgconn-user* *pgconn-pass* *pgconn-host*))) + (let* ((host (if (and (consp *pgconn-host*) (eq :unix (car *pgconn-host*))) + :unix + *pgconn-host*)) + (conspec (list dbname *pgconn-user* *pgconn-pass* host))) (if with-port (append conspec (list :port *pgconn-port*)) (append conspec (list *pgconn-port*))))) @@ -100,19 +111,17 @@ (defun list-databases (&optional (username "postgres")) "Connect to a local database and get the database list" - (pomo:with-connection (let ((*pgconn-user* username)) - (get-connection-spec "postgres")) - (loop for (dbname) in (pomo:query - "select datname + (let* ((*pgconn-user* username)) + (with-pgsql-transaction ("postgres") + (loop for (dbname) in (pomo:query + "select datname from pg_database where datname !~ 'postgres|template'") - collect dbname))) + collect dbname)))) (defun list-tables (dbname) "Return an alist of tables names and list of columns to pay attention to." - (pomo:with-connection - (get-connection-spec dbname) - + (with-pgsql-transaction (dbname) (loop for (relname colarray) in (pomo:query " select relname, array_agg(case when typname in ('date', 'timestamptz') then attnum end @@ -133,11 +142,9 @@ select relname, array_agg(case when typname in ('date', 'timestamptz') (defun list-tables-cols (dbname &key (schema "public") table-name-list) "Return an alist of tables names and number of columns." - (pomo:with-connection - (get-connection-spec dbname) - - (loop for (relname cols) - in (pomo:query (format nil " + (with-pgsql-transaction (dbname) + (loop for (relname cols) + in (pomo:query (format nil " select relname, count(attnum) from pg_class c join pg_namespace n on n.oid = c.relnamespace @@ -149,7 +156,7 @@ select relname, array_agg(case when typname in ('date', 'timestamptz') ~@[~{and relname = '~a'~^ ~}~] group by relname " schema table-name-list)) - collect (cons relname cols)))) + collect (cons relname cols)))) (defun list-tables-and-fkeys (&optional schema) "Yet another table listing query." @@ -164,9 +171,7 @@ select relname, array_agg(case when typname in ('date', 'timestamptz') (defun list-columns (dbname table-name &key schema) "Return a list of column names for given TABLE-NAME." - (pomo:with-connection - (get-connection-spec dbname) - + (with-pgsql-transaction (dbname) (pomo:query (format nil " select attname from pg_class c @@ -183,7 +188,7 @@ select relname, array_agg(case when typname in ('date', 'timestamptz') (defun reset-all-sequences (dbname &key tables) "Reset all sequences to the max value of the column they are attached to." - (pomo:with-connection (get-connection-spec dbname) + (with-pgsql-connection (dbname) (set-session-gucs *pg-settings*) (pomo:execute "set client_min_messages to warning;") (pomo:execute "listen seqs") diff --git a/test/sakila.load b/test/sakila.load index 8a37274..14b2402 100644 --- a/test/sakila.load +++ b/test/sakila.load @@ -1,6 +1,6 @@ load database from mysql://root@localhost/sakila - into postgresql://localhost:54393/sakila + into postgresql:///sakila WITH include drop, create tables, no truncate, create indexes, reset sequences, foreign keys