-
Notifications
You must be signed in to change notification settings - Fork 1
/
query.py
277 lines (206 loc) · 6.75 KB
/
query.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
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
import re
import event
class Clause:
""" A clause in a SQL query (e.g. SELECT, ORDER BY, ...). """
def __init__(self, operator, value):
""" 'operator' may not be None, but 'value' may. """
assert operator != None
self.operator = operator
self.value = value
def __str__(self):
if self.value is None: return ""
return " ".join([self.operator, self.value])
def __add__(self, s):
if self.value == None: return s
else: return " ".join([self.operator, self.value, str(s)])
def __radd__(self, s):
if self.value == None: return s
else: return " ".join([str(s), self.operator, self.value])
class Fields(Clause):
""" Table fields that we wish to refer to (e.g. to SELECT on). """
def __init__(self, fields):
assert fields != None
assert len(fields) > 0
self.fields = fields
Clause.__init__(self, "", ",".join(fields))
def names(self):
return [ re.sub(".* AS ", "", name) for name in self.fields ]
@classmethod
def conference(cls):
return Fields(
[
'conference', 'parent', 'name', 'abbreviation', 'description',
'permanentURL AS url', '`meeting-type` AS type_id', 'tags'
])
@classmethod
def events(cls):
return Fields(
[
"instance",
"url", "conference", "abbreviation", "Conferences.name AS name",
"startDate", "endDate",
"deadline", "extendedDeadline", "posterDeadline",
"Locations.location AS location_id",
"Locations.name AS location",
"Regions.name AS region",
"Regions.code AS regionCode",
"Countries.code AS country",
"proceedings", "Conferences.permanentURL AS conf_url"
])
@classmethod
def locations(cls):
return Fields(
[
"Locations.location AS location_id",
"Locations.name AS location",
"Regions.name AS region",
"Regions.code AS regionCode",
"Countries.code AS country",
])
@classmethod
def meeting_types(cls):
return Fields([ "`meeting-type` AS type_id", "name" ])
class Values(Clause):
""" Table values that we wish to set (e.g. foo = 42). """
def __init__(self, action, values):
assert values != None
assert len(values) > 0
# Put single quotes around strings, convert None to SQL-friendly NULL.
for (name, value) in values.items():
if value is None: values[name] = 'NULL'
elif isinstance(value, basestring): values[name] = "'%s'" % value
formatted_values = None
if action == 'SET':
formatted_values = ', '.join([
'%s = %s' % (name, value) for (name, value) in values.items() ])
elif action == 'VALUES':
formatted_values = '(%s)' % ', '.join(values.values())
else:
raise ValueError, "Unknown action '%s'" % action
Clause.__init__(self, action, formatted_values)
class Tables(Clause):
""" Tables (including JOINed tables) that we can operate on. """
def __init__(self, value, use_from = True):
assert value != None
Clause.__init__(self, 'FROM' if use_from else '', value)
@classmethod
def conference(cls):
return Tables("Conferences")
@classmethod
def events(cls):
return Tables("""ConferenceInstances
INNER JOIN Conferences USING (conference)
INNER JOIN Locations USING (location)
LEFT JOIN Regions USING (region)
INNER JOIN Countries ON ((Locations.country = Countries.country)
OR (Regions.country = Countries.country))""")
@classmethod
def locations(cls):
return Tables("""Locations
LEFT JOIN Regions USING (region)
INNER JOIN Countries ON ((Locations.country = Countries.country)
OR (Regions.country = Countries.country))""")
class Filter(Clause):
""" A filter which restricts a query (e.g. WHERE foo > 42). """
def __init__(self, value):
Clause.__init__(self, "WHERE", value)
def __and__(self, other):
if self.value is None: return other
if other.value is None: return self
return Filter('(%s) AND (%s)' % (self.value, other.value))
@classmethod
def by_date(cls, min_days, max_days):
return Filter(
"startDate BETWEEN ADDDATE(CURDATE(), %d) AND ADDDATE(CURDATE(), %d)"
% (min_days, max_days))
@classmethod
def recent(cls):
return Filter.by_date(-180, 0)
@classmethod
def upcoming(cls):
return Filter.by_date(0, 365)
@classmethod
def upcomingDeadlines(cls):
return Filter("""
(DATEDIFF(deadline, CURDATE()) >= -14)
OR (DATEDIFF(extendedDeadline, CURDATE()) >= -14)
OR (DATEDIFF(posterDeadline, CURDATE()) >= -14)
""")
@classmethod
def tags(cls, tags):
if len(tags) == 0: return Filter(None)
match = "tags REGEXP '([0-9]+,)*%d(,[0-9]+)*'"
sql = ' OR '.join([ match % i for i in tags ])
return Filter("(%s)" % sql)
class Order(Clause):
""" An ordering constraint (e.g. ORDER BY date). """
def __init__(self, value):
Clause.__init__(self, "ORDER BY", value)
@classmethod
def start_date(cls, reverse = False):
if reverse: return Order("startDate DESC")
else: return Order("startDate")
@classmethod
def deadline(cls):
return Order("""
CASE
WHEN (DATEDIFF(deadline, CURDATE()) < -14)
AND (extendedDeadline IS NULL
OR (DATEDIFF(extendedDeadline, CURDATE()) < -14))
THEN
posterDeadline
WHEN extendedDeadline IS NULL THEN deadline
ELSE extendedDeadline
END
""")
@classmethod
def locations(cls):
return Order("location")
class Select:
""" Get data from the database. """
def __init__(self,
filter,
order = Order.start_date(),
fields = Fields.events(),
source = Tables.events()):
self.where = filter
self.order = order
self.fields = fields
self.source = source
def execute(self, cursor):
cursor.execute(self.__str__())
results = cursor.fetchall()
conferences = []
# Use field names (either from the query, or, in the 'SELECT *' case,
# from the DB cursor) to construct a dictionary of values.
field_names = self.fields.names()
if len(field_names) == 1 and field_names[0] == '*':
field_names = [ i[0] for i in cursor.description ]
for result in results:
conferences.append(
event.Event(dict(zip(field_names, result))))
return conferences
def __str__(self):
return "SELECT" + self.fields + self.source + self.where + self.order
class Update:
""" Update data in the database. """
def __init__(self, table, values, filter):
self.table = table
self.values = values
self.filter = filter
def execute(self, cursor):
cursor.execute(self.__str__())
cursor.connection.commit()
def __str__(self):
return "UPDATE" + self.table + self.values + self.filter
class Insert:
""" Insert a new entry into the database. """
def __init__(self, table, fields, values):
self.table = table
self.fields = fields
self.values = values
def execute(self, cursor):
cursor.execute(self.__str__())
cursor.connection.commit()
def __str__(self):
return "INSERT INTO" + self.table + '(' + self.fields + ')' + self.values