Coverage for python/lsst/daf/butler/registry/interfaces/_dimensions.py: 90%
112 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-06 10:53 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-06 10:53 +0000
1# This file is part of daf_butler.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (http://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This software is dual licensed under the GNU General Public License and also
10# under a 3-clause BSD license. Recipients may choose which of these licenses
11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt,
12# respectively. If you choose the GPL option then the following text applies
13# (but note that there is still no warranty even if you opt for BSD instead):
14#
15# This program is free software: you can redistribute it and/or modify
16# it under the terms of the GNU General Public License as published by
17# the Free Software Foundation, either version 3 of the License, or
18# (at your option) any later version.
19#
20# This program is distributed in the hope that it will be useful,
21# but WITHOUT ANY WARRANTY; without even the implied warranty of
22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23# GNU General Public License for more details.
24#
25# You should have received a copy of the GNU General Public License
26# along with this program. If not, see <http://www.gnu.org/licenses/>.
27from __future__ import annotations
29__all__ = (
30 "DatabaseDimensionOverlapStorage",
31 "DatabaseDimensionRecordStorage",
32 "DimensionRecordStorage",
33 "DimensionRecordStorageManager",
34 "GovernorDimensionRecordStorage",
35 "SkyPixDimensionRecordStorage",
36)
38from abc import ABC, abstractmethod
39from collections.abc import Callable, Iterable, Mapping, Set
40from typing import TYPE_CHECKING, Any
42import sqlalchemy
43from lsst.daf.relation import Join, Relation, sql
45from ..._column_type_info import ColumnTypeInfo, LogicalColumn
46from ..._named import NamedKeyMapping
47from ...dimensions import (
48 DatabaseDimensionElement,
49 DataCoordinate,
50 DimensionElement,
51 DimensionGroup,
52 DimensionRecord,
53 DimensionUniverse,
54 GovernorDimension,
55 SkyPixDimension,
56)
57from ._versioning import VersionedExtension, VersionTuple
59if TYPE_CHECKING:
60 from .. import queries
61 from ._database import Database, StaticTablesContext
64OverlapSide = SkyPixDimension | tuple[DatabaseDimensionElement, str]
67class DimensionRecordStorage(ABC):
68 """An abstract base class that represents a way of storing the records
69 associated with a single `DimensionElement`.
71 Concrete `DimensionRecordStorage` instances should generally be constructed
72 via a call to `setupDimensionStorage`, which selects the appropriate
73 subclass for each element according to its configuration.
75 All `DimensionRecordStorage` methods are pure abstract, even though in some
76 cases a reasonable default implementation might be possible, in order to
77 better guarantee all methods are correctly overridden. All of these
78 potentially-defaultable implementations are extremely trivial, so asking
79 subclasses to provide them is not a significant burden.
80 """
82 @property
83 @abstractmethod
84 def element(self) -> DimensionElement:
85 """The element whose records this instance managers
86 (`DimensionElement`).
87 """
88 raise NotImplementedError()
90 @abstractmethod
91 def clearCaches(self) -> None:
92 """Clear any in-memory caches held by the storage instance.
94 This is called by `Registry` when transactions are rolled back, to
95 avoid in-memory caches from ever containing records that are not
96 present in persistent storage.
97 """
98 raise NotImplementedError()
100 @abstractmethod
101 def join(
102 self,
103 target: Relation,
104 join: Join,
105 context: queries.SqlQueryContext,
106 ) -> Relation:
107 """Join this dimension element's records to a relation.
109 Parameters
110 ----------
111 target : `~lsst.daf.relation.Relation`
112 Existing relation to join to. Implementations may require that
113 this relation already include dimension key columns for this
114 dimension element and assume that dataset or spatial join relations
115 that might provide these will be included in the relation tree
116 first.
117 join : `~lsst.daf.relation.Join`
118 Join operation to use when the implementation is an actual join.
119 When a true join is being simulated by other relation operations,
120 this objects `~lsst.daf.relation.Join.min_columns` and
121 `~lsst.daf.relation.Join.max_columns` should still be respected.
122 context : `.queries.SqlQueryContext`
123 Object that manages relation engines and database-side state (e.g.
124 temporary tables) for the query.
126 Returns
127 -------
128 joined : `~lsst.daf.relation.Relation`
129 New relation that includes this relation's dimension key and record
130 columns, as well as all columns in ``target``, with rows
131 constrained to those for which this element's dimension key values
132 exist in the registry and rows already exist in ``target``.
133 """
134 raise NotImplementedError()
136 @abstractmethod
137 def insert(self, *records: DimensionRecord, replace: bool = False, skip_existing: bool = False) -> None:
138 """Insert one or more records into storage.
140 Parameters
141 ----------
142 records
143 One or more instances of the `DimensionRecord` subclass for the
144 element this storage is associated with.
145 replace: `bool`, optional
146 If `True` (`False` is default), replace existing records in the
147 database if there is a conflict.
148 skip_existing : `bool`, optional
149 If `True` (`False` is default), skip insertion if a record with
150 the same primary key values already exists.
152 Raises
153 ------
154 TypeError
155 Raised if the element does not support record insertion.
156 sqlalchemy.exc.IntegrityError
157 Raised if one or more records violate database integrity
158 constraints.
160 Notes
161 -----
162 As `insert` is expected to be called only by a `Registry`, we rely
163 on `Registry` to provide transactionality, both by using a SQLALchemy
164 connection shared with the `Registry` and by relying on it to call
165 `clearCaches` when rolling back transactions.
166 """
167 raise NotImplementedError()
169 @abstractmethod
170 def sync(self, record: DimensionRecord, update: bool = False) -> bool | dict[str, Any]:
171 """Synchronize a record with the database, inserting it only if it does
172 not exist and comparing values if it does.
174 Parameters
175 ----------
176 record : `DimensionRecord`.
177 An instance of the `DimensionRecord` subclass for the
178 element this storage is associated with.
179 update: `bool`, optional
180 If `True` (`False` is default), update the existing record in the
181 database if there is a conflict.
183 Returns
184 -------
185 inserted_or_updated : `bool` or `dict`
186 `True` if a new row was inserted, `False` if no changes were
187 needed, or a `dict` mapping updated column names to their old
188 values if an update was performed (only possible if
189 ``update=True``).
191 Raises
192 ------
193 DatabaseConflictError
194 Raised if the record exists in the database (according to primary
195 key lookup) but is inconsistent with the given one.
196 TypeError
197 Raised if the element does not support record synchronization.
198 sqlalchemy.exc.IntegrityError
199 Raised if one or more records violate database integrity
200 constraints.
201 """
202 raise NotImplementedError()
204 @abstractmethod
205 def fetch_one(self, data_id: DataCoordinate, context: queries.SqlQueryContext) -> DimensionRecord | None:
206 """Retrieve a single record from storage.
208 Parameters
209 ----------
210 data_id : `DataCoordinate`
211 Data ID of the record to fetch. Implied dimensions do not need to
212 be present.
213 context : `.queries.SqlQueryContext`
214 Context to be used to execute queries when no cached result is
215 available.
217 Returns
218 -------
219 record : `DimensionRecord` or `None`
220 Fetched record, or *possibly* `None` if there was no match for the
221 given data ID.
222 """
223 raise NotImplementedError()
225 def get_record_cache(
226 self, context: queries.SqlQueryContext
227 ) -> Mapping[DataCoordinate, DimensionRecord] | None:
228 """Return a local cache of all `DimensionRecord` objects for this
229 element, fetching it if necessary.
231 Implementations that never cache records should return `None`.
233 Parameters
234 ----------
235 context : `.queries.SqlQueryContext`
236 Context to be used to execute queries when no cached result is
237 available.
239 Returns
240 -------
241 cache : `~collections.abc.Mapping` \
242 [ `DataCoordinate`, `DimensionRecord` ] or `None`
243 Mapping from data ID to dimension record, or `None`.
244 """
245 return None
247 @abstractmethod
248 def digestTables(self) -> list[sqlalchemy.schema.Table]:
249 """Return tables used for schema digest.
251 Returns
252 -------
253 tables : `list` [ `sqlalchemy.schema.Table` ]
254 Possibly empty list of tables for schema digest calculations.
255 """
256 raise NotImplementedError()
258 def _build_sql_payload(
259 self,
260 from_clause: sqlalchemy.sql.FromClause,
261 column_types: ColumnTypeInfo,
262 ) -> sql.Payload[LogicalColumn]:
263 """Construct a `lsst.daf.relation.sql.Payload` for a dimension table.
265 This is a conceptually "protected" helper method for use by subclass
266 `make_relation` implementations.
268 Parameters
269 ----------
270 from_clause : `sqlalchemy.sql.FromClause`
271 SQLAlchemy table or subquery to select from.
272 column_types : `ColumnTypeInfo`
273 Struct with information about column types that depend on registry
274 configuration.
276 Returns
277 -------
278 payload : `lsst.daf.relation.sql.Payload`
279 Relation SQL "payload" struct, containing a SQL FROM clause,
280 columns, and optional WHERE clause.
281 """
282 payload = sql.Payload[LogicalColumn](from_clause)
283 for tag, field_name in self.element.RecordClass.fields.columns.items():
284 if field_name == "timespan":
285 payload.columns_available[tag] = column_types.timespan_cls.from_columns(
286 from_clause.columns, name=field_name
287 )
288 else:
289 payload.columns_available[tag] = from_clause.columns[field_name]
290 return payload
293class GovernorDimensionRecordStorage(DimensionRecordStorage):
294 """Intermediate interface for `DimensionRecordStorage` objects that provide
295 storage for `GovernorDimension` instances.
296 """
298 @classmethod
299 @abstractmethod
300 def initialize(
301 cls,
302 db: Database,
303 dimension: GovernorDimension,
304 *,
305 context: StaticTablesContext | None = None,
306 config: Mapping[str, Any],
307 ) -> GovernorDimensionRecordStorage:
308 """Construct an instance of this class using a standardized interface.
310 Parameters
311 ----------
312 db : `Database`
313 Interface to the underlying database engine and namespace.
314 dimension : `GovernorDimension`
315 Dimension the new instance will manage records for.
316 context : `StaticTablesContext`, optional
317 If provided, an object to use to create any new tables. If not
318 provided, ``db.ensureTableExists`` should be used instead.
319 config : `~collections.abc.Mapping`
320 Extra configuration options specific to the implementation.
322 Returns
323 -------
324 storage : `GovernorDimensionRecordStorage`
325 A new `GovernorDimensionRecordStorage` subclass instance.
326 """
327 raise NotImplementedError()
329 @property
330 @abstractmethod
331 def element(self) -> GovernorDimension:
332 # Docstring inherited from DimensionRecordStorage.
333 raise NotImplementedError()
335 @property
336 @abstractmethod
337 def table(self) -> sqlalchemy.schema.Table:
338 """The SQLAlchemy table that backs this dimension
339 (`sqlalchemy.schema.Table`).
340 """
341 raise NotImplementedError()
343 def join(
344 self,
345 target: Relation,
346 join: Join,
347 context: queries.SqlQueryContext,
348 ) -> Relation:
349 # Docstring inherited.
350 # We use Join.partial(...).apply(...) instead of Join.apply(..., ...)
351 # for the "backtracking" insertion capabilities of the former; more
352 # specifically, if `target` is a tree that starts with SQL relations
353 # and ends with iteration-engine operations (e.g. region-overlap
354 # postprocessing), this will try to perform the join upstream in the
355 # SQL engine before the transfer to iteration.
356 return join.partial(self.make_relation(context)).apply(target)
358 @abstractmethod
359 def make_relation(self, context: queries.SqlQueryContext) -> Relation:
360 """Return a relation that represents this dimension element's table.
362 This is used to provide an implementation for
363 `DimensionRecordStorage.join`, and is also callable in its own right.
365 Parameters
366 ----------
367 context : `.queries.SqlQueryContext`
368 Object that manages relation engines and database-side state
369 (e.g. temporary tables) for the query.
371 Returns
372 -------
373 relation : `~lsst.daf.relation.Relation`
374 New relation that includes this relation's dimension key and
375 record columns, with rows constrained to those for which the
376 dimension key values exist in the registry.
377 """
378 raise NotImplementedError()
380 @abstractmethod
381 def get_record_cache(self, context: queries.SqlQueryContext) -> Mapping[DataCoordinate, DimensionRecord]:
382 raise NotImplementedError()
384 @abstractmethod
385 def registerInsertionListener(self, callback: Callable[[DimensionRecord], None]) -> None:
386 """Add a function or method to be called after new records for this
387 dimension are inserted by `insert` or `sync`.
389 Parameters
390 ----------
391 callback
392 Callable that takes a single `DimensionRecord` argument. This will
393 be called immediately after any successful insertion, in the same
394 transaction.
395 """
396 raise NotImplementedError()
399class SkyPixDimensionRecordStorage(DimensionRecordStorage):
400 """Intermediate interface for `DimensionRecordStorage` objects that provide
401 storage for `SkyPixDimension` instances.
402 """
404 @property
405 @abstractmethod
406 def element(self) -> SkyPixDimension:
407 # Docstring inherited from DimensionRecordStorage.
408 raise NotImplementedError()
411class DatabaseDimensionRecordStorage(DimensionRecordStorage):
412 """Intermediate interface for `DimensionRecordStorage` objects that provide
413 storage for `DatabaseDimensionElement` instances.
414 """
416 @classmethod
417 @abstractmethod
418 def initialize(
419 cls,
420 db: Database,
421 element: DatabaseDimensionElement,
422 *,
423 context: StaticTablesContext | None = None,
424 config: Mapping[str, Any],
425 governors: NamedKeyMapping[GovernorDimension, GovernorDimensionRecordStorage],
426 view_target: DatabaseDimensionRecordStorage | None = None,
427 ) -> DatabaseDimensionRecordStorage:
428 """Construct an instance of this class using a standardized interface.
430 Parameters
431 ----------
432 db : `Database`
433 Interface to the underlying database engine and namespace.
434 element : `DatabaseDimensionElement`
435 Dimension element the new instance will manage records for.
436 context : `StaticTablesContext`, optional
437 If provided, an object to use to create any new tables. If not
438 provided, ``db.ensureTableExists`` should be used instead.
439 config : `~collections.abc.Mapping`
440 Extra configuration options specific to the implementation.
441 governors : `NamedKeyMapping`
442 Mapping containing all governor dimension storage implementations.
443 view_target : `DatabaseDimensionRecordStorage`, optional
444 Storage object for the element this target's storage is a view of
445 (i.e. when `viewOf` is not `None`).
447 Returns
448 -------
449 storage : `DatabaseDimensionRecordStorage`
450 A new `DatabaseDimensionRecordStorage` subclass instance.
451 """
452 raise NotImplementedError()
454 @property
455 @abstractmethod
456 def element(self) -> DatabaseDimensionElement:
457 # Docstring inherited from DimensionRecordStorage.
458 raise NotImplementedError()
460 def join(
461 self,
462 target: Relation,
463 join: Join,
464 context: queries.SqlQueryContext,
465 ) -> Relation:
466 # Docstring inherited.
467 # See comment on similar code in GovernorDimensionRecordStorage.join
468 # for why we use `Join.partial` here.
469 return join.partial(self.make_relation(context)).apply(target)
471 @abstractmethod
472 def make_relation(self, context: queries.SqlQueryContext) -> Relation:
473 """Return a relation that represents this dimension element's table.
475 This is used to provide an implementation for
476 `DimensionRecordStorage.join`, and is also callable in its own right.
478 Parameters
479 ----------
480 context : `.queries.SqlQueryContext`
481 Object that manages relation engines and database-side state
482 (e.g. temporary tables) for the query.
484 Returns
485 -------
486 relation : `~lsst.daf.relation.Relation`
487 New relation that includes this relation's dimension key and
488 record columns, with rows constrained to those for which the
489 dimension key values exist in the registry.
490 """
491 raise NotImplementedError()
493 def connect(self, overlaps: DatabaseDimensionOverlapStorage) -> None:
494 """Inform this record storage object of the object that will manage
495 the overlaps between this element and another element.
497 This will only be called if ``self.element.spatial is not None``,
498 and will be called immediately after construction (before any other
499 methods). In the future, implementations will be required to call a
500 method on any connected overlap storage objects any time new records
501 for the element are inserted.
503 Parameters
504 ----------
505 overlaps : `DatabaseDimensionRecordStorage`
506 Object managing overlaps between this element and another
507 database-backed element.
508 """
509 raise NotImplementedError(f"{type(self).__name__} does not support spatial elements.")
511 def make_spatial_join_relation(
512 self,
513 other: DimensionElement,
514 context: queries.SqlQueryContext,
515 governor_constraints: Mapping[str, Set[str]],
516 ) -> Relation | None:
517 """Return a `lsst.daf.relation.Relation` that represents the spatial
518 overlap join between two dimension elements.
520 High-level code should generally call
521 `DimensionRecordStorageManager.make_spatial_join_relation` (which
522 delegates to this) instead of calling this method directly.
524 Parameters
525 ----------
526 other : `DimensionElement`
527 Element to compute overlaps with. Guaranteed by caller to be
528 spatial (as is ``self``), with a different topological family. May
529 be a `DatabaseDimensionElement` or a `SkyPixDimension`.
530 context : `.queries.SqlQueryContext`
531 Object that manages relation engines and database-side state
532 (e.g. temporary tables) for the query.
533 governor_constraints : `~collections.abc.Mapping` \
534 [ `str`, `~collections.abc.Set` ], optional
535 Constraints imposed by other aspects of the query on governor
536 dimensions.
538 Returns
539 -------
540 relation : `lsst.daf.relation.Relation` or `None`
541 Join relation. Should be `None` when no direct overlaps for this
542 combination are stored; higher-level code is responsible for
543 working out alternative approaches involving multiple joins.
544 """
545 return None
548class DatabaseDimensionOverlapStorage(ABC):
549 """A base class for objects that manage overlaps between a pair of
550 database-backed dimensions.
551 """
553 @classmethod
554 @abstractmethod
555 def initialize(
556 cls,
557 db: Database,
558 elementStorage: tuple[DatabaseDimensionRecordStorage, DatabaseDimensionRecordStorage],
559 governorStorage: tuple[GovernorDimensionRecordStorage, GovernorDimensionRecordStorage],
560 context: StaticTablesContext | None = None,
561 ) -> DatabaseDimensionOverlapStorage:
562 """Construct an instance of this class using a standardized interface.
564 Parameters
565 ----------
566 db : `Database`
567 Interface to the underlying database engine and namespace.
568 elementStorage : `tuple` [ `DatabaseDimensionRecordStorage` ]
569 Storage objects for the elements this object will related.
570 governorStorage : `tuple` [ `GovernorDimensionRecordStorage` ]
571 Storage objects for the governor dimensions of the elements this
572 object will related.
573 context : `StaticTablesContext`, optional
574 If provided, an object to use to create any new tables. If not
575 provided, ``db.ensureTableExists`` should be used instead.
577 Returns
578 -------
579 storage : `DatabaseDimensionOverlapStorage`
580 A new `DatabaseDimensionOverlapStorage` subclass instance.
581 """
582 raise NotImplementedError()
584 @property
585 @abstractmethod
586 def elements(self) -> tuple[DatabaseDimensionElement, DatabaseDimensionElement]:
587 """The pair of elements whose overlaps this object manages.
589 The order of elements is the same as their ordering within the
590 `DimensionUniverse`.
591 """
592 raise NotImplementedError()
594 @abstractmethod
595 def clearCaches(self) -> None:
596 """Clear any cached state about which overlaps have been
597 materialized.
598 """
599 raise NotImplementedError()
601 @abstractmethod
602 def digestTables(self) -> Iterable[sqlalchemy.schema.Table]:
603 """Return tables used for schema digest.
605 Returns
606 -------
607 tables : `~collections.abc.Iterable` [ `sqlalchemy.schema.Table` ]
608 Possibly empty set of tables for schema digest calculations.
609 """
610 raise NotImplementedError()
612 @abstractmethod
613 def make_relation(
614 self,
615 context: queries.SqlQueryContext,
616 governor_constraints: Mapping[str, Set[str]],
617 ) -> Relation | None:
618 """Return a `lsst.daf.relation.Relation` that represents the join
619 table.
621 High-level code should generally call
622 `DimensionRecordStorageManager.make_spatial_join_relation` (which
623 delegates to this) instead of calling this method directly.
625 Parameters
626 ----------
627 context : `.queries.SqlQueryContext`
628 Object that manages relation engines and database-side state
629 (e.g. temporary tables) for the query.
630 governor_constraints : `~collections.abc.Mapping` \
631 [ `str`, `~collections.abc.Set` ], optional
632 Constraints imposed by other aspects of the query on governor
633 dimensions; collections inconsistent with these constraints will be
634 skipped.
636 Returns
637 -------
638 relation : `lsst.daf.relation.Relation` or `None`
639 Join relation. Should be `None` when no direct overlaps for this
640 combination are stored; higher-level code is responsible for
641 working out alternative approaches involving multiple joins.
642 """
643 raise NotImplementedError()
646class DimensionRecordStorageManager(VersionedExtension):
647 """An interface for managing the dimension records in a `Registry`.
649 `DimensionRecordStorageManager` primarily serves as a container and factory
650 for `DimensionRecordStorage` instances, which each provide access to the
651 records for a different `DimensionElement`.
653 Parameters
654 ----------
655 universe : `DimensionUniverse`
656 Universe of all dimensions and dimension elements known to the
657 `Registry`.
659 Notes
660 -----
661 In a multi-layer `Registry`, many dimension elements will only have
662 records in one layer (often the base layer). The union of the records
663 across all layers forms the logical table for the full `Registry`.
664 """
666 def __init__(self, *, universe: DimensionUniverse, registry_schema_version: VersionTuple | None = None):
667 super().__init__(registry_schema_version=registry_schema_version)
668 self.universe = universe
670 @classmethod
671 @abstractmethod
672 def initialize(
673 cls,
674 db: Database,
675 context: StaticTablesContext,
676 *,
677 universe: DimensionUniverse,
678 registry_schema_version: VersionTuple | None = None,
679 ) -> DimensionRecordStorageManager:
680 """Construct an instance of the manager.
682 Parameters
683 ----------
684 db : `Database`
685 Interface to the underlying database engine and namespace.
686 context : `StaticTablesContext`
687 Context object obtained from `Database.declareStaticTables`; used
688 to declare any tables that should always be present in a layer
689 implemented with this manager.
690 universe : `DimensionUniverse`
691 Universe graph containing dimensions known to this `Registry`.
692 registry_schema_version : `VersionTuple` or `None`
693 Schema version of this extension as defined in registry.
695 Returns
696 -------
697 manager : `DimensionRecordStorageManager`
698 An instance of a concrete `DimensionRecordStorageManager` subclass.
699 """
700 raise NotImplementedError()
702 def __getitem__(self, element: DimensionElement | str) -> DimensionRecordStorage:
703 """Interface to `get` that raises `LookupError` instead of returning
704 `None` on failure.
705 """
706 r = self.get(element)
707 if r is None:
708 raise LookupError(f"No dimension element '{element}' found in this registry layer.")
709 return r
711 @abstractmethod
712 def get(self, element: DimensionElement | str) -> DimensionRecordStorage | None:
713 """Return an object that provides access to the records associated with
714 the given element, if one exists in this layer.
716 Parameters
717 ----------
718 element : `DimensionElement`
719 Element for which records should be returned.
721 Returns
722 -------
723 records : `DimensionRecordStorage` or `None`
724 The object representing the records for the given element in this
725 layer, or `None` if there are no records for that element in this
726 layer.
728 Notes
729 -----
730 Dimension elements registered by another client of the same layer since
731 the last call to `initialize` or `refresh` may not be found.
732 """
733 raise NotImplementedError()
735 @abstractmethod
736 def register(self, element: DimensionElement) -> DimensionRecordStorage:
737 """Ensure that this layer can hold records for the given element,
738 creating new tables as necessary.
740 Parameters
741 ----------
742 element : `DimensionElement`
743 Element for which a table should created (as necessary) and
744 an associated `DimensionRecordStorage` returned.
746 Returns
747 -------
748 records : `DimensionRecordStorage`
749 The object representing the records for the given element in this
750 layer.
752 Raises
753 ------
754 TransactionInterruption
755 Raised if this operation is invoked within a `Database.transaction`
756 context.
757 """
758 raise NotImplementedError()
760 @abstractmethod
761 def save_dimension_group(self, graph: DimensionGroup) -> int:
762 """Save a `DimensionGroup` definition to the database, allowing it to
763 be retrieved later via the returned key.
765 Parameters
766 ----------
767 dimensions : `DimensionGroup`
768 Set of dimensions to save.
770 Returns
771 -------
772 key : `int`
773 Integer used as the unique key for this `DimensionGroup` in the
774 database.
776 Raises
777 ------
778 TransactionInterruption
779 Raised if this operation is invoked within a `Database.transaction`
780 context.
781 """
782 raise NotImplementedError()
784 @abstractmethod
785 def load_dimension_group(self, key: int) -> DimensionGroup:
786 """Retrieve a `DimensionGroup` that was previously saved in the
787 database.
789 Parameters
790 ----------
791 key : `int`
792 Integer used as the unique key for this `DimensionGroup` in the
793 database.
795 Returns
796 -------
797 dimensions : `DimensionGroup`
798 Retrieved dimensions.
800 Raises
801 ------
802 KeyError
803 Raised if the given key cannot be found in the database.
804 """
805 raise NotImplementedError()
807 @abstractmethod
808 def clearCaches(self) -> None:
809 """Clear any in-memory caches held by nested `DimensionRecordStorage`
810 instances.
812 This is called by `Registry` when transactions are rolled back, to
813 avoid in-memory caches from ever containing records that are not
814 present in persistent storage.
815 """
816 raise NotImplementedError()
818 @abstractmethod
819 def make_spatial_join_relation(
820 self,
821 element1: str,
822 element2: str,
823 context: queries.SqlQueryContext,
824 governor_constraints: Mapping[str, Set[str]],
825 existing_relationships: Set[frozenset[str]] = frozenset(),
826 ) -> tuple[Relation, bool]:
827 """Create a relation that represents the spatial join between two
828 dimension elements.
830 Parameters
831 ----------
832 element1 : `str`
833 Name of one of the elements participating in the join.
834 element2 : `str`
835 Name of the other element participating in the join.
836 context : `.queries.SqlQueryContext`
837 Object that manages relation engines and database-side state
838 (e.g. temporary tables) for the query.
839 governor_constraints : `~collections.abc.Mapping` \
840 [ `str`, `collections.abc.Set` ], optional
841 Constraints imposed by other aspects of the query on governor
842 dimensions.
843 existing_relationships : `~collections.abc.Set` [ `frozenset` [ `str` \
844 ] ], optional
845 Relationships between dimensions that are already present in the
846 relation the result will be joined to. Spatial join relations
847 that duplicate these relationships will not be included in the
848 result, which may cause an identity relation to be returned if
849 a spatial relationship has already been established.
851 Returns
852 -------
853 relation : `lsst.daf.relation.Relation`
854 New relation that represents a spatial join between the two given
855 elements. Guaranteed to have key columns for all required
856 dimensions of both elements.
857 needs_refinement : `bool`
858 Whether the returned relation represents a conservative join that
859 needs refinement via native-iteration predicate.
860 """
861 raise NotImplementedError()
863 universe: DimensionUniverse
864 """Universe of all dimensions and dimension elements known to the
865 `Registry` (`DimensionUniverse`).
866 """