blob: 51d40a756ddd60afbebde75a6b2dc5f293b9c1d5 [file] [log] [blame]
showard35444862008-08-07 22:35:30 +00001from django.db import models as dbmodels, connection
2from django.utils import datastructures
3from autotest_lib.frontend.afe import model_logic, readonly_connection
4
5class TempManager(model_logic.ExtendedManager):
6 _GROUP_COUNT_NAME = 'group_count'
7
8 def _get_key_unless_is_function(self, field):
9 if '(' in field:
10 return field
11 return self._get_key_on_this_table(field)
12
13
14 def _get_field_names(self, fields):
15 return [self._get_key_unless_is_function(field) for field in fields]
16
17
showard8a6eb0c2008-10-01 11:38:59 +000018 @staticmethod
19 def _get_field_alias(field_sql):
20 field_sql = field_sql.lower()
21 if ' as ' in field_sql:
22 return field_sql.rsplit(' as ', 1)[1]
23 return field_sql
24
25
showard35444862008-08-07 22:35:30 +000026 def _get_group_query_sql(self, query, group_by, extra_select_fields):
27 group_fields = self._get_field_names(group_by)
showard8a6eb0c2008-10-01 11:38:59 +000028 select_fields = group_fields + extra_select_fields
showard35444862008-08-07 22:35:30 +000029
showard8a6eb0c2008-10-01 11:38:59 +000030 # add the extra fields to the query selects, so they'll be sortable and
showardd50ffb42008-09-04 02:47:45 +000031 # Django won't mess with any of them
showard8a6eb0c2008-10-01 11:38:59 +000032 for field_sql in extra_select_fields:
33 field_name = self._get_field_alias(field_sql)
34 query._select[field_name] = field_sql
showard35444862008-08-07 22:35:30 +000035
showard35444862008-08-07 22:35:30 +000036 _, where, params = query._get_sql_clause()
showardd50ffb42008-09-04 02:47:45 +000037
38 # insert GROUP BY clause into query
39 group_by_clause = 'GROUP BY ' + ', '.join(group_fields)
40 group_by_position = where.rfind('ORDER BY')
41 if group_by_position == -1:
42 group_by_position = len(where)
43 where = (where[:group_by_position] +
44 group_by_clause + ' ' +
45 where[group_by_position:])
showard35444862008-08-07 22:35:30 +000046
47 return ('SELECT ' + ', '.join(select_fields) + where), params
48
49
showard8a6eb0c2008-10-01 11:38:59 +000050 def execute_group_query(self, query, group_by, extra_select_fields=[]):
showard35444862008-08-07 22:35:30 +000051 """
showard8a6eb0c2008-10-01 11:38:59 +000052 Performs the given query grouped by the fields in group_by with the
53 given extra select fields added. Usually, the extra fields will use
54 group aggregation functions. Returns a list of dicts, where each dict
55 corresponds to single row and contains a key for each grouped field as
56 well as all of the extra select fields.
showard35444862008-08-07 22:35:30 +000057 """
58 sql, params = self._get_group_query_sql(query, group_by,
59 extra_select_fields)
60 cursor = readonly_connection.connection.cursor()
showard8a6eb0c2008-10-01 11:38:59 +000061 cursor.execute(sql, params)
62 field_names = [column_info[0] for column_info in cursor.description]
63 row_dicts = [dict(zip(field_names, row)) for row in cursor.fetchall()]
64 return row_dicts
65
66
67 def get_count_sql(self, query):
68 """
69 Get the SQL to properly select a per-group count of unique matches for
70 a grouped query.
71 """
72 if query._distinct:
73 pk_field = self._get_key_on_this_table(self.model._meta.pk.name)
74 count_sql = 'COUNT(DISTINCT %s)' % pk_field
75 else:
76 count_sql = 'COUNT(1)'
77 return count_sql + ' AS ' + self._GROUP_COUNT_NAME
showard35444862008-08-07 22:35:30 +000078
79
80 def _get_num_groups_sql(self, query, group_by):
81 group_fields = self._get_field_names(group_by)
showard8a6eb0c2008-10-01 11:38:59 +000082 query._order_by = None # this can mess up the query and isn't needed
showard35444862008-08-07 22:35:30 +000083 _, where, params = query._get_sql_clause()
84 return ('SELECT COUNT(DISTINCT %s) %s' % (','.join(group_fields),
85 where),
86 params)
87
88
89 def get_num_groups(self, query, group_by):
90 """
91 Returns the number of distinct groups for the given query grouped by the
92 fields in group_by.
93 """
94 sql, params = self._get_num_groups_sql(query, group_by)
95 cursor = readonly_connection.connection.cursor()
96 cursor.execute(sql, params)
97 return cursor.fetchone()[0]
98
99
100class Machine(dbmodels.Model):
101 machine_idx = dbmodels.IntegerField(primary_key=True)
102 hostname = dbmodels.CharField(unique=True, maxlength=300)
103 machine_group = dbmodels.CharField(blank=True, maxlength=240)
104 owner = dbmodels.CharField(blank=True, maxlength=240)
105
106 class Meta:
107 db_table = 'machines'
108
109
110class Kernel(dbmodels.Model):
111 kernel_idx = dbmodels.IntegerField(primary_key=True)
112 kernel_hash = dbmodels.CharField(maxlength=105, editable=False)
113 base = dbmodels.CharField(maxlength=90)
114 printable = dbmodels.CharField(maxlength=300)
115
116 class Meta:
117 db_table = 'kernels'
118
119
120class Patch(dbmodels.Model):
121 kernel = dbmodels.ForeignKey(Kernel, db_column='kernel_idx')
122 name = dbmodels.CharField(blank=True, maxlength=240)
123 url = dbmodels.CharField(blank=True, maxlength=900)
124 hash_ = dbmodels.CharField(blank=True, maxlength=105, db_column='hash')
125
126 class Meta:
127 db_table = 'patches'
128
129
130class Status(dbmodels.Model):
131 status_idx = dbmodels.IntegerField(primary_key=True)
132 word = dbmodels.CharField(maxlength=30)
133
134 class Meta:
135 db_table = 'status'
136
137
138class Job(dbmodels.Model):
139 job_idx = dbmodels.IntegerField(primary_key=True)
140 tag = dbmodels.CharField(unique=True, maxlength=300)
141 label = dbmodels.CharField(maxlength=300)
142 username = dbmodels.CharField(maxlength=240)
143 machine = dbmodels.ForeignKey(Machine, db_column='machine_idx')
144 queued_time = dbmodels.DateTimeField(null=True, blank=True)
145 started_time = dbmodels.DateTimeField(null=True, blank=True)
146 finished_time = dbmodels.DateTimeField(null=True, blank=True)
147
148 class Meta:
149 db_table = 'jobs'
150
151
showarde732ee72008-09-23 19:15:43 +0000152class Test(dbmodels.Model, model_logic.ModelExtensions):
showard35444862008-08-07 22:35:30 +0000153 test_idx = dbmodels.IntegerField(primary_key=True)
154 job = dbmodels.ForeignKey(Job, db_column='job_idx')
155 test = dbmodels.CharField(maxlength=90)
156 subdir = dbmodels.CharField(blank=True, maxlength=180)
157 kernel = dbmodels.ForeignKey(Kernel, db_column='kernel_idx')
158 status = dbmodels.ForeignKey(Status, db_column='status')
159 reason = dbmodels.CharField(blank=True, maxlength=3072)
160 machine = dbmodels.ForeignKey(Machine, db_column='machine_idx')
161 finished_time = dbmodels.DateTimeField(null=True, blank=True)
162 started_time = dbmodels.DateTimeField(null=True, blank=True)
163
showard35444862008-08-07 22:35:30 +0000164 class Meta:
165 db_table = 'tests'
166
167
showarde732ee72008-09-23 19:15:43 +0000168class TestAttribute(dbmodels.Model, model_logic.ModelExtensions):
169 # this isn't really a primary key, but it's necessary to appease Django
170 # and is harmless as long as we're careful
171 test = dbmodels.ForeignKey(Test, db_column='test_idx', primary_key=True)
showard35444862008-08-07 22:35:30 +0000172 attribute = dbmodels.CharField(maxlength=90)
173 value = dbmodels.CharField(blank=True, maxlength=300)
174
175 class Meta:
176 db_table = 'test_attributes'
177
178
179class IterationAttribute(dbmodels.Model):
showarde732ee72008-09-23 19:15:43 +0000180 # see comment on TestAttribute regarding primary_key=True
181 test = dbmodels.ForeignKey(Test, db_column='test_idx', primary_key=True)
showard35444862008-08-07 22:35:30 +0000182 iteration = dbmodels.IntegerField()
183 attribute = dbmodels.CharField(maxlength=90)
184 value = dbmodels.CharField(blank=True, maxlength=300)
185
186 class Meta:
187 db_table = 'iteration_attributes'
188
189
190class IterationResult(dbmodels.Model):
191 test = dbmodels.ForeignKey(Test, db_column='test_idx')
192 iteration = dbmodels.IntegerField()
193 attribute = dbmodels.CharField(maxlength=90)
194 value = dbmodels.FloatField(null=True, max_digits=12, decimal_places=31,
195 blank=True)
196
197 class Meta:
198 db_table = 'iteration_result'
199
200
201class TestLabel(dbmodels.Model, model_logic.ModelExtensions):
showardd50ffb42008-09-04 02:47:45 +0000202 name = dbmodels.CharField(maxlength=80, unique=True)
showard35444862008-08-07 22:35:30 +0000203 description = dbmodels.TextField(blank=True)
204 tests = dbmodels.ManyToManyField(Test, blank=True,
205 filter_interface=dbmodels.HORIZONTAL)
206
207 name_field = 'name'
208
209 class Meta:
210 db_table = 'test_labels'
211
212
213class SavedQuery(dbmodels.Model, model_logic.ModelExtensions):
214 # TODO: change this to foreign key once DBs are merged
215 owner = dbmodels.CharField(maxlength=80)
216 name = dbmodels.CharField(maxlength=100)
217 url_token = dbmodels.TextField()
218
219 class Meta:
220 db_table = 'saved_queries'
221
222
showardce12f552008-09-19 00:48:59 +0000223class EmbeddedGraphingQuery(dbmodels.Model, model_logic.ModelExtensions):
224 url_token = dbmodels.TextField(null=False, blank=False)
225 graph_type = dbmodels.CharField(maxlength=16, null=False, blank=False)
226 params = dbmodels.TextField(null=False, blank=False)
227 last_updated = dbmodels.DateTimeField(null=False, blank=False,
228 editable=False)
229 # refresh_time shows the time at which a thread is updating the cached
230 # image, or NULL if no one is updating the image. This is used so that only
231 # one thread is updating the cached image at a time (see
232 # graphing_utils.handle_plot_request)
233 refresh_time = dbmodels.DateTimeField(editable=False)
234 cached_png = dbmodels.TextField(editable=False)
235
236 class Meta:
237 db_table = 'embedded_graphing_queries'
238
239
showard35444862008-08-07 22:35:30 +0000240# views
241
242class TestViewManager(TempManager):
showardd50ffb42008-09-04 02:47:45 +0000243 class _CustomSqlQ(dbmodels.Q):
showard35444862008-08-07 22:35:30 +0000244 def __init__(self):
245 self._joins = datastructures.SortedDict()
showardd50ffb42008-09-04 02:47:45 +0000246 self._where, self._params = [], []
showard35444862008-08-07 22:35:30 +0000247
248
249 def add_join(self, table, condition, join_type, alias=None):
250 if alias is None:
251 alias = table
showard98ed33b2008-09-30 10:37:29 +0000252 condition = condition.replace('%', '%%')
showard35444862008-08-07 22:35:30 +0000253 self._joins[alias] = (table, join_type, condition)
254
255
showardd50ffb42008-09-04 02:47:45 +0000256 def add_where(self, where, params=[]):
257 self._where.append(where)
258 self._params.extend(params)
259
260
showard35444862008-08-07 22:35:30 +0000261 def get_sql(self, opts):
showardd50ffb42008-09-04 02:47:45 +0000262 return self._joins, self._where, self._params
showard35444862008-08-07 22:35:30 +0000263
264
265 def get_query_set(self):
266 query = super(TestViewManager, self).get_query_set()
showard5bf7c502008-08-20 01:22:22 +0000267
showard35444862008-08-07 22:35:30 +0000268 # add extra fields to selects, using the SQL itself as the "alias"
269 extra_select = dict((sql, sql)
270 for sql in self.model.extra_fields.iterkeys())
271 return query.extra(select=extra_select)
272
273
showard64aeecd2008-09-19 21:32:58 +0000274 def _add_join(self, query_set, join_table, join_condition='',
275 join_key='test_idx', suffix='', exclude=False,
276 force_left_join=False):
showard02813502008-08-20 20:52:56 +0000277 table_name = self.model._meta.db_table
showard64aeecd2008-09-19 21:32:58 +0000278 join_alias = join_table + suffix
279 full_join_key = join_alias + '.' + join_key
280 full_join_condition = '%s = %s.test_idx' % (full_join_key, table_name)
showardd50ffb42008-09-04 02:47:45 +0000281 if join_condition:
showard64aeecd2008-09-19 21:32:58 +0000282 full_join_condition += ' AND (' + join_condition + ')'
283 if exclude or force_left_join:
284 join_type = 'LEFT JOIN'
285 else:
286 join_type = 'INNER JOIN'
287
showardd50ffb42008-09-04 02:47:45 +0000288 filter_object = self._CustomSqlQ()
showard64aeecd2008-09-19 21:32:58 +0000289 filter_object.add_join(join_table,
290 full_join_condition,
291 join_type,
292 alias=join_alias)
293 if exclude:
294 filter_object.add_where(full_join_key + ' IS NULL')
295 return query_set.filter(filter_object).distinct()
296
297
298 def _add_label_joins(self, query_set, suffix=''):
299 query_set = self._add_join(query_set, 'test_labels_tests',
300 join_key='test_id', suffix=suffix,
301 force_left_join=True)
showardd50ffb42008-09-04 02:47:45 +0000302
303 second_join_alias = 'test_labels' + suffix
304 second_join_condition = ('%s.id = %s.testlabel_id' %
showard64aeecd2008-09-19 21:32:58 +0000305 (second_join_alias,
306 'test_labels_tests' + suffix))
307 filter_object = self._CustomSqlQ()
showardd50ffb42008-09-04 02:47:45 +0000308 filter_object.add_join('test_labels',
309 second_join_condition,
310 'LEFT JOIN',
311 alias=second_join_alias)
showard64aeecd2008-09-19 21:32:58 +0000312 return query_set.filter(filter_object)
showardd50ffb42008-09-04 02:47:45 +0000313
showard64aeecd2008-09-19 21:32:58 +0000314
315 def _add_attribute_join(self, query_set, suffix='', join_condition='',
316 exclude=False):
317 return self._add_join(query_set, 'test_attributes',
318 join_condition=join_condition,
319 suffix=suffix, exclude=exclude)
showardd50ffb42008-09-04 02:47:45 +0000320
321
322 def _get_label_ids_from_names(self, label_names):
showard64aeecd2008-09-19 21:32:58 +0000323 if not label_names:
324 return []
showardd50ffb42008-09-04 02:47:45 +0000325 query = TestLabel.objects.filter(name__in=label_names).values('id')
326 return [label['id'] for label in query]
showard02813502008-08-20 20:52:56 +0000327
328
showard64aeecd2008-09-19 21:32:58 +0000329 def get_query_set_with_joins(self, filter_data):
showardd50ffb42008-09-04 02:47:45 +0000330 exclude_labels = filter_data.pop('exclude_labels', [])
showard35444862008-08-07 22:35:30 +0000331 query_set = self.get_query_set()
showardd50ffb42008-09-04 02:47:45 +0000332 joined = False
showard35444862008-08-07 22:35:30 +0000333 # TODO: make this check more thorough if necessary
334 if 'test_labels' in filter_data.get('extra_where', ''):
showard02813502008-08-20 20:52:56 +0000335 query_set = self._add_label_joins(query_set)
showardd50ffb42008-09-04 02:47:45 +0000336 joined = True
337
showard64aeecd2008-09-19 21:32:58 +0000338 exclude_label_ids = self._get_label_ids_from_names(exclude_labels)
339 if exclude_label_ids:
340 condition = ('test_labels_tests_exclude.testlabel_id IN (%s)' %
341 ','.join(str(label_id)
342 for label_id in exclude_label_ids))
343 query_set = self._add_join(query_set, 'test_labels_tests',
344 join_key='test_id',
345 suffix='_exclude',
346 join_condition=condition,
347 exclude=True)
348 joined = True
349
350 include_attributes_where = filter_data.pop('include_attributes_where',
351 '')
352 exclude_attributes_where = filter_data.pop('exclude_attributes_where',
353 '')
354 if include_attributes_where:
355 query_set = self._add_attribute_join(
356 query_set, suffix='_include',
357 join_condition=include_attributes_where)
358 joined = True
359 if exclude_attributes_where:
360 query_set = self._add_attribute_join(
361 query_set, suffix='_exclude',
362 join_condition=exclude_attributes_where,
363 exclude=True)
364 joined = True
showardd50ffb42008-09-04 02:47:45 +0000365
366 if not joined:
showard35444862008-08-07 22:35:30 +0000367 filter_data['no_distinct'] = True
showardd50ffb42008-09-04 02:47:45 +0000368
showard35444862008-08-07 22:35:30 +0000369 return query_set
370
371
showard02813502008-08-20 20:52:56 +0000372 def query_test_ids(self, filter_data):
373 dicts = self.model.query_objects(filter_data).values('test_idx')
374 return [item['test_idx'] for item in dicts]
375
376
377 def _custom_select_query(self, query_set, selects):
378 query_selects, where, params = query_set._get_sql_clause()
379 if query_set._distinct:
380 distinct = 'DISTINCT '
381 else:
382 distinct = ''
383 sql_query = 'SELECT ' + distinct + ','.join(selects) + where
384 cursor = readonly_connection.connection.cursor()
385 cursor.execute(sql_query, params)
386 return cursor.fetchall()
387
388
389 def query_test_label_ids(self, filter_data):
showardd50ffb42008-09-04 02:47:45 +0000390 query_set = self.model.query_objects(filter_data)
391 query_set = self._add_label_joins(query_set, suffix='_list')
392 rows = self._custom_select_query(query_set, ['test_labels_list.id'])
393 return [row[0] for row in rows if row[0] is not None]
showard02813502008-08-20 20:52:56 +0000394
395
showard35444862008-08-07 22:35:30 +0000396class TestView(dbmodels.Model, model_logic.ModelExtensions):
397 extra_fields = {
398 'DATE(test_finished_time)' : 'test finished day',
399 }
400
401 group_fields = [
402 'test_name',
403 'status',
404 'kernel',
405 'hostname',
406 'job_tag',
407 'job_name',
408 'platform',
409 'reason',
410 'job_owner',
411 'test_finished_time',
412 'DATE(test_finished_time)',
413 ]
414
415 test_idx = dbmodels.IntegerField('test index', primary_key=True)
416 job_idx = dbmodels.IntegerField('job index', null=True, blank=True)
417 test_name = dbmodels.CharField(blank=True, maxlength=90)
418 subdir = dbmodels.CharField('subdirectory', blank=True, maxlength=180)
419 kernel_idx = dbmodels.IntegerField('kernel index')
420 status_idx = dbmodels.IntegerField('status index')
421 reason = dbmodels.CharField(blank=True, maxlength=3072)
422 machine_idx = dbmodels.IntegerField('host index')
423 test_started_time = dbmodels.DateTimeField(null=True, blank=True)
424 test_finished_time = dbmodels.DateTimeField(null=True, blank=True)
425 job_tag = dbmodels.CharField(blank=True, maxlength=300)
426 job_name = dbmodels.CharField(blank=True, maxlength=300)
427 job_owner = dbmodels.CharField('owner', blank=True, maxlength=240)
428 job_queued_time = dbmodels.DateTimeField(null=True, blank=True)
429 job_started_time = dbmodels.DateTimeField(null=True, blank=True)
430 job_finished_time = dbmodels.DateTimeField(null=True, blank=True)
431 hostname = dbmodels.CharField(blank=True, maxlength=300)
432 platform = dbmodels.CharField(blank=True, maxlength=240)
433 machine_owner = dbmodels.CharField(blank=True, maxlength=240)
434 kernel_hash = dbmodels.CharField(blank=True, maxlength=105)
435 kernel_base = dbmodels.CharField(blank=True, maxlength=90)
436 kernel = dbmodels.CharField(blank=True, maxlength=300)
437 status = dbmodels.CharField(blank=True, maxlength=30)
438
439 objects = TestViewManager()
440
441 def save(self):
442 raise NotImplementedError('TestView is read-only')
443
444
445 def delete(self):
446 raise NotImplementedError('TestView is read-only')
447
448
449 @classmethod
450 def query_objects(cls, filter_data, initial_query=None):
451 if initial_query is None:
showard64aeecd2008-09-19 21:32:58 +0000452 initial_query = cls.objects.get_query_set_with_joins(filter_data)
showard35444862008-08-07 22:35:30 +0000453 return super(TestView, cls).query_objects(filter_data,
454 initial_query=initial_query)
455
456
457 @classmethod
showarde732ee72008-09-23 19:15:43 +0000458 def list_objects(cls, filter_data, initial_query=None, fields=None):
459 # include extra fields
460 if fields is None:
461 fields = cls.get_field_dict().keys() + cls.extra_fields.keys()
462 return super(TestView, cls).list_objects(filter_data, initial_query,
463 fields)
showard35444862008-08-07 22:35:30 +0000464
465
466 class Meta:
467 db_table = 'test_view_2'