Merge branch 'master' of http://git.kpe.io/clsql
authorRuss Tyndall <russ@acceleration.net>
Sun, 17 Jan 2016 17:36:00 +0000 (12:36 -0500)
committerRuss Tyndall <russ@acceleration.net>
Sun, 17 Jan 2016 17:36:00 +0000 (12:36 -0500)
16 files changed:
ChangeLog
LATEST-TEST-RESULTS
clsql-postgresql-socket3.asd
clsql.asd
db-postgresql-socket3/sql.lisp
sql/conditions.lisp
sql/db-interface.lisp
sql/decimals.lisp [new file with mode: 0644]
sql/expressions.lisp
sql/oodml.lisp
sql/operations.lisp
sql/package.lisp
sql/utils.lisp
tests/test-init.lisp
tests/test-internal.lisp
tests/test-oodml.lisp

index 86362c5e6fd31d0439861650abf7d424664634fd..6cedb6c3742b5a88fcac5beed04e3f00f65eda3b 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,12 +1,37 @@
+2015-10-09 Russ Tyndall <russ@acceleration.net> 
+        * add decimals.lisp file https://github.com/tlikonen/cl-decimals
+       * use this for safe parsing of numeric / decimal / rational types
+       * Added because newer postgres print money types as currency strings
+       
 2015-08-12 Kevin Rosenberg <kevin@rosenberg.net>
        * Version 6.6.3 release
        * db-oracle/oracle-sql.lisp: Patch for PostgreSQL socket interface
        for unicode characters. Thanks to Jason Melbye.
+       
+2015-06-02 Daniel Kochmański <dkochmanski@turtle-solutions.eu>
+       * clsql.asd, sql/package.lisp: Add ECL compatibility fixes
+       * sql/db-interface.lisp: Fix declaration typo
+
+2015-04-06 Russ Tyndall <russ@acceleration.net>
+       * sql/operations, sql/expressions: add postgresql E-string
+       operator / expression.  Needed for correct regex handling
+       EG: [E "some string"]=> E'some string'
 
 2015-03-30 Kevin Rosenberg <kevin@rosenberg.net>
        * Version 6.6.2 release
        * db-oracle/oracle-sql.lisp: Remove extra hyphen, thanks to
        Thomas Vossen
+       
+2015-03-24 Russ Tyndall <russ@acceleration.net>
+       * sql/oodml.lisp: fixed call-next-method in the base of
+       read-sql-value and replaced with a continuable
+       sql-value-conversion-error
+       * default read-sql-value for list
+       * tests for sql-value-conversion-errors and list
+
+2015-03-18 Russ Tyndall <russ@acceleration.net>
+       * {uffi,db-mysql}/Makefile: remove -pie build hardening for
+       which caused load issues for Linux Mint
 
 2015-03-18 Kevin Rosenberg <kevin@rosenberg.net>
        * Version 6.6.1 release
        * Version 6.6.0 release
        * {uffi,db-mysql}/Makefile: Add build hardening for Debian
 
-2014-02-24 Russ Tyndall <russ@acceleration.net>
+2015-02-24 Russ Tyndall <russ@acceleration.net>
        * mysql-sql.lisp
        an error in type declarations generating a compilation warning
        was being treated as an error in recent SBCLs, fixed the type
        warning by correcting the type (still a ton of compliation
        notes)
 
-2014-02-23 Russ Tyndall <russ@acceleration.net>
+2015-02-23 Russ Tyndall <russ@acceleration.net>
        * sql/metaclasses.lisp
        made reinitialize-instance return the instance passed to it as
        SBCL now expected (mentioned on the SBCL-devel mailing list by
index 91103fb51177d24570dd1822c910cfced4137882..72725ce9d9e32d178e8a651e7fa6b7330babf137 100644 (file)
@@ -1,4 +1,4 @@
-Note from Russ Tyndall <russ@acceleration.net> 2013-01-30 :
+Note from Russ Tyndall <russ@acceleration.net> 2015-03-23 :
 
 This is the current results of running the test suite against all the database
 backends I have accessible, on SBCL / UBUNTU64bit.  It would be great to
@@ -7,20 +7,24 @@ tests so that all pass.  In the interim, I would like know that I am not
 increasing the number of failing tests
 
 :mysql
-1 out of 301 total tests failed: :FDDL/CACHE-TABLE-QUERIES/1.
+No tests failed.
+18 of 310 Tests skipped
 
 :odbc MSSQL2000/5
-1 out of 298 total tests failed: :FDDL/CACHE-TABLE-QUERIES/1.
+No tests failed.
+22 of 306 Tests skipped:
 
 :odbc postgres
-2 out of 311 total tests failed: :FDML/SELECT/36, :FDDL/CACHE-TABLE-QUERIES/1.
+*couldnt get them to run - foreign lib problems*
 
 :postgres-socket :postgres-socket-3
-5 out of 300 total tests failed: :TIME/PG/OODML/USEC, :TIME/PG/OODML/NO-USEC, 
-   :TIME/PG/FDML/USEC, :FDML/SELECT/36, :FDDL/CACHE-TABLE-QUERIES/1.
+4 out of 308 total tests failed: :TIME/PG/OODML/USEC, :TIME/PG/OODML/NO-USEC, 
+   :TIME/PG/FDML/USEC, :FDML/SELECT/36.
+20 of 308 Tests skipped:
 
 :sqlite3
-1 out of 300 total tests failed: :FDDL/INDEX/3.
+1 out of 308 total tests failed: :FDDL/INDEX/3.
+20 of 308 Tests skipped:
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
index 4f4bd2544a0c604fd506432dfd5fdd1d3bffa787..cd6a6d84685913dd9142f60773af7d410fc098e0 100644 (file)
   :description "Common Lisp SQL PostgreSQL Socket Driver"
   :long-description "cl-sql-postgresql-socket package provides a database driver to the PostgreSQL database via a socket interface."
 
-  :depends-on (clsql md5 :cl-postgres #+sbcl sb-bsd-sockets)
+  :depends-on (clsql
+               md5
+               :cl-postgres
+               (:feature sbcl sb-bsd-sockets))
   :components
   ((:module :db-postgresql-socket3
            :serial T
            :components ((:file "package")
-                        (:file "api")
-                        (:file "sql")))))
+                     (:file "api")
+                     (:file "sql")))))
index 5d9adf219577dd1fb0a6f2c36c64eacab78a0c5d..4f009505eed9ef3e3603214f2133f2ea5af69340 100644 (file)
--- a/clsql.asd
+++ b/clsql.asd
@@ -54,6 +54,7 @@ oriented interface."
                         (:file "base-classes" :depends-on ("package"))
                          (:file "conditions" :depends-on ("base-classes"))
                          (:file "db-interface" :depends-on ("conditions"))
+                         (:file "decimals" :depends-on ("package" "db-interface"))
                         (:file "utils" :depends-on ("package" "db-interface"))
                         (:file "time" :depends-on ("package" "conditions" "utils"))
                          (:file "generics" :depends-on ("package"))))
@@ -101,7 +102,7 @@ oriented interface."
   (operate 'test-op 'clsql-tests :force t))
 
 (defmethod perform :after ((o load-op) (c (eql (find-system 'clsql))))
-  (let* ((init-var (uffi:getenv "CLSQLINIT"))
+  (let* ((init-var (uffi::getenv "CLSQLINIT"))
          (init-file (or (when init-var (probe-file init-var))
                         (probe-file
                          (concatenate 'string
index 01816379e71a68da3f7d82c0714bad974b756a19..3172e6dbd7b2881aaebc1f9c2008fe1aa6aa1eeb 100644 (file)
       (etypecase host
         (null
          "localhost")
+        (keyword "unix")
         (pathname (namestring host))
         (string host))
       (when port
index 1969b962ae28728e565ea2d6a11fd03f04c49acc..e19805248bd5e6f9601a98db29a3e76157741238 100644 (file)
@@ -151,8 +151,9 @@ connection is no longer usable."))
 
 (defun error-converting-value (val type &optional (database *default-database*))
   (restart-case 
-      (error 'sql-value-conversion-error
-             :expected-type type :value val :database database)
+      (error (make-condition
+              'sql-value-conversion-error
+              :expected-type type :value val :database database))
     (continue ()
       :report "Continue using the unconverted value"
       (values val t))
index 5cdb719f3ea00cdc4915701fff2abec177aaff49..3454a84ee8d992372e07ba3abf7a7c3d07258a1c 100644 (file)
@@ -380,8 +380,8 @@ of TYPE_NAME (keyword) PRECISION SCALE NULLABLE.")
 
 (defgeneric db-type-has-auto-increment? (db-type)
   (:method (db-type)
-    (declare (ignore db-type)
-            nil))
+    (declare (ignore db-type))
+            nil)
   (:documentation "NIL [default] if database-type supports auto-incrementing columns."))
 
 ;;; Large objects support (Marc Battyani)
diff --git a/sql/decimals.lisp b/sql/decimals.lisp
new file mode 100644 (file)
index 0000000..b8df6fc
--- /dev/null
@@ -0,0 +1,419 @@
+;;; DECIMALS
+;;
+;; A decimal number parser and formatting package for Common Lisp.
+;;
+;; Author: Teemu Likonen <tlikonen@iki.fi>
+;;
+;; License: Public domain
+;;
+;; This program is distributed in the hope that it will be useful, but
+;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+(defpackage #:decimals
+  (:use #:cl)
+  (:export #:round-half-away-from-zero
+           #:format-decimal-number
+           #:parse-decimal-number
+           #:decimal-parse-error
+           #:define-decimal-formatter))
+
+(in-package #:decimals)
+
+
+(defun round-half-away-from-zero (number &optional (divisor 1))
+
+  "Divide _number_ by _divisor_ and round the result to the nearest integer.
+If the result is half-way between two integers round away from zero. Two
+values are returned: quotient and remainder.
+
+This is similar to `cl:round` function except that `cl:round` rounds to
+an even integer when number is exactly between two integers. Examples:
+
+    (round-half-away-from-zero 3/2) => 2, -1/2
+    (round 3/2)                     => 2, -1/2
+
+    (round-half-away-from-zero 5/2) => 3, -1/2
+    (round 5/2)                     => 2, 1/2"
+
+  (if (zerop number)
+      (values 0 0)
+      (let ((quotient (if (plusp number)
+                          (floor (+ (/ number divisor) 1/2))
+                          (ceiling (- (/ number divisor) 1/2)))))
+        (values quotient (- number (* quotient divisor))))))
+
+
+(defun divide-into-groups (string &key (separator #\Space) (from-end nil)
+                           (group-digits 3))
+
+  (assert (and (integerp group-digits)
+               (plusp group-digits))
+          (group-digits)
+          "The GROUP-DIGITS argument must be a positive integer")
+
+  (setf separator (princ-to-string separator))
+
+  (if (zerop (length separator))
+      string
+      (flet ((make-groups (string separator)
+               (loop :with length := (length string)
+                     :with result := (make-array length :element-type 'character
+                                                 :fill-pointer 0 :adjustable t)
+                     :for c :across string
+                     :for i :upfrom 1
+                     :do (vector-push-extend c result)
+                     :if (and (zerop (rem i group-digits))
+                              (< i length))
+                     :do (loop :for c :across separator
+                               :do (vector-push-extend c result))
+                     :finally (return result))))
+
+        (if from-end
+            (nreverse (make-groups (reverse string) (reverse separator)))
+            (make-groups string separator)))))
+
+
+(defun decimal-round-split (number &key
+                            (round-magnitude 0)
+                            (rounder #'round-half-away-from-zero)
+                            (positive-sign #\+)
+                            (negative-sign #\-)
+                            (zero-sign nil))
+
+  (assert (integerp round-magnitude) (round-magnitude)
+          "ROUND-MAGNITUDE argument must be an integer.")
+
+  (when (floatp number)
+    (setf number (rational number)))
+
+  (let ((divisor (expt 10 round-magnitude)))
+    (setf number (* divisor (funcall rounder number divisor))))
+
+  (let ((sign (cond ((plusp number) (or positive-sign ""))
+                    ((minusp number) (or negative-sign ""))
+                    (t (or zero-sign "")))))
+
+    (multiple-value-bind (integer fractional)
+        (truncate (abs number))
+      (let ((fractional-string
+             (with-output-to-string (out)
+               (loop :with next := fractional
+                     :with remainder
+                     :repeat (abs round-magnitude)
+                     :until (zerop next)
+                     :do
+                     (setf (values next remainder) (truncate (* next 10)))
+                     (princ next out)
+                     (setf next remainder)))))
+        (list (princ-to-string sign)
+              (princ-to-string integer)
+              fractional-string)))))
+
+
+(defun string-align (string width &key (side :left) (char #\Space))
+  (if (>= (length string) width)
+      string
+      (let ((result (make-string width :initial-element char)))
+        (ecase side
+          (:left (replace result string))
+          (:right (replace result string
+                           :start1 (- width (length string))))))))
+
+
+(defun format-decimal-number (number &key
+                              (round-magnitude 0)
+                              (rounder #'round-half-away-from-zero)
+                              (decimal-separator #\.)
+                              (integer-group-separator nil)
+                              (integer-group-digits 3)
+                              (integer-minimum-width 0)
+                              (integer-pad-char #\Space)
+                              (fractional-group-separator nil)
+                              (fractional-group-digits 3)
+                              (fractional-minimum-width 0)
+                              (fractional-pad-char #\Space)
+                              (show-trailing-zeros nil)
+                              (positive-sign nil)
+                              (negative-sign #\-)
+                              (zero-sign nil))
+
+  "Apply specified decimal number formatting rules to _number_ and
+return a formatted string.
+
+The second return value is (almost) the same formatted string divided
+into four strings. It's a list of four strings: sign, integer part,
+decimal separator and fractional part. Formatting arguments
+_integer-minimum-width_ and _fractional-minimum-width_ do not apply to
+the second return value. Everything else does.
+
+_Number_ must be of type `real`. This function uses `rational` types
+internally. If the given _number_ is a `float` it is first turned into
+`rational` by calling `cl:rational`.
+
+Formatting rules are specified with keyword arguments, as described
+below. The default value is in parentheses.
+
+  * `round-magnitude (0)`
+
+    This is the order of magnitude used for rounding. The value must be
+    an integer and it is interpreted as a power of 10.
+
+  * `show-trailing-zeros (nil)`
+
+    If the value is non-nil print all trailing zeros in fractional part.
+    Examples:
+
+        (format-decimal-number 1/5 :round-magnitude -3
+                               :show-trailing-zeros nil)
+        => \"0.2\"
+
+        (format-decimal-number 1/5 :round-magnitude -3
+                               :show-trailing-zeros t)
+        => \"0.200\"
+
+  * `rounder (#'round-half-away-from-zero)`
+
+    The value must be a function (or a symbol naming a function). It is
+    used to round the number to the specified round magnitude. The
+    function must work like `cl:truncate`, `cl:floor`, `cl:ceiling` and
+    `cl:round`, that is, take two arguments, a number and a divisor, and
+    return the quotient as the first value.
+
+    This package introduces another rounding function,
+    `round-half-away-from-zero`, which is used by default. See its
+    documentation for more information.
+
+  * `decimal-separator (#\\.)`
+
+    If the value is non-nil the `princ` output of the value will be
+    added between integer and fractional parts. Probably the most useful
+    types are `character` and `string`.
+
+  * `integer-group-separator    (nil)`
+  * `fractional-group-separator (nil)`
+
+    If the value is non-nil the digits in integer or fractional parts
+    are put in groups. The `princ` output of the value will be added
+    between digit groups.
+
+  * `integer-group-digits    (3)`
+  * `fractional-group-digits (3)`
+
+    The value is a positive integer defining the number of digits in
+    groups.
+
+  * `integer-minimum-width    (0)`
+  * `fractional-minimum-width (0)`
+
+    Format integer or fractional part using minimum of this amount of
+    characters, possibly using some padding characters (see below).
+    _positive-sign_, _negative-sign_ or _zero-sign_ (see below) is
+    included when calculating the width of the integer part. Similarly
+    _decimal-separator_ is included when calculating the width of the
+    fractional part.
+
+  * `integer-pad-char    (#\\Space)`
+  * `fractional-pad-char (#\\Space)`
+
+    The value is the padding character which is used to fill
+    _integer-minimum-width_ or _fractional-minimum-width_.
+
+  * `positive-sign (nil)`
+  * `negative-sign (#\\-)`
+  * `zero-sign     (nil)`
+
+    If values are non-nil these are used as the leading sign for
+    positive, negative and zero numbers. The `princ` output of the value
+    is used."
+
+  (destructuring-bind (sign integer fractional)
+      (decimal-round-split number
+                           :round-magnitude round-magnitude
+                           :rounder rounder
+                           :positive-sign positive-sign
+                           :negative-sign negative-sign
+                           :zero-sign zero-sign)
+
+    (setf decimal-separator (if decimal-separator
+                                (princ-to-string decimal-separator)
+                                "")
+          integer (divide-into-groups
+                   integer
+                   :separator (or integer-group-separator "")
+                   :group-digits integer-group-digits
+                   :from-end t)
+          fractional (divide-into-groups
+                      (if (and show-trailing-zeros
+                               (plusp (- (- (length fractional))
+                                         round-magnitude)))
+                          (replace (make-string (abs round-magnitude)
+                                                :initial-element #\0)
+                                   fractional)
+                          fractional)
+                      :separator (or fractional-group-separator "")
+                      :group-digits fractional-group-digits
+                      :from-end nil))
+
+    (values
+     (concatenate
+      'string
+      (string-align (concatenate 'string sign integer)
+                    integer-minimum-width
+                    :side :right :char integer-pad-char)
+      (string-align (if (plusp (length fractional))
+                        (concatenate 'string decimal-separator fractional)
+                        "")
+                    fractional-minimum-width
+                    :side :left :char fractional-pad-char))
+     (list sign integer decimal-separator fractional))))
+
+
+(defmacro define-decimal-formatter (name &body keyword-arguments)
+
+  "Define a decimal number formatter function to use with the `~/`
+directive of `cl:format`. The valid format is this:
+
+    (define-decimal-formatter name
+      (:keyword form)
+      ...)
+
+_Name_ is the symbol that names the function. _Keyword_ must be a valid
+keyword argument for the `format-decimal-number` function (see its
+documentation for more information). _Form_ is evaluated and the value
+is used with the _keyword_ argument. Macro's side effect is that global
+function _name_ is defined. It can be used with the `~/` directive of
+`cl:format` function.
+
+Examples:
+
+    (define-decimal-formatter my-formatter
+      (:round-magnitude -6)
+      (:decimal-separator \",\")
+      (:integer-group-separator \" \")
+      (:integer-minimum-width 4)
+      (:fractional-group-separator \" \")
+      (:fractional-minimum-width 10)
+      (:show-trailing-zeros t))
+    => MY-FORMATTER
+
+    (format nil \"~/my-formatter/\" 10/6)
+    => \"   1,666 667  \"
+
+    (format nil \"~/my-formatter/\" 100/8)
+    => \"  12,500 000  \"
+
+The `~/` directive function call can optionally take up to three
+arguments to override the defaults:
+
+    ~round-magnitude,integer-minimum-width,fractional-minimum-width/FUNCTION/
+
+For example:
+
+    (format nil \"~-2,3,4/my-formatter/\" 10/6)
+    => \"  1,67 \""
+
+  (let ((key-arg (gensym)))
+    `(let ((,key-arg (list ,@(loop :for (keyword value) :in keyword-arguments
+                                   :do (assert (keywordp keyword) (keyword)
+                                               "Keyword required.")
+                                   :collect keyword :collect value))))
+
+       (defun ,name (stream number &optional colon-p at-sign-p
+                     round-magnitude integer-minimum-width
+                     fractional-minimum-width)
+         (declare (ignore colon-p at-sign-p))
+
+         (let ((args (copy-list ,key-arg)))
+           (when round-magnitude
+             (setf (getf args :round-magnitude)
+                   round-magnitude))
+           (when integer-minimum-width
+             (setf (getf args :integer-minimum-width)
+                   integer-minimum-width))
+           (when fractional-minimum-width
+             (setf (getf args :fractional-minimum-width)
+                   fractional-minimum-width))
+           (princ (apply #'format-decimal-number number args) stream))))))
+
+
+(defun number-string-to-integer (string)
+  (handler-case (parse-integer string)
+    (parse-error () nil)))
+
+
+(defun number-string-to-fractional (string)
+  (when (every #'digit-char-p string)
+    (setf string (string-right-trim "0" string))
+    (handler-case (/ (parse-integer string)
+                     (expt 10 (length string)))
+      (parse-error () nil))))
+
+
+(define-condition decimal-parse-error (parse-error)
+  nil
+  (:report "Not a valid decimal number string.")
+  (:documentation
+   "Function `parse-decimal-number` signals this condition when it
+couldn't parse a decimal number from string."))
+
+
+(defun parse-decimal-number (string &key
+                             (decimal-separator #\.)
+                             (positive-sign #\+)
+                             (negative-sign #\-)
+                             (start 0) (end nil))
+
+  "Examine _string_ (or its substring from _start_ to _end_) for a
+decimal number. Assume that the decimal number is exact and return it as
+a rational number.
+
+Rules for parsing: First all leading and trailing `#\\Space` characters
+are stripped. The resulting string may start with a _positive-sign_ or a
+_negative-sign_ character. The latter causes this function to assume a
+negative number. The following characters in the string must include one
+or more digit characters and it may include one _decimal-separator_
+character which separates integer and fractional parts. All other
+characters are illegal. If these rules are not met a
+`decimal-parse-error` condition is signaled.
+
+Examples:
+
+    (parse-decimal-number \"0.2\")  => 1/5
+    (parse-decimal-number \".2\")   => 1/5
+    (parse-decimal-number \"+3.\")  => 3
+    (parse-decimal-number \" -7 \") => -7
+
+    (parse-decimal-number \"−12,345\"
+                          :decimal-separator #\\,
+                          :negative-sign #\\−)
+    => -2469/200"
+
+  (setf string (string-trim " " (subseq string start end)))
+  (if (not (plusp (length string)))
+      (error 'decimal-parse-error)
+      (let ((sign 1))
+        (cond ((char= (aref string 0) negative-sign)
+               (setf sign -1
+                     string (subseq string 1)))
+              ((char= (aref string 0) positive-sign)
+               (setf string (subseq string 1))))
+
+        (if (and (every (lambda (item)
+                          (or (digit-char-p item)
+                              (char= item decimal-separator)))
+                        string)
+                 (some #'digit-char-p string)
+                 (<= 0 (count decimal-separator string) 1))
+
+            (let ((pos (position decimal-separator string)))
+              (* sign
+                 (+ (or (number-string-to-integer (subseq string 0 pos))
+                        0)
+                    (if pos
+                        (or (number-string-to-fractional
+                             (subseq string (1+ pos)))
+                            0)
+                        0))))
+
+            (error 'decimal-parse-error)))))
index 4c57bc3e86f71ae6f97a174587bb93b546a034fb..4f0baf1230b0ab32353dc69026645306adae11e0 100644 (file)
           (remove-duplicates tabs :test #'database-identifier-equal))
         nil)))
 
-
-
 (defmethod output-sql ((expr sql-value-exp) database)
   (with-slots (modifier components)
     expr
@@ -1234,3 +1232,16 @@ uninclusive, and the args from that keyword to the end."
     returns nil if there are no children"
   (clsql-ors clauses))
 
+
+(defclass sql-escape-string-exp (%sql-expression)
+  ((string
+    :initarg :string
+    :initform nil))
+  (:documentation
+   "An escaped string string expression (postgresql E'stuff') ."))
+
+(defmethod output-sql ((exp sql-escape-string-exp) database)
+  (with-slots (string) exp
+    (when string
+      (write-char #\E *sql-stream*)
+      (output-sql string database))))
index 169fa89d70d6f0fe17e834749e9a27f337cfe2f8..072bc4a633838be2806c4505fc7da47400da6b34 100644 (file)
   (declare (ignore db-type))
   (cond
     ;; null value or type
-    ((or (equalp "nil" val) (eql 'null val)) nil) 
-    
+    ((or (null val)
+         (equalp "nil" val)
+         (eql 'null val)
+         (eql 'null type))
+     nil)
+
     ;; no specified type or already the right type
     ((or (null type)
          (ignore-errors (typep val type)))
      val)
 
     ;; actually convert
-    (t 
+    (t
      (let ((res (handler-bind
                     ;; all errors should be converted to sql-value-conversion-error
                     ((error (lambda (c)
-                              (when *debugger-hook*
-                                (invoke-debugger c))
                               (unless (typep c 'sql-value-conversion-error)
+                                ;; this was blowing up the tests till I
+                                ;; unbound *debugger-hook* not sure the answer,
+                                ;; as this is also imensely useful in actually
+                                ;; finding bugs below this point
+                                (when *debugger-hook* (invoke-debugger c))
                                 (error-converting-value val type database)))))
                   (call-next-method))))
        ;; if we didnt get the right type after converting, we should probably
        ;; error right away
-       (maybe-error-converting-value
-        res val type database)))))
+       (maybe-error-converting-value res val type database)))))
 
 (defmethod read-sql-value (val type database db-type)
+  "read a sql value, from :around read-eval is disabled read numbers in base 10"
   ;; errors, nulls and preconverted types are already handled in around
   (typecase type
     (symbol
                              (double-float 'double-float))))
                      (read-from-string val)))
            ;; maybe wrong type of float
-           (float val)) 
+           (float val))
          (if (eql type 'double-float) 1.0d0 1.0s0)))
-       (number (read-from-string val))
+       (number (read-decimal-value val))
        ((boolean generalized-boolean)
         (if (member val '(nil t))
             val
               (number (not (zerop val))))))
        ((wall-time duration) (parse-timestring val))
        (date (parse-datestring val))
-       (t (call-next-method))))
+       (list (read-from-string val))
+       (t (error-converting-value val type database))))
     (t (typecase val
          (string (read-from-string val))
          (t (error-converting-value val type database))))))
index 37d751e0f90abc09189de627df483e1aab454119..faa22b5af7a8788aea28ef66d14b7b5619edf734 100644 (file)
@@ -46,6 +46,9 @@
   (make-instance 'sql-function-exp
                  :name 'all :args rest))
 
+(defsql sql-e-string (:symbol "E") (&rest rest)
+  (make-instance 'sql-escape-string-exp :string (first rest)))
+
 (defsql sql-not (:symbol "not") (&rest rest)
   (make-instance 'sql-value-exp
                  :modifier 'not :components rest))
index 470be84e22d06ec30ea0dac20c305ac861f3f3c9..8915b06781011292d8fdbdbe7ca5f452c96ffb33 100644 (file)
@@ -37,6 +37,7 @@
           #+clsql-cmucl-mop #:mop
           #+allegro #:mop
           #+clisp #:clos
+          #+ecl #:mop
           #+lispworks #:clos
           #+scl #:clos
           #+openmcl #:openmcl-mop)
index be1396e021af6636032140a445781297b6ab3d21..df3b70581b144c3aca81c1f81649e4aa8da4e50c 100644 (file)
@@ -424,6 +424,26 @@ is replaced with replacement. [FROM http://cl-cookbook.sourceforge.net/strings.h
     (unless stream
       (get-output-stream-string out))))
 
+(defun read-decimal-value (string)
+  (let* ((comma 0)
+         (dot 0)
+         (last))
+    (loop for c across string
+          do (case c
+               (#\. (incf dot) (setf last 'dot))
+               (#\, (incf comma) (setf last 'comma))))
+    (let* ((bag (if (and (eql last 'dot) (eql dot 1))
+                    ".0123456789+-"
+                    ",0123456789+-"))
+           (clean (with-output-to-string (out)
+                    (loop for c across string
+                          do (when (find c bag :test #'char=)
+                               (write-char c out))))))
+      (if (and (eql last 'dot) (eql dot 1))
+          (decimals:parse-decimal-number clean)
+          (decimals:parse-decimal-number
+           clean :decimal-separator #\,)))))
+
 
 (defun filter-plist (plist &rest keys-to-remove)
   "Returns a copy of the given plist with indicated key-value pairs
index d1de92e94668eda0afacc5dc61e6148c2a2472af..8312784216c8fd84f50f4575f255bab0de691363 100644 (file)
 
          (%do-tests test-forms db-type)
 
-           (format *report-stream* "~&Tests skipped:")
+         (format *report-stream* "~&~D of ~D Tests skipped:"
+                 (length skip-tests)
+                 (length test-forms))
            (if skip-tests
                (let ((max-test-name (length (symbol-name (caar skip-tests)))))
                  (dolist (skipped (cdr skip-tests))
index 8ee2b031c6df7986a63d89a2125bd915c1167309..61076c6e716dcc5a65354668fc8d494508bffe9c 100644 (file)
        (list (clsql:sql [foo]) (clsql:sql [foo]) (clsql:sql [foo.bar])))
      ("FOO" "FOO" "FOO.BAR"))
 
-    ))
+    (deftest :currency/read-value/1
+      (list
+       (clsql-sys::read-decimal-value "$ 10,500.30")
+       (clsql-sys::read-decimal-value "$ 10.500,30")
+       (clsql-sys::read-decimal-value "-10 500,30")
+       (clsql-sys::read-decimal-value "$ 10.500,30"))
+      (1050030/100 1050030/100 -1050030/100 1050030/100))
 
+    ))
index 042fd48b6d71bb39cf8cb720ad60ae159dc76d85..6278d73cd64e40f78bd2ef415072328127a3b69f 100644 (file)
 
 (clsql-sys:file-enable-sql-reader-syntax)
 
+(defmacro has-sql-value-conversion-error (() &body body)
+  `(let (*debugger-hook*)
+    (handler-case
+        (progn ,@body nil)
+      (clsql-sys::sql-value-conversion-error (c)
+        (declare (ignore c))
+        t))))
 
 (setq *rt-oodml*
       '(
  :foo)
 
 (deftest :oodml/read-symbol-value/4-keyword-error
- (handler-case
-     (clsql-sys::read-sql-value
-      (clsql-sys::database-output-sql-as-type 'keyword 'foo nil nil)
-      'keyword nil nil)
-   (clsql-sys::sql-value-conversion-error (c) (declare (ignore c))
-     :error))
- :error)
+ (has-sql-value-conversion-error ()
+   (clsql-sys::read-sql-value
+    (clsql-sys::database-output-sql-as-type 'keyword 'foo nil nil)
+    'keyword nil nil))
+ T)
+
+(deftest :oodml/read-symbol-value/5-unknown-type-error-1
+ (has-sql-value-conversion-error ()
+   (clsql-sys::read-sql-value
+    (clsql-sys::database-output-sql-as-type 'bloop 'foo nil nil)
+    'bloop nil nil))
+ t)
+
+(deftest :oodml/read-symbol-value/6-unknown-type-error-2
+ (has-sql-value-conversion-error ()
+   (clsql-sys::read-sql-value
+    (clsql-sys::database-output-sql-as-type 'bloop 'foo nil nil)
+    '(or integer float) nil nil))
+ t)
+
+(deftest :oodml/read-symbol-value/read-list
+ (clsql-sys::read-sql-value
+  (clsql-sys::database-output-sql-as-type
+   'list '(("status" "new" "open")) nil nil)
+  'list nil nil)
+ (("status" "new" "open")))
 
 (deftest :oodml/select/1
     (with-dataset *ds-employees*