Skip to content

Commit 43506f8

Browse files
committed
fix: Compatibility with postgres 11, and allow specifying zero objects.
1 parent b7c0d8c commit 43506f8

File tree

7 files changed

+80
-49
lines changed

7 files changed

+80
-49
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## 0.15
44

5+
### 0.15.12
6+
- fix: Enable registering zero objects (enable without declaring anything).
7+
- fix: Add explicit relkind casts to ensure compatibility with postgres 11.
8+
59
### 0.15.11
610
- fix: Exclude extension-created objects from comparisons.
711
- fix: Quote audit column names.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "sqlalchemy-declarative-extensions"
3-
version = "0.15.11"
3+
version = "0.15.12"
44
authors = [
55
{name = "Dan Cardin", email = "ddcardin@gmail.com"},
66
]

src/sqlalchemy_declarative_extensions/api.py

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -224,41 +224,41 @@ def register_create_events(
224224
from sqlalchemy_declarative_extensions.trigger.ddl import trigger_ddl
225225
from sqlalchemy_declarative_extensions.view.ddl import view_ddl
226226

227-
concrete_schemas = metadata.info.get("schemas")
228-
concrete_roles = metadata.info.get("roles")
229-
concrete_grants = metadata.info.get("grants")
230-
concrete_views = Views.extract(metadata)
231-
concrete_procedures = metadata.info.get("procedures")
232-
concrete_functions = metadata.info.get("functions")
233-
concrete_triggers = metadata.info.get("triggers")
234-
concrete_databases = metadata.info.get("databases")
235-
concrete_rows = metadata.info.get("rows")
236-
237-
if concrete_databases and databases:
227+
concrete_schemas = metadata.info.get("schemas") or Schemas()
228+
concrete_roles = metadata.info.get("roles") or Roles()
229+
concrete_grants = metadata.info.get("grants") or Grants()
230+
concrete_views = Views.extract(metadata) or Views()
231+
concrete_procedures = metadata.info.get("procedures") or Procedures()
232+
concrete_functions = metadata.info.get("functions") or Functions()
233+
concrete_triggers = metadata.info.get("triggers") or Triggers()
234+
concrete_databases = metadata.info.get("databases") or Databases()
235+
concrete_rows = metadata.info.get("rows") or Rows()
236+
237+
if databases:
238238
database_filter = databases if isinstance(databases, list) else None
239239
event.listen(
240240
metadata,
241241
"before_create",
242242
database_ddl(concrete_databases, database_filter),
243243
)
244244

245-
if concrete_schemas and schemas:
245+
if schemas:
246246
schema_filter = schemas if isinstance(schemas, list) else None
247247
event.listen(
248248
metadata,
249249
"before_create",
250250
schema_ddl(concrete_schemas, schema_filter),
251251
)
252252

253-
if concrete_roles and roles:
253+
if roles:
254254
role_filter = roles if isinstance(roles, list) else None
255255
event.listen(
256256
metadata,
257257
"before_create",
258258
role_ddl(concrete_roles, role_filter),
259259
)
260260

261-
if concrete_grants and grants:
261+
if grants:
262262
event.listen(
263263
metadata,
264264
"before_create",
@@ -267,39 +267,39 @@ def register_create_events(
267267
# There should(?) be no need to handle dropping for grants,
268268
# they will be handled directly by table handling.
269269

270-
if concrete_views and views:
270+
if views:
271271
view_filter = views if isinstance(views, list) else None
272272
event.listen(
273273
metadata,
274274
"after_create",
275275
view_ddl(concrete_views, view_filter),
276276
)
277277

278-
if concrete_procedures and procedures:
278+
if procedures:
279279
procedure_filter = procedures if isinstance(procedures, list) else None
280280
event.listen(
281281
metadata,
282282
"after_create",
283283
procedure_ddl(concrete_procedures, procedure_filter),
284284
)
285285

286-
if concrete_functions and functions:
286+
if functions:
287287
function_filter = functions if isinstance(functions, list) else None
288288
event.listen(
289289
metadata,
290290
"after_create",
291291
function_ddl(concrete_functions, function_filter),
292292
)
293293

294-
if concrete_triggers and triggers:
294+
if triggers:
295295
trigger_filter = triggers if isinstance(triggers, list) else None
296296
event.listen(
297297
metadata,
298298
"after_create",
299299
trigger_ddl(concrete_triggers, trigger_filter),
300300
)
301301

302-
if concrete_rows and rows:
302+
if rows:
303303
row_filter = rows if isinstance(rows, list) else None
304304
event.listen(
305305
metadata,

src/sqlalchemy_declarative_extensions/dialects/postgresql/schema.py

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
from typing import Collection
2+
13
from sqlalchemy import (
4+
BindParameter,
25
String,
36
and_,
47
bindparam,
@@ -17,6 +20,11 @@
1720
char = CHAR(1)
1821

1922

23+
def char_literals(*literals: str) -> Collection[BindParameter]:
24+
"""Create a collection of CHAR literals."""
25+
return [literal(lit, char) for lit in literals]
26+
27+
2028
tables = table(
2129
"tables",
2230
column("table_schema"),
@@ -209,6 +217,8 @@ def _not_from_extension(obj_id_col, class_name):
209217
)
210218
)
211219

220+
object_acl_relkinds = char_literals("r", "S", "f", "n", "T", "v")
221+
namespace_acl_relkind = literal("n", char)
212222
object_acl_query = union(
213223
select(
214224
pg_namespace.c.nspname.label("schema"),
@@ -222,24 +232,13 @@ def _not_from_extension(obj_id_col, class_name):
222232
pg_roles, pg_class.c.relowner == pg_roles.c.oid
223233
)
224234
)
225-
.where(
226-
pg_class.c.relkind.cast(char).in_(
227-
[
228-
literal("r", char),
229-
literal("S", char),
230-
literal("f", char),
231-
literal("n", char),
232-
literal("T", char),
233-
literal("v", char),
234-
]
235-
)
236-
)
235+
.where(pg_class.c.relkind.cast(char).in_(object_acl_relkinds))
237236
.where(_table_not_pg)
238237
.where(_schema_not_pg()),
239238
select(
240239
literal(None).label("schema"),
241240
pg_namespace.c.nspname.label("name"),
242-
literal("n").cast(char).label("relkind"),
241+
namespace_acl_relkind.label("relkind"),
243242
pg_roles.c.rolname.label("owner"),
244243
pg_namespace.c.nspacl.cast(ARRAY(String)),
245244
)
@@ -257,34 +256,25 @@ def _not_from_extension(obj_id_col, class_name):
257256
.select_from(
258257
pg_class.join(pg_namespace, pg_class.c.relnamespace == pg_namespace.c.oid)
259258
)
260-
.where(
261-
pg_class.c.relkind.cast(char).in_(
262-
[
263-
literal("r", char),
264-
literal("S", char),
265-
literal("f", char),
266-
literal("n", char),
267-
literal("T", char),
268-
literal("v", char),
269-
]
270-
)
271-
)
259+
.where(pg_class.c.relkind.cast(char).in_(object_acl_relkinds))
272260
.where(_table_not_pg)
273261
.where(_schema_not_pg())
274262
.where(_schema_not_from_extension())
275263
)
276264

265+
view_relkinds = char_literals("v", "m")
266+
materialized_relkind = literal("m", char)
277267
views_query = (
278268
select(
279269
pg_namespace.c.nspname.label("schema"),
280270
pg_class.c.relname.label("name"),
281271
func.pg_get_viewdef(pg_class.c.oid).label("definition"),
282-
(pg_class.c.relkind == literal("m")).label("materialized"),
272+
(pg_class.c.relkind.cast(char) == materialized_relkind).label("materialized"),
283273
)
284274
.select_from(
285275
pg_class.join(pg_namespace, pg_class.c.relnamespace == pg_namespace.c.oid)
286276
)
287-
.where(pg_class.c.relkind.in_(["v", "m"]))
277+
.where(pg_class.c.relkind.cast(char).in_(view_relkinds))
288278
.where(_schema_not_pg(pg_namespace.c.nspname))
289279
.where(_schema_not_from_extension())
290280
.where(_not_from_extension(pg_class.c.oid, "pg_class"))
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import pytest
2+
from pytest_mock_resources import PostgresConfig, create_postgres_fixture
3+
from sqlalchemy import MetaData, text
4+
5+
from sqlalchemy_declarative_extensions import register_sqlalchemy_events
6+
from sqlalchemy_declarative_extensions.api import declare_database
7+
from sqlalchemy_declarative_extensions.role.base import Roles
8+
9+
metadata = MetaData()
10+
declare_database(metadata, roles=Roles(ignore_unspecified=True))
11+
12+
13+
register_sqlalchemy_events(
14+
metadata,
15+
functions=True,
16+
triggers=True,
17+
views=True,
18+
schemas=True,
19+
roles=True,
20+
grants=True,
21+
procedures=True,
22+
)
23+
24+
25+
@pytest.fixture
26+
def pmr_postgres_config():
27+
return PostgresConfig(port=None, image="postgres:11")
28+
29+
30+
pg = create_postgres_fixture(engine_kwargs={"echo": True}, session=True)
31+
32+
33+
def test_create(pg):
34+
pg.execute(text("CREATE VIEW foo AS SELECT 1"))
35+
pg.commit()
36+
37+
metadata.create_all(bind=pg.connection())

tests/view/test_sqlalchemy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class Baz:
5555
id = Column(types.Integer(), primary_key=True)
5656

5757

58-
register_sqlalchemy_events(Base.metadata, schemas=True, views=True, rows=True)
58+
register_sqlalchemy_events(Base.metadata, schemas=False, views=True, rows=True)
5959

6060
pg = create_postgres_fixture(
6161
scope="function", engine_kwargs={"echo": True}, session=True

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)