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