branch: elpa/pg
commit cd03d96a3d5d031e1c92f70f938d41b2d51c0282
Author: Eric Marsden <eric.mars...@risk-engineering.org>
Commit: Eric Marsden <eric.mars...@risk-engineering.org>

    Add workarounds for PostgreSQL variant CedarDB.
    
    Add additional tests for sequence functionality.
    
    Improve the test for floating point equality.
    
    Add tests for serialization of arrays using the extended query protocol.
---
 test/test-pg.el | 350 ++++++++++++++++++++++++++++++++++++++++----------------
 1 file changed, 253 insertions(+), 97 deletions(-)

diff --git a/test/test-pg.el b/test/test-pg.el
index fc026a4faed..014ea41e3b3 100755
--- a/test/test-pg.el
+++ b/test/test-pg.el
@@ -23,6 +23,17 @@
 ;; (setq process-adaptive-read-buffering nil)
 
 
+;; 
https://www.reidatcheson.com/floating%20point/comparison/2019/03/20/floating-point-comparison.html
+;;
+;; We were using  (defun approx= (x y) (< (/ (abs (- x y)) (max (abs x) (abs 
y))) 1e-5))
+;;  which exposed us to division by zero.
+(defun pgtest-approx= (x y)
+  (let ((smallest (min x y)))
+    (if (= (abs smallest) 0.0)
+        (< (abs (- x y)) 1e-5)
+      (< (/ (abs (- x y)) (max 1e-8 smallest)) 1e-5))))
+
+
 ;; Good practice for PostgreSQL is to replace use of the SERIAL type by 
"GENERATED ALWAYS AS
 ;; IDENTITY". However, several of the PostgreSQL variants that we want to test 
don't implement this
 ;; syntax, so we choose the syntax for this when we establish a connection.
@@ -233,7 +244,7 @@
       (message "Backend major-version is %s" (pgcon-server-version-major con))
       (message "Detected backend variant: %s" (pgcon-server-variant con))
       (unless (member (pgcon-server-variant con)
-                      '(cockroachdb cratedb yugabyte ydb xata greptimedb 
risingwave clickhouse octodb vertica arcadedb))
+                      '(cockroachdb cratedb yugabyte ydb xata greptimedb 
risingwave clickhouse octodb vertica arcadedb cedardb))
         (when (> (pgcon-server-version-major con) 11)
           (let* ((res (pg-exec con "SELECT current_setting('ssl_library')"))
                  (row (pg-result res :tuple 0)))
@@ -257,9 +268,10 @@
       (pgtest-add #'pg-test-edge-cases)
       (pgtest-add #'pg-test-procedures
                   :skip-variants '(cratedb spanner risingwave materialize ydb 
xata questdb thenile vertica))
-      ;; RisingWave is not able to parse a TZ value of "UTC-01:00" (POSIX 
format).
+      ;; RisingWave is not able to parse a TZ value of "UTC-01:00" (POSIX 
format). QuestDB does not
+      ;; support the timestamptz type. CedarDB des not support the timetz data 
type.
       (pgtest-add #'pg-test-date
-                  :skip-variants '(cratedb risingwave materialize ydb)
+                  :skip-variants '(cratedb risingwave materialize ydb questdb 
cedardb)
                   :need-emacs "29.1")
       ;; QuestDB does not support the timestamptz column type.
       (pgtest-add #'pg-run-tz-tests
@@ -268,7 +280,7 @@
       (pgtest-add #'pg-test-numeric
                   :skip-variants '(vertica))
       (pgtest-add #'pg-test-numeric-range
-                  :skip-variants '(xata cratedb cockroachdb ydb risingwave 
questdb clickhouse greptimedb spanner octodb vertica))
+                  :skip-variants '(xata cratedb cockroachdb ydb risingwave 
questdb clickhouse greptimedb spanner octodb vertica cedardb))
       (pgtest-add #'pg-test-prepared
                   :skip-variants '(ydb cratedb)
                   :need-emacs "28")
@@ -295,7 +307,7 @@
       (pgtest-add #'pg-test-result
                   :skip-variants  '(risingwave ydb spanner clickhouse vertica))
       (pgtest-add #'pg-test-cursors
-                  :skip-variants '(xata cratedb cockroachdb risingwave questdb 
greptimedb ydb materialize spanner octodb))
+                  :skip-variants '(xata cratedb cockroachdb risingwave questdb 
greptimedb ydb materialize spanner octodb cedardb))
       ;; CrateDB does not support the BYTEA type (!), nor sequences. Spanner 
does not support the encode() function.
       (pgtest-add #'pg-test-bytea
                   :skip-variants '(cratedb risingwave spanner materialize))
@@ -306,16 +318,16 @@
       (pgtest-add #'pg-test-array
                   :skip-variants '(cratedb risingwave questdb materialize 
clickhouse octodb))
       (pgtest-add #'pg-test-enums
-                  :skip-variants '(cratedb risingwave questdb greptimedb ydb 
materialize spanner octodb clickhouse vertica))
+                  :skip-variants '(cratedb risingwave questdb greptimedb ydb 
materialize spanner octodb clickhouse vertica cedardb))
       (pgtest-add #'pg-test-server-prepare
                   :skip-variants '(cratedb risingwave questdb greptimedb ydb 
octodb))
       (pgtest-add #'pg-test-comments
-                   :skip-variants '(ydb cratedb cockroachdb spanner questdb 
thenile))
+                   :skip-variants '(ydb cratedb cockroachdb spanner questdb 
thenile cedardb))
       (pgtest-add #'pg-test-metadata
                   :skip-variants '(cratedb cockroachdb risingwave materialize 
questdb greptimedb ydb spanner vertica))
       ;; CrateDB doesn't support the JSONB type. CockroachDB doesn't support 
casting to JSON.
       (pgtest-add #'pg-test-json
-                  :skip-variants '(xata cratedb risingwave questdb greptimedb 
ydb materialize spanner octodb vertica))
+                  :skip-variants '(xata cratedb risingwave questdb greptimedb 
ydb materialize spanner octodb vertica cedardb))
       (pgtest-add #'pg-test-schemas
                   :skip-variants '(xata cratedb risingwave questdb ydb 
materialize))
       (pgtest-add #'pg-test-hstore
@@ -325,11 +337,11 @@
       (pgtest-add #'pg-test-vector
                   :skip-variants '(xata cratedb materialize octodb vertica))
       (pgtest-add #'pg-test-tsvector
-                  :skip-variants '(xata cratedb cockroachdb risingwave questdb 
greptimedb ydb materialize spanner octodb vertica))
+                  :skip-variants '(xata cratedb cockroachdb risingwave questdb 
greptimedb ydb materialize spanner octodb vertica cedardb))
       (pgtest-add #'pg-test-bm25
                   :skip-variants '(xata cratedb cockroachdb risingwave 
materialize octodb vertica))
       (pgtest-add #'pg-test-geometric
-                  :skip-variants '(xata cratedb cockroachdb risingwave questdb 
materialize spanner octodb vertica))
+                  :skip-variants '(xata cratedb cockroachdb risingwave questdb 
materialize spanner octodb vertica cedardb))
       (pgtest-add #'pg-test-gis
                   :skip-variants '(xata cratedb cockroachdb risingwave 
materialize octodb))
       (pgtest-add #'pg-test-copy
@@ -342,7 +354,7 @@
                   :skip-variants '(xata cratedb questdb ydb vertica))
       ;; Many PostgreSQL variants only support UTF8 as the client encoding.
       (pgtest-add #'pg-test-client-encoding
-                  :skip-variants '(cratedb cockroachdb ydb risingwave 
materialize spanner greptimedb xata vertica))
+                  :skip-variants '(cratedb cockroachdb ydb risingwave 
materialize spanner greptimedb questdb xata vertica))
       (pgtest-add #'pg-test-unicode-names
                   :skip-variants '(xata cratedb cockroachdb risingwave questdb 
ydb spanner vertica))
       (pgtest-add #'pg-test-returning
@@ -354,11 +366,13 @@
       ;; than returning a more granular error code.
       (pgtest-add #'pg-test-error-sqlstate
                   :skip-variants '(cratedb risingwave))
-      (pgtest-add #'pg-test-notice)
+      ;; As of 2025-08, CedarDB does not implement DO.
+      (pgtest-add #'pg-test-notice
+                  :skip-variants '(cedardb))
       (pgtest-add #'pg-test-notify
-                  :skip-variants '(cratedb cockroachdb risingwave materialize 
greptimedb ydb questdb spanner vertica))
+                  :skip-variants '(cratedb cockroachdb risingwave materialize 
greptimedb ydb questdb spanner vertica cedardb))
       (pgtest-add #'pg-test-lo
-                  :skip-variants '(cratedb cockroachdb risingwave materialize 
greptimedb ydb questdb spanner vertica greenplum))
+                  :skip-variants '(cratedb cockroachdb risingwave materialize 
greptimedb ydb questdb spanner vertica greenplum cedardb))
       (dolist (test (reverse tests))
         (message "== Running test %s" test)
         (condition-case err
@@ -423,10 +437,9 @@
 
 (defun pg-test-prepared (con)
   (cl-labels ((row (query args) (pg-result (pg-exec-prepared con query args) 
:tuple 0))
-              (scalar (query args) (car (row query args)))
-              (approx= (x y) (< (/ (abs (- x y)) (max (abs x) (abs y))) 1e-5)))
+              (scalar (query args) (car (row query args))))
     (should (equal 42 (scalar "SELECT 42" (list))))
-    (should (approx= 42.0 (scalar "SELECT 42.00" (list))))
+    (should (pgtest-approx= 42.0 (scalar "SELECT 42.00" (list))))
     (should (equal nil (scalar "SELECT NULL" (list))))
     (unless (member (pgcon-server-variant con) '(immudb))
       (should (equal (list t nil) (row "SELECT $1, $2" `((t . "bool") (nil . 
"bool")))))
@@ -443,6 +456,7 @@
     ;; https://github.com/kagis/pgwire/blob/main/test/test.js
     (let ((typ (scalar "SELECT pg_typeof($1)::text" '((42 . "int4")))))
       (should (or (string= "integer" typ)
+                  (string= "int4" typ)
                   (string= "bigint" typ))))
     (let ((typs (row "SELECT pg_typeof($1)::text, $1::text" '(("foobles" . 
"text")))))
       (should (string= "foobles" (cl-second typs)))
@@ -476,12 +490,12 @@
     (should (eql 42 (scalar "SELECT $1 + 142" '((-100 . "int4")))))
     (should (eql 42 (scalar "SELECT $1 + 1" '((41 . "int8")))))
     (should (eql 42 (scalar "SELECT $1 + 142" '((-100 . "int8")))))
-    (should (approx= -55.0 (scalar "SELECT $1" '((-55.0 . "float4")))))
-    (should (approx= -55.0 (scalar "SELECT $1" '((-55.0 . "float8")))))
-    (should (approx= 42.0 (scalar "SELECT $1 + 1" '((41.0 . "float4")))))
-    (should (approx= 42.0 (scalar "SELECT $1 + 85.0" '((-43.0 . "float4")))))
-    (should (approx= 42.0 (scalar "SELECT $1 + 1" '((41.0 . "float8")))))
-    (should (approx= 42.0 (scalar "SELECT $1 + 85" '((-43.0 . "float8")))))
+    (should (pgtest-approx= -55.0 (scalar "SELECT $1" '((-55.0 . "float4")))))
+    (should (pgtest-approx= -55.0 (scalar "SELECT $1" '((-55.0 . "float8")))))
+    (should (pgtest-approx= 42.0 (scalar "SELECT $1 + 1" '((41.0 . 
"float4")))))
+    (should (pgtest-approx= 42.0 (scalar "SELECT $1 + 85.0" '((-43.0 . 
"float4")))))
+    (should (pgtest-approx= 42.0 (scalar "SELECT $1 + 1" '((41.0 . 
"float8")))))
+    (should (pgtest-approx= 42.0 (scalar "SELECT $1 + 85" '((-43.0 . 
"float8")))))
     (unless (member (pgcon-server-variant con) '(cratedb risingwave))
       ;; CrateDB returns an incorrect value ?8 here
       (should (eql ?Q (scalar "SELECT $1" '((?Q . "char"))))))
@@ -517,14 +531,14 @@
     ;; Risingwave does not support casting to JSON.
     (unless (member (pgcon-server-variant con) '(risingwave materialize))
       (let ((json (scalar "SELECT $1::json" '(("[66.7,-42.0,8]" . "text")))))
-        (should (approx= 66.7 (aref json 0)))
-        (should (approx= -42.0 (aref json 1)))))
+        (should (pgtest-approx= 66.7 (aref json 0)))
+        (should (pgtest-approx= -42.0 (aref json 1)))))
     ;; CrateDB does not support the JSONB type, not casting {foo=bar} syntax 
to JSON. CockroachDB
     ;; supports JSONB but not JSON.
     (unless (member (pgcon-server-variant con) '(cratedb cockroachdb 
risingwave materialize))
       (let ((json (scalar "SELECT $1::jsonb" '(("[66.7,-42.0,8]" . "text")))))
-        (should (approx= 66.7 (aref json 0)))
-        (should (approx= -42.0 (aref json 1))))
+        (should (pgtest-approx= 66.7 (aref json 0)))
+        (should (pgtest-approx= -42.0 (aref json 1))))
       (let ((json (scalar "SELECT $1::jsonb" '(("[5,7]" . "text")))))
         (should (eql 5 (aref json 0))))
       (let* ((ht (make-hash-table :test #'equal))
@@ -603,7 +617,6 @@
         (should (eql (cl-first tuple) (cl-incf counter)))))
     (should (eql counter rows))
     (pg-close-portal con portal))
-  (message "multiple fetch/suspend portal test complete")
   ;; check for unexpected pending messages in the stream (problem with old 
PostgreSQL versions)
   (let* ((res (pg-exec con "SELECT 55"))
          (tuple (pg-result res :tuple 0)))
@@ -758,7 +771,8 @@ bar$$"))))
 
 
 (defun pg-test-insert (con)
-  (cl-flet ((scalar (sql) (cl-first (pg-result (pg-exec con sql) :tuple 0))))
+  (cl-flet ((scalar (sql) (cl-first (pg-result (pg-exec con sql) :tuple 0)))
+            (random-word () (apply #'string (cl-loop for count to 15 collect 
(+ ?a (random 26))))))
     (let ((count 100))
       (when (pgtest-have-table con "count_test")
         (pg-exec con "DROP TABLE count_test"))
@@ -804,7 +818,28 @@ bar$$"))))
       (pgtest-flush-table con "w")
       (let ((res (pg-exec con "SELECT * FROM w")))
         (should (eql 3 (length (pg-result res :tuples)))))
-      (pg-exec con "DROP TABLE w"))))
+      (pg-exec con "DROP TABLE w"))
+    ;; Testing insert via UNNEST
+    (when-let* ((sql (pgtest-massage con "CREATE TABLE measurement(
+       id SERIAL PRIMARY KEY,
+       sensorid TEXT,
+       value FLOAT8,
+       ts TIMESTAMPTZ DEFAULT current_timestamp)")))
+      (pg-exec con "DROP TABLE IF EXISTS measurement")
+      (pg-exec con sql)
+      (let* ((size 30)  ;; 9988
+             (sensors (make-vector size nil))
+             (values (make-vector size 0.0))
+             (sql "INSERT INTO measurement(sensorid,value) SELECT * FROM 
unnest($1::text[], $2::float8[])"))
+        (dotimes (i size)
+          (setf (aref sensors i) (random-word))
+          (setf (aref values i) (cl-random 1000.0)))
+        (pg-exec-prepared con sql
+                          `((,sensors . "_text") (,values . "_float8")))
+        (let* ((res (pg-exec con "SELECT COUNT(*) FROM measurement"))
+               (row (pg-result res :tuple 0)))
+          (should (eql size (cl-first row))))
+        (pg-exec con "DROP TABLE measurement")))))
 
 (defun pg-test-insert/prepared (con)
   (cl-flet ((scalar (sql) (cl-first (pg-result (pg-exec con sql) :tuple 0))))
@@ -824,7 +859,88 @@ bar$$"))))
         (should (eql count (scalar "SELECT COUNT(*) FROM count_test")))
         (should (eql (/ (* (1- count) count) 2) (scalar "SELECT sum(key) FROM 
count_test")))
         (pg-exec con "DROP TABLE count_test"))
-      (should (not (pgtest-have-table con "count_test"))))))
+      (should (not (pgtest-have-table con "count_test")))
+      ;; Now test the serialization functions for array types
+      (when (pgtest-have-table con "sarray")
+        (pg-exec con "DROP TABLE sarray"))
+      (when-let* ((sql (pgtest-massage con "CREATE TABLE sarray(id SERIAL 
PRIMARY KEY, val int2)")))
+        (pg-exec con sql)
+        (let* ((size 203)
+               (values (make-vector size nil))
+               (sql "INSERT INTO sarray(val) SELECT * FROM 
unnest($1::int2[])"))
+          (dotimes (i size)
+            (setf (aref values i) i))
+          (pg-exec-prepared con sql `((,values . "_int2")))
+          (let* ((res (pg-exec con "SELECT val FROM sarray ORDER BY val"))
+                 (rows (pg-result res :tuples)))
+            (dotimes (i size)
+              (should (eql i (cl-first (nth i rows)))))))
+        (pg-exec con "DROP TABLE sarray"))
+      (when-let* ((sql (pgtest-massage con "CREATE TABLE sarray(id SERIAL 
PRIMARY KEY, val int4)")))
+        (pg-exec con sql)
+        (let* ((size 203)
+               (values (make-vector size nil))
+               (sql "INSERT INTO sarray(val) SELECT * FROM 
unnest($1::int4[])"))
+          (dotimes (i size)
+            (setf (aref values i) i))
+          (pg-exec-prepared con sql `((,values . "_int4")))
+          (let* ((res (pg-exec con "SELECT val FROM sarray ORDER BY val"))
+                 (rows (pg-result res :tuples)))
+            (dotimes (i size)
+              (should (eql i (cl-first (nth i rows)))))))
+        (pg-exec con "DROP TABLE sarray"))
+      (when-let* ((sql (pgtest-massage con "CREATE TABLE sarray(id SERIAL 
PRIMARY KEY, val int8)")))
+        (pg-exec con sql)
+        (let* ((size 403)
+               (values (make-vector size nil))
+               (sql "INSERT INTO sarray(val) SELECT * FROM 
unnest($1::int8[])"))
+          (dotimes (i size)
+            (setf (aref values i) (- i)))
+          (pg-exec-prepared con sql `((,values . "_int8")))
+          (let* ((res (pg-exec con "SELECT val FROM sarray ORDER BY val DESC"))
+                 (rows (pg-result res :tuples)))
+            (dotimes (i size)
+              (should (eql (- i) (cl-first (nth i rows)))))))
+        (pg-exec con "DROP TABLE sarray"))
+      (when-let* ((sql (pgtest-massage con "CREATE TABLE sarray(id SERIAL 
PRIMARY KEY, val float4)")))
+        (pg-exec con sql)
+        (let* ((size 103)
+               (values (make-vector size nil))
+               (sql "INSERT INTO sarray(val) SELECT * FROM 
unnest($1::float4[])"))
+          (dotimes (i size)
+            (setf (aref values i) i))
+          (pg-exec-prepared con sql `((,values . "_float4")))
+          (let* ((res (pg-exec con "SELECT val FROM sarray ORDER BY val"))
+                 (rows (pg-result res :tuples)))
+            (dotimes (i size)
+              (should (pgtest-approx= i (cl-first (nth i rows)))))))
+        (pg-exec con "DROP TABLE sarray"))
+      (when-let* ((sql (pgtest-massage con "CREATE TABLE sarray(id SERIAL 
PRIMARY KEY, val float8)")))
+        (pg-exec con sql)
+        (let* ((size 17)
+               (values (make-vector size nil))
+               (sql "INSERT INTO sarray(val) SELECT * FROM 
unnest($1::float8[])"))
+          (dotimes (i size)
+            (setf (aref values i) i))
+          (pg-exec-prepared con sql `((,values . "_float8")))
+          (let* ((res (pg-exec con "SELECT val FROM sarray ORDER BY val"))
+                 (rows (pg-result res :tuples)))
+            (dotimes (i size)
+              (should (pgtest-approx= i (cl-first (nth i rows)))))))
+        (pg-exec con "DROP TABLE sarray"))
+      (when-let* ((sql (pgtest-massage con "CREATE TABLE sarray(id SERIAL 
PRIMARY KEY, val TEXT)")))
+        (pg-exec con sql)
+        (let* ((size 17)
+               (values (make-vector size nil))
+               (sql "INSERT INTO sarray(val) SELECT * FROM 
unnest($1::text[])"))
+          (dotimes (i size)
+            (setf (aref values i) (format "%04d-value" i)))
+          (pg-exec-prepared con sql `((,values . "_text")))
+          (let* ((res (pg-exec con "SELECT val FROM sarray ORDER BY val"))
+                 (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")))))
 
 ;; 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
@@ -1040,8 +1156,7 @@ bar$$"))))
                      (encode-time (list 6 5 4 3 2 2001 nil -1 nil)))))))
 
 (defun pg-test-numeric (con)
-  (cl-flet ((scalar (sql) (car (pg-result (pg-exec con sql) :tuple 0)))
-            (approx= (x y) (< (/ (abs (- x y)) (max (abs x) (abs y))) 1e-5)))
+  (cl-flet ((scalar (sql) (car (pg-result (pg-exec con sql) :tuple 0))))
     (should (eql -1 (scalar "SELECT '-1'::int")))
     (should (eql 128 (scalar "SELECT 128::int2")))
     (should (eql -5 (scalar "SELECT -5::int2")))
@@ -1054,7 +1169,7 @@ bar$$"))))
     (should (eql 0 (scalar "SELECT (-32768)::int2 % (-1)::int2")))
     ;; RisingWave doesn't support numeric(x, y) or decimal(x, y).
     (unless (member (pgcon-server-variant con) '(risingwave questdb))
-      (should (approx= 3.14 (scalar "SELECT 3.14::decimal(10,2) as pi"))))
+      (should (pgtest-approx= 3.14 (scalar "SELECT 3.14::decimal(10,2) as 
pi"))))
     ;; CrateDB doesn't support the OID type, nor casting integers to bits.
     (unless (member (pgcon-server-variant con) '(cratedb risingwave 
materialize octodb))
       (should (eql 123 (scalar "SELECT 123::oid")))
@@ -1084,20 +1199,20 @@ bar$$"))))
       (should (eql (scalar "SELECT trunc(43.3)") 43))
       (should (eql (scalar "SELECT trunc(-42.3)") -42)))
     (unless (member (pgcon-server-variant con) '(cockroachdb))
-      (should (approx= (scalar "SELECT log(100)") 2))
+      (should (pgtest-approx= (scalar "SELECT log(100)") 2))
       ;; bignums only supported from Emacs 27.2 onwards
       (unless (member (pgcon-server-variant con) '(cratedb risingwave 
materialize))
         (when (fboundp 'bignump)
           (should (eql (scalar "SELECT factorial(25)") 
15511210043330985984000000)))))
     (unless (member (pgcon-server-variant con) '(materialize))
-      (should (approx= (scalar "SELECT pi()") 3.1415626)))
-    (should (approx= (scalar "SELECT -5.0") -5.0))
-    (should (approx= (scalar "SELECT 5e-14") 5e-14))
-    (should (approx= (scalar "SELECT 55.678::float4") 55.678))
-    (should (approx= (scalar "SELECT 55.678::float8") 55.678))
-    (should (approx= (scalar "SELECT 55.678::real") 55.678))
-    (should (approx= (scalar "SELECT 55.678::numeric") 55.678))
-    (should (approx= (scalar "SELECT -1000000000.123456789") 
-1000000000.123456789))
+      (should (pgtest-approx= (scalar "SELECT pi()") 3.1415626)))
+    (should (pgtest-approx= (scalar "SELECT -5.0") -5.0))
+    (should (pgtest-approx= (scalar "SELECT 5e-14") 5e-14))
+    (should (pgtest-approx= (scalar "SELECT 55.678::float4") 55.678))
+    (should (pgtest-approx= (scalar "SELECT 55.678::float8") 55.678))
+    (should (pgtest-approx= (scalar "SELECT 55.678::real") 55.678))
+    (should (pgtest-approx= (scalar "SELECT 55.678::numeric") 55.678))
+    (should (pgtest-approx= (scalar "SELECT -1000000000.123456789") 
-1000000000.123456789))
     (should (eql 1.0e+INF (scalar "SELECT 'Infinity'::float4")))
     (should (eql -1.0e+INF (scalar "SELECT '-Infinity'::float4")))
     (should (eql 1.0e+INF (scalar "SELECT 'Infinity'::float8")))
@@ -1106,7 +1221,7 @@ bar$$"))))
     (should (isnan (scalar "SELECT 'NaN'::float8")))
     ;; The cube root operator
     (unless (member (pgcon-server-variant con) '(cratedb materialize))
-      (should (approx= 3.0 (scalar "SELECT ||/ float8 '27'"))))
+      (should (pgtest-approx= 3.0 (scalar "SELECT ||/ float8 '27'"))))
     (should (string= (scalar "SELECT 42::decimal::text") "42"))
     (unless (member (pgcon-server-variant con) '(cratedb cockroachdb 
risingwave materialize))
       (should (string= (scalar "SELECT macaddr '08002b:010203'") 
"08:00:2b:01:02:03")))
@@ -1123,8 +1238,7 @@ bar$$"))))
       (should (eql (scalar "SELECT date '2001-10-01' - date '2001-09-28'") 
3)))))
 
 (defun pg-test-numeric-range (con)
-  (cl-flet ((scalar (sql) (car (pg-result (pg-exec con sql) :tuple 0)))
-            (approx= (x y) (< (/ (abs (- x y)) (max (abs x) (abs y))) 1e-5)))
+  (cl-flet ((scalar (sql) (car (pg-result (pg-exec con sql) :tuple 0))))
     (should (equal (list :range ?\[ 10 ?\) 20) (scalar "SELECT int4range(10, 
20)")))
     (should (equal (list :range ?\[ -4 ?\) 6) (scalar "SELECT int8range(-4, 
6)")))
     (should (equal (list :range ?\[ 5 ?\) 20) (scalar "SELECT int4range(5,15) 
+ int4range(10,20)")))
@@ -1138,17 +1252,17 @@ bar$$"))))
     (let ((range (scalar "SELECT numrange(33.33, 66.66)")))
       (should (eql :range (nth 0 range)))
       (should (eql ?\[ (nth 1 range)))
-      (should (approx= 33.33 (nth 2 range)))
+      (should (pgtest-approx= 33.33 (nth 2 range)))
       (should (eql ?\) (nth 3 range)))
-      (should (approx= 66.66 (nth 4 range))))
-    (should (approx= -40.0 (scalar "SELECT upper(numrange(-50.0,-40.00))")))
+      (should (pgtest-approx= 66.66 (nth 4 range))))
+    (should (pgtest-approx= -40.0 (scalar "SELECT 
upper(numrange(-50.0,-40.00))")))
     ;; range is unbounded on lower side
     (let ((range (scalar "SELECT numrange(NULL, 2.2)")))
       (should (eql :range (nth 0 range)))
       (should (eql ?\( (nth 1 range)))
       (should (eql nil (nth 2 range)))
       (should (eql ?\) (nth 3 range)))
-      (should (approx= 2.2 (nth 4 range))))
+      (should (pgtest-approx= 2.2 (nth 4 range))))
     (should (equal (list :range ?\[ 42 ?\) nil) (scalar "SELECT 
int8range(42,NULL)")))
     (should (equal (list :range ?\( nil ?\) nil) (scalar "SELECT 
numrange(NULL, NULL)")))))
 
@@ -1266,16 +1380,36 @@ bar$$"))))
   (cl-flet ((scalar (sql) (car (pg-result (pg-exec con sql) :tuple 0))))
     (pg-exec con "DROP SEQUENCE IF EXISTS foo_seq")
     (pg-exec con "CREATE SEQUENCE IF NOT EXISTS foo_seq INCREMENT 20 START 
WITH 400")
-    (should (equal 400 (scalar "SELECT nextval('foo_seq')")))
+    (should (eql 400 (scalar "SELECT nextval('foo_seq')")))
     (unless (member (pgcon-server-variant con) '(yugabyte))
-      (should (equal 400 (scalar "SELECT last_value FROM pg_sequences WHERE 
sequencename='foo_seq'"))))
-    (should (equal 420 (scalar "SELECT nextval('foo_seq')")))
-    (should (equal 440 (scalar "SELECT nextval('foo_seq')")))
-    (pg-exec con "DROP SEQUENCE foo_seq")))
+      (should (eql 400 (scalar "SELECT last_value FROM pg_sequences WHERE 
sequencename='foo_seq'"))))
+    (should (eql 420 (scalar "SELECT nextval('foo_seq')")))
+    (should (eql 440 (scalar "SELECT nextval('foo_seq')")))
+    (pg-exec con "DROP SEQUENCE foo_seq")
+    (pg-exec con "DROP SEQUENCE IF EXISTS test_seq_nocycle")
+    (pg-exec con "CREATE SEQUENCE test_seq_nocycle START 1 INCREMENT 1 
MAXVALUE 2 NO CYCLE")
+    (should (eql 1 (scalar "SELECT nextval('test_seq_nocycle')")))
+    (should (eql 2 (scalar "SELECT nextval('test_seq_nocycle')")))
+    (should (eql 'ok (condition-case nil
+                         (scalar "SELECT nextval('test_seq_nocycle')")
+                       (pg-sequence-limit-exceeded 'ok))))
+    (pg-exec con "DROP SEQUENCE test_seq_nocycle")
+    (pg-exec con "DROP SEQUENCE IF EXISTS test_seq_cycle")
+    (pg-exec con "CREATE SEQUENCE test_seq_cycle START 1 INCREMENT 1 MAXVALUE 
2 CYCLE")
+    (should (eql 1 (scalar "SELECT nextval('test_seq_cycle')")))
+    (should (eql 2 (scalar "SELECT nextval('test_seq_cycle')")))
+    ;; should cycle
+    (should (eql 1 (scalar "SELECT nextval('test_seq_cycle')")))
+    (pg-exec con "DROP SEQUENCE test_seq_cycle")
+    (pg-exec con "DROP SEQUENCE IF EXISTS test_seq_setval")
+    (pg-exec con "CREATE SEQUENCE test_seq_setval")
+    (should (eql 50 (scalar "SELECT setval('test_seq_setval', 50)")))
+    (should (eql 50 (scalar "SELECT currval('test_seq_setval')")))
+    (should (eql 51 (scalar "SELECT nextval('test_seq_setval')")))
+    (pg-exec con "DROP SEQUENCE test_seq_setval")))
 
 (defun pg-test-array (con)
-  (cl-flet ((scalar (sql) (car (pg-result (pg-exec con sql) :tuple 0)))
-            (approx= (x y) (< (/ (abs (- x y)) (max (abs x) (abs y))) 1e-5)))
+  (cl-flet ((scalar (sql) (car (pg-result (pg-exec con sql) :tuple 0))))
     (should (equal (vector 7 8) (scalar "SELECT ARRAY[7,8]")))
     (should (equal (vector 9 10 11) (scalar "SELECT '{9,10,11}'::int[]")))
     (should (equal (vector 1234) (scalar "SELECT ARRAY[1234::int2]")))
@@ -1293,10 +1427,10 @@ bar$$"))))
     (should (equal (vector) (scalar "SELECT '{}'::float8[]")))
     (let ((vec (scalar "SELECT ARRAY[3.14::float]")))
       (should (floatp (aref vec 0)))
-      (should (approx= 3.14 (aref vec 0))))
+      (should (pgtest-approx= 3.14 (aref vec 0))))
     (let ((vec (scalar "SELECT ARRAY[CAST(3.14 AS DOUBLE PRECISION)]")))
       (should (floatp (aref vec 0)))
-      (should (approx= 3.14 (aref vec 0))))
+      (should (pgtest-approx= 3.14 (aref vec 0))))
     (should (equal (vector 4 20) (scalar "SELECT ARRAY[4] || 20")))
     (should (eql 6 (scalar "SELECT array_length('{1,2,3,4,5,6}'::int4[], 1)")))
     (should (equal (vector 42) (scalar "SELECT array_agg(42)")))
@@ -1314,8 +1448,8 @@ bar$$"))))
 ;;       (should (equal (vector 1 2 3) (cl-first row))))
     (let ((vec (scalar "SELECT ARRAY[44.3, 8999.5]")))
       (should (equal 2 (length vec)))
-      (should (approx= 44.3 (aref vec 0)))
-      (should (approx= 8999.5 (aref vec 1))))))
+      (should (pgtest-approx= 44.3 (aref vec 0)))
+      (should (pgtest-approx= 8999.5 (aref vec 1))))))
 
 ;; TODO: we do not currently handle multidimension arrays correctly
 ;; (should (equal (vector (vector 4 5) (vector 6 7))
@@ -1367,7 +1501,7 @@ bar$$"))))
     (pg-exec con "DROP SCHEMA pgeltestschema")))
 
 (defun pg-test-metadata (con)
-  (unless (member (pgcon-server-variant con) '(xata))
+  (unless (member (pgcon-server-variant con) '(xata cedardb))
     (pg-exec con "SET work_mem TO '2MB'")
     (pg-exec con "EXPLAIN (COSTS OFF) SELECT 42")
     (pg-exec con "RESET work_mem"))
@@ -1540,8 +1674,7 @@ bar$$"))))
 
 ;; https://www.postgresql.org/docs/15/functions-json.html
 (defun pg-test-json (con)
-  (cl-flet ((scalar (sql) (car (pg-result (pg-exec con sql) :tuple 0)))
-            (approx= (x y) (< (/ (abs (- x y)) (max (abs x) (abs y))) 1e-5)))
+  (cl-flet ((scalar (sql) (car (pg-result (pg-exec con sql) :tuple 0))))
     (should (eql 42 (scalar "SELECT to_json(42)")))
     (should (eql -56 (scalar "SELECT CAST ('-56' as json)")))
     (should (eql nil (scalar "SELECT JSON(NULL)")))
@@ -1550,7 +1683,7 @@ bar$$"))))
       (should (eql 1 (gethash "a" dct))))
     (unless (member (pgcon-server-variant con) '(alloydb))
       (when (>= (pgcon-server-version-major con) 17)
-        (should (approx= 155.6 (scalar "SELECT json_scalar(155.6)")))
+        (should (pgtest-approx= 155.6 (scalar "SELECT json_scalar(155.6)")))
         (should (string= "155.6" (scalar "SELECT json_scalar('155.6')")))
         (should (string= "144" (scalar "SELECT json_serialize('144')")))))
     (let ((json (scalar "SELECT '[5,7]'::json")))
@@ -1558,11 +1691,11 @@ bar$$"))))
     (let ((json (scalar "SELECT '[5,7]'::jsonb")))
       (should (eql 5 (aref json 0))))
     (let ((json (scalar "SELECT '[66.7,-42.0,8]'::json")))
-      (should (approx= 66.7 (aref json 0)))
-      (should (approx= -42.0 (aref json 1))))
+      (should (pgtest-approx= 66.7 (aref json 0)))
+      (should (pgtest-approx= -42.0 (aref json 1))))
     (let ((json (scalar "SELECT '[66.7,-42.0,8]'::jsonb")))
-      (should (approx= 66.7 (aref json 0)))
-      (should (approx= -42.0 (aref json 1))))
+      (should (pgtest-approx= 66.7 (aref json 0)))
+      (should (pgtest-approx= -42.0 (aref json 1))))
     ;; JSON null in JSONB type is not the same as PostgreSQL NULL value!
     (should (eql nil (scalar "SELECT 'null'::jsonb is null")))
     (should (eql nil (scalar "SELECT '{\"name\": null}'::jsonb->'name' IS 
NULL")))
@@ -1709,8 +1842,10 @@ bar$$"))))
 
 ;; Testing support for the pgvector extension.
 (defun pg-test-vector (con)
-  (when (pg-vector-setup con)
-    (cl-flet ((scalar (sql) (car (pg-result (pg-exec con sql) :tuple 0))))
+  (cl-flet ((scalar (sql) (car (pg-result (pg-exec con sql) :tuple 0))))
+    (pg-vector-setup con)
+    (unless (zerop (scalar "SELECT COUNT(*) FROM pg_type WHERE 
typname='vector'"))
+      (message "Testing pgvector support")
       (let ((v (scalar "SELECT '[4,5,6]'::vector")))
         (should (eql 4 (aref v 0))))
       (let ((v (scalar "SELECT '[0.003,0.004,1.567,6.777]'::vector")))
@@ -1722,6 +1857,18 @@ bar$$"))))
         (should (eql 5 d)))
       (let ((d (scalar "SELECT cosine_distance('[1,2]'::vector, '[0,0]')")))
         (should (eql 0.0e+NaN d)))
+      (let ((d (scalar "SELECT '[1,2,3]'::vector <+> '[4,5,6]'::vector")))
+        (should (eql 9 d)))
+      (let ((d (scalar "SELECT l1_distance('[1,2,3]'::vector, 
'[4,5,6]'::vector)")))
+        (should (eql 9 d)))
+      (let ((d (scalar "SELECT '[1,1,1,1]'::vector <=> '[2,2,2,2]'::vector")))
+        (should (eql 0 d)))
+      (let ((d (scalar "SELECT cosine_distance('[1,1,1,1]'::vector, 
'[2,2,2,2]'::vector)")))
+        (should (eql 0 d)))
+      (let ((d (scalar "SELECT '[1,2,3]'::vector <#> '[4,5,6]'::vector")))
+        (should (eql -32 d)))
+      (let ((d (scalar "SELECT inner_product('[1,2,3]'::vector, 
'[4,5,6]'::vector)")))
+        (should (eql 32 d)))
       (when (pg-function-p con "gen_random_uuid")
         (pg-exec con "DROP TABLE IF EXISTS items")
         (let ((sql (pgtest-massage con "CREATE TABLE items (
@@ -1820,8 +1967,7 @@ bar$$"))))
 
 (defun pg-test-geometric (con)
   (cl-labels ((row (query args) (pg-result (pg-exec-prepared con query args) 
:tuple 0))
-              (scalar (query args) (car (row query args)))
-              (approx= (x y) (< (/ (abs (- x y)) (max (abs x) (abs y))) 1e-5)))
+              (scalar (query args) (car (row query args))))
     (pg-geometry-setup con)
     (let* ((raw "(45.6,67) ")
            (p1 (pg--point-parser "(45.6,67) " nil)))
@@ -1833,7 +1979,7 @@ bar$$"))))
       (should (floatp (cdr p3)))
       (should (<= -10 (cdr p3) -9)))
     (let ((p4 (pg--point-parser "(33e4, -3.1456e4)" nil)))
-      (should (approx= 33e4 (car p4)))
+      (should (pgtest-approx= 33e4 (car p4)))
       (should (<= -35000 (cdr p4) 31000)))
     (let ((p5 (pg--point-parser "(a,b)" nil)))
       (should (eql nil p5)))
@@ -1842,7 +1988,7 @@ bar$$"))))
       (should (eql 3 (car p7)))
       (should (eql 4 (cdr p7))))
     (let ((p8 (pg--point-parser "(12.1,4e-4) " nil)))
-      (should (approx= 4 (* 1e4 (cdr p8)))))
+      (should (pgtest-approx= 4 (* 1e4 (cdr p8)))))
     (let ((p9 (pg--point-parser " (55,7866677)" nil)))
       (should (eql 55 (car p9))))
     (let ((p10 (pg--point-parser "(22.6,6) " nil)))
@@ -1850,7 +1996,7 @@ bar$$"))))
     (let ((point (scalar "SELECT '(82,91.0)'::point" nil)))
       (should (consp point))
       (should (eql 82 (car point)))
-      (should (approx= 91.0 (cdr point))))
+      (should (pgtest-approx= 91.0 (cdr point))))
     (when (pg-function-p con "gen_random_uuid")
       (pg-exec con "DROP TABLE IF EXISTS with_point")
       (pg-exec con (pgtest-massage con "CREATE TABLE with_point(
@@ -1905,7 +2051,7 @@ bar$$"))))
       (pg-exec con "DROP TABLE with_line")
       (let ((lseg (pg--lseg-parser " [(4,5), (6.7, 4e1)]" nil)))
         (should (eql 4 (car (aref lseg 0))))
-        (should (approx= 4e1 (cdr (aref lseg 1)))))
+        (should (pgtest-approx= 4e1 (cdr (aref lseg 1)))))
       (pg-exec con "DROP TABLE IF EXISTS with_lseg")
       (pg-exec con (pgtest-massage con "CREATE TABLE with_lseg(
              id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
@@ -1916,7 +2062,7 @@ bar$$"))))
              (ls (cl-first (pg-result res :tuple 0))))
         (should (eql 2 (car (aref ls 0))))
         (should (eql 3 (cdr (aref ls 0))))
-        (should (approx= 55.5 (car (aref ls 1)))))
+        (should (pgtest-approx= 55.5 (car (aref ls 1)))))
       (pg-exec con "DROP TABLE with_lseg")
       (let ((box (pg--box-parser "(4,5), (-66,-77e0) " nil)))
         (should (eql 4 (car (aref box 0))))
@@ -1930,9 +2076,9 @@ bar$$"))))
              (res (pg-exec-prepared con "SELECT $1" `((,bx . "box"))))
              (bx (cl-first (pg-result res :tuple 0))))
         ;; the box corners are output in the order upper-right, lower-left
-        (should (approx= 55.6 (car (aref bx 0))))
+        (should (pgtest-approx= 55.6 (car (aref bx 0))))
         (should (eql 3 (cdr (aref bx 0))))
-        (should (approx= -23.2 (cdr (aref bx 1)))))
+        (should (pgtest-approx= -23.2 (cdr (aref bx 1)))))
       (pg-exec con "DROP TABLE with_box")
       (let* ((path (pg--path-parser "[(4,5),(6,7), (55e1,66.1),(0,0) ]" nil))
              (points (pg-geometry-path-points path)))
@@ -1973,14 +2119,13 @@ bar$$"))))
              (points (pg-geometry-polygon-points pg)))
         (should (eql 4 (length points)))
         (should (eql 3 (cdr (cl-first points))))
-        (should (approx= 7.77 (cdr (car (last points))))))
+        (should (pgtest-approx= 7.77 (cdr (car (last points))))))
       (pg-exec con "DROP TABLE with_polygon"))))
 
 ;; PostGIS parsing tests. These tests require the geosop commandline utility 
to be installed.
 (defun pg-test-gis (con)
   (cl-labels ((row (query) (pg-result (pg-exec con query) :tuple 0))
-              (scalar (query) (car (row query)))
-              (approx= (x y) (< (/ (abs (- x y)) (max (abs x) (abs y))) 1e-5)))
+              (scalar (query) (car (row query))))
     (when (pg-setup-postgis con)
       (message "Testing PostGIS support...")
       (let* ((res (pg-exec con "SELECT 'SRID=4326;POINT(0 0)'::geometry"))
@@ -2143,8 +2288,9 @@ bar$$"))))
     (should (string= "UPDATE 1" (pg-result r5 :status)))
     (should (string= "foob" (car (pg-result r6 :tuple 0))))
     (message "status of DROP is %s" (pg-result r7 :status))
+    ;; With PostgreSQL the status is "DROP TABLE"; with CedarDB it is "DROP".
     (unless (member (pgcon-server-variant con) '(cratedb))
-      (should (string= "DROP TABLE" (pg-result r7 :status))))
+      (should (string-prefix-p "DROP" (pg-result r7 :status))))
     (should (eql (length (pg-result r8 :tuples)) 10))
     (message "=============================================="))
    (let ((res (pg-exec con "SELECT 1 UNION SELECT 2")))
@@ -2172,7 +2318,7 @@ bar$$"))))
      (let ((res (pg-exec con "EXPLAIN ANALYZE SELECT 42")))
        ;; CrateDB returns "EXPLAIN 1". The output from EXPLAIN ANALYZE is 
returned as a hash table.
        (unless (member (pgcon-server-variant con) '(cratedb))
-         (should (string= "EXPLAIN" (pg-result res :status)))
+         (should (string-prefix-p "EXPLAIN" (pg-result res :status)))
          (should (cl-every (lambda (r) (stringp (car r))) (pg-result res 
:tuples))))))
    ;; check query with empty column list
    (unless (member (pgcon-server-variant con) '(cratedb))
@@ -2214,10 +2360,10 @@ bar$$"))))
     (pg-exec con "CREATE INDEX idx_foobles ON foobles(a)")
     (pg-exec con "INSERT INTO foobles VALUES (42, 'foo')")
     (pg-exec con "INSERT INTO foobles VALUES (66, 'bizzle')")
-    (unless (member (pgcon-server-variant con) '(risingwave materialize))
+    (unless (member (pgcon-server-variant con) '(risingwave materialize 
cedardb))
       (pg-exec con "REINDEX INDEX idx_foobles"))
     (when (and (> (pgcon-server-version-major con) 11)
-               (not (member (pgcon-server-variant con) '(risingwave greenplum 
orioledb))))
+               (not (member (pgcon-server-variant con) '(risingwave greenplum 
orioledb cedardb))))
       (pg-exec con "REINDEX TABLE CONCURRENTLY foobles"))
     (pg-exec con "DROP TABLE foobles"))
   (let* ((r (pg-exec con "SHOW ALL"))
@@ -2549,6 +2695,14 @@ bar$$"))))
     (should (eql 'ok (condition-case nil
                          (pg-exec con "PREPARE pgeltestq1(text, int, float, 
boolean, smallint) AS SELECT 42")
                        (pg-duplicate-prepared-statement 'ok))))
+    (should (eql 'ok (condition-case nil
+                         (progn
+                           (pg-exec con "CREATE TABLE test_duplicate(a 
INTEGER)")
+                           (pg-exec con "CREATE TABLE test_duplicate(a 
INTEGER)"))
+                       (pg-duplicate-table 'ok))))
+    (should (eql 'ok (condition-case nil
+                         (pg-exec con "CREATE TABLE duplicate_column(a 
INTEGER, a INTEGER)")
+                       (pg-duplicate-column 'ok))))
     (should (eql 'ok (condition-case nil
                          (progn
                            (pg-exec con "PREPARE pgeltestq2(text, int, float, 
boolean, smallint) AS SELECT 42")
@@ -2748,10 +2902,11 @@ bar$$"))))
   (with-pg-transaction con
     (let* ((oid (pg-lo-create con "rw"))
            (fd (pg-lo-open con oid "rw")))
-      (let* ((sql "SELECT oid FROM pg_catalog.pg_largeobject_metadata WHERE 
oid=$1")
-             (res (pg-exec-prepared con sql `((,oid . "int4"))))
-             (rows (pg-result res :tuples)))
-        (should (eql 1 (length rows))))
+      (unless (member (pgcon-server-variant con) '(yugabyte))
+        (let* ((sql "SELECT oid FROM pg_catalog.pg_largeobject_metadata WHERE 
oid=$1")
+               (res (pg-exec-prepared con sql `((,oid . "int4"))))
+               (rows (pg-result res :tuples)))
+          (should (eql 1 (length rows)))))
       (pg-lo-write con fd "Hi there mate")
       (pg-lo-lseek con fd 3 pg-SEEK_SET)
       (should (eql 3 (pg-lo-tell con fd)))
@@ -2813,12 +2968,13 @@ bar$$"))))
 (defun pg-test-lo-import (con)
   (message "Testing lo-import and friends")
   (with-pg-transaction con
-    (let* ((oid (pg-lo-import con "/etc/group"))
-           (sql "SELECT oid FROM pg_catalog.pg_largeobject_metadata WHERE 
oid=$1")
-           (res (pg-exec-prepared con sql `((,oid . "int4"))))
-           (rows (pg-result res :tuples)))
-      (should (eql 1 (length rows)))
-      (should (eql oid (caar rows)))
+    (let ((oid (pg-lo-import con "/etc/group")))
+      (unless (member (pgcon-server-variant con) '(yugabyte))
+        (let* ((sql "SELECT oid FROM pg_catalog.pg_largeobject_metadata WHERE 
oid=$1")
+               (res (pg-exec-prepared con sql `((,oid . "int4"))))
+               (rows (pg-result res :tuples)))
+          (should (eql 1 (length rows)))
+          (should (eql oid (caar rows)))))
       (pg-lo-export con oid "/tmp/group")
       (cond ((zerop (call-process "diff" nil nil nil "/tmp/group" 
"/etc/group"))
              (message "lo-import test succeeded")


Reply via email to