Changeset 47
- Timestamp:
- 03/24/07 23:54:40
- Files:
-
- trunk/geniusql/adapters.py (modified) (1 diff)
- trunk/geniusql/decompile.py (modified) (1 diff)
- trunk/geniusql/providers/ado.py (modified) (14 diffs)
- trunk/geniusql/test/zoo_fixture.py (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/geniusql/adapters.py
r46 r47 536 536 # Use decimal instead of float to avoid rounding errors. 537 537 def coerce_datetime_timedelta(self, hints): 538 # This is a fallback for DB's which do not have an INTERVAL data type. 539 # If your DB has an INTERVAL datatype, you should override this and 540 # use the native INTERVAL type instead. You'll usually also have to 541 # update the date arithmetic inside the decompiler. 538 542 return self.int_type(self.numeric_max_bytes) 539 543 trunk/geniusql/decompile.py
r46 r47 472 472 # Binary operations # 473 473 474 # So, the question is, do we really want to support this level of detail475 # in attribute expressions or do we want to tell users to move all that476 # to Python? I think we do want it in SQL, because that's the only way477 # to efficiently support INSERT INTO. But it means a lot of work and478 # a lot of database-specific adaptation to deal with implicit typing. :(479 480 474 # Resultant type for a binary operation between two types. 481 475 result_type = {} trunk/geniusql/providers/ado.py
r46 r47 58 58 adUseClient = 3 59 59 60 # 12/30/1899, the zero-Date for ADO = 69359461 # "Sure, there are two 4-byte integers stored. But they are 62 # packed together into a BINARY(8). The first 4-byte being 63 # the elapsed number days since SQL Server's base date of 64 # 1900-01-01. The Second 4-bytes Store the Time of Day 65 # Represented as the Number of Milliseconds After Midnight." 66 # http://www.sql-server-performance.com/fk_datetime.asp 67 zeroHour = datetime.datetime(1899, 12, 30) 68 69 # Note also that SQL Server allows DATETIME in the range: 70 # "1753-01-01 00:00:00.0" to "9999-12-31 23:59:59.997". 71 72 dbtypes = { 73 0: 'EMPTY', 2: 'SMALLINT',74 3: 'INTEGER', 4: 'SINGLE',75 5: 'DOUBLE', 6: 'CURRENCY',76 7: 'DATE', 8: 'BSTR',77 9: 'IDISPATCH', 10: 'ERROR',78 11: 'BOOLEAN', 12: 'VARIANT',79 13: 'IUNKNOWN', 14: 'DECIMAL',80 16: 'TINYINT', 17: 'UNSIGNEDTINYINT',81 18: 'UNSIGNEDSMALLINT', 19: 'UNSIGNEDINT',82 20: 'BIGINT', 21: 'UNSIGNEDBIGINT',83 72: 'GUID', 128: 'BINARY',84 129: 'CHAR', 130: 'WCHAR',85 131: 'NUMERIC', 132: 'USERDEFINED',86 133: 'DBDATE', 134: 'DBTIME',87 135: 'DBTIMESTAMP', 200: 'VARCHAR',88 201: 'LONGVARCHAR', 202: 'VARWCHAR',89 203: 'LONGVARWCHAR', 204: 'VARBINARY',90 205: 'LONGVARBINARY'60 # See http://www.carlprothman.net/Technology/DataTypeMapping/tabid/97/Default.aspx 61 dbtypes = {# ADO Name SQL Server MS Access 62 0: 'EMPTY', 63 2: 'SMALLINT', # SMALLINT INTEGER 64 3: 'INTEGER', # IDENTITY (6.5), INT AUTONUMBER, LONG 65 4: 'SINGLE', # REAL SINGLE 66 5: 'DOUBLE', # FLOAT DOUBLE 67 6: 'CURRENCY', # MONEY, SMALLMONEY CURRENCY 68 7: 'DATE', # DATETIME (Access 97) 69 8: 'BSTR', 9: 'IDISPATCH', 10: 'ERROR', 70 11: 'BOOLEAN', # BIT YESNO 71 12: 'VARIANT', # SQL_VARIANT (2000 +) 72 13: 'IUNKNOWN', 14: 'DECIMAL', 16: 'TINYINT', 73 17: 'UNSIGNEDTINYINT',# TINYINT BYTE 74 18: 'UNSIGNEDSMALLINT', 19: 'UNSIGNEDINT', 75 20: 'BIGINT', # BIGINT 76 21: 'UNSIGNEDBIGINT', 77 72: 'GUID', 78 128: 'BINARY', # BINARY, TIMESTAMP 79 129: 'CHAR', # CHAR 80 130: 'WCHAR', # NCHAR (7.0+) 81 131: 'NUMERIC', # DECIMAL, NUMERIC DECIMAL (Access 2000) 82 132: 'USERDEFINED', 83 133: 'DBDATE', 134: 'DBTIME', 84 135: 'DBTIMESTAMP', # DATETIME, SMALLDATETIME DATETIME (ODBC 97) 85 200: 'VARCHAR', # VARCHAR TEXT (Access 97) 86 201: 'LONGVARCHAR', # TEXT MEMO (Access 97) 87 202: 'VARWCHAR', # NVARCHAR TEXT (Access 2000) 88 203: 'LONGVARWCHAR', # NTEXT (7.0+) MEMO (Access 2000+) 89 204: 'VARBINARY', # VARBINARY 90 205: 'LONGVARBINARY', # IMAGE OLEOBJECT 91 91 } 92 92 … … 102 102 103 103 104 def timedelta_from_com(com_date):105 """Return a valid datetime.timedelta from a COM date/time object."""106 com_date = float(com_date)107 108 # MS Access represents dates and times as floats. If the value is109 # before the epoch (12/30/1899), the seconds will be SUBTRACTED110 # from the float. For example, -2.01 is in the morning and -2.99111 # is in the evening of the same day. Therefore, when we split off112 # our seconds we must use the abs value of the fractional portion.113 neg = (com_date < 0)114 com_date = abs(com_date)115 116 days = int(com_date)117 # Must do both int() and round() or we'll be up to 1 second off.118 secs = int(round(86400 * (com_date - days)))119 120 result = datetime.timedelta(days, secs)121 if neg:122 return -result123 else:124 return result125 126 127 def datetime_from_com(com_date):128 """Return a valid datetime.datetime from a COM date/time object."""129 com_date = float(com_date)130 131 # MS Access represents dates and times as floats. If the value is132 # before the epoch (12/30/1899), the seconds will be SUBTRACTED133 # from the float. For example, -2.01 is in the morning and -2.99134 # is in the evening of the same day. Therefore, when we split off135 # our seconds we must use the abs value of the fractional portion.136 # Note that we do this differently from timedelta_from_com,137 # because there we need to subtract seconds, and here we add them.138 days = int(com_date)139 140 # Must do both int() and round() or we'll be up to 1 second off.141 secs = int(round(86400 * abs(com_date - days)))142 143 return zeroHour + datetime.timedelta(days, secs)144 145 146 104 class AdapterFromADO(adapters.AdapterFromDB): 147 105 """Coerce incoming values from ADO to Python datatypes.""" 148 106 149 107 encoding = 'ISO-8859-1' 108 epoch = datetime.datetime(1899, 12, 30) 109 110 def timedelta_from_com(self, com_date): 111 """Return a valid datetime.timedelta from a COM date/time object.""" 112 com_date = float(com_date) 113 114 # MS Access represents dates and times as floats. If the value is 115 # before the epoch (12/30/1899), the seconds will be SUBTRACTED 116 # from the float. For example, -2.01 is in the morning and -2.99 117 # is in the evening of the same day. Therefore, when we split off 118 # our seconds we must use the abs value of the fractional portion. 119 neg = (com_date < 0) 120 com_date = abs(com_date) 121 122 days = int(com_date) 123 # Must do both int() and round() or we'll be up to 1 second off. 124 secs = int(round(86400 * (com_date - days))) 125 126 result = datetime.timedelta(days, secs) 127 if neg: 128 return -result 129 else: 130 return result 150 131 151 132 def coerce_any_to_datetime_timedelta(self, value): 152 133 # Assume pywintypes.TimeType 153 return timedelta_from_com(value) 134 return self.timedelta_from_com(value) 135 136 def coerce_any_to_datetime_time(self, value): 137 t = self.timedelta_from_com(value) 138 if t.days: 139 raise ValueError("Time values greater than 23:59:59 not allowed.") 140 h, m = divmod(t.seconds, 3600) 141 m, s = divmod(m, 60) 142 return datetime.time(int(h), int(m), int(s)) 143 144 def datetime_from_com(self, com_date): 145 """Return a valid datetime.datetime from a COM date/time object.""" 146 com_date = float(com_date) 147 148 # MS Access represents dates and times as floats. If the value is 149 # before the epoch (12/30/1899), the seconds will be SUBTRACTED 150 # from the float. For example, -2.01 is in the morning and -2.99 151 # is in the evening of the same day. Therefore, when we split off 152 # our seconds we must use the abs value of the fractional portion. 153 # Note that we do this differently from timedelta_from_com, 154 # because there we need to subtract seconds, and here we add them. 155 days = int(com_date) 156 157 # Must do both int() and round() or we'll be up to 1 second off. 158 secs = int(round(86400 * abs(com_date - days))) 159 160 return self.epoch + datetime.timedelta(days, secs) 154 161 155 162 def coerce_any_to_datetime_datetime(self, value): … … 167 174 # value.Format(). Therefore, grab the float value and figure 168 175 # the date ourselves. Use 1-second resolution only. 169 return datetime_from_com(value)176 return self.datetime_from_com(value) 170 177 171 178 def coerce_any_to_datetime_date(self, value): … … 182 189 value = float(value) 183 190 days = int(value) 184 return zeroHour.date() + datetime.timedelta(days) 185 186 def coerce_any_to_datetime_time(self, value): 187 # See coerce_any_to_datetime 188 t = timedelta_from_com(value) 189 h, m = divmod(t.seconds, 3600) 190 m, s = divmod(m, 60) 191 return datetime.time(int(h) + (t.days * 24), int(m), int(s)) 191 return self.epoch.date() + datetime.timedelta(days) 192 192 193 193 def coerce_any_to_decimal_Decimal(self, value): … … 325 325 newsql = self.DATEADD(op1.sql, op2.sql) 326 326 elif op == "-": 327 newsql = self.DATE DIFF(op1.sql, op2.sql)327 newsql = self.DATESUB(op1.sql, op2.sql) 328 328 elif t1 is datetime.datetime: 329 329 if t2 is datetime.datetime: … … 334 334 newsql = self.DATETIMEADD(op1.sql, op2.sql) 335 335 elif op == "-": 336 newsql = self.DATETIME DIFF(op1.sql, op2.sql)336 newsql = self.DATETIMESUB(op1.sql, op2.sql) 337 337 elif t1 is datetime.timedelta: 338 338 if t2 is datetime.timedelta: … … 402 402 # = is already case-insensitive in ADO. 403 403 return self.get_expr(x.sql + " = " + y.sql, bool) 404 405 def builtins_now(self):406 return self.get_expr("GETDATE()", datetime.datetime)407 408 def builtins_today(self):409 return self.get_expr("DATEADD(dd, DATEDIFF(dd, 0, getdate()), 0)",410 datetime.date)411 412 def builtins_year(self, x):413 return self.get_expr("DATEPART(year, " + x.sql + ")", int)414 415 def builtins_month(self, x):416 return self.get_expr("DATEPART(month, " + x.sql + ")", int)417 418 def builtins_day(self, x):419 return self.get_expr("DATEPART(day, " + x.sql + ")", int)420 404 421 405 def func__builtin___len(self, x): … … 839 823 840 824 825 # "Sure, there are two 4-byte integers stored. But they are 826 # packed together into a BINARY(8). The first 4-byte being 827 # the elapsed number days since SQL Server's base date of 828 # 1900-01-01. The Second 4-bytes Store the Time of Day 829 # Represented as the Number of Milliseconds After Midnight." 830 # http://www.sql-server-performance.com/fk_datetime.asp 831 832 # Note also that SQL Server allows DATETIME in the range: 833 # "1753-01-01 00:00:00.0" to "9999-12-31 23:59:59.997". 834 841 835 842 836 class ADOSQLDecompiler_SQLServer(ADOSQLDecompiler): … … 855 849 856 850 def DATEADD(dt, td): 857 """Return the SQL to add a number a timedelta to a date.""" 858 return "DATEADD(dd, %s, %s)" % (dt, td) 851 """Return the SQL to add a timedelta to a date.""" 852 # Days, seconds seems like a good way to avoid overflow. 853 return ("DATEADD(dd, FLOOR(%s / 86400), " 854 "DATEADD(ss, (%s %% 86400), %s))" 855 % (td, td, dt)) 859 856 DATEADD = staticmethod(DATEADD) 857 858 def DATESUB(dt, td): 859 """Return the SQL to subtract a timedelta from a date.""" 860 return "(%s - FLOOR(%s / 86400.0))" % (dt, td) 861 DATESUB = staticmethod(DATESUB) 860 862 861 863 def DATEDIFF(d1, d2): 862 864 """Return the SQL to subtract one date from another.""" 863 865 # Amazing what a difference a little ".0" can make. 864 return " (DATEDIFF(dd, %s, %s) * 86400.0)" % (d2, d1)866 return "CAST(DATEDIFF(dd, %s, %s) * 86400.0 AS NUMERIC)" % (d2, d1) 865 867 DATEDIFF = staticmethod(DATEDIFF) 866 868 867 869 def DATETIMEADD(dt, td): 868 870 """Return the SQL to add a timedelta to a datetime.""" 869 return ("DATEADD(dd, %s, DATEADD(s, %s, DATEADD(ms, %s, %s)))" 870 % (td.days, td.seconds, td.microseconds, dt)) 871 return "(%s + (%s / 86400.0))" % (dt, td) 871 872 DATETIMEADD = staticmethod(DATETIMEADD) 872 873 873 874 def DATETIMEDIFF(d1, d2): 874 875 """Return the SQL to subtract one datetime from another.""" 875 return ("(DATEDIFF(dd, %(d2)s, %(d1)s) * 86400.0)" 876 " + ((DATEPART(hh, %(d1)s) - DATEPART(hh, %(d2)s)) * 3600.0)" 877 " + ((DATEPART(n, %(d1)s) - DATEPART(n, %(d2)s)) * 60)" 878 " + (DATEPART(s, %(d1)s) - DATEPART(s, %(d2)s))" 879 % {'d1': d1, 'd2': d2}) 876 return "CAST(CAST(%s - %s AS FLOAT) * 86400 AS NUMERIC)" % (d1, d2) 880 877 DATETIMEDIFF = staticmethod(DATETIMEDIFF) 878 879 def DATETIMESUB(dt, td): 880 """Return the SQL to subtract a timedelta from a datetime.""" 881 return "(%s - (%s / 86400.0))" % (dt, td) 882 DATETIMESUB = staticmethod(DATETIMESUB) 883 884 def TIMEDELTAADD(op1, op, op2): 885 return "(%s %s %s)" % (op1.sql, op, op2.sql) 886 TIMEDELTAADD = staticmethod(TIMEDELTAADD) 887 888 def builtins_now(self): 889 return self.get_expr("GETDATE()", datetime.datetime) 890 891 def builtins_today(self): 892 return self.get_expr("DATEADD(dd, DATEDIFF(dd, 0, getdate()), 0)", 893 datetime.date) 894 895 def builtins_year(self, x): 896 return self.get_expr("DATEPART(year, " + x.sql + ")", int) 897 898 def builtins_month(self, x): 899 return self.get_expr("DATEPART(month, " + x.sql + ")", int) 881 900 882 901 def builtins_day(self, x): … … 909 928 return ("(CASE WHEN ISNUMERIC(%s)=1 THEN CAST(%s AS int) END)" 910 929 % (colref, colref)) 930 931 932 class AdapterFromADOSQL_SQLServer(AdapterFromADO): 933 934 def coerce_any_to_datetime_time(self, value): 935 # Floats returned from SQL Server will be 2 days off 936 # because its epoch is 2 days later than MS Access. 937 return AdapterFromADO.coerce_any_to_datetime_time(self, float(value) - 2) 938 939 def coerce_any_to_datetime_timedelta(self, value): 940 # We're using the fallback type for timedelta (secs * 86400). 941 days, secs = divmod(long(value), 86400) 942 return datetime.timedelta(int(days), int(secs)) 911 943 912 944 … … 1032 1064 decompiler = ADOSQLDecompiler_SQLServer 1033 1065 adaptertosql = AdapterToADOSQL_SQLServer() 1066 adapterfromdb = AdapterFromADOSQL_SQLServer() 1034 1067 typeadapter = TypeAdapter_SQLServer() 1035 1068 connectionmanager = SQLServerConnectionManager … … 1076 1109 class ADOSQLDecompiler_MSAccess(ADOSQLDecompiler): 1077 1110 sql_cmp_op = ('<', '<=', '=', '<>', '>', '>=', 'in', 'not in') 1111 1112 epoch = datetime.datetime(1899, 12, 30) 1078 1113 1079 1114 def _compare_strings(self, op1, op, op2): … … 1118 1153 return "CDate(Fix(%s) - Fix(%s))" % (d1, d2) 1119 1154 DATEDIFF = staticmethod(DATEDIFF) 1155 DATESUB = DATEDIFF 1120 1156 1121 1157 def DATETIMEADD(dt, td): … … 1128 1164 return "CDate(%s - %s)" % (d1, d2) 1129 1165 DATETIMEDIFF = staticmethod(DATETIMEDIFF) 1166 DATETIMESUB = DATETIMEDIFF 1130 1167 1131 1168 def TIMEDELTAADD(op1, op, op2): trunk/geniusql/test/zoo_fixture.py
r46 r47 175 175 lion = schema['Animal'].insert(Species='Lion', ZooID=wap)['ID'] 176 176 schema['Animal'].insert(Species='Slug', Legs=1, Lifespan=.75, 177 # Test our 8000-byte limit178 PreviousZoos=["f" * (8000 - 14)])177 # Test our 8000-byte limit (ok, 7900) 178 PreviousZoos=["f" * (7900 - 14)]) 179 179 180 180 tiger = schema['Animal'].insert(Species='Tiger', ZooID=sdz, … … 302 302 self.assertEqual(tiger['PreviousZoos'], ["animal\\universe"]) 303 303 304 # Test our 8000-byte limit .305 # len(pickle.dumps(["f" * ( 8000 - 14)]) == 8000304 # Test our 8000-byte limit (ok, 7900; the row is too long) 305 # len(pickle.dumps(["f" * (7900 - 14)]) == 7900 306 306 slug = schema['Animal'].select(Species='Slug') 307 self.assertEqual(len(slug['PreviousZoos'][0]), 8000 - 14)307 self.assertEqual(len(slug['PreviousZoos'][0]), 7900 - 14) 308 308 309 309 # Exhibits
