Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/23.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix two silent failures that left the suite reporting success while testing nothing. 44 protocol references could not be resolved -- the bare names `JoinSupport` and `SchemaSupport`, which carry no dot for the resolver to split, and `IndexSupport`, which stopped resolving when core's `protocols.py` became a `protocols/` package -- so the full-text-search compatibility test skipped all 11 of its parametrisations and every join test skipped, on every backend. And a `Sequence[...]` annotation was read as a relation name, because `typing.get_origin` yields `collections.abc.Sequence` rather than the concrete type the relation test matched, so a parameter asking for a list of columns received a `Sequence` object; seven expression classes were unconstructible for that reason, and the container check now matches the `collections.abc` ABCs before the relation test runs.
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,13 @@
UnsupportedFeatureError,
)
from rhosocial.activerecord.backend.dialect.mixins import ViewMixin
from rhosocial.activerecord.backend.dialect.protocols import ViewSupport
from rhosocial.activerecord.backend.expression import Column, QueryExpression, TableExpression
from rhosocial.activerecord.backend.dialect.protocols import ViewObjectSupport
from rhosocial.activerecord.backend.expression import Column, QueryExpression
from rhosocial.activerecord.backend.expression.objects import (
MaterializedView,
Table,
)
from rhosocial.activerecord.backend.expression.sources import NamedRelationRef
from rhosocial.activerecord.backend.expression.statements.ddl_view import (
CreateMaterializedViewExpression,
DropMaterializedViewExpression,
Expand Down Expand Up @@ -126,7 +131,7 @@ def _source_query(dialect):
return QueryExpression(
dialect=dialect,
select=[Column(dialect, "id")],
from_=TableExpression(dialect, "source_table"),
from_=NamedRelationRef(dialect, Table(dialect, "source_table")),
)


Expand Down Expand Up @@ -195,7 +200,9 @@ def _render_generic_create(self, dialect):
parameters.
"""
expression = CreateMaterializedViewExpression(
dialect=dialect, view_name="mv_contract", query=_source_query(dialect)
dialect=dialect,
view=MaterializedView(dialect, "mv_contract"),
query=_source_query(dialect),
)
try:
return _render(expression)
Expand Down Expand Up @@ -224,7 +231,9 @@ def test_drop_renders(self, ddl_dialect):
if not _mv_supported(ddl_dialect):
pytest.skip("backend does not advertise materialized view support")
expression = DropMaterializedViewExpression(
dialect=ddl_dialect, view_name="mv_contract", if_exists=True
dialect=ddl_dialect,
view=MaterializedView(ddl_dialect, "mv_contract"),
if_exists=True,
)
sql, params = _render(expression)
assert sql.upper().startswith("DROP "), f"DROP rendered as {sql!r}"
Expand All @@ -245,7 +254,8 @@ def test_refresh_renders_statement(self, ddl_dialect):
if not _mv_refresh_supported(ddl_dialect):
pytest.skip("backend does not advertise materialized view refresh")
expression = RefreshMaterializedViewExpression(
dialect=ddl_dialect, view_name="mv_contract"
dialect=ddl_dialect,
view=MaterializedView(ddl_dialect, "mv_contract"),
)
sql, params = _render(expression)
assert sql.strip(), "REFRESH rendered an empty statement"
Expand All @@ -260,7 +270,7 @@ def test_refresh_renders_statement(self, ddl_dialect):
class TestMaterializedViewProtocolDeclaration:
"""The capability must be reachable through the declared protocol."""

def test_probe_is_declared_by_view_support(self, ddl_dialect):
def test_probe_is_declared_by_view_object_support(self, ddl_dialect):
if not _mv_supported(ddl_dialect):
pytest.skip("backend does not advertise materialized view support")
assert isinstance(ddl_dialect, ViewSupport)
assert isinstance(ddl_dialect, ViewObjectSupport)
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from rhosocial.activerecord.base.field_proxy import FieldProxy
from rhosocial.activerecord.field import UUIDMixin
from rhosocial.activerecord.model import ActiveRecord, AsyncActiveRecord
from rhosocial.activerecord.backend.expression.objects import Table


class BareItem(ActiveRecord):
Expand Down Expand Up @@ -190,7 +191,7 @@ class SpecComment(ActiveRecord):
ForeignKeyConstraint(
None,
columns=["order_id"],
foreign_key_table="ddl_spec_orders",
foreign_key_table=Table(None, "ddl_spec_orders"),
foreign_key_columns=["id"],
on_delete=ReferentialAction.CASCADE,
name="fk_ddl_spec_comments_order",
Expand All @@ -213,7 +214,7 @@ class AsyncSpecComment(AsyncActiveRecord):
ForeignKeyConstraint(
None,
columns=["order_id"],
foreign_key_table="ddl_spec_orders",
foreign_key_table=Table(None, "ddl_spec_orders"),
foreign_key_columns=["id"],
on_delete=ReferentialAction.CASCADE,
name="fk_ddl_spec_comments_order",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,6 @@ def test_foreign_key_declaration(self, spec_comment_class):
constraint = spec_comment_class.table_constraints()[0]
assert isinstance(constraint, ForeignKeyConstraint)
assert constraint.columns == ["order_id"]
assert constraint.foreign_key_table == "ddl_spec_orders"
assert constraint.foreign_key_table.name == "ddl_spec_orders"
assert constraint.foreign_key_columns == ["id"]
assert constraint.on_delete.value == "CASCADE"
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,6 @@ async def test_foreign_key_declaration(self, async_spec_comment_class):
constraint = async_spec_comment_class.table_constraints()[0]
assert isinstance(constraint, ForeignKeyConstraint)
assert constraint.columns == ["order_id"]
assert constraint.foreign_key_table == "ddl_spec_orders"
assert constraint.foreign_key_table.name == "ddl_spec_orders"
assert constraint.foreign_key_columns == ["id"]
assert constraint.on_delete.value == "CASCADE"
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def test_common_sql_standard_features(order_fixtures):


@pytest.mark.requires_protocol(
('rhosocial.activerecord.backend.dialect.protocols.IndexSupport',
('rhosocial.activerecord.backend.dialect.protocols.FulltextIndexSupport',
'supports_fulltext_search'))
def test_fulltext_search_compatibility(annotated_query_fixtures):
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ async def test_common_sql_standard_features(async_order_fixtures):


@pytest.mark.requires_protocol(
('rhosocial.activerecord.backend.dialect.protocols.IndexSupport',
('rhosocial.activerecord.backend.dialect.protocols.FulltextIndexSupport',
'supports_fulltext_search'))
async def test_fulltext_search_compatibility(async_annotated_query_fixtures):
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from rhosocial.activerecord.testsuite.utils import requires_protocol


@requires_protocol("SchemaSupport", "supports_schema")
@requires_protocol("rhosocial.activerecord.backend.dialect.protocols.CreateSchemaSupport", "supports_schema")
def test_cross_schema_join(schema_fixtures):
"""JOIN across two schemas with WHERE + ORDER BY."""
Customer, Order = schema_fixtures
Expand All @@ -41,7 +41,7 @@ def test_cross_schema_join(schema_fixtures):
"Expected joined rows for alice to be (100, 'alice') and (300, 'alice')"


@requires_protocol("SchemaSupport", "supports_schema")
@requires_protocol("rhosocial.activerecord.backend.dialect.protocols.CreateSchemaSupport", "supports_schema")
def test_cross_schema_count_with_condition(schema_fixtures):
"""Scalar COUNT over a cross-schema join."""
Customer, Order = schema_fixtures
Expand All @@ -60,7 +60,7 @@ def test_cross_schema_count_with_condition(schema_fixtures):
assert total == 3, "Expected count over the cross-schema join to be 3"


@requires_protocol("SchemaSupport", "supports_schema")
@requires_protocol("rhosocial.activerecord.backend.dialect.protocols.CreateSchemaSupport", "supports_schema")
def test_cross_schema_writes_are_scoped(schema_fixtures):
"""save() lands in the owning schema; update/delete stay scoped by qualifiers."""
Customer, Order = schema_fixtures
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from rhosocial.activerecord.testsuite.utils import requires_protocol


@requires_protocol("SchemaSupport", "supports_schema")
@requires_protocol("rhosocial.activerecord.backend.dialect.protocols.CreateSchemaSupport", "supports_schema")
async def test_async_cross_schema_join(async_schema_fixtures):
"""JOIN across two schemas with WHERE + ORDER BY (async)."""
AsyncCustomer, AsyncOrder = async_schema_fixtures
Expand All @@ -33,7 +33,7 @@ async def test_async_cross_schema_join(async_schema_fixtures):
], "Expected joined rows for async_alice to match the seeded amounts"


@requires_protocol("SchemaSupport", "supports_schema")
@requires_protocol("rhosocial.activerecord.backend.dialect.protocols.CreateSchemaSupport", "supports_schema")
async def test_async_cross_schema_writes_are_scoped(async_schema_fixtures):
"""Writes land in the owning schema and stay scoped by qualifiers (async)."""
AsyncCustomer, AsyncOrder = async_schema_fixtures
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from rhosocial.activerecord.testsuite.utils import requires_protocol


@requires_protocol("SchemaSupport", "supports_schema")
@requires_protocol("rhosocial.activerecord.backend.dialect.protocols.CreateSchemaSupport", "supports_schema")
def test_same_named_tables_coexist(mixed_schema_fixtures):
"""Identical table names in default and SCHEMA_A hold independent rows."""
User, Order, MixedSchemaOrder = mixed_schema_fixtures
Expand All @@ -28,7 +28,7 @@ def test_same_named_tables_coexist(mixed_schema_fixtures):
assert MixedSchemaOrder.query().count() == 3, "Expected 3 rows in schema-qualified orders"


@requires_protocol("SchemaSupport", "supports_schema")
@requires_protocol("rhosocial.activerecord.backend.dialect.protocols.CreateSchemaSupport", "supports_schema")
def test_primary_key_lookup_is_namespace_scoped(mixed_schema_fixtures):
"""The same PK value resolves to different rows per namespace."""
User, Order, MixedSchemaOrder = mixed_schema_fixtures
Expand All @@ -46,7 +46,7 @@ def test_primary_key_lookup_is_namespace_scoped(mixed_schema_fixtures):
), "Expected schema-qualified order lookup to return schema-row"


@requires_protocol("SchemaSupport", "supports_schema")
@requires_protocol("rhosocial.activerecord.backend.dialect.protocols.CreateSchemaSupport", "supports_schema")
def test_update_all_and_delete_all_stay_scoped(mixed_schema_fixtures):
"""Bulk writes on one namespace never leak into the other."""
User, Order, MixedSchemaOrder = mixed_schema_fixtures
Expand All @@ -71,7 +71,7 @@ def test_update_all_and_delete_all_stay_scoped(mixed_schema_fixtures):
assert Order.query().count() == 1, "Expected default-schema count to remain at 1"


@requires_protocol("SchemaSupport", "supports_schema")
@requires_protocol("rhosocial.activerecord.backend.dialect.protocols.CreateSchemaSupport", "supports_schema")
def test_join_default_user_with_schema_order(mixed_schema_fixtures):
"""JOIN from the default namespace to a schema-qualified table."""
User, Order, MixedSchemaOrder = mixed_schema_fixtures
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from rhosocial.activerecord.testsuite.utils import requires_protocol


@requires_protocol("SchemaSupport", "supports_schema")
@requires_protocol("rhosocial.activerecord.backend.dialect.protocols.CreateSchemaSupport", "supports_schema")
async def test_async_same_named_tables_coexist(async_mixed_schema_fixtures):
"""Identical table names in default and SCHEMA_A hold independent rows."""
AsyncUser, AsyncOrder, AsyncMixedSchemaOrder = async_mixed_schema_fixtures
Expand All @@ -23,7 +23,7 @@ async def test_async_same_named_tables_coexist(async_mixed_schema_fixtures):
"Expected 1 row in schema-qualified orders"


@requires_protocol("SchemaSupport", "supports_schema")
@requires_protocol("rhosocial.activerecord.backend.dialect.protocols.CreateSchemaSupport", "supports_schema")
async def test_async_update_all_stays_scoped(async_mixed_schema_fixtures):
"""Bulk update on the schema-qualified namespace leaves defaults untouched."""
AsyncUser, AsyncOrder, AsyncMixedSchemaOrder = async_mixed_schema_fixtures
Expand All @@ -47,7 +47,7 @@ async def test_async_update_all_stays_scoped(async_mixed_schema_fixtures):
), "Expected both schema-qualified orders to be shipped"


@requires_protocol("SchemaSupport", "supports_schema")
@requires_protocol("rhosocial.activerecord.backend.dialect.protocols.CreateSchemaSupport", "supports_schema")
async def test_async_join_default_user_with_schema_order(async_mixed_schema_fixtures):
"""Async JOIN from the default namespace to a schema-qualified table."""
AsyncUser, AsyncOrder, AsyncMixedSchemaOrder = async_mixed_schema_fixtures
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def _norm(sql: str) -> str:
return re.sub(r"\s+", " ", cleaned).lower()


@requires_protocol("SchemaSupport", "supports_schema")
@requires_protocol("rhosocial.activerecord.backend.dialect.protocols.CreateSchemaSupport", "supports_schema")
def test_schema_model_qualifies_range_not_columns(mixed_schema_fixtures):
"""A schema-bound model qualifies its FROM range, never three-part cols."""
_, _, MixedSchemaOrder = mixed_schema_fixtures
Expand All @@ -29,7 +29,7 @@ def test_schema_model_qualifies_range_not_columns(mixed_schema_fixtures):
"Expected column refs to stay two-part"


@requires_protocol("JoinSupport", "supports_inner_join")
@requires_protocol("rhosocial.activerecord.backend.dialect.protocols.JoinSupport", "supports_inner_join")
def test_schema_join_columns_stay_two_part(mixed_schema_fixtures):
"""Even joined against default-schema tables, schema-model columns stay
two-part (TABLE.COLUMN) in ON/WHERE positions."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

from rhosocial.activerecord.query import CTEQuery, AsyncCTEQuery
from rhosocial.activerecord.backend.expression import statements, core, query_parts
from rhosocial.activerecord.backend.expression.objects import Table
from rhosocial.activerecord.backend.expression.sources import NamedRelationRef
from rhosocial.activerecord.testsuite.utils import requires_cte
from rhosocial.activerecord.backend.dialect.protocols import SetOperationSupport
from rhosocial.activerecord.testsuite.utils import requires_protocol
Expand Down Expand Up @@ -185,7 +187,7 @@ def test_cte_with_query_expression_as_subquery(self, order_fixtures):
query_expr = statements.QueryExpression(
dialect,
select=[core.Column(dialect, "id"), core.Column(dialect, "status"), core.Column(dialect, "total_amount")],
from_=core.TableExpression(dialect, Order.table_name()),
from_=NamedRelationRef(dialect, Table(dialect, Order.table_name())),
where=query_parts.WhereClause(dialect, condition=core.Column(dialect, "status") == core.Literal(dialect, 'active'))
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

from rhosocial.activerecord.query import CTEQuery, AsyncCTEQuery
from rhosocial.activerecord.backend.expression import statements, core, query_parts
from rhosocial.activerecord.backend.expression.objects import Table
from rhosocial.activerecord.backend.expression.sources import NamedRelationRef
from rhosocial.activerecord.testsuite.utils import requires_cte
from rhosocial.activerecord.backend.dialect.protocols import SetOperationSupport
from rhosocial.activerecord.testsuite.utils import requires_protocol
Expand Down Expand Up @@ -188,7 +190,7 @@ async def test_cte_with_query_expression_as_subquery(self, async_order_fixtures)
query_expr = statements.QueryExpression(
dialect,
select=[core.Column(dialect, "id"), core.Column(dialect, "status"), core.Column(dialect, "total_amount")],
from_=core.TableExpression(dialect, AsyncOrder.table_name()),
from_=NamedRelationRef(dialect, Table(dialect, AsyncOrder.table_name())),
where=query_parts.WhereClause(dialect, condition=core.Column(dialect, "status") == core.Literal(dialect, 'active'))
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from rhosocial.activerecord.backend.expression import functions


@requires_protocol("JoinSupport", "supports_inner_join")
@requires_protocol("rhosocial.activerecord.backend.dialect.protocols.JoinSupport", "supports_inner_join")
def test_join_group_by_count(order_fixtures):
"""Count orders per user across the join."""
User, Order, _ = order_fixtures
Expand All @@ -32,7 +32,7 @@ def test_join_group_by_count(order_fixtures):
assert counts == {"agg_alice": 2, "agg_bob": 1}


@requires_protocol("JoinSupport", "supports_inner_join")
@requires_protocol("rhosocial.activerecord.backend.dialect.protocols.JoinSupport", "supports_inner_join")
def test_join_group_by_sum_having(order_fixtures):
"""HAVING filters aggregated groups after a join."""
User, Order, _ = order_fixtures
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from rhosocial.activerecord.backend.expression import functions


@requires_protocol("JoinSupport", "supports_inner_join")
@requires_protocol("rhosocial.activerecord.backend.dialect.protocols.JoinSupport", "supports_inner_join")
async def test_join_group_by_count(async_order_fixtures):
AsyncUser, AsyncOrder, _ = async_order_fixtures
dialect = AsyncUser.__backend__.dialect
Expand All @@ -31,7 +31,7 @@ async def test_join_group_by_count(async_order_fixtures):
assert counts == {"agg_alice": 2, "agg_bob": 1}


@requires_protocol("JoinSupport", "supports_inner_join")
@requires_protocol("rhosocial.activerecord.backend.dialect.protocols.JoinSupport", "supports_inner_join")
async def test_join_group_by_sum_having(async_order_fixtures):
AsyncUser, AsyncOrder, _ = async_order_fixtures
dialect = AsyncUser.__backend__.dialect
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def _norm(sql: str) -> str:
return re.sub(r"\s+", " ", cleaned).lower()


@requires_protocol("JoinSupport", "supports_inner_join")
@requires_protocol("rhosocial.activerecord.backend.dialect.protocols.JoinSupport", "supports_inner_join")
def test_join_columns_are_table_qualified(order_fixtures):
"""ON predicates and select lists qualify columns with their table."""
User, Order, _ = order_fixtures
Expand All @@ -46,7 +46,7 @@ def test_join_columns_are_table_qualified(order_fixtures):
assert [r["order_number"] for r in rows] == ["q1"]


@requires_protocol("JoinSupport", "supports_inner_join")
@requires_protocol("rhosocial.activerecord.backend.dialect.protocols.JoinSupport", "supports_inner_join")
async def test_aliased_self_join_references_alias_only(tree_fixtures):
"""Self-join columns address the runtime alias, not the base table."""
NodeModel = tree_fixtures[0]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from rhosocial.activerecord.testsuite.utils import requires_protocol


@requires_protocol("JoinSupport", "supports_inner_join")
@requires_protocol("rhosocial.activerecord.backend.dialect.protocols.JoinSupport", "supports_inner_join")
def test_join_filtered_by_in_subquery(order_fixtures):
"""Only users having a 'big' order survive an IN-subquery filter."""
User, Order, _ = order_fixtures
Expand All @@ -29,7 +29,7 @@ def test_join_filtered_by_in_subquery(order_fixtures):
assert [r["username"] for r in rows] == ["sub_rich"]


@requires_protocol("JoinSupport", "supports_inner_join")
@requires_protocol("rhosocial.activerecord.backend.dialect.protocols.JoinSupport", "supports_inner_join")
def test_three_table_join_with_in_subquery(order_fixtures):
"""Chained joins remain composable with a subquery predicate."""
User, Order, OrderItem = order_fixtures
Expand Down
Loading
Loading