Commits

Kirill Simonov committed f55ead4

engine.sqlite: added support for decimal domain (represented as REAL).

Comments (0)

Files changed (9)

src/htsql/core/fmt/text.py

             value = value.normalize()
             sign, digits, exp = value.as_tuple()
         if exp > 0:
-            value = value.quantize(decimal.Decimal(1))
+            # Guard against InvalidOperation:
+            # quantize result has too many digits for current context
+            if exp+len(digits) < 28:
+                value = value.quantize(decimal.Decimal(1))
         return unicode(value)
 
 

src/htsql_sqlite/core/connect.py

 from htsql.core.adapter import adapt
 from htsql.core.error import Error
 from htsql.core.context import context
-from htsql.core.domain import (BooleanDomain, TextDomain, DateDomain,
-        TimeDomain, DateTimeDomain)
+from htsql.core.domain import (BooleanDomain, TextDomain, IntegerDomain,
+        DecimalDomain, FloatDomain, DateDomain, TimeDomain, DateTimeDomain)
 import sqlite3
 import datetime
 import os.path
+import decimal
 
 
 def sqlite3_power(x, y):
         if isinstance(value, bool):
             return value
         if not isinstance(value, int):
-            raise SQLiteError("expected a Boolean value, got %r" % value)
+            raise Error("Expected a Boolean value, got", repr(value))
         return (value != 0)
 
 
+class UnscrambleSQLiteInteger(Unscramble):
+
+    adapt(IntegerDomain)
+
+    @staticmethod
+    def convert(value):
+        if value is None:
+            return None
+        if isinstance(value, (int, long)):
+            return value
+        raise Error("Expected an integer value, got", repr(value))
+
+
+class UnscrambleSQLiteDecimal(Unscramble):
+
+    adapt(DecimalDomain)
+
+    @staticmethod
+    def convert(value):
+        if value is None:
+            return None
+        if isinstance(value, float):
+            return decimal.Decimal(str(value))
+        raise Error("Expected a decimal value, got", repr(value))
+
+
+class UnscrambleSQLiteFloat(Unscramble):
+
+    adapt(FloatDomain)
+
+    @staticmethod
+    def convert(value):
+        if value is None:
+            return None
+        if isinstance(value, float):
+            return value
+        raise Error("Expected a float value, got", repr(value))
+
+
 class UnscrambleSQLiteText(Unscramble):
 
     adapt(TextDomain)
         if value is None:
             return None
         if isinstance(value, str):
-            value = value.decode('utf-8')
+            try:
+                value = value.decode('utf-8')
+            except UnicodeDecodeError:
+                pass
         if not isinstance(value, unicode):
-            value = unicode(value)
+            raise Error("Expected a text value, got", repr(value))
         return value
 
 
         if isinstance(value, datetime.date):
             return value
         if not isinstance(value, (str, unicode)):
-            raise SQLiteError("expected a date value, got %r" % value)
+            raise Error("Expected a date value, got", repr(value))
         converter = sqlite3.converters['DATE']
         value = converter(value)
         return value
         if isinstance(value, datetime.time):
             return value
         if not isinstance(value, (str, unicode)):
-            raise SQLiteError("expected a time value, got %r" % value)
+            raise Error("Expected a time value, got", repr(value))
         # FIXME: verify that the value is in valid format.
         hour, minute, second = value.split(':')
         hour = int(hour)
         if isinstance(value, datetime.datetime):
             return value
         if not isinstance(value, (str, unicode)):
-            raise SQLiteError("expected a timestamp value, got %r" % value)
+            raise Error("Expected a timestamp value, got", repr(value))
         converter = sqlite3.converters['TIMESTAMP']
         value = converter(value)
         return value

src/htsql_sqlite/core/introspect.py

 from htsql.core.adapter import Protocol, call
 from htsql.core.introspect import Introspect
 from htsql.core.entity import make_catalog
-from htsql.core.domain import (BooleanDomain, IntegerDomain,
-                               FloatDomain, TextDomain, DateDomain,
-                               TimeDomain, DateTimeDomain, OpaqueDomain)
+from htsql.core.domain import (BooleanDomain, IntegerDomain, DecimalDomain,
+        FloatDomain, TextDomain, DateDomain, TimeDomain, DateTimeDomain,
+        OpaqueDomain)
 from htsql.core.connect import connect
 
 
         return TextDomain()
 
 
+class IntrospectSQLiteDecimalDomain(IntrospectSQLiteDomain):
+
+    call('dec', 'num')
+
+    def __call__(self):
+        return DecimalDomain()
+
+
 class IntrospectSQLiteFloatDomain(IntrospectSQLiteDomain):
 
     call('real', 'floa', 'doub')

src/htsql_sqlite/core/tr/__init__.py

 #
 
 
-from . import bind, coerce, compile, dump, signature
+from . import compile, dump, signature
 
 

src/htsql_sqlite/core/tr/bind.py

-#
-# Copyright (c) 2006-2013, Prometheus Research, LLC
-#
-
-
-from htsql.core.domain import IntegerDomain, FloatDomain
-from htsql.core.tr.fn.bind import (match, CorrelateDecimalRoundTo,
-                                   CorrelateDecimalTruncTo,
-                                   CorrelateDecimalAvg)
-from htsql.core.tr.fn.signature import RoundToSig, TruncToSig
-
-
-class SQLiteCorrelateDecimalAvg(CorrelateDecimalAvg):
-
-    domains = [FloatDomain()]
-    codomain = FloatDomain()
-
-
-class SQLiteCorrelateFloatRoundTo(CorrelateDecimalRoundTo):
-
-    match(RoundToSig, (FloatDomain, IntegerDomain))
-
-
-class SQLiteCorrelateFloatTruncTo(CorrelateDecimalTruncTo):
-
-    match(TruncToSig, (FloatDomain, IntegerDomain))
-
-

src/htsql_sqlite/core/tr/coerce.py

-#
-# Copyright (c) 2006-2013, Prometheus Research, LLC
-#
-
-
-from htsql.core.adapter import adapt
-from htsql.core.domain import DecimalDomain, FloatDomain
-from htsql.core.tr.coerce import UnaryCoerce
-
-
-class SQLiteUnaryCoerceDecimal(UnaryCoerce):
-
-    adapt(DecimalDomain)
-
-    def __call__(self):
-        return FloatDomain()
-
-

src/htsql_sqlite/core/tr/dump.py

 class SQLiteDumpDecimal(DumpDecimal):
 
     def __call__(self):
-        raise Error("Decimal data type is not supported")
+        assert self.value.is_finite()
+        value = float(self.value)
+        if value >= 0.0:
+            self.write(u"%s" % value)
+        else:
+            self.write(u"(%s)" % value)
 
 
 class SQLiteDumpDate(DumpDate):
 class SQLiteDumpToDecimal(DumpToDecimal):
 
     def __call__(self):
-        raise Error("Decimal data type is not supported")
+        self.format("CAST({base} AS REAL)", base=self.base)
 
 
 class SQLiteDumpToText(DumpToText):

test/input/library.yaml

   # Inadmissible operand
   - uri: /{round(271828e-5,2)}
     expect: 400
-    ifndef: sqlite
   - uri: /{trunc(271828e-5,2)}
     expect: 400
-    ifndef: sqlite
 
 ########################################################################
 

test/output/sqlite.yaml

           body: |2
              | decimal('1E-10') |
             -+------------------+-
-             |            1e-10 |
+             |            1E-10 |
 
              ----
              /{decimal('1E-10')}
           body: |2
              | 4154781481226426191177580544000000.808017424794512875886459904961710757005754368000000000 |
             -+-------------------------------------------------------------------------------------------+-
-             |                                                                         4.15478148123e+33 |
+             |                                                                         4.15478148123E+33 |
 
              ----
              /{4154781481226426191177580544000000.808017424794512875886459904961710757005754368000000000}
-             SELECT 4.154781481226426e+33
+             SELECT 4.15478148123e+33
         - uri: /{decimal('vingt-cinq')}
           status: 400 Bad Request
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
-            invalid float literal: vingt-cinq
+            invalid decimal literal: vingt-cinq
             While translating:
                 /{decimal('vingt-cinq')}
                   ^^^^^^^^^^^^^^^^^^^^^
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
-            invalid float literal: cinq
+            invalid decimal literal: cinq
             While translating:
                 /{'cinq'!=4.9}
                   ^^^^^^
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
-            invalid float literal: cinq
+            invalid decimal literal: cinq
             While translating:
                 /{'cinq'>4.9}
                   ^^^^^^
              ----
              /{decimal(60),decimal(271828e-5)}
              SELECT 60.0,
-                    2.71828
+                    CAST(2.71828 AS REAL)
         - uri: /{float(60), float(2.125)}
           status: 200 OK
           headers:
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
-            Cannot convert a value of type boolean to float
+            Cannot convert a value of type boolean to decimal
             While translating:
                 /{decimal(true())}
                   ^^^^^^^^^^^^^^^
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
-            Cannot apply operator '-' to values of types (float, date)
+            Cannot apply operator '-' to values of types (decimal, date)
             Valid types:
                 (integer, integer)
                 (integer, decimal)
 
              ----
              /{7/0,7/0.0,7/0e0}
-             SELECT (7.0 / 0.0)
+             SELECT (7.0 / 0.0),
+                    (7.0 / 0.0)
         - uri: /{round(3272.78125), round(3272.78125,2), round(3272.78125,-2)}
           status: 200 OK
           headers:
              ----
              /{round(9973,-2)}
              SELECT (ROUND(9973.0 / 100.0) * 100.0)
+        - uri: /{round(271828e-5,2)}
+          status: 400 Bad Request
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |
+            Cannot apply function 'round' to values of types (float, integer)
+            Valid types:
+                (integer, integer)
+                (decimal, integer)
+            While translating:
+                /{round(271828e-5,2)}
+                  ^^^^^^^^^^^^^^^^^^
+        - uri: /{trunc(271828e-5,2)}
+          status: 400 Bad Request
+          headers:
+          - [Content-Type, text/plain; charset=UTF-8]
+          body: |
+            Cannot apply function 'trunc' to values of types (float, integer)
+            Valid types:
+                (integer, integer)
+                (decimal, integer)
+            While translating:
+                /{trunc(271828e-5,2)}
+                  ^^^^^^^^^^^^^^^^^^
       - id: text-functions-and-operators
         tests:
         - uri: /{text(null()), text('OMGWTFBBQ')}
                         },
                         {
                           "domain": {
-                            "type": "float"
+                            "type": "decimal"
                           },
                           "header": "2.125",
                           "syntax": "2.125"
             <tr><th class="htsql-empty-header"></th><th>null()</th><th>'HTSQL'</th><th>true()</th><th>false()</th><th>60</th><th>2.125</th><th>271828e-5</th><th>text(null())</th><th>text('')</th><th>text('OMGWTFBBQ')</th><th>date('2010-04-15')</th><th>time('20:13:04.5')</th><th>datetime('2010-04-15 20:13')</th></tr>
             </thead>
             <tbody>
-            <tr class="htsql-odd-row"><td class="htsql-index">1</td><td class="htsql-untyped-type htsql-null-value"></td><td class="htsql-untyped-type">HTSQL</td><td class="htsql-boolean-type htsql-true-value">true</td><td class="htsql-boolean-type htsql-false-value">false</td><td class="htsql-integer-type">60</td><td class="htsql-float-type">2.125</td><td class="htsql-float-type">2.71828</td><td class="htsql-text-type htsql-null-value"></td><td class="htsql-text-type htsql-empty-value"></td><td class="htsql-text-type">OMGWTFBBQ</td><td class="htsql-date-type">2010-04-15</td><td class="htsql-time-type">20:13:04.500000</td><td class="htsql-datetime-type">2010-04-15 20:13:00</td></tr>
+            <tr class="htsql-odd-row"><td class="htsql-index">1</td><td class="htsql-untyped-type htsql-null-value"></td><td class="htsql-untyped-type">HTSQL</td><td class="htsql-boolean-type htsql-true-value">true</td><td class="htsql-boolean-type htsql-false-value">false</td><td class="htsql-integer-type">60</td><td class="htsql-decimal-type">2.125</td><td class="htsql-float-type">2.71828</td><td class="htsql-text-type htsql-null-value"></td><td class="htsql-text-type htsql-empty-value"></td><td class="htsql-text-type">OMGWTFBBQ</td><td class="htsql-date-type">2010-04-15</td><td class="htsql-time-type">20:13:04.500000</td><td class="htsql-datetime-type">2010-04-15 20:13:00</td></tr>
             </tbody>
             </table>
             </body>
             Valid types:
                 (integer, integer)
                 (decimal, integer)
-                (float, integer)
             While translating:
                 /trunc(school,department)
                  ^^^^^^^^^^^^^^^^^^^^^^^^
           headers:
           - [Content-Type, text/plain; charset=UTF-8]
           body: |
-            invalid float literal: !
+            invalid decimal literal: !
             While translating:
                 /decimal('!')
                  ^^^^^^^^^^^^