-
-
Notifications
You must be signed in to change notification settings - Fork 117
Description
Thanks for Geoalchemy2!
We're constructing ORM instances by specifying a WKT value as a string and inserting them as rows into PostgreSQL. If the ORM model instance is cached, later queries returning that row use the original string for the geometry, rather than the WKBElement
value returned from the queries.
In particular, if an instance with geometry specified like Val(..., point='POINT(0 1)')
is add
'd to a session, and code is still holding a reference to it when a later query runs, that query will return the original instance directly without updating any properties (even though the query explicitly returns the appropriate WKBElement
). Notes about the reproducer below:
- the debug logging shows that the
sa.select(Val)
query is reading and converting the WKB correctly even in theBUG
case - the printed
queried.point
is a string in theBUG
case butWKBElement
in theGOOD
ones - expiring the instance doesn't work well when using async SQLAlchemy, but expunging does
from geoalchemy2 import Geography
import sqlalchemy as sa
from sqlalchemy import orm
Base = orm.declarative_base()
class Val(Base):
__tablename__ = "val"
id = sa.Column(sa.BIGINT, primary_key=True)
point = sa.Column(Geography("POINT"))
url = sa.engine.url.URL.create(
"postgresql",
username="postgres",
password="postgres",
host="localhost",
port=5431,
database="geoalchemy_testing",
)
engine = sa.create_engine(url)
with engine.begin() as conn:
Base.metadata.drop_all(conn)
Base.metadata.create_all(conn)
def run(mode):
print(f"\n\n**** {mode=}")
with orm.Session(engine) as session:
original = Val(id=1, point="POINT(0 1)")
session.add(original)
session.flush()
if mode == "expire":
session.expire(original)
elif mode == "expunge":
session.expunge(original)
result = session.execute(sa.select(Val))
queried = result.scalars().one()
print(f"{queried.point=}\n{queried is original=}")
engine.echo = "debug"
# BUG:
run(None)
# output:
# ...
# 2022-05-26 11:40:04,357 DEBUG sqlalchemy.engine.Engine Row (1, <WKBElement at 0x10732f5b0; 01010000000000000000000000000000000000f03f>)
# queried.point='POINT(0 1)'
# queried is original=True
# ...
engine.echo = False
# GOOD:
run("expire")
# output:
# queried.point=<WKBElement at 0x102494550; 01010000000000000000000000000000000000f03f>
# queried is original=True
run("expunge")
# output:
# queried.point=<WKBElement at 0x102494490; 01010000000000000000000000000000000000f03f>
# queried is original=False
geoalchemy2==0.11.1
greenlet==1.1.2
packaging==21.3
psycopg2-binary==2.9.3
pyparsing==3.0.9
sqlalchemy==1.4.36
This may not be a Geoalchemy2 bug per-se, but it's error prone: later calls to geoalchemy2.point.to_shape(queried.point)
will explode because the input is a string, not something it understands.
Are there any SQLAlchemy tricks Geoalchemy2 can do to alleviate some of the danger here? For instance, convert to geometries specified as raw strings to be stored as WKTElement
, so that they can be at least be safely passed to to_shape
.