Engine

class lsst.daf.relation.sql.Engine(*, name: str = 'sql', functions: dict[str, _F] = <factory>, relation_name_counter: int = 0)

Bases: GenericConcreteEngine[Callable[…, ColumnElement[Any]]], Generic[_L]

A concrete engine class for relations backed by a SQL database.

See the sql module documentation for details.

Attributes Summary

EMPTY_COLUMNS_NAME

Name of the column added to a SQL SELECT query in order to represent relations that have no real columns.

name

Name of the engine; primarily used for display purposes (str).

relation_name_counter

An integer counter used to generate relation names (int).

Methods Summary

append_binary(operation, lhs, rhs)

Hook for maintaining the engine's conform invariants through BinaryOperation.apply.

append_unary(operation, target)

Hook for maintaining the engine's conform invariants through UnaryOperation.apply.

backtrack_unary(operation, tree, preferred)

Attempt to insert a unary operation in another engine upstream of this one by via operation commutators.

conform(relation)

Ensure a relation tree satisfies this engine's invariants.

convert_column_expression(expression, ...)

Convert a ColumnExpression to a logical column.

convert_column_literal(value)

Convert a Python literal value to a logical column.

convert_flattened_predicate(predicate, ...)

Flatten all logical AND operators in a Predicate and convert each to a boolean SQLAlchemy expression.

convert_predicate(predicate, columns_available)

Convert a Predicate to a SQLAlchemy expression.

convert_sort_term(term, columns_available)

Convert a SortTerm to a SQLAlchemy expression.

expect_column_scalar(logical_column)

Convert a logical column value to a SQLAlchemy expression.

extract_mapping(tags, sql_columns)

Extract a mapping with ColumnTag keys and logical column values from a SQLAlchemy column collection.

get_doomed_payload(columns)

Return a payload for a leaf relation that has no rows.

get_function(name)

Return the named column expression function.

get_identifier(tag)

Return the SQL identifier that should be used to represent the given column.

get_join_identity_payload()

Return a payload for a leaf relation that is the join identity.

get_relation_name([prefix])

Return a name suitable for a new relation in this engine.

handle_empty_columns(columns)

Handle the edge case where a SELECT statement has no columns, by adding a literal column that should be ignored.

make_doomed_relation(columns, messages[, name])

Construct a leaf relation with no rows and one or more messages explaining why.

make_join_identity_relation([name])

Construct a leaf relation with no columns and exactly one row.

make_leaf(columns, payload, *[, min_rows, ...])

Create a nontrivial leaf relation in this engine.

materialize(target[, name, name_prefix])

Mark that a target relation's payload should be cached.

select_items(items, sql_from, *extra)

Construct a SQLAlchemy representation of a SELECT query.

to_executable(relation[, extra_columns])

Convert a relation tree to an executable SQLAlchemy expression.

to_payload(relation)

Internal recursive implementation of to_executable.

transfer(target[, payload])

Mark that a relation's payload should be transferred from some other engine to this one.

Attributes Documentation

EMPTY_COLUMNS_NAME: ClassVar[str] = 'IGNORED'

Name of the column added to a SQL SELECT query in order to represent relations that have no real columns.

name: str = 'sql'

Name of the engine; primarily used for display purposes (str).

relation_name_counter: int = 0

An integer counter used to generate relation names (int).

Methods Documentation

append_binary(operation: BinaryOperation, lhs: Relation, rhs: Relation) Select

Hook for maintaining the engine’s conform invariants through BinaryOperation.apply.

This method should only be called by BinaryOperation.apply and the engine’s own methods and helper classes. External code should call BinaryOperation.apply or a Relation factory method instead.

Parameters:
operationBinaryOperation

Operation to apply; should already be filtered through BinaryOperation._begin_apply.

lhsRelation

One relation to apply the operation to directly.

rhsRelation

The other relation to apply the operation to directly.

Returns:
relationRelation

Relation that includes the given operation acting on lhs and rhs, or a simplified equivalent.

Notes

Implementations should delegate back to UnaryOperation._finish_apply to actually create a UnaryOperationRelation and perform final simplification and checks. This is all the default implementation does.

append_unary(operation: UnaryOperation, target: Relation) Select

Hook for maintaining the engine’s conform invariants through UnaryOperation.apply.

This method should only be called by UnaryOperation.apply and the engine’s own methods and helper classes. External code should call UnaryOperation.apply or a Relation factory method instead.

Parameters:
operationUnaryOperation

Operation to apply; should already be filtered through UnaryOperation._begin_apply.

targetRelation

Relation to apply the operation to directly.

Returns:
relationRelation

Relation that includes the given operation acting on target, or a simplified equivalent.

Notes

Implementations should delegate back to UnaryOperation._finish_apply to actually create a UnaryOperationRelation and perform final simplification and checks. This is all the default implementation does.

backtrack_unary(operation: UnaryOperation, tree: Relation, preferred: Engine) tuple[Relation, bool, tuple[str, ...]]

Attempt to insert a unary operation in another engine upstream of this one by via operation commutators.

Parameters:
operationUnaryOperation

Unary operation to apply.

treeRelation

Relation tree the operation logically acts on; any upstream insertion of the given operation should be equivalent to applying it to the root of this tree. Caller guarantees that tree.engine == self.

preferredEngine

Engine in which the operation or its commuted equivalent should be performed.

Returns:
new_treeRelation

Possibly-updated relation tree.

donebool

If True, the operation has been fully inserted upstream in the preferred engine. If False, either tree was returned unmodified or only a part of the operation (e.g. a projection whose columns are superset of the given projection’s) was inserted upstream.

messagesSequence [ str ]

Messages explaining why backtracking insertion was unsuccessful or incomplete. Should be sentences with no trailing . and no capitalization; they will be joined with semicolons.

conform(relation: Relation) Select

Ensure a relation tree satisfies this engine’s invariants.

This can include reordering operations (in a way consistent with their commutators) and/or inserting MarkerRelation nodes.

Parameters:
relationRelation

Original relation tree.

Returns:
conformedRelation

Relation tree that satisfies this engine’s invariants.

Notes

The default implementation returns the given relation. Engines with a non-trivial conform implementation should always call it on any relations they are passed, as algorithms that process the relation tree are not guaranteed to maintain those invariants themselves. It is recommended to use a custom MarkerRelation to indicate trees that satisfy invariants, allowing the corresponding conform implementation to short-circuit quickly.

convert_column_expression(expression: ColumnExpression, columns_available: Mapping[ColumnTag, _L]) _L

Convert a ColumnExpression to a logical column.

Parameters:
expressionColumnExpression

Expression to convert.

columns_availableMapping

Mapping from ColumnTag to logical column, typically produced by extract_mapping or obtained from Payload.columns_available.

Returns:
logical_column

SQLAlchemy expression object or other logical column value.

See also

Logical Columns
convert_column_literal(value: Any) _L

Convert a Python literal value to a logical column.

Parameters:
value

Python value to convert.

Returns:
logical_column

SQLAlchemy expression object or other logical column value.

See also

Logical Columns

Notes

This method must be overridden to support a custom logical columns.

convert_flattened_predicate(predicate: Predicate, columns_available: Mapping[ColumnTag, _L]) list[sqlalchemy.sql.elements.ColumnElement]

Flatten all logical AND operators in a Predicate and convert each to a boolean SQLAlchemy expression.

Parameters:
predicatePredicate

Predicate to convert.

columns_availableMapping

Mapping from ColumnTag to logical column, typically produced by extract_mapping or obtained from Payload.columns_available.

Returns:
sqllist [ sqlalchemy.sql.ColumnElement ]

List of boolean SQLAlchemy expressions to be combined with the AND operator.

convert_predicate(predicate: Predicate, columns_available: Mapping[ColumnTag, _L]) ColumnElement

Convert a Predicate to a SQLAlchemy expression.

Parameters:
predicatePredicate

Predicate to convert.

columns_availableMapping

Mapping from ColumnTag to logical column, typically produced by extract_mapping or obtained from Payload.columns_available.

Returns:
sqlsqlalchemy.sql.ColumnElement

Boolean SQLAlchemy expression.

convert_sort_term(term: SortTerm, columns_available: Mapping[ColumnTag, _L]) ColumnElement

Convert a SortTerm to a SQLAlchemy expression.

Parameters:
termSortTerm

Sort term to convert.

columns_availableMapping

Mapping from ColumnTag to logical column, typically produced by extract_mapping or obtained from Payload.columns_available.

Returns:
sqlsqlalchemy.sql.ColumnElement

Scalar SQLAlchemy expression.

expect_column_scalar(logical_column: _L) ColumnElement

Convert a logical column value to a SQLAlchemy expression.

Parameters:
logical_column

SQLAlchemy expression object or other logical column value.

Returns:
sqlsqlalchemy.sql.ColumnElement

SQLAlchemy expression object.

See also

Logical Columns

Notes

The default implementation assumes the logical column type is just a SQLAlchemy type and returns the given object unchanged. Subclasses with a custom logical column type should override to at least assert that the value is in fact a SQLAlchemy expression. This is only called in contexts where true SQLAlchemy expressions are required, such as in ORDER BY or WHERE clauses.

extract_mapping(tags: Iterable[ColumnTag], sql_columns: ColumnCollection) dict[lsst.daf.relation._columns._tag.ColumnTag, _L]

Extract a mapping with ColumnTag keys and logical column values from a SQLAlchemy column collection.

Parameters:
tagsIterable

Set of ColumnTag objects whose logical columns should be extracted.

sql_columnssqlalchemy.sql.ColumnCollection

SQLAlchemy collection of columns, such as sqlalchemy.sql.FromClause.columns.

Returns:
logical_columnsdict

Dictionary mapping ColumnTag to logical column type.

Notes

This method must be overridden to support a custom logical columns.

get_doomed_payload(columns: Set[ColumnTag]) Payload[_L]

Return a payload for a leaf relation that has no rows.

Parameters:
columnsSet [ ColumnTag ]

The columns the relation should have.

Returns:
payload

The engine-specific content for this relation.

get_function(name: str) _F | None

Return the named column expression function.

Parameters:
namestr

Name of the function, from ColumnFunction.name or PredicateFunction.name

Returns:
function

Engine-specific callable, or None if no match was found.

Notes

This implementation first looks for a symbol with this name in the built-in operator module, to handle the common case (shared by both the iteration and sql engines) where these functions are appropriate for the engine due to operator overloading. When this fails, the name is looked up in the functions attribute.

get_identifier(tag: ColumnTag) str

Return the SQL identifier that should be used to represent the given column.

Parameters:
tagColumnTag

Object representing a column.

Returns:
identifierstr

SQL identifier for this column.

Notes

This method may be overridden to replace special characters not supported by a particular DBMS (even after quoting, which SQLAlchemy handles transparently), deal with case transformation, or ensure identifiers are not truncated (e.g. by PostgreSQL’s 64-char limit). The default implementation returns tag.qualified_name unchanged.

get_join_identity_payload() Payload[_L]

Return a payload for a leaf relation that is the join identity.

Returns:
payload

The engine-specific content for this relation.

get_relation_name(prefix: str = 'leaf') str

Return a name suitable for a new relation in this engine.

Parameters:
prefixstr, optional

Prefix to include in the returned name.

Returns:
namestr

Name for the relation; guaranteed to be unique over all of the relations in this engine.

Notes

This implementation combines the given prefix with both the current relation_name_counter value and a random hexadecimal suffix.

handle_empty_columns(columns: list[sqlalchemy.sql.elements.ColumnElement]) None

Handle the edge case where a SELECT statement has no columns, by adding a literal column that should be ignored.

Parameters:
columnslist [ sqlalchemy.sql.ColumnElement ]

List of SQLAlchemy column objects. This may have no elements when this method is called, and must always have at least one element when it returns.

make_doomed_relation(columns: Set[ColumnTag], messages: Sequence[str], name: str = '0') Relation

Construct a leaf relation with no rows and one or more messages explaining why.

Parameters:
columnsSet [ ColumnTag ]

The columns in this relation.

messagesSequence [ str ]

One or more messages explaining why the relation has no rows.

namestr, optional

Name used to identify and reconstruct this relation.

Returns:
relationRelation

Doomed relation.

Notes

This is simplify a convenience method that delegates to LeafRelation.make_doomed. Derived engines with a nontrivial conform should override this method to conform the return value.

make_join_identity_relation(name: str = 'I') Relation

Construct a leaf relation with no columns and exactly one row.

Parameters:
engineEngine

The engine that is responsible for interpreting this relation.

namestr, optional

Name used to identify and reconstruct this relation.

Returns:
relationRelation

Relation with no columns and one row.

make_leaf(columns: Set[ColumnTag], payload: Payload[_L], *, min_rows: int = 0, max_rows: int | None = None, name: str = '', messages: Sequence[str] = (), name_prefix: str = 'leaf', parameters: Any = None) Relation

Create a nontrivial leaf relation in this engine.

This is a convenience method that simply forwards all arguments to the LeafRelation constructor, and then wraps the result in a Select; see LeafRelation for details.

materialize(target: Relation, name: str | None = None, name_prefix: str = 'materialization_') Select

Mark that a target relation’s payload should be cached.

Parameters:
targetRelation

Relation to mark.

namestr, optional

Name to use for the cached payload within the engine.

name_prefixstr, optional

Prefix to pass to get_relation_name; ignored if name is provided.

Returns:
relationRelation

New relation that marks its upstream tree for caching, unless the materialization was simplified away.

See also

Processor.materialize

Notes

The base class implementation calls Materialization.simplify to avoid materializations of leaf relations or other materializations. Override implementations should generally do the same.

select_items(items: Iterable[tuple[lsst.daf.relation._columns._tag.ColumnTag, _L]], sql_from: FromClause, *extra: ColumnElement) Select

Construct a SQLAlchemy representation of a SELECT query.

Parameters:
itemsIterable [ tuple ]

Iterable of (ColumnTag, logical column) pairs. This is typically the items() of a mapping returned by extract_mapping or obtained from Payload.columns_available.

sql_fromsqlalchemy.sql.FromClause

SQLAlchemy representation of a FROM clause, such as a single table, aliased subquery, or join expression. Must provide all columns referenced by items.

*extrasqlalchemy.sql.ColumnElement

Additional SQL column expressions to include.

Returns:
selectsqlalchemy.sql.Select

SELECT query.

Notes

This method is responsible for handling the case where items is empty, typically by delegating to handle_empty_columns.

This method must be overridden to support a custom logical columns.

to_executable(relation: Relation, extra_columns: Iterable[sqlalchemy.sql.ColumnElement] = ()) sqlalchemy.sql.expression.SelectBase

Convert a relation tree to an executable SQLAlchemy expression.

Parameters:
relationRelation

The relation tree to convert.

extra_columnsIterable

Iterable of additional SQLAlchemy column objects to include directly in the SELECT clause.

Returns:
selectsqlalchemy.sql.expression.SelectBase

A SQLAlchemy SELECT or compound SELECT query.

Notes

This method requires all relations in the tree to have the same engine (self). It also cannot handle Materialization operations unless they have already been processed once already (and hence have a payload attached). Use the Processor function to handle both of these cases.

to_payload(relation: Relation) Payload[_L]

Internal recursive implementation of to_executable.

This method should not be called by external code, but it may be overridden and called recursively by derived engine classes.

Parameters:
relationRelation

Relation to convert. This method handles all operation relation types other than Chain and the operations managed by Select.

Returns:
payloadPayload

Struct containing a SQLAlchemy represenation of a simple SELECT query.

transfer(target: Relation, payload: Any | None = None) Select

Mark that a relation’s payload should be transferred from some other engine to this one.

Parameters:
targetRelation

Relation to transfer. If target.engine == self, this relation will be returned directly and no transfer will be performed. Back-to-back transfers from one engine to another and back again are also simplified away (via a call to Transfer.simplify). Sequences of transfers involving more than two engines are not simplified.

payload, optional

Destination-engine-specific content for the relation to attach to the transfer. Most Transfer relations do not have a payload; their ability to do so is mostly to support the special relation trees returned by the Processor class.

Returns:
relationRelation

New relation that marks its upstream tree to be transferred to a new engine.

See also

Processor.transfer

Notes

The default implementation calls conform on the target relation using the target relation’s engine (i.e. not self). All override implementations should do this as well.