From: Mike Bayer Date: Sun, 24 Jan 2010 18:41:30 +0000 (+0000) Subject: - oracle + firebird: "case sensitivity" feature will detect an all-lowercase X-Git-Tag: rel_0_6beta1~35 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=dd01f817b738a81bf9e5e4632387454f0f177dd6;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - oracle + firebird: "case sensitivity" feature will detect an all-lowercase case-sensitive column name during reflect and add "quote=True" to the generated Column, so that proper quoting is maintained. --- diff --git a/CHANGES b/CHANGES index e322df15de..021cd4a236 100644 --- a/CHANGES +++ b/CHANGES @@ -711,6 +711,11 @@ CHANGES - using types.BigInteger with Oracle will generate NUMBER(19) [ticket:1125] + + - "case sensitivity" feature will detect an all-lowercase + case-sensitive column name during reflect and add + "quote=True" to the generated Column, so that proper + quoting is maintained. - firebird - the keys() method of RowProxy() now returns the result @@ -723,6 +728,11 @@ CHANGES - using new dialect.initialize() feature to set up version-dependent behavior. + - "case sensitivity" feature will detect an all-lowercase + case-sensitive column name during reflect and add + "quote=True" to the generated Column, so that proper + quoting is maintained. + - mssql - MSSQL + Pyodbc + FreeTDS now works for the most part, with possible exceptions regarding binary data as well as diff --git a/lib/sqlalchemy/dialects/firebird/base.py b/lib/sqlalchemy/dialects/firebird/base.py index aab2174502..e4a2c568a4 100644 --- a/lib/sqlalchemy/dialects/firebird/base.py +++ b/lib/sqlalchemy/dialects/firebird/base.py @@ -484,7 +484,8 @@ class FBDialect(default.DefaultDialect): if row is None: break name = self.normalize_name(row['fname']) - + orig_colname = row['fname'] + # get the data type colspec = row['ftype'].rstrip() coltype = self.ischema_names.get(colspec) @@ -523,6 +524,9 @@ class FBDialect(default.DefaultDialect): 'nullable' : not bool(row['null_flag']), 'default' : defvalue } + + if orig_colname.lower() == orig_colname: + col_d['quote'] = True # if the PK is a single field, try to see if its linked to # a sequence thru a trigger diff --git a/lib/sqlalchemy/dialects/oracle/base.py b/lib/sqlalchemy/dialects/oracle/base.py index 9267969610..bb9ed32506 100644 --- a/lib/sqlalchemy/dialects/oracle/base.py +++ b/lib/sqlalchemy/dialects/oracle/base.py @@ -718,8 +718,8 @@ class OracleDialect(default.DefaultDialect): table_name=table_name, owner=schema) for row in c: - (colname, coltype, length, precision, scale, nullable, default) = \ - (self.normalize_name(row[0]), row[1], row[2], row[3], row[4], row[5]=='Y', row[6]) + (colname, orig_colname, coltype, length, precision, scale, nullable, default) = \ + (self.normalize_name(row[0]), row[0], row[1], row[2], row[3], row[4], row[5]=='Y', row[6]) if coltype == 'NUMBER' : coltype = NUMBER(precision, scale) @@ -740,6 +740,9 @@ class OracleDialect(default.DefaultDialect): 'nullable': nullable, 'default': default, } + if orig_colname.lower() == orig_colname: + cdict['quote'] = True + columns.append(cdict) return columns diff --git a/lib/sqlalchemy/engine/reflection.py b/lib/sqlalchemy/engine/reflection.py index 0d49b38bcd..57f2205c16 100644 --- a/lib/sqlalchemy/engine/reflection.py +++ b/lib/sqlalchemy/engine/reflection.py @@ -292,7 +292,9 @@ class Inspector(object): } if 'autoincrement' in col_d: col_kw['autoincrement'] = col_d['autoincrement'] - + if 'quote' in col_d: + col_kw['quote'] = col_d['quote'] + colargs = [] if col_d.get('default') is not None: # the "default" value is assumed to be a literal SQL expression, diff --git a/lib/sqlalchemy/test/requires.py b/lib/sqlalchemy/test/requires.py index be6ae9594b..4f6c81a204 100644 --- a/lib/sqlalchemy/test/requires.py +++ b/lib/sqlalchemy/test/requires.py @@ -9,8 +9,10 @@ from testing import \ _block_unconditionally as no_support, \ _chain_decorators_on, \ exclude, \ - emits_warning_on + emits_warning_on,\ + skip_if +import testing def deferrable_constraints(fn): """Target database must support derferable constraints.""" @@ -106,6 +108,11 @@ def savepoints(fn): exclude('mysql', '<', (5, 0, 3), 'not supported by database'), ) +def denormalized_names(fn): + """Target database must have 'denormalized', i.e. UPPERCASE as case insensitive names.""" + + return skip_if(lambda: not testing.db.dialect.requires_name_normalize)(fn) + def schemas(fn): """Target database must support external schemas, and have one named 'test_schema'.""" diff --git a/test/engine/test_reflection.py b/test/engine/test_reflection.py index 95f985db3d..1582c86e4a 100644 --- a/test/engine/test_reflection.py +++ b/test/engine/test_reflection.py @@ -7,7 +7,8 @@ from sqlalchemy import MetaData from sqlalchemy.test.schema import Table from sqlalchemy.test.schema import Column import sqlalchemy as sa -from sqlalchemy.test import TestBase, ComparesTables, testing, engines +from sqlalchemy.test import TestBase, ComparesTables, \ + testing, engines, AssertsCompiledSQL create_inspector = Inspector.from_engine @@ -995,6 +996,31 @@ def dropViews(con, schema=None): con.execute(sa.sql.text(query)) +class ReverseCasingReflectTest(TestBase, AssertsCompiledSQL): + + @testing.requires.denormalized_names + def setup(self): + testing.db.execute(""" + CREATE TABLE weird_casing( + col1 char(20), + "Col2" char(20), + "col3" char(20) + ) + """) + + @testing.requires.denormalized_names + def teardown(self): + testing.db.execute("drop table weird_casing") + + @testing.requires.denormalized_names + def test_direct_quoting(self): + m = MetaData(testing.db) + t = Table("weird_casing", m, autoload=True) + self.assert_compile( + t.select(), + 'SELECT weird_casing.col1, weird_casing."Col2", weird_casing."col3" FROM weird_casing' + ) + class ComponentReflectionTest(TestBase): @testing.requires.schemas