/
base.py
228 lines (177 loc) · 5.51 KB
/
base.py
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
"""
This namespace defines the DBFunction abstract class and its subclasses. These subclasses
represent functions that have identifiers, display names and hints, and their instances
hold parameters. Each DBFunction subclass defines how its instance can be converted into an
SQLAlchemy expression.
Hints hold information about what kind of input the function might expect and what output
can be expected from it. This is used to provide interface information without constraining its
user.
These classes might be used, for example, to define a filter for an SQL query, or to
access hints on what composition of functions and parameters should be valid.
"""
from abc import ABC, abstractmethod
from sqlalchemy import column, not_, and_, or_, func, literal
from db.functions import hints
class DBFunction(ABC):
id = None
name = None
hints = None
# Optionally lists the SQL functions this DBFunction depends on.
# Will be checked against SQL functions defined on a database to tell if it
# supports this DBFunction. Either None or a tuple of SQL function name
# strings.
depends_on = None
def __init__(self, parameters):
if self.id is None:
raise ValueError('DBFunction subclasses must define an ID.')
if self.name is None:
raise ValueError('DBFunction subclasses must define a name.')
if self.depends_on is not None and not isinstance(self.depends_on, tuple):
raise ValueError('DBFunction subclasses\' depends_on attribute must either be None or a tuple of SQL function names.')
self.parameters = parameters
@property
def referenced_columns(self):
"""Walks the expression tree, collecting referenced columns.
Useful when checking if all referenced columns are present in the queried relation."""
columns = set([])
for parameter in self.parameters:
if isinstance(parameter, ColumnReference):
columns.add(parameter.column)
elif isinstance(parameter, DBFunction):
columns.update(parameter.referenced_columns)
return columns
@staticmethod
@abstractmethod
def to_sa_expression():
return None
class Literal(DBFunction):
id = 'literal'
name = 'Literal'
hints = tuple([
hints.parameter_count(1),
hints.parameter(1, hints.literal),
])
@staticmethod
def to_sa_expression(primitive):
return literal(primitive)
class ColumnReference(DBFunction):
id = 'column_reference'
name = 'Column Reference'
hints = tuple([
hints.parameter_count(1),
hints.parameter(1, hints.column),
])
@property
def column(self):
return self.parameters[0]
@staticmethod
def to_sa_expression(column_name):
return column(column_name)
class List(DBFunction):
id = 'list'
name = 'List'
@staticmethod
def to_sa_expression(*items):
return list(items)
class Empty(DBFunction):
id = 'empty'
name = 'Empty'
hints = tuple([
hints.returns(hints.boolean),
hints.parameter_count(1),
])
@staticmethod
def to_sa_expression(value):
return value.is_(None)
class Not(DBFunction):
id = 'not'
name = 'Not'
hints = tuple([
hints.returns(hints.boolean),
hints.parameter_count(1),
])
@staticmethod
def to_sa_expression(value):
return not_(value)
class Equal(DBFunction):
id = 'equal'
name = 'Equal'
hints = tuple([
hints.returns(hints.boolean),
hints.parameter_count(2),
])
@staticmethod
def to_sa_expression(value1, value2):
return value1 == value2
class Greater(DBFunction):
id = 'greater'
name = 'Greater'
hints = tuple([
hints.returns(hints.boolean),
hints.parameter_count(2),
hints.all_parameters(hints.comparable),
])
@staticmethod
def to_sa_expression(value1, value2):
return value1 > value2
class Lesser(DBFunction):
id = 'lesser'
name = 'Lesser'
hints = tuple([
hints.returns(hints.boolean),
hints.parameter_count(2),
hints.all_parameters(hints.comparable),
])
@staticmethod
def to_sa_expression(value1, value2):
return value1 < value2
class In(DBFunction):
id = 'in'
name = 'In'
hints = tuple([
hints.returns(hints.boolean),
hints.parameter_count(2),
hints.parameter(2, hints.array),
])
@staticmethod
def to_sa_expression(value1, value2):
return value1.in_(value2)
class And(DBFunction):
id = 'and'
name = 'And'
hints = tuple([
hints.returns(hints.boolean),
])
@staticmethod
def to_sa_expression(*values):
return and_(*values)
class Or(DBFunction):
id = 'or'
name = 'Or'
hints = tuple([
hints.returns(hints.boolean),
])
@staticmethod
def to_sa_expression(*values):
return or_(*values)
class StartsWith(DBFunction):
id = 'starts_with'
name = 'Starts With'
hints = tuple([
hints.returns(hints.boolean),
hints.parameter_count(2),
hints.all_parameters(hints.string_like),
])
@staticmethod
def to_sa_expression(string, prefix):
return string.like(f'{prefix}%')
class ToLowercase(DBFunction):
id = 'to_lowercase'
name = 'To Lowercase'
hints = tuple([
hints.parameter_count(1),
hints.all_parameters(hints.string_like),
])
@staticmethod
def to_sa_expression(string):
return func.lower(string)