Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

# This file is part of daf_butler. 

# 

# Developed for the LSST Data Management System. 

# This product includes software developed by the LSST Project 

# (http://www.lsst.org). 

# See the COPYRIGHT file at the top-level directory of this distribution 

# for details of code ownership. 

# 

# This program is free software: you can redistribute it and/or modify 

# it under the terms of the GNU General Public License as published by 

# the Free Software Foundation, either version 3 of the License, or 

# (at your option) any later version. 

# 

# This program is distributed in the hope that it will be useful, 

# but WITHOUT ANY WARRANTY; without even the implied warranty of 

# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

# GNU General Public License for more details. 

# 

# You should have received a copy of the GNU General Public License 

# along with this program. If not, see <http://www.gnu.org/licenses/>. 

from __future__ import annotations 

 

__all__ = ["QueryDimensionRecordStorage"] 

 

from typing import Optional 

 

import sqlalchemy 

 

from ...core import DataCoordinate, Dimension, DimensionElement, DimensionRecord, Timespan 

from ...core.dimensions.schema import makeElementTableSpec 

from ...core.utils import NamedKeyDict 

from ..interfaces import Database, DimensionRecordStorage, StaticTablesContext 

from ..queries import QueryBuilder 

 

 

class QueryDimensionRecordStorage(DimensionRecordStorage): 

"""A read-only record storage implementation backed by SELECT query. 

 

At present, the only query this class supports is a SELECT DISTNCT over the 

table for some other dimension that has this dimension as an implied 

dependency. For example, we can use this class to provide access to the 

set of ``abstract_filter`` names referenced by any ``physical_filter``. 

 

Parameters 

---------- 

db : `Database` 

Interface to the database engine and namespace that will hold these 

dimension records. 

element : `DimensionElement` 

The element whose records this storage will manage. 

""" 

def __init__(self, db: Database, element: DimensionElement): 

self._db = db 

self._element = element 

self._target = element.universe[element.viewOf] 

self._targetSpec = makeElementTableSpec(self._target) 

self._query = None # Constructed on first use. 

58 ↛ 59line 58 didn't jump to line 59, because the condition on line 58 was never true if element not in self._target.graph.dimensions: 

raise NotImplementedError("Query-backed dimension must be a dependency of its target.") 

assert isinstance(element, Dimension), "An element cannot be a dependency unless it is a dimension." 

61 ↛ 62line 61 didn't jump to line 62, because the condition on line 61 was never true if element.metadata: 

raise NotImplementedError("Cannot use query to back dimension with metadata.") 

63 ↛ 64line 63 didn't jump to line 64, because the condition on line 63 was never true if element.implied: 

raise NotImplementedError("Cannot use query to back dimension with implied dependencies.") 

65 ↛ 66line 65 didn't jump to line 66, because the condition on line 65 was never true if element.alternateKeys: 

raise NotImplementedError("Cannot use query to back dimension with alternate unique keys.") 

67 ↛ 68line 67 didn't jump to line 68, because the condition on line 67 was never true if element.spatial: 

raise NotImplementedError("Cannot use query to back spatial dimension.") 

69 ↛ 70line 69 didn't jump to line 70, because the condition on line 69 was never true if element.temporal: 

raise NotImplementedError("Cannot use query to back temporal dimension.") 

 

@classmethod 

def initialize(cls, db: Database, element: DimensionElement, *, 

context: Optional[StaticTablesContext] = None) -> DimensionRecordStorage: 

# Docstring inherited from DimensionRecordStorage. 

return cls(db, element) 

 

@property 

def element(self) -> DimensionElement: 

# Docstring inherited from DimensionRecordStorage.element. 

return self._element 

 

def clearCaches(self): 

# Docstring inherited from DimensionRecordStorage.clearCaches. 

pass 

 

def _ensureQuery(self): 

88 ↛ exitline 88 didn't return from function '_ensureQuery', because the condition on line 88 was never false if self._query is None: 

targetTable = self._db.getExistingTable(self._target.name, self._targetSpec) 

columns = [] 

# The only columns for this dimension are ones for its required 

# dependencies and its own primary key (guaranteed by the checks in 

# the ctor). 

for dimension in self.element.graph.required: 

95 ↛ 98line 95 didn't jump to line 98, because the condition on line 95 was never false if dimension == self.element: 

columns.append(targetTable.columns[dimension.name].label(dimension.primaryKey.name)) 

else: 

columns.append(targetTable.columns[dimension.name].label(dimension.name)) 

# This query doesn't do a SELECT DISTINCT, because that's confusing 

# and potentially wasteful if we apply a restrictive WHERE clause, 

# as SelectableDimensionRecordStorage.fetch will do. 

# Instead, we add DISTINCT in join() only. 

self._query = sqlalchemy.sql.select( 

columns, distinct=True 

).select_from( 

targetTable 

).alias( 

self.element.name 

) 

 

def join( 

self, 

builder: QueryBuilder, *, 

regions: Optional[NamedKeyDict[DimensionElement, sqlalchemy.sql.ColumnElement]] = None, 

timespans: Optional[NamedKeyDict[DimensionElement, Timespan[sqlalchemy.sql.ColumnElement]]] = None, 

): 

# Docstring inherited from DimensionRecordStorage. 

assert regions is None, "Should be guaranteed by constructor checks." 

assert timespans is None, "Should be guaranteed by constructor checks." 

120 ↛ 125line 120 didn't jump to line 125, because the condition on line 120 was never true if self._target in builder.summary.mustHaveKeysJoined: 

# Do nothing; the target dimension is already being included, so 

# joining against a subquery referencing it would just produce a 

# more complicated query that's guaranteed to return the same 

# results. 

return 

self._ensureQuery() 

joinOn = builder.startJoin(self._query, list(self.element.graph.required), 

self.element.RecordClass.__slots__) 

builder.finishJoin(self._query, joinOn) 

 

def insert(self, *records: DimensionRecord): 

# Docstring inherited from DimensionRecordStorage.insert. 

raise TypeError(f"Cannot insert {self.element.name} records.") 

 

def fetch(self, dataId: DataCoordinate) -> Optional[DimensionRecord]: 

# Docstring inherited from DimensionRecordStorage.fetch. 

RecordClass = self.element.RecordClass 

# Given the restrictions imposed at construction, we know there's 

# nothing to actually fetch: everything we need is in the data ID. 

return RecordClass.fromDict(dataId)