Hello,

I've been trying to insert a row based on a correlated subquery that 
returns a UUID. The insert query itself returns a long, which somehow 
SQLAlchemy tries to process as a UUID/GUID.

I'm using SQLAlchemy 0.9.8, Psycopg2 2.5.4 and PostgreSQL 9.1.


Here is an example that demonstrates the problem:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import logging
import uuid

from sqlalchemy import create_engine, MetaData, Table, Column, ForeignKey, 
BigInteger, Text, select
from sqlalchemy.types import TypeDecorator, CHAR
from sqlalchemy.dialects.postgresql import UUID

# This GUID class comes from the SQLAlchemy documentation.
# 
http://docs.sqlalchemy.org/en/latest/core/types.html#backend-agnostic-guid-type
class GUID(TypeDecorator):
    """Platform-independent GUID type.

    Uses Postgresql's UUID type, otherwise uses
    CHAR(32), storing as stringified hex values.

    """
    impl = CHAR

    def load_dialect_impl(self, dialect):
        if dialect.name == 'postgresql':
            return dialect.type_descriptor(UUID())
        else:
            return dialect.type_descriptor(CHAR(32))

    def process_bind_param(self, value, dialect):
        if value is None:
            return value
        elif dialect.name == 'postgresql':
            return str(value)
        else:
            if not isinstance(value, uuid.UUID):
                return "%.32x" % uuid.UUID(value)
            else:
                # hexstring
                return "%.32x" % value

    def process_result_value(self, value, dialect):
        if value is None:
            return value
        else:
            return uuid.UUID(value)

logging.basicConfig()
logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)

metadata = MetaData()

table_a = Table('table_a',metadata,
    Column('id', GUID(), primary_key=True),
)
    
table_b = Table('table_b', metadata, 
    Column('id', BigInteger, primary_key=True),
    Column('ref_a', ForeignKey('table_a.id', onupdate='CASCADE', ondelete='SET 
NULL')),
    Column('value', Text)
)

engine = create_engine('postgresql:///test')
metadata.create_all(engine)

test_uuid = str(uuid.uuid4())

conn = engine.connect()

# INSERT INTO table_a (id) VALUES (%(id)s)
conn.execute(table_a.insert().values(id=test_uuid))

# INSERT INTO table_b (ref_a, value) VALUES (%(ref_a)s, %(value)s) 
RETURNING table_b.id
conn.execute(table_b.insert().values(ref_a=test_uuid, value='X'))

# INSERT INTO table_b (ref_a, value) 
#     VALUES ((SELECT table_a.id FROM table_a WHERE table_a.id = %(id_1)s 
LIMIT %(param_1)s), %(value)s)
find_a_stmt = select([table_a.c.id]).where(table_a.c.id == test_uuid).limit(
1)
conn.execute(table_b.insert(inline=True).values(ref_a=find_a_stmt, value='Y'
))

# INSERT INTO table_b (ref_a, value) 
#     VALUES ((SELECT table_a.id FROM table_a WHERE table_a.id = %(id_1)s 
LIMIT %(param_1)s), %(value)s)
# RETURNING table_b.id
find_a_stmt = select([table_a.c.id]).where(table_a.c.id == test_uuid).limit(
1)
conn.execute(table_b.insert().values(ref_a=find_a_stmt, value='Z'))

conn.close()





The last insert causes this error:

Traceback (most recent call last):
  File "dbtest.py", line 82, in <module>
    conn.execute(table_b.insert().values(ref_a=find_a_stmt, value='Z'))
  File 
"/tmp/dbtest/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", 
line 729, in execute
    return meth(self, multiparams, params)
  File 
"/tmp/dbtest/local/lib/python2.7/site-packages/sqlalchemy/sql/elements.py", 
line 322, in _execute_on_connection
    return connection._execute_clauseelement(self, multiparams, params)
  File 
"/tmp/dbtest/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", 
line 826, in _execute_clauseelement
    compiled_sql, distilled_params
  File 
"/tmp/dbtest/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", 
line 978, in _execute_context
    context._fetch_implicit_returning(result)
  File 
"/tmp/dbtest/local/lib/python2.7/site-packages/sqlalchemy/engine/default.py"
, line 815, in _fetch_implicit_returning
    ipk.append(row[c])
  File 
"/tmp/dbtest/local/lib/python2.7/site-packages/sqlalchemy/sql/type_api.py", 
line 915, in process
    return process_value(value, dialect)
  File "dbtest.py", line 44, in process_result_value
    return uuid.UUID(value)
  File "/usr/lib/python2.7/uuid.py", line 131, in __init__
    hex = hex.replace('urn:', '').replace('uuid:', '')
AttributeError: 'long' object has no attribute 'replace'





It appears that it is trying to process the result of "RETURNING 
table_b.id" as a GUID, as if it was expecting the type of the result of the 
subquery statement.
The workaround I've found relies on using insert(inline=True), which is 
fine when the returned value is not needed, but the default is inline=False.

I'm just wondering whether I'm not using the type decorator correctly and 
this behaviour is expected, or whether this is a due to a bug.

(This problem doesn't seem to happen if table_a.id is of type Text instead 
of GUID.)


Best wishes,

Bruno.

-- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sqlalchemy+unsubscr...@googlegroups.com.
To post to this group, send email to sqlalchemy@googlegroups.com.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

Reply via email to