Hi,
I've stumbled across a weird problem where SQLAlchemy or GeoAlchemy generates invalid SQL for PostGIS. It appears related to GeoAlchemy, since updates work for columns that are not geometries.
I have this small example:
from geoalchemy import *
from sqlalchemy import *
from sqlalchemy.orm import sessionmaker, mapper, relationship
class Geom(object):
def __init__(self, name, wkt):
self.name = name
self.geometry = WKTSpatialElement(wkt, srid=3006)
def setup(connection_string):
engine = create_engine(connection_string, echo=True)
metadata = MetaData(engine)
geometry_table = Table('Geom', metadata,
Column('id', Integer, primary_key=True),
Column('name', String),
GeometryExtensionColumn('geometry', Geometry(srid=3006)),
)
mapper(Geom, geometry_table)
GeometryDDL(geometry_table)
metadata.create_all()
return sessionmaker(bind=engine)
if __name__ == '__main__':
from sys import argv
session = setup(argv[1])
# Create row
s = session()
s.add(Geom('One', 'POINT(0 0)'))
s.commit()
# Update name of row
s = session()
geoms = s.query(Geom)
geoms[0].name = 'Two'
s.commit()
# Update geometry of row
s = session()
geoms = s.query(Geom)
geoms[0].geometry = WKTSpatialElement('POINT(1 1)', srid=3006)
s.commit()
Running this creates a new row, updates the name of the row, but fails to update the geometry (the last session/transaction).
The problem is that the issued UPDATE statement includes the table name in the SET part, while Postgresql's documentation explicitly says you should not:
Do not include the table's name in the specification of a target column — for example, UPDATE tab SET tab.col = 1 is invalid.
(http://www.postgresql.org/docs/9.1/static/sql-update.html)
A log when running shows the problem quite clearly:
2013-03-13 11:50:50,614 INFO sqlalchemy.engine.base.Engine select version()
2013-03-13 11:50:50,614 INFO sqlalchemy.engine.base.Engine {}
2013-03-13 11:50:50,617 INFO sqlalchemy.engine.base.Engine select current_schema()
2013-03-13 11:50:50,617 INFO sqlalchemy.engine.base.Engine {}
2013-03-13 11:50:50,621 INFO sqlalchemy.engine.base.Engine select relname from pg_class c join pg_namespace n on n.oid=c.relnamespace where n.nspname=current_schema() and relname=%(name)s
2013-03-13 11:50:50,621 INFO sqlalchemy.engine.base.Engine {'name': u'Geom'}
2013-03-13 11:50:50,630 INFO sqlalchemy.engine.base.Engine
CREATE TABLE "Geom" (
id SERIAL NOT NULL,
name VARCHAR,
PRIMARY KEY (id)
)
2013-03-13 11:50:50,630 INFO sqlalchemy.engine.base.Engine {}
2013-03-13 11:50:50,710 INFO sqlalchemy.engine.base.Engine COMMIT
2013-03-13 11:50:50,727 INFO sqlalchemy.engine.base.Engine SELECT AddGeometryColumn(%(AddGeometryColumn_2)s, %(AddGeometryColumn_3)s, %(AddGeometryColumn_4)s, %(AddGeometryColumn_5)s, %(AddGeometryCol
umn_6)s, %(AddGeometryColumn_7)s) AS "AddGeometryColumn_1"
2013-03-13 11:50:50,729 INFO sqlalchemy.engine.base.Engine {'AddGeometryColumn_3': 'Geom', 'AddGeometryColumn_2': 'public', 'AddGeometryColumn_5': 3006, 'AddGeometryColumn_4': 'geometry', 'AddGeometry
Column_7': 2, 'AddGeometryColumn_6': 'GEOMETRY'}
2013-03-13 11:50:50,736 INFO sqlalchemy.engine.base.Engine COMMIT
2013-03-13 11:50:50,760 INFO sqlalchemy.engine.base.Engine CREATE INDEX "idx_Geom_geometry" ON "public"."Geom" USING GIST (geometry)
2013-03-13 11:50:50,760 INFO sqlalchemy.engine.base.Engine {}
2013-03-13 11:50:50,766 INFO sqlalchemy.engine.base.Engine COMMIT
2013-03-13 11:50:50,779 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2013-03-13 11:50:50,779 INFO sqlalchemy.engine.base.Engine INSERT INTO "Geom" (name, geometry) VALUES (%(name)s, GeomFromText(%(GeomFromText_1)s, %(GeomFromText_2)s)) RETURNING "Geom".id
2013-03-13 11:50:50,780 INFO sqlalchemy.engine.base.Engine {'GeomFromText_2': 3006, 'name': 'One', 'GeomFromText_1': 'POINT(0 0)'}
2013-03-13 11:50:50,783 INFO sqlalchemy.engine.base.Engine COMMIT
2013-03-13 11:50:50,795 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2013-03-13 11:50:50,796 INFO sqlalchemy.engine.base.Engine SELECT "Geom".id AS "Geom_id", "Geom".name AS "Geom_name", ST_AsBinary("Geom".geometry) AS "Geom_geometry"
FROM "Geom"
LIMIT %(param_1)s
2013-03-13 11:50:50,796 INFO sqlalchemy.engine.base.Engine {'param_1': 1}
2013-03-13 11:50:50,799 INFO sqlalchemy.engine.base.Engine UPDATE "Geom" SET name=%(name)s WHERE "Geom".id = %(Geom_id)s
2013-03-13 11:50:50,799 INFO sqlalchemy.engine.base.Engine {'name': 'Two', 'Geom_id': 1}
2013-03-13 11:50:50,801 INFO sqlalchemy.engine.base.Engine COMMIT
2013-03-13 11:50:50,811 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2013-03-13 11:50:50,812 INFO sqlalchemy.engine.base.Engine SELECT "Geom".id AS "Geom_id", "Geom".name AS "Geom_name", ST_AsBinary("Geom".geometry) AS "Geom_geometry"
FROM "Geom"
LIMIT %(param_1)s
2013-03-13 11:50:50,812 INFO sqlalchemy.engine.base.Engine {'param_1': 1}
2013-03-13 11:50:50,815 INFO sqlalchemy.engine.base.Engine UPDATE "Geom" SET "Geom".geometry=GeomFromText(%(GeomFromText_1)s, %(GeomFromText_2)s) WHERE "Geom".id = %(Geom_id)s
2013-03-13 11:50:50,815 INFO sqlalchemy.engine.base.Engine {'GeomFromText_1': 'POINT(1 1)', 'GeomFromText_2': 3006, 'Geom_id': 1}
2013-03-13 11:50:50,816 INFO sqlalchemy.engine.base.Engine ROLLBACK
Traceback (most recent call last):
File "test.py", line 42, in <module>
s.commit()
File "D:\Python27\lib\site-packages\sqlalchemy\orm\session.py", line 693, in commit
self.transaction.commit()
File "D:\Python27\lib\site-packages\sqlalchemy\orm\session.py", line 332, in commit
self._prepare_impl()
File "D:\Python27\lib\site-packages\sqlalchemy\orm\session.py", line 311, in _prepare_impl
self.session.flush()
File "D:\Python27\lib\site-packages\sqlalchemy\orm\session.py", line 1788, in flush
self._flush(objects)
File "D:\Python27\lib\site-packages\sqlalchemy\orm\session.py", line 1870, in _flush
flush_context.execute()
File "D:\Python27\lib\site-packages\sqlalchemy\orm\unitofwork.py", line 372, in execute
rec.execute(self)
File "D:\Python27\lib\site-packages\sqlalchemy\orm\unitofwork.py", line 525, in execute
uow
File "D:\Python27\lib\site-packages\sqlalchemy\orm\persistence.py", line 58, in save_obj
mapper, table, update)
File "D:\Python27\lib\site-packages\sqlalchemy\orm\persistence.py", line 488, in _emit_update_statements
params)
File "D:\Python27\lib\site-packages\sqlalchemy\engine\base.py", line 664, in execute
params)
File "D:\Python27\lib\site-packages\sqlalchemy\engine\base.py", line 764, in _execute_clauseelement
compiled_sql, distilled_params
File "D:\Python27\lib\site-packages\sqlalchemy\engine\base.py", line 878, in _execute_context
context)
File "D:\Python27\lib\site-packages\sqlalchemy\engine\base.py", line 871, in _execute_context
context)
File "D:\Python27\lib\site-packages\sqlalchemy\engine\default.py", line 320, in do_execute
cursor.execute(statement, parameters)
sqlalchemy.exc.ProgrammingError: (ProgrammingError) column "Geom" of relation "Geom" does not exist
LINE 1: UPDATE "Geom" SET "Geom".geometry=GeomFromText('POINT(1 1)',...
^
'UPDATE "Geom" SET "Geom".geometry=GeomFromText(%(GeomFromText_1)s, %(GeomFromText_2)s) WHERE "Geom".id = %(Geom_id)s' {'GeomFromText_1': 'POINT(1 1)', 'GeomFromText_2': 3006, 'Geom_id': 1}
Is there something I'm missing? Why is GeoAlchemy generating invalid SQL?
I'm using GeoAlchemy 0.7.1 and SQLAlchemy 0.8.0b2, and running Python 2.7. I'm using PostgreSQL 9.1.8.