GeoAlchemy2 0.17.0__py3-none-any.whl → 0.18.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -48,8 +48,10 @@ def _generate_stubs() -> str:
48
48
  # this file is automatically generated
49
49
  from typing import List
50
50
 
51
+ import sqlalchemy
51
52
  from sqlalchemy.sql import functions
52
53
  from sqlalchemy.sql.elements import ColumnElement
54
+ from sqlalchemy.sql.selectable import FromClause
53
55
 
54
56
  import geoalchemy2.types
55
57
 
@@ -60,8 +62,8 @@ class TableRowElement(ColumnElement):
60
62
  """The cache is disabled for this class."""
61
63
 
62
64
  def __init__(self, selectable: bool) -> None: ...
63
- @property # type: ignore[override]
64
- def _from_objects(self) -> List[bool]: ...
65
+ @property
66
+ def _from_objects(self) -> List[FromClause]: ...
65
67
  '''
66
68
  stub_file_parts = [header]
67
69
 
@@ -3,9 +3,11 @@
3
3
  import sqlalchemy
4
4
  from packaging import version
5
5
  from sqlalchemy import Column
6
+ from sqlalchemy import String
6
7
  from sqlalchemy.sql import expression
7
8
  from sqlalchemy.types import TypeDecorator
8
9
 
10
+ from geoalchemy2.elements import WKBElement
9
11
  from geoalchemy2.types import Geometry
10
12
 
11
13
  _SQLALCHEMY_VERSION_BEFORE_14 = version.parse(sqlalchemy.__version__) < version.parse("1.4")
@@ -102,3 +104,23 @@ def before_drop(table, bind, **kw):
102
104
 
103
105
  def after_drop(table, bind, **kw):
104
106
  return # pragma: no cover
107
+
108
+
109
+ def compile_bin_literal(wkb_clause):
110
+ """Compile a binary literal for WKBElement."""
111
+ wkb_data = wkb_clause.value
112
+ if isinstance(wkb_data, bytes | memoryview | WKBElement):
113
+ if isinstance(wkb_data, memoryview):
114
+ wkb_data = wkb_data.tobytes()
115
+ if isinstance(wkb_data, bytes):
116
+ wkb_data = WKBElement._wkb_to_hex(wkb_data)
117
+ elif isinstance(wkb_data, WKBElement):
118
+ wkb_data = wkb_data.desc
119
+
120
+ wkb_clause = expression.bindparam(
121
+ key=wkb_clause.key,
122
+ value=wkb_data,
123
+ type_=String(),
124
+ unique=True,
125
+ )
126
+ return wkb_clause
@@ -16,6 +16,7 @@ from geoalchemy2 import functions
16
16
  from geoalchemy2.admin.dialects.common import _check_spatial_type
17
17
  from geoalchemy2.admin.dialects.common import _format_select_args
18
18
  from geoalchemy2.admin.dialects.common import _spatial_idx_name
19
+ from geoalchemy2.admin.dialects.common import compile_bin_literal
19
20
  from geoalchemy2.admin.dialects.common import setup_create_drop
20
21
  from geoalchemy2.admin.dialects.sqlite import _SQLITE_FUNCTIONS
21
22
  from geoalchemy2.admin.dialects.sqlite import get_col_dim
@@ -383,3 +384,38 @@ def create_spatial_ref_sys_view(bind):
383
384
  FROM gpkg_spatial_ref_sys;"""
384
385
  )
385
386
  )
387
+
388
+
389
+ def _compile_GeomFromWKB_gpkg(element, compiler, *, identifier, **kw):
390
+ # Store the SRID
391
+ clauses = list(element.clauses)
392
+ try:
393
+ srid = clauses[1].value
394
+ except (IndexError, TypeError, ValueError):
395
+ srid = element.type.srid
396
+
397
+ if kw.get("literal_binds", False):
398
+ wkb_clause = compile_bin_literal(clauses[0])
399
+ prefix = "unhex("
400
+ suffix = ")"
401
+ else:
402
+ wkb_clause = clauses[0]
403
+ prefix = ""
404
+ suffix = ""
405
+
406
+ compiled = compiler.process(wkb_clause, **kw)
407
+
408
+ if srid > 0:
409
+ return "{}({}{}{}, {})".format(identifier, prefix, compiled, suffix, srid)
410
+ else:
411
+ return "{}({}{}{})".format(identifier, prefix, compiled, suffix)
412
+
413
+
414
+ @compiles(functions.ST_GeomFromWKB, "geopackage") # type: ignore
415
+ def _gpkg_ST_GeomFromWKB(element, compiler, **kw):
416
+ return _compile_GeomFromWKB_gpkg(element, compiler, identifier="GeomFromWKB", **kw)
417
+
418
+
419
+ @compiles(functions.ST_GeomFromEWKB, "geopackage") # type: ignore
420
+ def _gpkg_ST_GeomFromEWKB(element, compiler, **kw):
421
+ return _compile_GeomFromWKB_gpkg(element, compiler, identifier="GeomFromEWKB", **kw)
@@ -3,6 +3,7 @@
3
3
  from sqlalchemy.ext.compiler import compiles
4
4
 
5
5
  from geoalchemy2 import functions
6
+ from geoalchemy2.admin.dialects.common import compile_bin_literal
6
7
  from geoalchemy2.admin.dialects.mysql import after_create # noqa
7
8
  from geoalchemy2.admin.dialects.mysql import after_drop # noqa
8
9
  from geoalchemy2.admin.dialects.mysql import before_create # noqa
@@ -10,15 +11,15 @@ from geoalchemy2.admin.dialects.mysql import before_drop # noqa
10
11
  from geoalchemy2.admin.dialects.mysql import reflect_geometry_column # noqa
11
12
  from geoalchemy2.elements import WKBElement
12
13
  from geoalchemy2.elements import WKTElement
13
- from geoalchemy2.shape import to_shape
14
14
 
15
15
 
16
16
  def _cast(param):
17
17
  if isinstance(param, memoryview):
18
18
  param = param.tobytes()
19
19
  if isinstance(param, bytes):
20
- data_element = WKBElement(param)
21
- param = to_shape(data_element).wkt.encode("utf-8")
20
+ param = WKBElement(param)
21
+ if isinstance(param, WKBElement):
22
+ param = param.as_wkb().desc
22
23
  return param
23
24
 
24
25
 
@@ -72,46 +73,46 @@ register_mariadb_mapping(_MARIADB_FUNCTIONS)
72
73
 
73
74
 
74
75
  def _compile_GeomFromText_MariaDB(element, compiler, **kw):
75
- element.identifier = "ST_GeomFromText"
76
+ identifier = "ST_GeomFromText"
76
77
  compiled = compiler.process(element.clauses, **kw)
77
78
  try:
78
79
  clauses = list(element.clauses)
79
80
  data_element = WKTElement(clauses[0].value)
80
- srid = max(0, data_element.srid)
81
+ srid = data_element.srid
81
82
  if srid <= 0:
82
- srid = max(0, element.type.srid)
83
- if len(clauses) > 1 and srid > 0:
84
- clauses[1].value = srid
83
+ srid = element.type.srid
85
84
  except Exception:
86
- srid = max(0, element.type.srid)
85
+ srid = element.type.srid
87
86
 
88
87
  if srid > 0:
89
- res = "{}({}, {})".format(element.identifier, compiled, srid)
88
+ res = "{}({}, {})".format(identifier, compiled, srid)
90
89
  else:
91
- res = "{}({})".format(element.identifier, compiled)
90
+ res = "{}({})".format(identifier, compiled)
92
91
  return res
93
92
 
94
93
 
95
94
  def _compile_GeomFromWKB_MariaDB(element, compiler, **kw):
96
- element.identifier = "ST_GeomFromText"
97
-
95
+ identifier = "ST_GeomFromWKB"
96
+ # Store the SRID
97
+ clauses = list(element.clauses)
98
98
  try:
99
- clauses = list(element.clauses)
100
- data_element = WKBElement(clauses[0].value)
101
- srid = max(0, data_element.srid)
102
- if srid <= 0:
103
- srid = max(0, element.type.srid)
104
- if len(clauses) > 1 and srid > 0:
105
- clauses[1].value = srid
106
- except Exception:
107
- srid = max(0, element.type.srid)
108
- compiled = compiler.process(element.clauses, **kw)
99
+ srid = clauses[1].value
100
+ except (IndexError, TypeError, ValueError):
101
+ srid = element.type.srid
102
+
103
+ if kw.get("literal_binds", False):
104
+ wkb_clause = compile_bin_literal(clauses[0])
105
+ else:
106
+ wkb_clause = clauses[0]
107
+ prefix = "unhex("
108
+ suffix = ")"
109
+
110
+ compiled = compiler.process(wkb_clause, **kw)
109
111
 
110
112
  if srid > 0:
111
- res = "{}({}, {})".format(element.identifier, compiled, srid)
113
+ return "{}({}{}{}, {})".format(identifier, prefix, compiled, suffix, srid)
112
114
  else:
113
- res = "{}({})".format(element.identifier, compiled)
114
- return res
115
+ return "{}({}{}{})".format(identifier, prefix, compiled, suffix)
115
116
 
116
117
 
117
118
  @compiles(functions.ST_GeomFromText, "mariadb") # type: ignore
@@ -1,16 +1,29 @@
1
1
  """This module defines specific functions for MySQL dialect."""
2
2
 
3
3
  from sqlalchemy import text
4
+ from sqlalchemy.dialects.mysql.base import ischema_names as _mysql_ischema_names
4
5
  from sqlalchemy.ext.compiler import compiles
5
6
  from sqlalchemy.sql.sqltypes import NullType
6
7
 
7
8
  from geoalchemy2 import functions
8
9
  from geoalchemy2.admin.dialects.common import _check_spatial_type
9
10
  from geoalchemy2.admin.dialects.common import _spatial_idx_name
11
+ from geoalchemy2.admin.dialects.common import compile_bin_literal
10
12
  from geoalchemy2.admin.dialects.common import setup_create_drop
11
13
  from geoalchemy2.types import Geography
12
14
  from geoalchemy2.types import Geometry
13
15
 
16
+ # Register Geometry, Geography and Raster to SQLAlchemy's reflection subsystems.
17
+ _mysql_ischema_names["geometry"] = Geometry
18
+ _mysql_ischema_names["point"] = Geometry
19
+ _mysql_ischema_names["linestring"] = Geometry
20
+ _mysql_ischema_names["polygon"] = Geometry
21
+ _mysql_ischema_names["multipoint"] = Geometry
22
+ _mysql_ischema_names["multilinestring"] = Geometry
23
+ _mysql_ischema_names["multipolygon"] = Geometry
24
+ _mysql_ischema_names["geometrycollection"] = Geometry
25
+
26
+
14
27
  _POSSIBLE_TYPES = [
15
28
  "geometry",
16
29
  "point",
@@ -124,6 +137,7 @@ def after_create(table, bind, **kw):
124
137
  if (
125
138
  _check_spatial_type(col.type, (Geometry, Geography), dialect)
126
139
  and col.type.spatial_index is True
140
+ and col.computed is None
127
141
  ):
128
142
  # If the index does not exist, define it and create it
129
143
  if not [i for i in table.indexes if col in i.columns.values()]:
@@ -175,25 +189,39 @@ register_mysql_mapping(_MYSQL_FUNCTIONS)
175
189
 
176
190
 
177
191
  def _compile_GeomFromText_MySql(element, compiler, **kw):
178
- element.identifier = "ST_GeomFromText"
192
+ identifier = "ST_GeomFromText"
179
193
  compiled = compiler.process(element.clauses, **kw)
180
194
  srid = element.type.srid
181
195
 
182
196
  if srid > 0:
183
- return "{}({}, {})".format(element.identifier, compiled, srid)
197
+ return "{}({}, {})".format(identifier, compiled, srid)
184
198
  else:
185
- return "{}({})".format(element.identifier, compiled)
199
+ return "{}({})".format(identifier, compiled)
186
200
 
187
201
 
188
202
  def _compile_GeomFromWKB_MySql(element, compiler, **kw):
189
- element.identifier = "ST_GeomFromWKB"
190
- compiled = compiler.process(element.clauses, **kw)
191
- srid = element.type.srid
203
+ # Store the SRID
204
+ clauses = list(element.clauses)
205
+ try:
206
+ srid = clauses[1].value
207
+ except (IndexError, TypeError, ValueError):
208
+ srid = element.type.srid
209
+
210
+ if kw.get("literal_binds", False):
211
+ wkb_clause = compile_bin_literal(clauses[0])
212
+ prefix = "unhex("
213
+ suffix = ")"
214
+ else:
215
+ wkb_clause = clauses[0]
216
+ prefix = ""
217
+ suffix = ""
218
+
219
+ compiled = compiler.process(wkb_clause, **kw)
192
220
 
193
221
  if srid > 0:
194
- return "{}({}, {})".format(element.identifier, compiled, srid)
222
+ return "{}({}{}{}, {})".format(element.identifier, prefix, compiled, suffix, srid)
195
223
  else:
196
- return "{}({})".format(element.identifier, compiled)
224
+ return "{}({}{}{})".format(element.identifier, prefix, compiled, suffix)
197
225
 
198
226
 
199
227
  @compiles(functions.ST_GeomFromText, "mysql") # type: ignore
@@ -1,20 +1,37 @@
1
1
  """This module defines specific functions for Postgresql dialect."""
2
2
 
3
+ import sqlalchemy
4
+ from packaging import version
3
5
  from sqlalchemy import Index
4
6
  from sqlalchemy import text
7
+ from sqlalchemy.dialects.postgresql.base import ischema_names as _postgresql_ischema_names
8
+ from sqlalchemy.ext.compiler import compiles
5
9
  from sqlalchemy.sql import func
6
10
  from sqlalchemy.sql import select
7
11
 
12
+ from geoalchemy2 import functions
8
13
  from geoalchemy2.admin.dialects.common import _check_spatial_type
9
14
  from geoalchemy2.admin.dialects.common import _format_select_args
10
15
  from geoalchemy2.admin.dialects.common import _spatial_idx_name
16
+ from geoalchemy2.admin.dialects.common import compile_bin_literal
11
17
  from geoalchemy2.admin.dialects.common import setup_create_drop
12
18
  from geoalchemy2.types import Geography
13
19
  from geoalchemy2.types import Geometry
20
+ from geoalchemy2.types import Raster
21
+
22
+ _SQLALCHEMY_VERSION_BEFORE_2 = version.parse(sqlalchemy.__version__) < version.parse("2")
23
+
24
+ # Register Geometry, Geography and Raster to SQLAlchemy's reflection subsystems.
25
+ _postgresql_ischema_names["geometry"] = Geometry
26
+ _postgresql_ischema_names["geography"] = Geography
27
+ _postgresql_ischema_names["raster"] = Raster
14
28
 
15
29
 
16
30
  def check_management(column):
17
31
  """Check if the column should be managed."""
32
+ if _check_spatial_type(column.type, Raster):
33
+ # Raster columns are not managed
34
+ return _SQLALCHEMY_VERSION_BEFORE_2
18
35
  return getattr(column.type, "use_typmod", None) is False
19
36
 
20
37
 
@@ -24,27 +41,34 @@ def create_spatial_index(bind, table, col):
24
41
  postgresql_ops = {col.name: "gist_geometry_ops_nd"}
25
42
  else:
26
43
  postgresql_ops = {}
44
+ if _check_spatial_type(col.type, Raster):
45
+ col_func = func.ST_ConvexHull(col)
46
+ else:
47
+ col_func = col
27
48
  idx = Index(
28
49
  _spatial_idx_name(table.name, col.name),
29
- col,
50
+ col_func,
30
51
  postgresql_using="gist",
31
52
  postgresql_ops=postgresql_ops,
32
53
  _column_flag=True,
33
54
  )
34
- idx.create(bind=bind)
55
+ if bind is not None:
56
+ idx.create(bind=bind)
57
+ return idx
35
58
 
36
59
 
37
60
  def reflect_geometry_column(inspector, table, column_info):
38
61
  """Reflect a column of type Geometry with Postgresql dialect."""
39
- if not isinstance(column_info.get("type"), Geometry):
62
+ if not _check_spatial_type(column_info.get("type"), (Geometry, Geography, Raster)):
40
63
  return
41
64
  geo_type = column_info["type"]
42
65
  geometry_type = geo_type.geometry_type
43
66
  coord_dimension = geo_type.dimension
44
- if geometry_type.endswith("ZM"):
45
- coord_dimension = 4
46
- elif geometry_type[-1] in ["Z", "M"]:
47
- coord_dimension = 3
67
+ if geometry_type is not None:
68
+ if geometry_type.endswith("ZM"):
69
+ coord_dimension = 4
70
+ elif geometry_type[-1] in ["Z", "M"]:
71
+ coord_dimension = 3
48
72
 
49
73
  # Query to check a given column has spatial index
50
74
  if table.schema is not None:
@@ -52,31 +76,39 @@ def reflect_geometry_column(inspector, table, column_info):
52
76
  else:
53
77
  schema_part = ""
54
78
 
55
- has_index_query = """SELECT (indexrelid IS NOT NULL) AS has_index
56
- FROM (
57
- SELECT
58
- n.nspname,
59
- c.relname,
60
- c.oid AS relid,
61
- a.attname,
62
- a.attnum
63
- FROM pg_attribute a
64
- INNER JOIN pg_class c ON (a.attrelid=c.oid)
65
- INNER JOIN pg_type t ON (a.atttypid=t.oid)
66
- INNER JOIN pg_namespace n ON (c.relnamespace=n.oid)
67
- WHERE t.typname='geometry'
68
- AND c.relkind='r'
69
- ) g
70
- LEFT JOIN pg_index i ON (g.relid = i.indrelid AND g.attnum = ANY(i.indkey))
71
- WHERE relname = '{}' AND attname = '{}'{};
72
- """.format(
73
- table.name, column_info["name"], schema_part
79
+ # Check if the column has a spatial index (the regular expression checks for the column name
80
+ # in the index definition, which is required for functional indexes)
81
+ has_index_query = """SELECT EXISTS (
82
+ SELECT 1
83
+ FROM pg_class t
84
+ JOIN pg_namespace n ON n.oid = t.relnamespace
85
+ JOIN pg_index ix ON t.oid = ix.indrelid
86
+ JOIN pg_class i ON i.oid = ix.indexrelid
87
+ JOIN pg_am am ON i.relam = am.oid
88
+ WHERE
89
+ t.relname = '{table_name}'{schema_part}
90
+ AND am.amname = 'gist'
91
+ AND (
92
+ EXISTS (
93
+ SELECT 1
94
+ FROM pg_attribute a
95
+ WHERE a.attrelid = t.oid
96
+ AND a.attnum = ANY(ix.indkey)
97
+ AND a.attname = '{col_name}'
98
+ )
99
+ OR pg_get_indexdef(
100
+ ix.indexrelid
101
+ ) ~ '(^|[^a-zA-Z0-9_])("?{col_name}"?)($|[^a-zA-Z0-9_])'
102
+ )
103
+ );""".format(
104
+ table_name=table.name, col_name=column_info["name"], schema_part=schema_part
74
105
  )
75
106
  spatial_index = inspector.bind.execute(text(has_index_query)).scalar()
76
107
 
77
108
  # Set attributes
78
- column_info["type"].geometry_type = geometry_type
79
- column_info["type"].dimension = coord_dimension
109
+ if not _check_spatial_type(column_info["type"], Raster):
110
+ column_info["type"].geometry_type = geometry_type
111
+ column_info["type"].dimension = coord_dimension
80
112
  column_info["type"].spatial_index = bool(spatial_index)
81
113
 
82
114
  # Spatial indexes are automatically reflected with PostgreSQL dialect
@@ -95,7 +127,7 @@ def before_create(table, bind, **kw):
95
127
  for idx in current_indexes:
96
128
  for col in table.info["_saved_columns"]:
97
129
  if (
98
- _check_spatial_type(col.type, Geometry, dialect) and check_management(col)
130
+ _check_spatial_type(col.type, (Geometry, Raster), dialect) and check_management(col)
99
131
  ) and col in idx.columns.values():
100
132
  table.indexes.remove(idx)
101
133
  if idx.name != _spatial_idx_name(table.name, col.name) or not getattr(
@@ -124,9 +156,9 @@ def after_create(table, bind, **kw):
124
156
  stmt = stmt.execution_options(autocommit=True)
125
157
  bind.execute(stmt)
126
158
 
127
- # Add spatial indices for the Geometry and Geography columns
159
+ # Add spatial indices for the Geometry, Geography and Raster columns
128
160
  if (
129
- _check_spatial_type(col.type, (Geometry, Geography), dialect)
161
+ _check_spatial_type(col.type, (Geometry, Geography, Raster), dialect)
130
162
  and col.type.spatial_index is True
131
163
  ):
132
164
  # If the index does not exist, define it and create it
@@ -146,6 +178,9 @@ def before_drop(table, bind, **kw):
146
178
 
147
179
  # Drop the managed Geometry columns
148
180
  for col in gis_cols:
181
+ if _check_spatial_type(col.type, Raster):
182
+ # Raster columns are dropped with the table, no need to drop them separately
183
+ continue
149
184
  args = [table.schema] if table.schema else []
150
185
  args.extend([table.name, col.name])
151
186
 
@@ -160,3 +195,38 @@ def after_drop(table, bind, **kw):
160
195
  saved_cols = table.info.pop("_saved_columns", None)
161
196
  if saved_cols is not None:
162
197
  table.columns = saved_cols
198
+
199
+
200
+ def _compile_GeomFromWKB_Postgresql(element, compiler, **kw):
201
+ # Store the SRID
202
+ clauses = list(element.clauses)
203
+ try:
204
+ srid = clauses[1].value
205
+ except (IndexError, TypeError, ValueError):
206
+ srid = element.type.srid
207
+
208
+ if kw.get("literal_binds", False):
209
+ wkb_clause = compile_bin_literal(clauses[0])
210
+ prefix = "decode("
211
+ suffix = ", 'hex')"
212
+ else:
213
+ wkb_clause = clauses[0]
214
+ prefix = ""
215
+ suffix = ""
216
+
217
+ compiled = compiler.process(wkb_clause, **kw)
218
+
219
+ if srid > 0:
220
+ return "{}({}{}{}, {})".format(element.identifier, prefix, compiled, suffix, srid)
221
+ else:
222
+ return "{}({}{}{})".format(element.identifier, prefix, compiled, suffix)
223
+
224
+
225
+ @compiles(functions.ST_GeomFromWKB, "postgresql") # type: ignore
226
+ def _PostgreSQL_ST_GeomFromWKB(element, compiler, **kw):
227
+ return _compile_GeomFromWKB_Postgresql(element, compiler, **kw)
228
+
229
+
230
+ @compiles(functions.ST_GeomFromEWKB, "postgresql") # type: ignore
231
+ def _PostgreSQL_ST_GeomFromEWKB(element, compiler, **kw):
232
+ return _compile_GeomFromWKB_Postgresql(element, compiler, **kw)
@@ -4,6 +4,7 @@ import os
4
4
  from typing import Optional
5
5
 
6
6
  from sqlalchemy import text
7
+ from sqlalchemy.dialects.sqlite.base import ischema_names as _sqlite_ischema_names
7
8
  from sqlalchemy.ext.compiler import compiles
8
9
  from sqlalchemy.sql import func
9
10
  from sqlalchemy.sql import select
@@ -12,12 +13,26 @@ from geoalchemy2 import functions
12
13
  from geoalchemy2.admin.dialects.common import _check_spatial_type
13
14
  from geoalchemy2.admin.dialects.common import _format_select_args
14
15
  from geoalchemy2.admin.dialects.common import _spatial_idx_name
16
+ from geoalchemy2.admin.dialects.common import compile_bin_literal
15
17
  from geoalchemy2.admin.dialects.common import setup_create_drop
16
18
  from geoalchemy2.types import Geography
17
19
  from geoalchemy2.types import Geometry
20
+ from geoalchemy2.types import Raster
18
21
  from geoalchemy2.types import _DummyGeometry
19
22
  from geoalchemy2.utils import authorized_values_in_docstring
20
23
 
24
+ # Register Geometry, Geography and Raster to SQLAlchemy's reflection subsystems.
25
+ _sqlite_ischema_names["GEOMETRY"] = Geometry
26
+ _sqlite_ischema_names["POINT"] = Geometry
27
+ _sqlite_ischema_names["LINESTRING"] = Geometry
28
+ _sqlite_ischema_names["POLYGON"] = Geometry
29
+ _sqlite_ischema_names["MULTIPOINT"] = Geometry
30
+ _sqlite_ischema_names["MULTILINESTRING"] = Geometry
31
+ _sqlite_ischema_names["MULTIPOLYGON"] = Geometry
32
+ _sqlite_ischema_names["CURVE"] = Geometry
33
+ _sqlite_ischema_names["GEOMETRYCOLLECTION"] = Geometry
34
+ _sqlite_ischema_names["RASTER"] = Raster
35
+
21
36
 
22
37
  def load_spatialite_driver(dbapi_conn, *args):
23
38
  """Load SpatiaLite extension in SQLite connection.
@@ -171,7 +186,7 @@ def get_col_dim(col):
171
186
  """Get dimension of the column type."""
172
187
  if col.type.dimension == 4:
173
188
  dimension = "XYZM"
174
- elif col.type.dimension == 2:
189
+ elif col.type.dimension == 2 or col.type.geometry_type is None:
175
190
  dimension = "XY"
176
191
  else:
177
192
  if col.type.geometry_type.endswith("M"):
@@ -183,6 +198,9 @@ def get_col_dim(col):
183
198
 
184
199
  def create_spatial_index(bind, table, col):
185
200
  """Create spatial index on the given column."""
201
+ if col.computed is not None:
202
+ # Do not create spatial index for computed columns
203
+ return
186
204
  stmt = select(*_format_select_args(func.CreateSpatialIndex(table.name, col.name)))
187
205
  stmt = stmt.execution_options(autocommit=True)
188
206
  bind.execute(stmt)
@@ -190,6 +208,10 @@ def create_spatial_index(bind, table, col):
190
208
 
191
209
  def disable_spatial_index(bind, table, col):
192
210
  """Disable spatial indexes if present."""
211
+ if col.computed is not None:
212
+ # Do not disable spatial index for computed columns because it can not exist
213
+ return
214
+ # Check if the spatial index is enabled
193
215
  stmt = select(*_format_select_args(func.CheckSpatialIndex(table.name, col.name)))
194
216
  if bind.execute(stmt).fetchone()[0] is not None:
195
217
  stmt = select(*_format_select_args(func.DisableSpatialIndex(table.name, col.name)))
@@ -276,7 +298,7 @@ def before_create(table, bind, **kw):
276
298
  current_indexes = set(table.indexes)
277
299
  for idx in current_indexes:
278
300
  for col in table.info["_saved_columns"]:
279
- if (_check_spatial_type(col.type, Geometry, dialect)) and col in idx.columns.values():
301
+ if _check_spatial_type(col.type, Geometry, dialect) and col in idx.columns.values():
280
302
  table.indexes.remove(idx)
281
303
  if idx.name != _spatial_idx_name(table.name, col.name) or not getattr(
282
304
  col.type, "spatial_index", False
@@ -293,11 +315,17 @@ def after_create(table, bind, **kw):
293
315
  table.columns = table.info.pop("_saved_columns")
294
316
  for col in table.columns:
295
317
  # Add the managed Geometry columns with RecoverGeometryColumn()
296
- if _check_spatial_type(col.type, Geometry, dialect):
318
+ if _check_spatial_type(col.type, Geometry, dialect) and col.computed is None:
297
319
  col.type = col._actual_type
298
320
  del col._actual_type
299
321
  dimension = get_col_dim(col)
300
- args = [table.name, col.name, col.type.srid, col.type.geometry_type, dimension]
322
+ args = [
323
+ table.name,
324
+ col.name,
325
+ col.type.srid,
326
+ col.type.geometry_type or "GEOMETRY",
327
+ dimension,
328
+ ]
301
329
 
302
330
  stmt = select(*_format_select_args(func.RecoverGeometryColumn(*args)))
303
331
  stmt = stmt.execution_options(autocommit=True)
@@ -322,6 +350,9 @@ def before_drop(table, bind, **kw):
322
350
  dialect, gis_cols, regular_cols = setup_create_drop(table, bind)
323
351
 
324
352
  for col in gis_cols:
353
+ if col.computed is not None:
354
+ # Computed columns are not managed
355
+ continue
325
356
  # Disable spatial indexes if present
326
357
  disable_spatial_index(bind, table, col)
327
358
 
@@ -341,7 +372,7 @@ def after_drop(table, bind, **kw):
341
372
  # the ST_ prefix.
342
373
  _SQLITE_FUNCTIONS = {
343
374
  "ST_GeomFromEWKT": "GeomFromEWKT",
344
- "ST_GeomFromEWKB": "GeomFromEWKB",
375
+ # "ST_GeomFromEWKB": "GeomFromEWKB",
345
376
  "ST_AsBinary": "AsBinary",
346
377
  "ST_AsEWKB": "AsEWKB",
347
378
  "ST_AsGeoJSON": "AsGeoJSON",
@@ -372,3 +403,41 @@ def register_sqlite_mapping(mapping):
372
403
 
373
404
 
374
405
  register_sqlite_mapping(_SQLITE_FUNCTIONS)
406
+
407
+
408
+ def _compile_GeomFromWKB_SQLite(element, compiler, *, identifier, **kw):
409
+ element.identifier = identifier
410
+
411
+ # Store the SRID
412
+ clauses = list(element.clauses)
413
+ try:
414
+ srid = clauses[1].value
415
+ element.type.srid = srid
416
+ except (IndexError, TypeError, ValueError):
417
+ srid = element.type.srid
418
+
419
+ if kw.get("literal_binds", False):
420
+ wkb_clause = compile_bin_literal(clauses[0])
421
+ prefix = "unhex("
422
+ suffix = ")"
423
+ else:
424
+ wkb_clause = clauses[0]
425
+ prefix = ""
426
+ suffix = ""
427
+
428
+ compiled = compiler.process(wkb_clause, **kw)
429
+
430
+ if srid > 0:
431
+ return "{}({}{}{}, {})".format(identifier, prefix, compiled, suffix, srid)
432
+ else:
433
+ return "{}({}{}{})".format(identifier, prefix, compiled, suffix)
434
+
435
+
436
+ @compiles(functions.ST_GeomFromWKB, "sqlite") # type: ignore
437
+ def _SQLite_ST_GeomFromWKB(element, compiler, **kw):
438
+ return _compile_GeomFromWKB_SQLite(element, compiler, identifier="GeomFromWKB", **kw)
439
+
440
+
441
+ @compiles(functions.ST_GeomFromEWKB, "sqlite") # type: ignore
442
+ def _SQLite_ST_GeomFromEWKB(element, compiler, **kw):
443
+ return _compile_GeomFromWKB_SQLite(element, compiler, identifier="GeomFromEWKB", **kw)
@@ -535,6 +535,7 @@ def create_geo_table(context, revision, op):
535
535
  schema=op.schema,
536
536
  _namespace_metadata=op._namespace_metadata,
537
537
  _constraints_included=op._constraints_included,
538
+ **op.kw,
538
539
  )
539
540
  else:
540
541
  new_op = op