branch: elpa/pg commit b03cdf7b7245f21e0218056824744299cc3f5e87 Author: Eric Marsden <eric.mars...@risk-engineering.org> Commit: Eric Marsden <eric.mars...@risk-engineering.org>
Add parsing and serialization support for the data type --- CHANGELOG.md | 2 ++ pg.el | 38 +++++++++++++++++++++++++++++++++++++- test/test-pg.el | 26 ++++++++++++++++++++++---- 3 files changed, 61 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cec95a03bb6..8e7b593f8a8 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ - Add serialization support for the `_varchar` data type. +- Add parsing and serialization support for the `_uuid` data type. + - Add workarounds and detection code for the CedarDB PostgreSQL variant. - New error classes `pg-duplicate-table` and `pg-duplicate-column`, subclasses of diff --git a/pg.el b/pg.el index 0a4520f5174..f4742d27481 100644 --- a/pg.el +++ b/pg.el @@ -2546,6 +2546,8 @@ the PostgreSQL connection CON." (pg-register-parser "text" #'pg-text-parser) (pg-register-parser "varchar" #'pg-text-parser) (pg-register-parser "xml" #'pg-text-parser) + +;; TODO; could verify the UUID syntax here, but it seems unnecessary to double guess PostgreSQL. (pg-register-parser "uuid" #'pg-text-parser) (pg-register-parser "bytea" @@ -2787,6 +2789,21 @@ Uses text encoding ENCODING." (pg-register-parser "money" #'pg-text-parser) +(defun pg-uuidarray-parser (str encoding) + "Parse PostgreSQL value STR as an array of UUID values. +Uses text encoding ENCODING." + (let ((len (length str))) + (unless (and (eql (aref str 0) ?{) + (eql (aref str (1- len)) ?})) + (signal 'pg-protocol-error (list "Unexpected format for UUID array"))) + (let ((maybe-items (cl-subseq str 1 (- len 1)))) + (if (zerop (length maybe-items)) + (vector) + (let ((items (split-string maybe-items ","))) + (apply #'vector (mapcar (lambda (x) (pg-text-parser x encoding)) items))))))) + +(pg-register-parser "_uuid" #'pg-uuidarray-parser) + ;; format for ISO dates is "1999-10-24" (defun pg-date-parser (str _encoding) "Parse PostgreSQL value STR as a date." @@ -3160,9 +3177,28 @@ Respects floating-point infinities and NaN." (concat "{" (string-join (mapcar (lambda (v) (pg--serialize-float v encoding)) vector) ",") "}"))) (pg-register-textual-serializer "_float8" - (lambda (vector encoding) + (lambda (vector encoding) (concat "{" (string-join (mapcar (lambda (v) (pg--serialize-float v encoding)) vector) ",") "}"))) +(pg-register-textual-serializer "_uuid" + (lambda (vector encoding) + (let ((uuid-rx (rx string-start + (group (repeat 8 xdigit)) ?- + (group (repeat 4 xdigit)) ?- + (group (repeat 4 xdigit)) ?- + (group (repeat 4 xdigit)) ?- + (group (repeat 12 xdigit)) + string-end))) + (with-temp-buffer + (insert "{") + (cl-loop + for uuid across vector + do (unless (string-match uuid-rx uuid) + (pg-signal-type-error "Expecting a UUID, got %s" uuid)) + (insert uuid ",")) + (delete-char -1) ; the last comma + (insert "}") + (buffer-string))))) ;; pwdhash = md5(password + username).hexdigest() diff --git a/test/test-pg.el b/test/test-pg.el index 2d423ad7199..9e1a09b685d 100755 --- a/test/test-pg.el +++ b/test/test-pg.el @@ -992,7 +992,19 @@ bar$$")))) (rows (pg-result res :tuples))) (dotimes (i size) (should (string= (format "%04d-value" i) (cl-first (nth i rows))))))) - (pg-exec con "DROP TABLE sarray"))))) + (pg-exec con "DROP TABLE sarray")) + (when (pgtest-have-table con "uuidarray") + (pg-exec con "DROP TABLE uuidarray")) + (when-let* ((sql (pgtest-massage con "CREATE TABLE uuidarray(id SERIAL PRIMARY KEY, val UUID[])"))) + (pg-exec con sql) + (pg-exec-prepared con "INSERT INTO uuidarray(val) VALUES($1)" + '((["a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11" "c4792ecb-c00a-43a2-bd74-5b0ed551c599"] . "_uuid"))) + (pgtest-flush-table con "uuidarray") + (let* ((res (pg-exec con "SELECT val FROM uuidarray")) + (ua (cl-first (pg-result res :tuple 0)))) + (should (string-equal-ignore-case (aref ua 0) "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"))) + (pg-exec con "DROP TABLE uuidarray"))))) + ;; Check the mixing of prepared queries, cached prepared statements, normal simple queries, to check ;; that the cache works as expected and that the backend retains prepared statements. TODO: should @@ -1355,6 +1367,11 @@ bar$$")))) (should (string-equal-ignore-case "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11" (scalar "SELECT 'A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11'::uuid"))) + (let ((uuids (scalar "SELECT '{\"a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11\", + \"c4792ecb-c00a-43a2-bd74-5b0ed551c599\"}'::uuid[]"))) + (should (vectorp uuids)) + (should (string-equal-ignore-case (aref uuids 0) "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11")) + (should (string-equal-ignore-case (aref uuids 1) "c4792ecb-c00a-43a2-bd74-5b0ed551c599"))) ;; Apparently only defined from PostgreSQL v13 onwards. (when (pg-function-p con "gen_random_uuid") (dotimes (_i 30) @@ -1480,6 +1497,7 @@ bar$$")))) (should (equal (vector) (scalar "SELECT '{}'::bool[]"))) (should (equal (vector) (scalar "SELECT '{}'::float4[]"))) (should (equal (vector) (scalar "SELECT '{}'::float8[]"))) + (should (equal (vector "AB1234" "4321BA") (scalar "SELECT '{\"AB1234\",\"4321BA\"}'::bpchar[]"))) (let ((vec (scalar "SELECT ARRAY[3.14::float]"))) (should (floatp (aref vec 0))) (should (pgtest-approx= 3.14 (aref vec 0)))) @@ -1499,9 +1517,9 @@ bar$$")))) ;; this is returning _bpchar. (should (equal (vector ?a ?b ?c) (scalar "SELECT CAST('{a,b,c}' AS CHAR[])")))) (should (equal (vector "foo" "bar") (scalar "SELECT '{foo, bar}'::text[]"))) -;; (let* ((res (pg-exec-prepared con "SELECT $1" '(("{1,2,3}" . "_int4")))) -;; (row (pg-result res :tuple 0))) -;; (should (equal (vector 1 2 3) (cl-first row)))) + (let* ((res (pg-exec-prepared con "SELECT $1" '(([1 2 3] . "_int4")))) + (row (pg-result res :tuple 0))) + (should (equal (vector 1 2 3) (cl-first row)))) (let ((vec (scalar "SELECT ARRAY[44.3, 8999.5]"))) (should (equal 2 (length vec))) (should (pgtest-approx= 44.3 (aref vec 0)))