Allow more PostgreSQL URI options, fix #199.

As per PostgreSQL documentation on connection strings, allow overriding
of main URI components in the options parts, with a percent-encoded
syntax for parameters. It allows to bypass the main URI parser
limitations as seen in #199 (how to have a password start with a
colon?).

See:
 http://www.postgresql.org/docs/9.3/interactive/libpq-connect.html#LIBPQ-CONNSTRING
This commit is contained in:
Dimitri Fontaine 2015-05-22 23:39:04 +02:00
parent ba7b27b60a
commit d1fce3728a
3 changed files with 54 additions and 11 deletions

View File

@ -596,8 +596,9 @@ Where:
you may use several parameters by separating them away using an
ampersand (`&`) character.
Only two options are supported here, *tablename* (which might be
qualified with a schema name) and *sslmode*.
Only some options are supported here, *tablename* (which might be
qualified with a schema name) *sslmode*, *host*, *port*, *dbname*,
*user* and *password*.
The *sslmode* parameter values can be one of `disable`, `allow`,
`prefer` or `require`.
@ -606,6 +607,11 @@ Where:
*tablename* option directly, without spelling out the `tablename=`
parts.
The options override the main URI components when both are given, and
using the percent-encoded option parameters allow using passwords
starting with a colon and bypassing other URI components parsing
limitations.
### Regular Expressions
Several clauses listed in the following accept *regular expressions* with

View File

@ -33,6 +33,7 @@
#:metabang-bind ; the bind macro
#:mssql ; M$ SQL connectivity
#:uuid ; Transforming MS SQL unique identifiers
#:quri ; decode URI parameters
)
:components
((:module "src"

View File

@ -73,16 +73,17 @@
(defrule hostname (or ipv4 socket-directory network-name)
(:identity t))
(defun process-hostname (hostname)
(destructuring-bind (type &optional name) hostname
(ecase type
(:unix (if name (cons :unix name) :unix))
(:ipv4 name)
(:host name))))
(defrule dsn-hostname (and (? hostname) (? dsn-port))
(:lambda (host-port)
(destructuring-bind (host &optional port) host-port
(append (list :host
(when host
(destructuring-bind (type &optional name) host
(ecase type
(:unix (if name (cons :unix name) :unix))
(:ipv4 name)
(:host name)))))
(append (list :host (when host (process-hostname host)))
port))))
(defrule dsn-dbname (and "/" (? namestring))
@ -125,7 +126,39 @@
(bind (((_ table-name) opt-tn))
table-name)))
(defrule dsn-option (or dsn-option-ssl dsn-option-table-name))
(defrule uri-param (+ (not "&")) (:text t))
(defmacro make-dsn-option-rule (name param &optional (rule 'uri-param) fn)
`(defrule ,name (and ,param "=" ,rule)
(:lambda (x)
(let ((cons (first (quri:url-decode-params (text x)))))
(setf (car cons) (intern (string-upcase (car cons)) "KEYWORD"))
(when ,fn
(setf (cdr cons) (funcall ,fn (cdr cons))))
cons))))
(make-dsn-option-rule dsn-option-host "host" uri-param
(lambda (hostname)
(process-hostname
(parse 'hostname
;; special case Unix Domain Socket paths
(cond ((char= (aref hostname 0) #\/)
(format nil "unix:~a" hostname))
(t hostname))))))
(make-dsn-option-rule dsn-option-port "port"
(+ (digit-char-p character))
#'parse-integer)
(make-dsn-option-rule dsn-option-dbname "dbname")
(make-dsn-option-rule dsn-option-user "user")
(make-dsn-option-rule dsn-option-pass "password")
(defrule dsn-option (or dsn-option-ssl
dsn-option-host
dsn-option-port
dsn-option-dbname
dsn-option-user
dsn-option-pass
dsn-option-table-name))
(defrule another-dsn-option (and "&" dsn-option)
(:lambda (source)
@ -154,7 +187,10 @@
dbname
table-name
use-ssl)
(apply #'append uri)
;; we want the options to take precedence over the URI components,
;; so we destructure the URI again and prepend options here.
(destructuring-bind (prefix user-pass host-port dbname options) uri
(apply #'append options prefix user-pass host-port (list dbname)))
;; Default to environment variables as described in
;; http://www.postgresql.org/docs/9.3/static/app-psql.html
(declare (ignore type))