blob: 8861b84b66cc2064c56ae62a13553473698f58fe [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
showard7c199df2008-10-03 10:17:15 +000011 return self.get_key_on_this_table(field)
showard35444862008-08-07 22:35:30 +000012
13
14 def _get_field_names(self, fields):
15 return [self._get_key_unless_is_function(field) for field in fields]
16
17
18 def _get_group_query_sql(self, query, group_by, extra_select_fields):
19 group_fields = self._get_field_names(group_by)
showard35444862008-08-07 22:35:30 +000020
showard7c199df2008-10-03 10:17:15 +000021 select_fields = list(group_fields)
22 for field_name, field_sql in extra_select_fields.iteritems():
23 field_sql = self._get_key_unless_is_function(field_sql)
24 select_fields.append(field_sql + ' AS ' + field_name)
25 # add the extra fields to the query selects, so they'll be sortable
26 # and Django won't mess with any of them
showard8a6eb0c2008-10-01 11:38:59 +000027 query._select[field_name] = field_sql
showard35444862008-08-07 22:35:30 +000028
showard35444862008-08-07 22:35:30 +000029 _, where, params = query._get_sql_clause()
showardd50ffb42008-09-04 02:47:45 +000030
31 # insert GROUP BY clause into query
showard7c199df2008-10-03 10:17:15 +000032 group_by_clause = ' GROUP BY ' + ', '.join(group_fields)
showardd50ffb42008-09-04 02:47:45 +000033 group_by_position = where.rfind('ORDER BY')
34 if group_by_position == -1:
35 group_by_position = len(where)
36 where = (where[:group_by_position] +
37 group_by_clause + ' ' +
38 where[group_by_position:])
showard35444862008-08-07 22:35:30 +000039
40 return ('SELECT ' + ', '.join(select_fields) + where), params
41
42
showard8a6eb0c2008-10-01 11:38:59 +000043 def execute_group_query(self, query, group_by, extra_select_fields=[]):
showard35444862008-08-07 22:35:30 +000044 """
showard8a6eb0c2008-10-01 11:38:59 +000045 Performs the given query grouped by the fields in group_by with the
showard7c199df2008-10-03 10:17:15 +000046 given extra select fields added. extra_select_fields should be a dict
47 mapping field alias to field SQL. Usually, the extra fields will use
showard8a6eb0c2008-10-01 11:38:59 +000048 group aggregation functions. Returns a list of dicts, where each dict
49 corresponds to single row and contains a key for each grouped field as
50 well as all of the extra select fields.
showard35444862008-08-07 22:35:30 +000051 """
52 sql, params = self._get_group_query_sql(query, group_by,
53 extra_select_fields)
54 cursor = readonly_connection.connection.cursor()
showard8a6eb0c2008-10-01 11:38:59 +000055 cursor.execute(sql, params)
56 field_names = [column_info[0] for column_info in cursor.description]
57 row_dicts = [dict(zip(field_names, row)) for row in cursor.fetchall()]
58 return row_dicts
59
60
61 def get_count_sql(self, query):
62 """
63 Get the SQL to properly select a per-group count of unique matches for
showard7c199df2008-10-03 10:17:15 +000064 a grouped query. Returns a tuple (field alias, field SQL)
showard8a6eb0c2008-10-01 11:38:59 +000065 """
66 if query._distinct:
showard7c199df2008-10-03 10:17:15 +000067 pk_field = self.get_key_on_this_table()
showard8a6eb0c2008-10-01 11:38:59 +000068 count_sql = 'COUNT(DISTINCT %s)' % pk_field
69 else:
70 count_sql = 'COUNT(1)'
showard7c199df2008-10-03 10:17:15 +000071 return self._GROUP_COUNT_NAME, count_sql
showard35444862008-08-07 22:35:30 +000072
73
74 def _get_num_groups_sql(self, query, group_by):
75 group_fields = self._get_field_names(group_by)
showard8a6eb0c2008-10-01 11:38:59 +000076 query._order_by = None # this can mess up the query and isn't needed
showard35444862008-08-07 22:35:30 +000077 _, where, params = query._get_sql_clause()
78 return ('SELECT COUNT(DISTINCT %s) %s' % (','.join(group_fields),
79 where),
80 params)
81
82
83 def get_num_groups(self, query, group_by):
84 """
85 Returns the number of distinct groups for the given query grouped by the
86 fields in group_by.
87 """
88 sql, params = self._get_num_groups_sql(query, group_by)
89 cursor = readonly_connection.connection.cursor()
90 cursor.execute(sql, params)
91 return cursor.fetchone()[0]
92
93
94class Machine(dbmodels.Model):
95 machine_idx = dbmodels.IntegerField(primary_key=True)
96 hostname = dbmodels.CharField(unique=True, maxlength=300)
97 machine_group = dbmodels.CharField(blank=True, maxlength=240)
98 owner = dbmodels.CharField(blank=True, maxlength=240)
99
100 class Meta:
101 db_table = 'machines'
102
103
104class Kernel(dbmodels.Model):
105 kernel_idx = dbmodels.IntegerField(primary_key=True)
106 kernel_hash = dbmodels.CharField(maxlength=105, editable=False)
107 base = dbmodels.CharField(maxlength=90)
108 printable = dbmodels.CharField(maxlength=300)
109
110 class Meta:
111 db_table = 'kernels'
112
113
114class Patch(dbmodels.Model):
115 kernel = dbmodels.ForeignKey(Kernel, db_column='kernel_idx')
116 name = dbmodels.CharField(blank=True, maxlength=240)
117 url = dbmodels.CharField(blank=True, maxlength=900)
118 hash_ = dbmodels.CharField(blank=True, maxlength=105, db_column='hash')
119
120 class Meta:
121 db_table = 'patches'
122
123
124class Status(dbmodels.Model):
125 status_idx = dbmodels.IntegerField(primary_key=True)
126 word = dbmodels.CharField(maxlength=30)
127
128 class Meta:
129 db_table = 'status'
130
131
132class Job(dbmodels.Model):
133 job_idx = dbmodels.IntegerField(primary_key=True)
134 tag = dbmodels.CharField(unique=True, maxlength=300)
135 label = dbmodels.CharField(maxlength=300)
136 username = dbmodels.CharField(maxlength=240)
137 machine = dbmodels.ForeignKey(Machine, db_column='machine_idx')
138 queued_time = dbmodels.DateTimeField(null=True, blank=True)
139 started_time = dbmodels.DateTimeField(null=True, blank=True)
140 finished_time = dbmodels.DateTimeField(null=True, blank=True)
141
142 class Meta:
143 db_table = 'jobs'
144
145
showarde732ee72008-09-23 19:15:43 +0000146class Test(dbmodels.Model, model_logic.ModelExtensions):
showard35444862008-08-07 22:35:30 +0000147 test_idx = dbmodels.IntegerField(primary_key=True)
148 job = dbmodels.ForeignKey(Job, db_column='job_idx')
149 test = dbmodels.CharField(maxlength=90)
150 subdir = dbmodels.CharField(blank=True, maxlength=180)
151 kernel = dbmodels.ForeignKey(Kernel, db_column='kernel_idx')
152 status = dbmodels.ForeignKey(Status, db_column='status')
153 reason = dbmodels.CharField(blank=True, maxlength=3072)
154 machine = dbmodels.ForeignKey(Machine, db_column='machine_idx')
155 finished_time = dbmodels.DateTimeField(null=True, blank=True)
156 started_time = dbmodels.DateTimeField(null=True, blank=True)
157
showard35444862008-08-07 22:35:30 +0000158 class Meta:
159 db_table = 'tests'
160
161
showarde732ee72008-09-23 19:15:43 +0000162class TestAttribute(dbmodels.Model, model_logic.ModelExtensions):
163 # this isn't really a primary key, but it's necessary to appease Django
164 # and is harmless as long as we're careful
165 test = dbmodels.ForeignKey(Test, db_column='test_idx', primary_key=True)
showard35444862008-08-07 22:35:30 +0000166 attribute = dbmodels.CharField(maxlength=90)
167 value = dbmodels.CharField(blank=True, maxlength=300)
168
169 class Meta:
170 db_table = 'test_attributes'
171
172
173class IterationAttribute(dbmodels.Model):
showarde732ee72008-09-23 19:15:43 +0000174 # see comment on TestAttribute regarding primary_key=True
175 test = dbmodels.ForeignKey(Test, db_column='test_idx', primary_key=True)
showard35444862008-08-07 22:35:30 +0000176 iteration = dbmodels.IntegerField()
177 attribute = dbmodels.CharField(maxlength=90)
178 value = dbmodels.CharField(blank=True, maxlength=300)
179
180 class Meta:
181 db_table = 'iteration_attributes'
182
183
184class IterationResult(dbmodels.Model):
185 test = dbmodels.ForeignKey(Test, db_column='test_idx')
186 iteration = dbmodels.IntegerField()
187 attribute = dbmodels.CharField(maxlength=90)
188 value = dbmodels.FloatField(null=True, max_digits=12, decimal_places=31,
189 blank=True)
190
191 class Meta:
192 db_table = 'iteration_result'
193
194
195class TestLabel(dbmodels.Model, model_logic.ModelExtensions):
showardd50ffb42008-09-04 02:47:45 +0000196 name = dbmodels.CharField(maxlength=80, unique=True)
showard35444862008-08-07 22:35:30 +0000197 description = dbmodels.TextField(blank=True)
198 tests = dbmodels.ManyToManyField(Test, blank=True,
199 filter_interface=dbmodels.HORIZONTAL)
200
201 name_field = 'name'
202
203 class Meta:
204 db_table = 'test_labels'
205
206
207class SavedQuery(dbmodels.Model, model_logic.ModelExtensions):
208 # TODO: change this to foreign key once DBs are merged
209 owner = dbmodels.CharField(maxlength=80)
210 name = dbmodels.CharField(maxlength=100)
211 url_token = dbmodels.TextField()
212
213 class Meta:
214 db_table = 'saved_queries'
215
216
showardce12f552008-09-19 00:48:59 +0000217class EmbeddedGraphingQuery(dbmodels.Model, model_logic.ModelExtensions):
218 url_token = dbmodels.TextField(null=False, blank=False)
219 graph_type = dbmodels.CharField(maxlength=16, null=False, blank=False)
220 params = dbmodels.TextField(null=False, blank=False)
221 last_updated = dbmodels.DateTimeField(null=False, blank=False,
222 editable=False)
223 # refresh_time shows the time at which a thread is updating the cached
224 # image, or NULL if no one is updating the image. This is used so that only
225 # one thread is updating the cached image at a time (see
226 # graphing_utils.handle_plot_request)
227 refresh_time = dbmodels.DateTimeField(editable=False)
228 cached_png = dbmodels.TextField(editable=False)
229
230 class Meta:
231 db_table = 'embedded_graphing_queries'
232
233
showard35444862008-08-07 22:35:30 +0000234# views
235
236class TestViewManager(TempManager):
showardd50ffb42008-09-04 02:47:45 +0000237 class _CustomSqlQ(dbmodels.Q):
showard35444862008-08-07 22:35:30 +0000238 def __init__(self):
239 self._joins = datastructures.SortedDict()
showardd50ffb42008-09-04 02:47:45 +0000240 self._where, self._params = [], []
showard35444862008-08-07 22:35:30 +0000241
242
243 def add_join(self, table, condition, join_type, alias=None):
244 if alias is None:
245 alias = table
showard7c199df2008-10-03 10:17:15 +0000246 condition = model_logic.ModelExtensions.escape_user_sql(condition)
showard35444862008-08-07 22:35:30 +0000247 self._joins[alias] = (table, join_type, condition)
248
249
showardd50ffb42008-09-04 02:47:45 +0000250 def add_where(self, where, params=[]):
251 self._where.append(where)
252 self._params.extend(params)
253
254
showard35444862008-08-07 22:35:30 +0000255 def get_sql(self, opts):
showardd50ffb42008-09-04 02:47:45 +0000256 return self._joins, self._where, self._params
showard35444862008-08-07 22:35:30 +0000257
258
259 def get_query_set(self):
260 query = super(TestViewManager, self).get_query_set()
showard5bf7c502008-08-20 01:22:22 +0000261
showard35444862008-08-07 22:35:30 +0000262 # add extra fields to selects, using the SQL itself as the "alias"
263 extra_select = dict((sql, sql)
264 for sql in self.model.extra_fields.iterkeys())
265 return query.extra(select=extra_select)
266
267
showard64aeecd2008-09-19 21:32:58 +0000268 def _add_join(self, query_set, join_table, join_condition='',
269 join_key='test_idx', suffix='', exclude=False,
270 force_left_join=False):
showard02813502008-08-20 20:52:56 +0000271 table_name = self.model._meta.db_table
showard64aeecd2008-09-19 21:32:58 +0000272 join_alias = join_table + suffix
273 full_join_key = join_alias + '.' + join_key
274 full_join_condition = '%s = %s.test_idx' % (full_join_key, table_name)
showardd50ffb42008-09-04 02:47:45 +0000275 if join_condition:
showard64aeecd2008-09-19 21:32:58 +0000276 full_join_condition += ' AND (' + join_condition + ')'
277 if exclude or force_left_join:
278 join_type = 'LEFT JOIN'
279 else:
280 join_type = 'INNER JOIN'
281
showardd50ffb42008-09-04 02:47:45 +0000282 filter_object = self._CustomSqlQ()
showard64aeecd2008-09-19 21:32:58 +0000283 filter_object.add_join(join_table,
284 full_join_condition,
285 join_type,
286 alias=join_alias)
287 if exclude:
288 filter_object.add_where(full_join_key + ' IS NULL')
289 return query_set.filter(filter_object).distinct()
290
291
292 def _add_label_joins(self, query_set, suffix=''):
293 query_set = self._add_join(query_set, 'test_labels_tests',
294 join_key='test_id', suffix=suffix,
295 force_left_join=True)
showardd50ffb42008-09-04 02:47:45 +0000296
297 second_join_alias = 'test_labels' + suffix
298 second_join_condition = ('%s.id = %s.testlabel_id' %
showard64aeecd2008-09-19 21:32:58 +0000299 (second_join_alias,
300 'test_labels_tests' + suffix))
301 filter_object = self._CustomSqlQ()
showardd50ffb42008-09-04 02:47:45 +0000302 filter_object.add_join('test_labels',
303 second_join_condition,
304 'LEFT JOIN',
305 alias=second_join_alias)
showard64aeecd2008-09-19 21:32:58 +0000306 return query_set.filter(filter_object)
showardd50ffb42008-09-04 02:47:45 +0000307
showard64aeecd2008-09-19 21:32:58 +0000308
309 def _add_attribute_join(self, query_set, suffix='', join_condition='',
310 exclude=False):
311 return self._add_join(query_set, 'test_attributes',
312 join_condition=join_condition,
313 suffix=suffix, exclude=exclude)
showardd50ffb42008-09-04 02:47:45 +0000314
315
316 def _get_label_ids_from_names(self, label_names):
showard64aeecd2008-09-19 21:32:58 +0000317 if not label_names:
318 return []
showardd50ffb42008-09-04 02:47:45 +0000319 query = TestLabel.objects.filter(name__in=label_names).values('id')
320 return [label['id'] for label in query]
showard02813502008-08-20 20:52:56 +0000321
322
showard64aeecd2008-09-19 21:32:58 +0000323 def get_query_set_with_joins(self, filter_data):
showardd50ffb42008-09-04 02:47:45 +0000324 exclude_labels = filter_data.pop('exclude_labels', [])
showard35444862008-08-07 22:35:30 +0000325 query_set = self.get_query_set()
showardd50ffb42008-09-04 02:47:45 +0000326 joined = False
showard35444862008-08-07 22:35:30 +0000327 # TODO: make this check more thorough if necessary
328 if 'test_labels' in filter_data.get('extra_where', ''):
showard02813502008-08-20 20:52:56 +0000329 query_set = self._add_label_joins(query_set)
showardd50ffb42008-09-04 02:47:45 +0000330 joined = True
331
showard64aeecd2008-09-19 21:32:58 +0000332 exclude_label_ids = self._get_label_ids_from_names(exclude_labels)
333 if exclude_label_ids:
334 condition = ('test_labels_tests_exclude.testlabel_id IN (%s)' %
335 ','.join(str(label_id)
336 for label_id in exclude_label_ids))
337 query_set = self._add_join(query_set, 'test_labels_tests',
338 join_key='test_id',
339 suffix='_exclude',
340 join_condition=condition,
341 exclude=True)
342 joined = True
343
344 include_attributes_where = filter_data.pop('include_attributes_where',
345 '')
346 exclude_attributes_where = filter_data.pop('exclude_attributes_where',
347 '')
348 if include_attributes_where:
349 query_set = self._add_attribute_join(
350 query_set, suffix='_include',
351 join_condition=include_attributes_where)
352 joined = True
353 if exclude_attributes_where:
354 query_set = self._add_attribute_join(
355 query_set, suffix='_exclude',
356 join_condition=exclude_attributes_where,
357 exclude=True)
358 joined = True
showardd50ffb42008-09-04 02:47:45 +0000359
360 if not joined:
showard35444862008-08-07 22:35:30 +0000361 filter_data['no_distinct'] = True
showardd50ffb42008-09-04 02:47:45 +0000362
showard35444862008-08-07 22:35:30 +0000363 return query_set
364
365
showard02813502008-08-20 20:52:56 +0000366 def query_test_ids(self, filter_data):
367 dicts = self.model.query_objects(filter_data).values('test_idx')
368 return [item['test_idx'] for item in dicts]
369
370
371 def _custom_select_query(self, query_set, selects):
372 query_selects, where, params = query_set._get_sql_clause()
373 if query_set._distinct:
374 distinct = 'DISTINCT '
375 else:
376 distinct = ''
377 sql_query = 'SELECT ' + distinct + ','.join(selects) + where
378 cursor = readonly_connection.connection.cursor()
379 cursor.execute(sql_query, params)
380 return cursor.fetchall()
381
382
383 def query_test_label_ids(self, filter_data):
showardd50ffb42008-09-04 02:47:45 +0000384 query_set = self.model.query_objects(filter_data)
385 query_set = self._add_label_joins(query_set, suffix='_list')
386 rows = self._custom_select_query(query_set, ['test_labels_list.id'])
387 return [row[0] for row in rows if row[0] is not None]
showard02813502008-08-20 20:52:56 +0000388
389
showard35444862008-08-07 22:35:30 +0000390class TestView(dbmodels.Model, model_logic.ModelExtensions):
391 extra_fields = {
392 'DATE(test_finished_time)' : 'test finished day',
393 }
394
395 group_fields = [
396 'test_name',
397 'status',
398 'kernel',
399 'hostname',
400 'job_tag',
401 'job_name',
402 'platform',
403 'reason',
404 'job_owner',
405 'test_finished_time',
406 'DATE(test_finished_time)',
407 ]
408
409 test_idx = dbmodels.IntegerField('test index', primary_key=True)
410 job_idx = dbmodels.IntegerField('job index', null=True, blank=True)
411 test_name = dbmodels.CharField(blank=True, maxlength=90)
412 subdir = dbmodels.CharField('subdirectory', blank=True, maxlength=180)
413 kernel_idx = dbmodels.IntegerField('kernel index')
414 status_idx = dbmodels.IntegerField('status index')
415 reason = dbmodels.CharField(blank=True, maxlength=3072)
416 machine_idx = dbmodels.IntegerField('host index')
417 test_started_time = dbmodels.DateTimeField(null=True, blank=True)
418 test_finished_time = dbmodels.DateTimeField(null=True, blank=True)
419 job_tag = dbmodels.CharField(blank=True, maxlength=300)
420 job_name = dbmodels.CharField(blank=True, maxlength=300)
421 job_owner = dbmodels.CharField('owner', blank=True, maxlength=240)
422 job_queued_time = dbmodels.DateTimeField(null=True, blank=True)
423 job_started_time = dbmodels.DateTimeField(null=True, blank=True)
424 job_finished_time = dbmodels.DateTimeField(null=True, blank=True)
425 hostname = dbmodels.CharField(blank=True, maxlength=300)
426 platform = dbmodels.CharField(blank=True, maxlength=240)
427 machine_owner = dbmodels.CharField(blank=True, maxlength=240)
428 kernel_hash = dbmodels.CharField(blank=True, maxlength=105)
429 kernel_base = dbmodels.CharField(blank=True, maxlength=90)
430 kernel = dbmodels.CharField(blank=True, maxlength=300)
431 status = dbmodels.CharField(blank=True, maxlength=30)
432
433 objects = TestViewManager()
434
435 def save(self):
436 raise NotImplementedError('TestView is read-only')
437
438
439 def delete(self):
440 raise NotImplementedError('TestView is read-only')
441
442
443 @classmethod
444 def query_objects(cls, filter_data, initial_query=None):
445 if initial_query is None:
showard64aeecd2008-09-19 21:32:58 +0000446 initial_query = cls.objects.get_query_set_with_joins(filter_data)
showard35444862008-08-07 22:35:30 +0000447 return super(TestView, cls).query_objects(filter_data,
448 initial_query=initial_query)
449
450
451 @classmethod
showarde732ee72008-09-23 19:15:43 +0000452 def list_objects(cls, filter_data, initial_query=None, fields=None):
453 # include extra fields
454 if fields is None:
455 fields = cls.get_field_dict().keys() + cls.extra_fields.keys()
456 return super(TestView, cls).list_objects(filter_data, initial_query,
457 fields)
showard35444862008-08-07 22:35:30 +0000458
459
460 class Meta:
461 db_table = 'test_view_2'