From: Mike Bayer Date: Wed, 26 Sep 2007 14:55:44 +0000 (+0000) Subject: - the behavior of String/Unicode types regarding that they auto-convert X-Git-Tag: rel_0_4beta6~12 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e37a3a961c9f5bf822f86682c0f96418281d3e42;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - the behavior of String/Unicode types regarding that they auto-convert to TEXT/CLOB when no length is present now occurs *only* for an exact type of String or Unicode with no arguments. If you use VARCHAR or NCHAR (subclasses of String/Unicode) with no length, they will be interpreted by the dialect as VARCHAR/NCHAR; no "magic" conversion happens there. This is less surprising behavior and in particular this helps Oracle keep string-based bind parameters as VARCHARs and not CLOBs [ticket:793]. --- diff --git a/CHANGES b/CHANGES index 05c00b92da..5ebec6b471 100644 --- a/CHANGES +++ b/CHANGES @@ -5,8 +5,10 @@ CHANGES 0.4.0beta6 ---------- -- Added full list of SQLite reserved keywords so that they get escaped - properly. +- the Session identity map is now *weak referencing* by default, use +weak_identity_map=False to use a regular dict. The weak dict we are using +is customized to detect instances which are "dirty" and maintain a temporary +strong reference to those instances until changes are flushed. - Mapper compilation has been reorganized such that most compilation occurs upon mapper construction. This allows us to have fewer calls to @@ -17,6 +19,9 @@ CHANGES inheritance relationships need to be constructed in inheritance order (which should be the normal case anyway). +- Added full list of SQLite reserved keywords so that they get escaped +properly. + - Removed "parameters" argument from clauseelement.compile(), replaced with "column_keys". The parameters sent to execute() only interact with the insert/update statement compilation process in terms of the column names @@ -48,14 +53,17 @@ CHANGES of foreign key attributes during a flush where the parent object is deleted. -- the Session identity map is now *weak referencing* by default, use - weak_identity_map=False to use a regular dict. The weak dict we are using - is customized to detect instances which are "dirty" and maintain a temporary - strong reference to those instances until changes are flushed. - - Column defaults and onupdates, executing inline, will add parenthesis for subqueries and other parenthesis-requiring expressions +- the behavior of String/Unicode types regarding that they auto-convert + to TEXT/CLOB when no length is present now occurs *only* for an exact type + of String or Unicode with no arguments. If you use VARCHAR or NCHAR + (subclasses of String/Unicode) with no length, they will be interpreted + by the dialect as VARCHAR/NCHAR; no "magic" conversion happens there. + This is less surprising behavior and in particular this helps Oracle keep + string-based bind parameters as VARCHARs and not CLOBs [ticket:793]. + - Fixes to ShardedSession to work with deferred columns [ticket:771]. - User-defined shard_chooser() function must accept "clause=None" argument; diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py index dac5d7a743..d649fc0ff0 100644 --- a/lib/sqlalchemy/sql/expression.py +++ b/lib/sqlalchemy/sql/expression.py @@ -1758,9 +1758,11 @@ class _BindParamClause(ClauseElement, _CompareMixin): self.type = type_ # TODO: move to types module, obviously + # using VARCHAR/NCHAR so that we dont get the genericized "String" + # type which usually resolves to TEXT/CLOB type_map = { - str : sqltypes.String, - unicode : sqltypes.Unicode, + str : sqltypes.VARCHAR, + unicode : sqltypes.NCHAR, int : sqltypes.Integer, float : sqltypes.Numeric, type(None):sqltypes.NullType diff --git a/lib/sqlalchemy/types.py b/lib/sqlalchemy/types.py index 71b4bbec1b..4b91a1e036 100644 --- a/lib/sqlalchemy/types.py +++ b/lib/sqlalchemy/types.py @@ -304,12 +304,14 @@ class String(TypeEngine, Concatenable): def get_search_list(self): l = super(String, self).get_search_list() - if self.length is None: + # if we are String or Unicode with no length, + # return TEXT as the highest-priority type + # to be adapted by the dialect + if self.length is None and l[0] in (String, Unicode): return (TEXT,) + l else: return l - def get_dbapi_type(self, dbapi): return dbapi.STRING diff --git a/test/dialect/oracle.py b/test/dialect/oracle.py index cbad7ced89..f993536167 100644 --- a/test/dialect/oracle.py +++ b/test/dialect/oracle.py @@ -120,6 +120,25 @@ myothertable.othername != :myothertable_othername OR EXISTS (select yay from foo "ON addresses.address_type_id = address_types_1.id WHERE addresses.user_id = :addresses_user_id ORDER BY addresses.rowid, " "address_types.rowid") +class TypesTest(SQLCompileTest): + def test_no_clobs_for_string_params(self): + """test that simple string params get a DBAPI type of VARCHAR, not CLOB. + this is to prevent setinputsizes from setting up cx_oracle.CLOBs on + string-based bind params [ticket:793].""" + + class FakeDBAPI(object): + def __getattr__(self, attr): + return attr + + dialect = oracle.OracleDialect() + dbapi = FakeDBAPI() + + b = bindparam("foo", "hello world!") + assert b.type.dialect_impl(dialect).get_dbapi_type(dbapi) == 'STRING' + + b = bindparam("foo", u"hello world!") + assert b.type.dialect_impl(dialect).get_dbapi_type(dbapi) == 'STRING' + class SequenceTest(SQLCompileTest): def test_basic(self): seq = Sequence("my_seq_no_schema") diff --git a/test/sql/testtypes.py b/test/sql/testtypes.py index 3620899672..fced14e1d7 100644 --- a/test/sql/testtypes.py +++ b/test/sql/testtypes.py @@ -4,7 +4,7 @@ import datetime, os from sqlalchemy import * from sqlalchemy import types import sqlalchemy.engine.url as url -from sqlalchemy.databases import mssql, oracle, mysql +from sqlalchemy.databases import mssql, oracle, mysql, postgres from testlib import * @@ -123,6 +123,33 @@ class AdaptTest(PersistTest): t2 = mysql.MSVarBinary() assert isinstance(dialect.type_descriptor(t1), mysql.MSVarBinary) assert isinstance(dialect.type_descriptor(t2), mysql.MSVarBinary) + + def teststringadapt(self): + """test that String with no size becomes TEXT, *all* others stay as varchar/String""" + + oracle_dialect = oracle.OracleDialect() + mysql_dialect = mysql.MySQLDialect() + postgres_dialect = postgres.PGDialect() + + for dialect, start, test in [ + (oracle_dialect, String(), oracle.OracleText), + (oracle_dialect, VARCHAR(), oracle.OracleString), + (oracle_dialect, String(50), oracle.OracleString), + (oracle_dialect, Unicode(), oracle.OracleText), + (oracle_dialect, NCHAR(), oracle.OracleString), + (mysql_dialect, String(), mysql.MSText), + (mysql_dialect, VARCHAR(), mysql.MSString), + (mysql_dialect, String(50), mysql.MSString), + (mysql_dialect, Unicode(), mysql.MSText), + (mysql_dialect, NCHAR(), mysql.MSNChar), + (postgres_dialect, String(), postgres.PGText), + (postgres_dialect, VARCHAR(), postgres.PGString), + (postgres_dialect, String(50), postgres.PGString), + (postgres_dialect, Unicode(), postgres.PGText), + (postgres_dialect, NCHAR(), postgres.PGString), + ]: + assert isinstance(start.dialect_impl(dialect), test), "wanted %r got %r" % (test, start.dialect_impl(dialect)) + class UserDefinedTest(PersistTest):