Visitar URL original
Support TextualSelect ORM relationship mappings by subham-hq · Pull Request #13515 · sqlalchemy/sqlalchemy · GitHub
Skip to content

Support TextualSelect ORM relationship mappings - #13515

Open
subham-hq wants to merge 1 commit into
sqlalchemy:mainfrom
subham-hq:fix/textualselect-derived-from-8326
Open

subham-hq wants to merge 1 commit into
sqlalchemy:mainfrom
subham-hq:fix/textualselect-derived-from-8326

Conversation

@subham-hq

Copy link
Copy Markdown

Summary

This change adds a concrete is_derived_from() implementation to TextualSelect so that a TextualSelect-derived subquery can participate in ORM relationship configuration without raising NotImplementedError.

The implementation intentionally returns False. A TextualSelect is backed by opaque textual SQL rather than a SQLAlchemy-maintained selectable expression tree, so SQLAlchemy cannot reliably establish structural FromClause ancestry from the text itself. Returning False provides the conservative derivation result expected by the selectable contract while allowing ORM relationship setup to continue.

This addresses #8326.

Problem

A mapped class can use a subquery derived from text(...).columns(...).subquery(...) as its selectable. When such a mapping also defines a relationship and that relationship is used while compiling a statement such as:

select(Book).join(Book.author)

ORM relationship configuration evaluates selectable ancestry through is_derived_from().

The subquery wrapper delegates that check to its underlying selectable. For a TextualSelect, the inherited implementation ultimately raises NotImplementedError, causing mapper / relationship configuration to fail during statement compilation.

On current main, the failure path is effectively:

ORM relationship configuration
    -> relationship join-condition analysis
    -> Subquery.is_derived_from(...)
    -> TextualSelect.is_derived_from(...)
    -> inherited default implementation
    -> NotImplementedError

This is the current form of the issue originally reported in #8326.

Change

TextualSelect now implements:

def is_derived_from(self, fromclause: Optional[FromClause]) -> bool:
    return False

This follows the behavior proposed in the issue discussion.

The result is deliberately conservative:

  • TextualSelect represents textual SQL and does not expose a structured selectable tree from which SQLAlchemy can safely infer table ancestry.
  • Returning False means that SQLAlchemy cannot establish that the textual selectable is structurally derived from the supplied FromClause.
  • It does not attempt to parse SQL text or infer lineage from table names embedded in the SQL string.
  • It does not imply broader automatic relationship or foreign-key inference for arbitrary textual SQL.

The change therefore satisfies the selectable API contract needed by ORM relationship configuration without introducing unsupported SQL-text introspection.

Regression Test

A regression test was added to test/orm/test_subquery_relations.py.

The test:

  1. defines an Author table;
  2. creates a TextualSelect selecting id, title, and author_id from a textual publication query;
  3. maps Book imperatively to the resulting subquery;
  4. configures a view-only Book.author relationship using an explicit primaryjoin with foreign(...);
  5. compiles select(Book).join(Book.author); and
  6. verifies the generated SQL.

Before the production change, this test fails with:

NotImplementedError

from the delegated is_derived_from() call.

After the change, the statement compiles successfully and the test passes.

Validation

The change was validated with the focused regression test and the complete related ORM test module.

Focused regression test:

PYTHONPATH=lib python -m pytest \
  test/orm/test_subquery_relations.py::TextualSelectRelationshipTest::test_join_relationship \
  -q

Result:

1 passed

Related ORM test module:

PYTHONPATH=lib python -m pytest test/orm/test_subquery_relations.py -q

Result:

81 passed

The original standalone reproduction was also rerun against the modified checkout:

PYTHONPATH=lib python /tmp/repro_8326.py

It now compiles the expected join instead of raising NotImplementedError.

Formatting and lint checks:

black --check \
  lib/sqlalchemy/sql/selectable.py \
  test/orm/test_subquery_relations.py
2 files would be left unchanged.
flake8p \
  lib/sqlalchemy/sql/selectable.py \
  test/orm/test_subquery_relations.py

Result: no lint errors.

Whitespace validation:

git diff --cached --check

Result: clean.

Changelog

Added doc/build/changelog/unreleased_21/8326.rst with usecase, orm tags describing the supported ORM mapping use case.

Scope

This PR is intentionally narrow. It implements the missing derivation contract needed for the ORM relationship configuration path described in #8326 and adds regression coverage for that behavior.

It does not attempt to infer foreign-key relationships from textual SQL or establish structural table lineage for arbitrary TextualSelect contents.

Fixes: #8326

Implement is_derived_from() for TextualSelect so ORM relationship configuration can handle textual subqueries without raising NotImplementedError.

Add a regression test covering a relationship join from a class mapped to a TextualSelect-derived subquery.

Fixes: sqlalchemy#8326
@sqla-tester

Copy link
Copy Markdown
Collaborator

Thanks! Issue #8326 is now marked code review in progress and no longer open for pull requests, so this pull request holds the review for it and another one won't land on top of your work. If this pull request is abandoned, a maintainer can put open for pull requests back on #8326 to reopen it to others.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

support TextualSelect with ORM mappings

2 participants