Coverage for tests/test_expressions.py: 32%
68 statements
« prev ^ index » next coverage.py v6.4.4, created at 2022-08-31 04:05 -0700
« prev ^ index » next coverage.py v6.4.4, created at 2022-08-31 04:05 -0700
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# (https://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 <https://www.gnu.org/licenses/>.
22import unittest
24from lsst.daf.butler import DataCoordinate, DimensionUniverse
25from lsst.daf.butler.core import NamedKeyDict, TimespanDatabaseRepresentation
26from lsst.daf.butler.registry.queries._structs import QueryColumns
27from lsst.daf.butler.registry.queries.expressions import (
28 CheckVisitor,
29 NormalForm,
30 NormalFormExpression,
31 ParserYacc,
32 convertExpressionToSql,
33)
34from sqlalchemy.dialects import postgresql, sqlite
35from sqlalchemy.schema import Column
38class FakeDatasetRecordStorageManager:
39 ingestDate = Column("ingest_date")
42class ConvertExpressionToSqlTestCase(unittest.TestCase):
43 """A test case for convertExpressionToSql method"""
45 def setUp(self):
46 self.universe = DimensionUniverse()
48 def test_simple(self):
49 """Test with a trivial expression"""
51 parser = ParserYacc()
52 tree = parser.parse("1 > 0")
53 self.assertIsNotNone(tree)
55 columns = QueryColumns()
56 elements = NamedKeyDict()
57 column_element = convertExpressionToSql(
58 tree, self.universe, columns, elements, {}, TimespanDatabaseRepresentation.Compound
59 )
60 self.assertEqual(str(column_element.compile()), ":param_1 > :param_2")
61 self.assertEqual(str(column_element.compile(compile_kwargs={"literal_binds": True})), "1 > 0")
63 def test_time(self):
64 """Test with a trivial expression including times"""
66 parser = ParserYacc()
67 tree = parser.parse("T'1970-01-01 00:00/tai' < T'2020-01-01 00:00/tai'")
68 self.assertIsNotNone(tree)
70 columns = QueryColumns()
71 elements = NamedKeyDict()
72 column_element = convertExpressionToSql(
73 tree, self.universe, columns, elements, {}, TimespanDatabaseRepresentation.Compound
74 )
75 self.assertEqual(str(column_element.compile()), ":param_1 < :param_2")
76 self.assertEqual(
77 str(column_element.compile(compile_kwargs={"literal_binds": True})), "0 < 1577836800000000000"
78 )
80 def test_ingest_date(self):
81 """Test with an expression including ingest_date which is native UTC"""
83 parser = ParserYacc()
84 tree = parser.parse("ingest_date < T'2020-01-01 00:00/utc'")
85 self.assertIsNotNone(tree)
87 columns = QueryColumns()
88 columns.datasets = FakeDatasetRecordStorageManager()
89 elements = NamedKeyDict()
90 column_element = convertExpressionToSql(
91 tree, self.universe, columns, elements, {}, TimespanDatabaseRepresentation.Compound
92 )
94 # render it, needs specific dialect to convert column to expression
95 dialect = postgresql.dialect()
96 self.assertEqual(str(column_element.compile(dialect=dialect)), "ingest_date < TIMESTAMP %(param_1)s")
97 self.assertEqual(
98 str(column_element.compile(dialect=dialect, compile_kwargs={"literal_binds": True})),
99 "ingest_date < TIMESTAMP '2020-01-01 00:00:00.000000'",
100 )
102 dialect = sqlite.dialect()
103 self.assertEqual(str(column_element.compile(dialect=dialect)), "datetime(ingest_date) < datetime(?)")
104 self.assertEqual(
105 str(column_element.compile(dialect=dialect, compile_kwargs={"literal_binds": True})),
106 "datetime(ingest_date) < datetime('2020-01-01 00:00:00.000000')",
107 )
110class CheckVisitorTestCase(unittest.TestCase):
111 """Tests for CheckVisitor class."""
113 def test_governor(self):
114 """Test with governor dimension in expression"""
116 parser = ParserYacc()
118 universe = DimensionUniverse()
119 graph = universe.extract(("instrument", "visit"))
120 dataId = DataCoordinate.makeEmpty(universe)
121 defaults = DataCoordinate.makeEmpty(universe)
123 # governor-only constraint
124 tree = parser.parse("instrument = 'LSST'")
125 expr = NormalFormExpression.fromTree(tree, NormalForm.DISJUNCTIVE)
126 binds = {}
127 visitor = CheckVisitor(dataId, graph, binds, defaults)
128 expr.visit(visitor)
130 tree = parser.parse("'LSST' = instrument")
131 expr = NormalFormExpression.fromTree(tree, NormalForm.DISJUNCTIVE)
132 binds = {}
133 visitor = CheckVisitor(dataId, graph, binds, defaults)
134 expr.visit(visitor)
136 # use bind for governor
137 tree = parser.parse("instrument = instr")
138 expr = NormalFormExpression.fromTree(tree, NormalForm.DISJUNCTIVE)
139 binds = {"instr": "LSST"}
140 visitor = CheckVisitor(dataId, graph, binds, defaults)
141 expr.visit(visitor)
144if __name__ == "__main__": 144 ↛ 145line 144 didn't jump to line 145, because the condition on line 144 was never true
145 unittest.main()