| 50 | | def coerce_datetime_time_to_any(self, value): |
|---|
| | 79 | def binary_op(self, op1, op, sqlop, op2): |
|---|
| | 80 | # See http://www.ibphoenix.com/main.nfs?a=ibphoenix&s=1154534295:6&page=ibp_60_sql_date_fs |
|---|
| | 81 | if op2.pytype is datetime.date: |
|---|
| | 82 | if op == "-": |
|---|
| | 83 | return "((%s - %s) * 86400)" % (op1.sql, op2.sql) |
|---|
| | 84 | elif op2.pytype is datetime.timedelta: |
|---|
| | 85 | return "(%s %s (%s / 86400))" % (op1.sql, op, op2.sql) |
|---|
| | 86 | raise TypeError("unsupported operand type(s) for %s: " |
|---|
| | 87 | "%r and %r" % (op, op1.pytype, op2.pytype)) |
|---|
| | 88 | |
|---|
| | 89 | |
|---|
| | 90 | class Firebird_timedelta_adapter(adapters.timedelta_to_SQL92DECIMAL): |
|---|
| | 91 | |
|---|
| | 92 | def pull(self, value, dbtype): |
|---|
| | 93 | # TIMESTAMP - TIMESTAMP => DECIMAL(18, 9) Days + Fraction of day |
|---|
| | 94 | # DATE - DATE => DECIMAL(9, 0) representing # of Days |
|---|
| | 95 | # However, in binary_op (below) we multiplied them all by 86400. |
|---|
| | 96 | if isinstance(value, tuple): |
|---|
| | 97 | value = normalize(value, float)[0] |
|---|
| | 98 | days, seconds = divmod(value, 86400) |
|---|
| | 99 | return datetime.timedelta(int(days), int(round(seconds))) |
|---|
| | 100 | |
|---|
| | 101 | def binary_op(self, op1, op, sqlop, op2): |
|---|
| | 102 | # See http://www.ibphoenix.com/main.nfs?a=ibphoenix&s=1154534295:6&page=ibp_60_sql_date_fs |
|---|
| | 103 | if op2.pytype is datetime.timedelta: |
|---|
| | 104 | return "(%s %s %s)" % (op1.sql, op, op2.sql) |
|---|
| | 105 | elif op == "+": |
|---|
| | 106 | if op2.pytype is datetime.date: |
|---|
| | 107 | return "((%s / 86400) + %s)" % (op1.sql, op2.sql) |
|---|
| | 108 | elif op2.pytype is datetime.datetime: |
|---|
| | 109 | return ("((CAST(%s AS DOUBLE PRECISION) / 86400) + %s)" |
|---|
| | 110 | % (op1.sql, op2.sql)) |
|---|
| | 111 | raise TypeError("unsupported operand type(s) for %s: " |
|---|
| | 112 | "%r and %r" % (op, op1.pytype, op2.pytype)) |
|---|
| | 113 | |
|---|
| | 114 | |
|---|
| | 115 | class Firebird_time_adapter(adapters.time_to_SQL92TIME): |
|---|
| | 116 | |
|---|
| | 117 | def push(self, value, dbtype): |
|---|
| 109 | | |
|---|
| 110 | | # !!?!??! kinterbasdb already converts to datetime? Rapture! |
|---|
| 111 | | def coerce_any_to_datetime_datetime(self, value): |
|---|
| 112 | | return value |
|---|
| 113 | | |
|---|
| 114 | | def coerce_any_to_datetime_date(self, value): |
|---|
| 115 | | return value |
|---|
| 116 | | |
|---|
| 117 | | def coerce_any_to_datetime_time(self, value): |
|---|
| 118 | | return value |
|---|
| 119 | | |
|---|
| 120 | | def coerce_any_to_datetime_timedelta(self, value): |
|---|
| 121 | | # TIMESTAMP - TIMESTAMP => DECIMAL(18, 9) Days + Fraction of day |
|---|
| 122 | | # DATE - DATE => DECIMAL(9, 0) representing # of Days |
|---|
| 123 | | # However, in binary_op (below) we multiplied them all by 86400. |
|---|
| 124 | | if isinstance(value, tuple): |
|---|
| 125 | | value = self.normalize(value, float)[0] |
|---|
| 126 | | days, seconds = divmod(value, 86400) |
|---|
| 127 | | return datetime.timedelta(int(days), int(round(seconds))) |
|---|
| 128 | | |
|---|
| 129 | | |
|---|
| 130 | | class TypeAdapterFirebird(adapters.TypeAdapter): |
|---|
| 131 | | """Return the SQL typename of a DB column.""" |
|---|
| 132 | | |
|---|
| 133 | | # Max decimal precision for NUMERIC columns. |
|---|
| 134 | | numeric_max_precision = 18 |
|---|
| 135 | | numeric_max_bytes = 9 |
|---|
| 136 | | |
|---|
| 137 | | numeric_text_type = "BLOB" |
|---|
| 138 | | |
|---|
| 139 | | _reverse_types = adapters.TypeAdapter._reverse_types.copy() |
|---|
| 140 | | _reverse_types.update({ |
|---|
| 141 | | 'LONG': int, |
|---|
| 142 | | 'SHORT': int, |
|---|
| 143 | | 'INT64': long, |
|---|
| 144 | | 'VARYING': str, |
|---|
| 145 | | 'NCHAR': unicode, |
|---|
| 146 | | 'NATIONAL': unicode, |
|---|
| 147 | | }) |
|---|
| 148 | | |
|---|
| 149 | | def coerce_str(self, hints): |
|---|
| 150 | | # The bytes hint shall not reflect the usual 4-byte base for varchar. |
|---|
| 151 | | |
|---|
| 152 | | # Although Firebird allows VARCHAR of 32765, 255 is usually the max |
|---|
| 153 | | # for which an index can be created. |
|---|
| 154 | | # TODO: this needs some serious work, so that the full size can be |
|---|
| 155 | | # allowed while only allowing indices of 255. |
|---|
| 156 | | default = 127 |
|---|
| 157 | | |
|---|
| 158 | | bytes = int(hints.get('bytes', default)) |
|---|
| 159 | | if 1 <= bytes <= 32765: |
|---|
| 160 | | return "VARCHAR(%s)" % bytes |
|---|
| 161 | | return "BLOB" |
|---|
| 162 | | |
|---|
| 163 | | def coerce_bool(self, hints): |
|---|
| 164 | | return "SMALLINT" |
|---|
| 165 | | |
|---|
| 166 | | def int_type(self, bytes): |
|---|
| 167 | | """Return a datatype which can handle the given number of bytes.""" |
|---|
| 168 | | if bytes <= 2: |
|---|
| 169 | | return "SMALLINT" |
|---|
| 170 | | elif bytes <= 4: |
|---|
| 171 | | return "INTEGER" |
|---|
| | 177 | |
|---|
| | 178 | |
|---|
| | 179 | # ---------------------------- Database types ---------------------------- # |
|---|
| | 180 | |
|---|
| | 181 | |
|---|
| | 182 | # See http://www.ibexpert.info/documentation/%20%203.%20Database%20Objects/%20%203.%20Field/%20%203.%20Datatype/387.html |
|---|
| | 183 | # <data_type> = { |
|---|
| | 184 | # {SMALLINT | INTEGER | FLOAT | DOUBLE PRECISION} [<array_dim>] |
|---|
| | 185 | # | {DECIMAL | NUMERIC} [(precision [, scale])] [<array_dim] |
|---|
| | 186 | # | DATE [<array_dim>] |
|---|
| | 187 | # | {CHAR | CHARACTER | CHARACTER VARYING | VARCHAR} |
|---|
| | 188 | # [(int)] [<array_dim>] [CHARACTER SET charname] |
|---|
| | 189 | # | {NCHAR | NATIONAL CHARACTER | NATIONAL CHAR} |
|---|
| | 190 | # [VARYING] [(int)] [<array_dim>] |
|---|
| | 191 | # | BLOB [SUB_TYPE {int | subtype_name}) (SEGMENT SIZE int] |
|---|
| | 192 | # [CHARACTER SET charname] |
|---|
| | 193 | # | BLOB [(seglen [, subtype])] |
|---|
| | 194 | # } |
|---|
| | 195 | |
|---|
| | 196 | class SMALLINT(dbtypes.SQL92SMALLINT): |
|---|
| | 197 | synonyms = ['SHORT'] |
|---|
| | 198 | default_adapters = {int: Firebird_int_adapter(), |
|---|
| | 199 | long: Firebird_int_adapter(), |
|---|
| | 200 | bool: adapters.bool_to_SQL92BIT(), |
|---|
| | 201 | } |
|---|
| | 202 | |
|---|
| | 203 | class INTEGER(dbtypes.SQL92INTEGER): |
|---|
| | 204 | synonyms = ['LONG'] |
|---|
| | 205 | default_adapters = {int: Firebird_int_adapter(), |
|---|
| | 206 | long: Firebird_int_adapter(), |
|---|
| | 207 | bool: adapters.bool_to_SQL92BIT(), |
|---|
| | 208 | } |
|---|
| | 209 | |
|---|
| | 210 | class INT64(dbtypes.SQL92INTEGER): |
|---|
| | 211 | _bytes = max_bytes = 8 |
|---|
| | 212 | default_adapters = {int: Firebird_int_adapter(), |
|---|
| | 213 | long: Firebird_int_adapter(), |
|---|
| | 214 | bool: adapters.bool_to_SQL92BIT(), |
|---|
| | 215 | } |
|---|
| | 216 | |
|---|
| | 217 | |
|---|
| | 218 | class FLOAT(dbtypes.SQL92REAL): |
|---|
| | 219 | _precision = max_precision = 32 |
|---|
| | 220 | |
|---|
| | 221 | class DOUBLE(dbtypes.SQL92DOUBLE): |
|---|
| | 222 | """Base class for SQL 92 DOUBLE PRECISION types.""" |
|---|
| | 223 | synonyms = ['DOUBLE PRECISION'] |
|---|
| | 224 | _precision = max_precision = 64 |
|---|
| | 225 | |
|---|
| | 226 | |
|---|
| | 227 | class NUMERIC(dbtypes.SQL92DECIMAL): |
|---|
| | 228 | # Depending on the dialect, FB will convert NUMERIC to another type |
|---|
| | 229 | # if the precision or scale are not specified. |
|---|
| | 230 | |
|---|
| | 231 | # SQL dialect 1 only allows max_precision of 15, but dialect 3 is 18 |
|---|
| | 232 | max_precision = 18 |
|---|
| | 233 | |
|---|
| | 234 | default_adapters = {int: adapters.number_to_SQL92DECIMAL(int), |
|---|
| | 235 | long: adapters.number_to_SQL92DECIMAL(long), |
|---|
| | 236 | float: adapters.number_to_SQL92DECIMAL(float), |
|---|
| | 237 | datetime.timedelta: Firebird_timedelta_adapter(), |
|---|
| | 238 | } |
|---|
| | 239 | if typerefs.fixedpoint: |
|---|
| | 240 | default_adapters[typerefs.fixedpoint.FixedPoint] = Firebird_fixedpoint_adapter() |
|---|
| | 241 | if typerefs.decimal: |
|---|
| | 242 | if hasattr(typerefs.decimal, "Decimal"): |
|---|
| | 243 | default_adapters[typerefs.decimal.Decimal] = Firebird_decimal_Decimal_adapter() |
|---|
| 173 | | # Anything larger than 8 bytes, use decimal/numeric. |
|---|
| 174 | | return "NUMERIC(%s, 0)" % (bytes * 2) |
|---|
| | 245 | default_adapters[typerefs.decimal] = Firebird_decimal_adapter() |
|---|
| | 246 | |
|---|
| | 247 | |
|---|
| | 248 | |
|---|
| | 249 | class CHAR(dbtypes.SQL92CHAR): |
|---|
| | 250 | synonyms = ['CHARACTER'] |
|---|
| | 251 | max_bytes = 32767 |
|---|
| | 252 | default_adapters = dbtypes.SQL92CHAR.default_adapters.copy() |
|---|
| | 253 | default_adapters.update({str: Firebird_str_to_SQL92VARCHAR(), |
|---|
| | 254 | unicode: Firebird_unicode_to_SQL92VARCHAR(), |
|---|
| | 255 | None: Firebird_Pickler, |
|---|
| | 256 | }) |
|---|
| | 257 | |
|---|
| | 258 | class VARCHAR(dbtypes.SQL92VARCHAR): |
|---|
| | 259 | synonyms = ['CHARACTER VARYING', 'CHAR VARYING', 'VARYING'] |
|---|
| | 260 | max_bytes = 32767 |
|---|
| | 261 | # Although Firebird allows VARCHAR of 32767, 255 is usually the max |
|---|
| | 262 | # for which an index can be created. |
|---|
| | 263 | # TODO: this needs some serious work, so that the full size can be |
|---|
| | 264 | # allowed while only allowing indices of 255. |
|---|
| | 265 | default_adapters = dbtypes.SQL92CHAR.default_adapters.copy() |
|---|
| | 266 | default_adapters.update({str: Firebird_str_to_SQL92VARCHAR(), |
|---|
| | 267 | unicode: Firebird_unicode_to_SQL92VARCHAR(), |
|---|
| | 268 | None: Firebird_Pickler(), |
|---|
| | 269 | }) |
|---|
| | 270 | |
|---|
| | 271 | class NCHAR(dbtypes.SQL92CHAR): |
|---|
| | 272 | # 'The only difference to the NCHAR/VARCHAR datatype is that |
|---|
| | 273 | # NCHAR/VARCHAR automatically defines a special character set for |
|---|
| | 274 | # this table column: "CHARACTER SET ISO8859_1"' |
|---|
| | 275 | default_pytype = unicode |
|---|
| | 276 | max_bytes = 32767 |
|---|
| | 277 | default_adapters = dbtypes.SQL92CHAR.default_adapters.copy() |
|---|
| | 278 | default_adapters.update({str: Firebird_str_to_SQL92VARCHAR(), |
|---|
| | 279 | unicode: Firebird_unicode_to_SQL92VARCHAR(), |
|---|
| | 280 | None: Firebird_Pickler(), |
|---|
| | 281 | }) |
|---|
| | 282 | |
|---|
| | 283 | class NVARCHAR(dbtypes.SQL92VARCHAR): |
|---|
| | 284 | default_pytype = unicode |
|---|
| | 285 | max_bytes = 32767 |
|---|
| | 286 | default_adapters = dbtypes.SQL92CHAR.default_adapters.copy() |
|---|
| | 287 | default_adapters.update({str: Firebird_str_to_SQL92VARCHAR(), |
|---|
| | 288 | unicode: Firebird_unicode_to_SQL92VARCHAR(), |
|---|
| | 289 | None: Firebird_Pickler(), |
|---|
| | 290 | }) |
|---|
| | 291 | |
|---|
| | 292 | |
|---|
| | 293 | class DATE(dbtypes.SQL92DATE): |
|---|
| | 294 | # 'SQL dialect 1: DATE also includes a time slice |
|---|
| | 295 | # (equivalent to TIMESTAMP in dialect 3).' |
|---|
| | 296 | |
|---|
| | 297 | # 'Valid dates are from January 1, 100 AD through February 28, 32,767 AD. |
|---|
| | 298 | # Note: for DATE arithmetic purposes, DATE 0 (the integer value of zero) |
|---|
| | 299 | # as a DATE in InterBase/Firebird is November 17, 1898.' |
|---|
| | 300 | _min = datetime.date(100, 1, 1) |
|---|
| | 301 | ## _max = datetime.date(32767, 2, 28) |
|---|
| | 302 | _max = datetime.date(9999, 12, 31) |
|---|
| | 303 | default_adapters = {datetime.date: Firebird_date_adapter()} |
|---|
| | 304 | |
|---|
| | 305 | class TIME(dbtypes.SQL92TIME): |
|---|
| | 306 | default_adapters = {datetime.time: Firebird_time_adapter()} |
|---|
| | 307 | |
|---|
| | 308 | class TIMESTAMP(dbtypes.SQL92TIMESTAMP): |
|---|
| | 309 | # 'The range is from January 1,100 AD to February 28, 32768 AD.' |
|---|
| | 310 | _min = datetime.datetime(100, 1, 1) |
|---|
| | 311 | ## _max = datetime.datetime(32768, 2, 28, 23, 59, 59) |
|---|
| | 312 | _max = datetime.datetime(9999, 12, 31, 23, 59, 59) |
|---|
| | 313 | default_adapters = {datetime.datetime: Firebird_datetime_adapter()} |
|---|
| | 314 | |
|---|
| | 315 | |
|---|
| | 316 | |
|---|
| | 317 | class ComparableInfinity(object): |
|---|
| | 318 | |
|---|
| | 319 | def __cmp__(self, other): |
|---|
| | 320 | if isinstance(other, self.__class__): |
|---|
| | 321 | return False |
|---|
| | 322 | return True |
|---|
| | 323 | |
|---|
| | 324 | class BLOB(dbtypes.TEXT): |
|---|
| | 325 | # 'It is important when using blobs in a database, to consider the |
|---|
| | 326 | # database page size carefully. Blobs are created as part of a data |
|---|
| | 327 | # row, but because a blob could be of unlimited length, what is |
|---|
| | 328 | # actually stored with the data row is a BlobID, the data for the |
|---|
| | 329 | # blob is stored separately on special blob pages elsewhere in the |
|---|
| | 330 | # database.' |
|---|
| | 331 | # |
|---|
| | 332 | # Max Blob Size: |
|---|
| | 333 | # |
|---|
| | 334 | # 1Kb page size => 64 Mb |
|---|
| | 335 | # 2Kb page size => 512 Mb |
|---|
| | 336 | # 4Kb page size => 4 Gb |
|---|
| | 337 | # 8Kb page size => 32 Gb |
|---|
| | 338 | # 16kb page size => Big enough :-). |
|---|
| | 339 | # TEXT has no hard byte limit. |
|---|
| | 340 | _bytes = max_bytes = ComparableInfinity() |
|---|
| | 341 | |
|---|
| | 342 | |
|---|
| | 343 | class FirebirdTypeSet(dbtypes.DatabaseTypeSet): |
|---|
| | 344 | |
|---|
| | 345 | known_types = { |
|---|
| | 346 | 'float': [FLOAT, DOUBLE], |
|---|
| | 347 | 'varchar': [VARCHAR, BLOB], |
|---|
| | 348 | 'char': [CHAR, BLOB], |
|---|
| | 349 | 'int': [SMALLINT, INTEGER, INT64], |
|---|
| | 350 | 'bool': [SMALLINT], |
|---|
| | 351 | 'datetime': [TIMESTAMP], |
|---|
| | 352 | 'date': [DATE], |
|---|
| | 353 | 'time': [TIME], |
|---|
| | 354 | 'timedelta': [], |
|---|
| | 355 | 'numeric': [NUMERIC], |
|---|
| | 356 | 'other': [], |
|---|
| | 357 | } |
|---|
| 256 | | |
|---|
| 257 | | def binary_op(self, op): |
|---|
| 258 | | op2, op1 = self.stack.pop(), self.stack.pop() |
|---|
| 259 | | if op1 is decompile.cannot_represent or op2 is decompile.cannot_represent: |
|---|
| 260 | | self.stack.append(decompile.cannot_represent) |
|---|
| 261 | | return |
|---|
| 262 | | |
|---|
| 263 | | t1, t2 = op1.pytype, op2.pytype |
|---|
| 264 | | |
|---|
| 265 | | # See http://www.ibphoenix.com/main.nfs?a=ibphoenix&s=1154534295:6&page=ibp_60_sql_date_fs |
|---|
| 266 | | newsql = None |
|---|
| 267 | | if t1 is datetime.date: |
|---|
| 268 | | if t2 is datetime.date: |
|---|
| 269 | | if op == "-": |
|---|
| 270 | | newsql = "((%s - %s) * 86400)" % (op1.sql, op2.sql) |
|---|
| 271 | | elif t2 is datetime.timedelta: |
|---|
| 272 | | newsql = "(%s %s (%s / 86400))" % (op1.sql, op, op2.sql) |
|---|
| 273 | | elif t1 is datetime.datetime: |
|---|
| 274 | | if t2 is datetime.datetime: |
|---|
| 275 | | if op == "-": |
|---|
| 276 | | newsql = ("((%s - %s) * CAST(86400 AS DOUBLE PRECISION))" |
|---|
| 277 | | % (op1.sql, op2.sql)) |
|---|
| 278 | | elif t2 is datetime.timedelta: |
|---|
| 279 | | newsql = ("(%s %s (CAST(%s AS DOUBLE PRECISION) / 86400))" |
|---|
| 280 | | % (op1.sql, op, op2.sql)) |
|---|
| 281 | | elif t1 is datetime.timedelta: |
|---|
| 282 | | if t2 is datetime.timedelta: |
|---|
| 283 | | newsql = "(%s %s %s)" % (op1.sql, op, op2.sql) |
|---|
| 284 | | elif op == "+": |
|---|
| 285 | | if t2 is datetime.date: |
|---|
| 286 | | newsql = "((%s / 86400) + %s)" % (op1.sql, op2.sql) |
|---|
| 287 | | elif t2 is datetime.datetime: |
|---|
| 288 | | newsql = ("((CAST(%s AS DOUBLE PRECISION) / 86400) + %s)" |
|---|
| 289 | | % (op1.sql, op2.sql)) |
|---|
| 290 | | else: |
|---|
| 291 | | newsql = "(%s %s %s)" % (op1.sql, op, op2.sql) |
|---|
| 292 | | |
|---|
| 293 | | if newsql is None: |
|---|
| 294 | | raise TypeError("unsupported operand type(s) for %s: " |
|---|
| 295 | | "%r and %r" % (op, t1, t2)) |
|---|
| 296 | | |
|---|
| 297 | | # re-use op1 |
|---|
| 298 | | op1.pytype = self.result_type[(t1, op, t2)] |
|---|
| 299 | | op1.sql = newsql |
|---|
| 300 | | if not op1.name.startswith("expr_"): |
|---|
| 301 | | op1.name = "expr_%s" % op1.name |
|---|
| 302 | | self.stack.append(op1) |
|---|