- changed component to sql
- changed title to support positional bind params at the compiled SQL expression level
- changed milestone to blue sky
- marked as enhancement
primary key values are ignored when using insert(values=<a tuple>)
Using sqlalchemy 0.3.11.
The following test case works for databases which have positional input parameters (sqlite), but the test case fails for database which have named input parameters (Oracle). The test case takes a db uri as a parameter to the script.
#!/usr/bin/env python
from sys import argv
from sqlalchemy import *
db_uri = argv[1](1)
metadata = BoundMetaData(db_uri)
hubs_t = Table('hubs', metadata,
Column('hub_id', SmallInteger, primary_key=True),
Column('hub_code', String(2), nullable=False),
Column('hub_name', String(16), nullable=False),
Column('tz', String(16), nullable=False))
metadata.create_all()
hubs = Table('hubs', metadata, mustexist=True)
session = create_session()
metadata.engine.echo = True
hubs.insert(values=(1, 'us', 'US', 'US/Eastern')).execute()
Here are the successful results with sqlite:
$ ./test_case.py sqlite://
2008-01-25 11:10:57,534 INFO sqlalchemy.engine.base.Engine.0x..50 INSERT INTO hubs (hub_id, hub_code, hub_name, tz) VALUES (?, ?, ?, ?)
2008-01-25 11:10:57,534 INFO sqlalchemy.engine.base.Engine.0x..50 ['us', 'US', 'US/Eastern'](1,)
2008-01-25 11:10:57,534 INFO sqlalchemy.engine.base.Engine.0x..50 COMMIT
Here is the unsuccessful run with oracle:
$ ./test_case.py oracle://username:password@INSTANCE
2008-01-25 11:12:13,869 INFO sqlalchemy.engine.base.Engine.0x..90 INSERT INTO hubs (hub_id, hub_code, hub_name, tz) VALUES (:hub_id, :hub_code, :hub_name, :tz)
2008-01-25 11:12:13,869 INFO sqlalchemy.engine.base.Engine.0x..90 {'hub_id': None, 'hub_code': 'us', 'tz': 'US/Eastern', 'hub_name': 'US'}
2008-01-25 11:12:13,889 INFO sqlalchemy.engine.base.Engine.0x..90 ROLLBACK
Traceback (most recent call last):
File "./test_case.py", line 18, in <module>
hubs.insert(values=(1, 'us', 'US', 'US/Eastern')).execute()
File "/opt/cnet-python/modules/cnet-python-sqlalchemy/0.3.11-2_64el5/lib/sqlalchemy/sql.py", line 1208, in execute
return self.compile(bind=self.bind, parameters=compile_params).execute(*multiparams, **params)
File "/opt/cnet-python/modules/cnet-python-sqlalchemy/0.3.11-2_64el5/lib/sqlalchemy/sql.py", line 1098, in execute
return e.execute_compiled(self, *multiparams, **params)
File "/opt/cnet-python/modules/cnet-python-sqlalchemy/0.3.11-2_64el5/lib/sqlalchemy/engine/base.py", line 780, in execute_compiled
return connection.execute_compiled(compiled, *multiparams, **params)
File "/opt/cnet-python/modules/cnet-python-sqlalchemy/0.3.11-2_64el5/lib/sqlalchemy/engine/base.py", line 568, in execute_compiled
self._execute_raw(context)
File "/opt/cnet-python/modules/cnet-python-sqlalchemy/0.3.11-2_64el5/lib/sqlalchemy/engine/base.py", line 581, in _execute_raw
self._execute(context)
File "/opt/cnet-python/modules/cnet-python-sqlalchemy/0.3.11-2_64el5/lib/sqlalchemy/engine/base.py", line 599, in _execute
raise exceptions.SQLError(context.statement, context.parameters, e)
sqlalchemy.exceptions.SQLError: (DatabaseError) ORA-01400: cannot insert NULL into ("UMPIRE"."HUBS"."HUB_ID")
'INSERT INTO hubs (hub_id, hub_code, hub_name, tz) VALUES (:hub_id, :hub_code, :hub_name, :tz)' {'hub_id': None, 'hub_code': 'us', 'tz': 'US/Eastern', 'hub_name': 'US'}
Note how the hub_id value gets transformed from a value of 1 to a value of None.
At the earlier points, the 1 is in the data:
> /opt/cnet-python/site-packages-2.5/sqlalchemy/sql.py(3182)_process_colparams()
-> return parameters
(Pdb) p parameters
{'hub_id': _BindParamClause('hub_id', 1, type=SmallInteger()), 'tz': _BindParamClause('tz', 'US/Eastern', type=String(length=16,convert_unicode=False)), 'hub_name': _BindParamClause('hub_name', 'US', type=String(length=16,convert_unicode=False)), 'hub_code': _BindParamClause('hub_code', 'us', type=String(length=2,convert_unicode=False))}
However, after the compile method on line 1065 of sqlalchemy/sql.py runs, then the data is transformed so that hub_id's value becomes None.
I stepped through the code a bit, and I think it may be related to the fact that hub_id is marked as the Primary Key: I saw reference to primary_key in the code that was went through. I also saw a dictionary with the value {'hub_id':None} being passed around. I think the parameters object and this one-item dictionary may get merged at one point, and this {'hub_id':None} overwrote the 'hub_id': _BindParamClause('hub_id', 1, type=SmallInteger()) value.
I have a work-around to this, so this isn't an urgent fix. For a workaround, I am doing:
def tcols(table, *values):
"""Construct a dict of column names/values. Values are supplied
in table column order."""
return dict(zip(table.c.keys(), values))
i = hubs.insert()
i.execute(tcols(hubs, 1, 'us', 'US', 'US/Eastern'))
FYI, I am using this code to pre-populate a database with test data. That would initialize the database with config data before running unit tests on code that's actually using ORM code.
Comments (8)
-
repo owner -
Account Deleted Replying to zzzeek:
values
is not supposed to accept a tuple in any case; we only support named parameters at the compiled SQL construct level.If 'values' does not support a tuple, then there is a documentation error; the documentation says it's supported. See:
http://www.sqlalchemy.org/docs/03/sqlconstruction.html#sql_insert
# INSERT with a list: SQLusers.insert(values=(3, 'jane', 'sdfadfas')).execute()
If it's not intended to be supported, then the documentation should be updated. I don't really need the tuple feature to be supported; I only used it because it was mentioned in the documentation, and for writing this loadTestData code, it was easier to write the test data I was loading as a list of values, rather than typing in the field names for every row I inserted. That's why for the work-around, I wrote that little tcols one-liner function to make it easier to load the test data.
Thanks.
-
repo owner - changed title to tableobject.insert(values=tupleobject).execute() bug
- removed milestone
- changed status to wontfix
wow, I apologize, you are absolutely correct. I had forgotten about this particular behavior.
The issue with your oracle test is that you haven't defined a Sequence for your table.
hubs_t = Table('hubs', metadata, Column('hub_id', SmallInteger, Sequence('some_seq'), primary_key=True), Column('hub_code', String(2), nullable=False), Column('hub_name', String(16), nullable=False), Column('tz', String(16), nullable=False))
once you do that, and ensure that the sequence is created in oracle (which SA will do for you if its also issuing CREATE TABLE) you'll find that the test works on both 0.3 and 0.4.
-
repo owner - removed status
- changed status to open
-
repo owner just tested with oracle and 0.3/0.4, and it is in fact a bug in 0.3 regarding PK. 0.4 works fine. I'd suggest upgrading to version 0.4.
-
repo owner - changed status to resolved
- changed title to primary key values are ignored when using insert(values=<a tuple>)
this issue only exists in version 0.3 and appears to be limited only to Oracle (and I'd guess possibly Firebird which has a similar PK mechanism). Since compilation and execution has been reworked in 0.4 im marking this as fixed for now.
after that, I'm going to quit being drunk all day...
-
Account Deleted Thanks. I decided the next step I'll do is upgrade my app to use 0.4. I've been doing development on this app using sqlite. I've just now started integration testing with Oracle instead of sqlite. Since I now have the test data loaded in Oracle, I tried running my app's unit tests. Of the 15 unit tests which all succeeded on sqlite, 9 failed on Oracle with unique constraint violations. So, it may be PK-related. I'm going to upgrade to 0.4 and see if that solves the problems, before digging in to see if I have any problems in my code.
-
Account Deleted I upgraded my application to use sqlalchemy 0.4.2p3, and now all 15 unit tests pass with both sqlite and Oracle.
I updated the WhatsNewIn04 wiki page with some information from my experience in upgrading my app from 0.3 to 0.4.
- Log in to comment
values
is not supposed to accept a tuple in any case; we only support named parameters at the compiled SQL construct level. I doubt the argument would work in 0.4 as a tuple. You can only send positional arguments to theexecute()
method with non-compiled SQL statement, and they will only work with databases which accept positional arguments since they are passed straight through.changing this to an enhancement request. If positional parameters are to be supported, the feature needs to be considered in its totality, including
positional_bind(1)
,text()
constructs being able to accept?
as a bind param in addition to:bindparam
, etc.