Skip to content

Commit 5448675

Browse files
Feat: Refine MSSQL WKB conversion and callable bind handling
1 parent fa411c7 commit 5448675

9 files changed

Lines changed: 630 additions & 188 deletions

File tree

TEST.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ Install the Python dependencies::
9595
$ pip install -r requirements.txt -r requirements-mypy.txt
9696
$ pip install psycopg2-binary pyodbc "Shapely>=1.3.0"
9797

98+
The manual requirements include ``wkb-wkt-converter>=0.6.1``, which provides
99+
the WKB/EWKB conversion helpers used by the tests and runtime bind processors.
100+
98101
The tox environments also install these full-suite dependencies from ``tox.ini``:
99102
``psycopg2-binary`` and ``pyodbc`` on CPython, ``psycopg2cffi`` on PyPy, and
100103
``Shapely`` for shape conversion tests.

geoalchemy2/admin/dialects/mssql.py

Lines changed: 109 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -625,11 +625,11 @@ def _process_wkb_value(value, extended=False):
625625
if value is None:
626626
return None
627627
if isinstance(value, WKBElement):
628-
value = value.data
628+
value = _wkb_wkt.to_wkb_no_srid(value.data) if extended else value.data
629+
elif extended:
630+
value = _wkb_wkt.to_wkb_no_srid(value)
629631
if isinstance(value, memoryview):
630632
value = value.tobytes()
631-
if extended:
632-
value = _wkb_wkt.to_wkb_no_srid(value)
633633

634634
return value
635635

@@ -739,7 +739,13 @@ def __call__(self):
739739
return value
740740

741741

742-
def _coerce_wkt_bind_clause(wkt_clause, strip_srid=False, literal=False, spatial_type=None):
742+
def _coerce_wkt_bind_clause(
743+
wkt_clause,
744+
strip_srid=False,
745+
literal=False,
746+
spatial_type=None,
747+
compiler=None,
748+
):
743749
if not hasattr(wkt_clause, "value"):
744750
return wkt_clause
745751

@@ -751,13 +757,21 @@ def _coerce_wkt_bind_clause(wkt_clause, strip_srid=False, literal=False, spatial
751757
unique=True,
752758
)
753759

760+
if compiler is not None and getattr(wkt_clause, "callable", None) is not None:
761+
return _make_mssql_callable_bind_clause(
762+
wkt_clause,
763+
compiler,
764+
_MSSQLWKTBindType(strip_srid=strip_srid, spatial_type=spatial_type),
765+
_get_mssql_dynamic_ewkt_shared_callable,
766+
)
767+
754768
return expression.type_coerce(
755769
wkt_clause,
756770
_MSSQLWKTBindType(strip_srid=strip_srid, spatial_type=spatial_type),
757771
)
758772

759773

760-
def _coerce_wkb_bind_clause(wkb_clause, extended=False, literal=False):
774+
def _coerce_wkb_bind_clause(wkb_clause, extended=False, literal=False, compiler=None):
761775
if not hasattr(wkb_clause, "value"):
762776
return wkb_clause
763777

@@ -769,6 +783,14 @@ def _coerce_wkb_bind_clause(wkb_clause, extended=False, literal=False):
769783
unique=True,
770784
)
771785

786+
if compiler is not None and getattr(wkb_clause, "callable", None) is not None:
787+
return _make_mssql_callable_bind_clause(
788+
wkb_clause,
789+
compiler,
790+
_MSSQLWKBBindType(extended=extended),
791+
_get_mssql_dynamic_ewkb_shared_callable,
792+
)
793+
772794
return expression.type_coerce(wkb_clause, _MSSQLWKBBindType(extended=extended))
773795

774796

@@ -1104,13 +1126,77 @@ def _mssql_dynamic_ewkt_bind_identifier(source_bind):
11041126
return getattr(source_bind, "_identifying_key", source_bind.key)
11051127

11061128

1107-
def _make_mssql_dynamic_ewkt_bind_clauses(wkt_clause, default_srid=0):
1129+
def _mssql_dynamic_callable_identifier(source_bind):
1130+
return (
1131+
_mssql_dynamic_ewkt_bind_identifier(source_bind),
1132+
getattr(source_bind, "callable", None),
1133+
)
1134+
1135+
1136+
def _get_mssql_dynamic_ewkt_shared_callable(wkt_clause, compiler, *, consumer_count):
1137+
callable_cache = getattr(
1138+
compiler,
1139+
"_geoalchemy2_mssql_dynamic_ewkt_callable_cache",
1140+
None,
1141+
)
1142+
if callable_cache is None:
1143+
callable_cache = {}
1144+
compiler._geoalchemy2_mssql_dynamic_ewkt_callable_cache = callable_cache
1145+
1146+
callable_key = _mssql_dynamic_callable_identifier(wkt_clause)
1147+
shared_callable = callable_cache.get(callable_key)
1148+
if shared_callable is None:
1149+
shared_callable = _MSSQLDynamicEWKTCallable(
1150+
wkt_clause.callable,
1151+
consumer_count=consumer_count,
1152+
)
1153+
callable_cache[callable_key] = shared_callable
1154+
else:
1155+
shared_callable.add_consumers(consumer_count)
1156+
return shared_callable
1157+
1158+
1159+
def _get_mssql_dynamic_ewkb_shared_callable(wkb_clause, compiler, *, consumer_count):
1160+
callable_cache = getattr(
1161+
compiler,
1162+
"_geoalchemy2_mssql_dynamic_ewkb_callable_cache",
1163+
None,
1164+
)
1165+
if callable_cache is None:
1166+
callable_cache = {}
1167+
compiler._geoalchemy2_mssql_dynamic_ewkb_callable_cache = callable_cache
1168+
1169+
callable_key = _mssql_dynamic_callable_identifier(wkb_clause)
1170+
shared_callable = callable_cache.get(callable_key)
1171+
if shared_callable is None:
1172+
shared_callable = _MSSQLDynamicEWKTCallable(
1173+
wkb_clause.callable,
1174+
consumer_count=consumer_count,
1175+
)
1176+
callable_cache[callable_key] = shared_callable
1177+
else:
1178+
shared_callable.add_consumers(consumer_count)
1179+
return shared_callable
1180+
1181+
1182+
def _make_mssql_callable_bind_clause(clause, compiler, bind_type, shared_callable_getter):
1183+
return expression.bindparam(
1184+
key=clause.key,
1185+
callable_=shared_callable_getter(clause, compiler, consumer_count=1),
1186+
required=clause.required,
1187+
type_=bind_type,
1188+
unique=getattr(clause, "unique", False),
1189+
)
1190+
1191+
1192+
def _make_mssql_dynamic_ewkt_bind_clauses(wkt_clause, default_srid=0, shared_callable=None):
11081193
text_key, srid_key = _mssql_dynamic_ewkt_bind_keys(wkt_clause)
11091194
bind_kwargs = {
11101195
"required": wkt_clause.required,
11111196
}
11121197
if getattr(wkt_clause, "callable", None) is not None:
1113-
shared_callable = _MSSQLDynamicEWKTCallable(wkt_clause.callable)
1198+
if shared_callable is None:
1199+
shared_callable = _MSSQLDynamicEWKTCallable(wkt_clause.callable)
11141200
bind_kwargs["callable_"] = shared_callable
11151201
elif not wkt_clause.required:
11161202
bind_kwargs["value"] = getattr(wkt_clause, "value", None)
@@ -1137,9 +1223,17 @@ def _get_mssql_dynamic_ewkt_bind_clauses(wkt_clause, compiler, default_srid=0):
11371223

11381224
source_identifier = _mssql_dynamic_ewkt_bind_identifier(wkt_clause)
11391225
if source_identifier not in cache:
1226+
shared_callable = None
1227+
if getattr(wkt_clause, "callable", None) is not None:
1228+
shared_callable = _get_mssql_dynamic_ewkt_shared_callable(
1229+
wkt_clause,
1230+
compiler,
1231+
consumer_count=2,
1232+
)
11401233
cache[source_identifier] = _make_mssql_dynamic_ewkt_bind_clauses(
11411234
wkt_clause,
11421235
default_srid=default_srid,
1236+
shared_callable=shared_callable,
11431237
)
11441238
return cache[source_identifier]
11451239

@@ -1191,22 +1285,11 @@ def _get_mssql_dynamic_ewkb_bind_clauses(wkb_clause, compiler, default_srid=0):
11911285
if cache_key not in cache:
11921286
shared_callable = None
11931287
if getattr(wkb_clause, "callable", None) is not None:
1194-
callable_cache = getattr(
1288+
shared_callable = _get_mssql_dynamic_ewkb_shared_callable(
1289+
wkb_clause,
11951290
compiler,
1196-
"_geoalchemy2_mssql_dynamic_ewkb_callable_cache",
1197-
None,
1291+
consumer_count=2,
11981292
)
1199-
if callable_cache is None:
1200-
callable_cache = {}
1201-
compiler._geoalchemy2_mssql_dynamic_ewkb_callable_cache = callable_cache
1202-
1203-
callable_key = _mssql_dynamic_ewkt_bind_identifier(wkb_clause)
1204-
shared_callable = callable_cache.get(callable_key)
1205-
if shared_callable is None:
1206-
shared_callable = _MSSQLDynamicEWKTCallable(wkb_clause.callable)
1207-
callable_cache[callable_key] = shared_callable
1208-
else:
1209-
shared_callable.add_consumers(2)
12101293

12111294
cache[cache_key] = _make_mssql_dynamic_ewkb_bind_clauses(
12121295
wkb_clause,
@@ -1457,6 +1540,7 @@ def _compile_mssql_geom_from_text(element, compiler, strip_srid=False, **kw):
14571540
strip_srid=strip_srid,
14581541
literal=kw.get("literal_binds", False),
14591542
spatial_type=spatial_type,
1543+
compiler=compiler,
14601544
)
14611545
compiled_wkt = compiler.process(wkt_clause, **kw)
14621546

@@ -1526,7 +1610,10 @@ def _compile_mssql_geom_from_wkb(element, compiler, extended=False, **kw):
15261610
or _should_coerce_wkb_bind_clause(clauses[0])
15271611
):
15281612
wkb_clause = _coerce_wkb_bind_clause(
1529-
clauses[0], extended=extended, literal=kw.get("literal_binds", False)
1613+
clauses[0],
1614+
extended=extended,
1615+
literal=kw.get("literal_binds", False),
1616+
compiler=compiler,
15301617
)
15311618

15321619
if kw.get("literal_binds", False) and hasattr(wkb_clause, "value"):

geoalchemy2/elements.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,6 @@ def as_wkb(self) -> WKBElement:
169169
"""Return this element as a plain :class:`WKBElement` (no SRID embedded).
170170
171171
The SRID is preserved as a Python attribute on the returned element.
172-
Unsupported WKT geometry types raise ``ValueError`` from the converter.
173172
"""
174173
wkb_bytes = _wkb_wkt.to_wkb_no_srid(self.data)
175174
return WKBElement(wkb_bytes, srid=self.srid, extended=False)
@@ -178,7 +177,6 @@ def as_ewkb(self) -> WKBElement:
178177
"""Return this element as an extended :class:`WKBElement` (SRID embedded).
179178
180179
If the element has no valid SRID, the result is equivalent to :meth:`as_wkb`.
181-
Unsupported WKT geometry types raise ``ValueError`` from the converter.
182180
"""
183181
if self.srid > 0:
184182
wkb_bytes = _wkb_wkt.to_wkb(self.data, srid=self.srid)

geoalchemy2/types/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,8 +443,9 @@ def __getattr__(self, key):
443443
try:
444444
type_ = self.type.typemap[key]
445445
except KeyError:
446+
type_name = type(self.type).__name__
446447
raise AttributeError(
447-
f"Type '{self.type}' doesn't have an attribute: '{key}'"
448+
f"Type '{type_name}' doesn't have an attribute: '{key}'"
448449
) from None
449450

450451
return CompositeElement(self.expr, key, type_)

0 commit comments

Comments
 (0)