blob: 8e5ee3d6a05a13b0b9420a0e307294ba972ea564 [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
18 def _get_group_query_sql(self, query, group_by, extra_select_fields):
19 group_fields = self._get_field_names(group_by)
20 if query._distinct:
21 pk_field = self._get_key_on_this_table(self.model._meta.pk.name)
22 count_sql = 'COUNT(DISTINCT %s)' % pk_field
23 else:
24 count_sql = 'COUNT(1)'
25 select_fields = (group_fields +
26 [count_sql + ' AS ' + self._GROUP_COUNT_NAME] +
27 extra_select_fields)
28
showardd50ffb42008-09-04 02:47:45 +000029 # add the count field to the query selects, so they'll be sortable and
30 # Django won't mess with any of them
showard35444862008-08-07 22:35:30 +000031 query._select[self._GROUP_COUNT_NAME] = count_sql
32
showard35444862008-08-07 22:35:30 +000033 _, where, params = query._get_sql_clause()
showardd50ffb42008-09-04 02:47:45 +000034
35 # insert GROUP BY clause into query
36 group_by_clause = 'GROUP BY ' + ', '.join(group_fields)
37 group_by_position = where.rfind('ORDER BY')
38 if group_by_position == -1:
39 group_by_position = len(where)
40 where = (where[:group_by_position] +
41 group_by_clause + ' ' +
42 where[group_by_position:])
showard35444862008-08-07 22:35:30 +000043
44 return ('SELECT ' + ', '.join(select_fields) + where), params
45
46
47 def get_group_counts(self, query, group_by, extra_select_fields=[]):
48 """
49 Performs the given query grouped by the fields in group_by. Returns a
50 list of rows, where each row is a list containing the value of each
51 field in group_by, followed by the group count.
52 """
53 sql, params = self._get_group_query_sql(query, group_by,
54 extra_select_fields)
55 cursor = readonly_connection.connection.cursor()
56 num_rows = cursor.execute(sql, params)
57 return cursor.fetchall()
58
59
60 def _get_num_groups_sql(self, query, group_by):
61 group_fields = self._get_field_names(group_by)
62 query._order_by = None # this can mess up the query is isn't needed
63 _, where, params = query._get_sql_clause()
64 return ('SELECT COUNT(DISTINCT %s) %s' % (','.join(group_fields),
65 where),
66 params)
67
68
69 def get_num_groups(self, query, group_by):
70 """
71 Returns the number of distinct groups for the given query grouped by the
72 fields in group_by.
73 """
74 sql, params = self._get_num_groups_sql(query, group_by)
75 cursor = readonly_connection.connection.cursor()
76 cursor.execute(sql, params)
77 return cursor.fetchone()[0]
78
79
80class Machine(dbmodels.Model):
81 machine_idx = dbmodels.IntegerField(primary_key=True)
82 hostname = dbmodels.CharField(unique=True, maxlength=300)
83 machine_group = dbmodels.CharField(blank=True, maxlength=240)
84 owner = dbmodels.CharField(blank=True, maxlength=240)
85
86 class Meta:
87 db_table = 'machines'
88
89
90class Kernel(dbmodels.Model):
91 kernel_idx = dbmodels.IntegerField(primary_key=True)
92 kernel_hash = dbmodels.CharField(maxlength=105, editable=False)
93 base = dbmodels.CharField(maxlength=90)
94 printable = dbmodels.CharField(maxlength=300)
95
96 class Meta:
97 db_table = 'kernels'
98
99
100class Patch(dbmodels.Model):
101 kernel = dbmodels.ForeignKey(Kernel, db_column='kernel_idx')
102 name = dbmodels.CharField(blank=True, maxlength=240)
103 url = dbmodels.CharField(blank=True, maxlength=900)
104 hash_ = dbmodels.CharField(blank=True, maxlength=105, db_column='hash')
105
106 class Meta:
107 db_table = 'patches'
108
109
110class Status(dbmodels.Model):
111 status_idx = dbmodels.IntegerField(primary_key=True)
112 word = dbmodels.CharField(maxlength=30)
113
114 class Meta:
115 db_table = 'status'
116
117
118class Job(dbmodels.Model):
119 job_idx = dbmodels.IntegerField(primary_key=True)
120 tag = dbmodels.CharField(unique=True, maxlength=300)
121 label = dbmodels.CharField(maxlength=300)
122 username = dbmodels.CharField(maxlength=240)
123 machine = dbmodels.ForeignKey(Machine, db_column='machine_idx')
124 queued_time = dbmodels.DateTimeField(null=True, blank=True)
125 started_time = dbmodels.DateTimeField(null=True, blank=True)
126 finished_time = dbmodels.DateTimeField(null=True, blank=True)
127
128 class Meta:
129 db_table = 'jobs'
130
131
showarde732ee72008-09-23 19:15:43 +0000132class Test(dbmodels.Model, model_logic.ModelExtensions):
showard35444862008-08-07 22:35:30 +0000133 test_idx = dbmodels.IntegerField(primary_key=True)
134 job = dbmodels.ForeignKey(Job, db_column='job_idx')
135 test = dbmodels.CharField(maxlength=90)
136 subdir = dbmodels.CharField(blank=True, maxlength=180)
137 kernel = dbmodels.ForeignKey(Kernel, db_column='kernel_idx')
138 status = dbmodels.ForeignKey(Status, db_column='status')
139 reason = dbmodels.CharField(blank=True, maxlength=3072)
140 machine = dbmodels.ForeignKey(Machine, db_column='machine_idx')
141 finished_time = dbmodels.DateTimeField(null=True, blank=True)
142 started_time = dbmodels.DateTimeField(null=True, blank=True)
143
showard35444862008-08-07 22:35:30 +0000144 class Meta:
145 db_table = 'tests'
146
147
showarde732ee72008-09-23 19:15:43 +0000148class TestAttribute(dbmodels.Model, model_logic.ModelExtensions):
149 # this isn't really a primary key, but it's necessary to appease Django
150 # and is harmless as long as we're careful
151 test = dbmodels.ForeignKey(Test, db_column='test_idx', primary_key=True)
showard35444862008-08-07 22:35:30 +0000152 attribute = dbmodels.CharField(maxlength=90)
153 value = dbmodels.CharField(blank=True, maxlength=300)
154
155 class Meta:
156 db_table = 'test_attributes'
157
158
159class IterationAttribute(dbmodels.Model):
showarde732ee72008-09-23 19:15:43 +0000160 # see comment on TestAttribute regarding primary_key=True
161 test = dbmodels.ForeignKey(Test, db_column='test_idx', primary_key=True)
showard35444862008-08-07 22:35:30 +0000162 iteration = dbmodels.IntegerField()
163 attribute = dbmodels.CharField(maxlength=90)
164 value = dbmodels.CharField(blank=True, maxlength=300)
165
166 class Meta:
167 db_table = 'iteration_attributes'
168
169
170class IterationResult(dbmodels.Model):
171 test = dbmodels.ForeignKey(Test, db_column='test_idx')
172 iteration = dbmodels.IntegerField()
173 attribute = dbmodels.CharField(maxlength=90)
174 value = dbmodels.FloatField(null=True, max_digits=12, decimal_places=31,
175 blank=True)
176
177 class Meta:
178 db_table = 'iteration_result'
179
180
181class TestLabel(dbmodels.Model, model_logic.ModelExtensions):
showardd50ffb42008-09-04 02:47:45 +0000182 name = dbmodels.CharField(maxlength=80, unique=True)
showard35444862008-08-07 22:35:30 +0000183 description = dbmodels.TextField(blank=True)
184 tests = dbmodels.ManyToManyField(Test, blank=True,
185 filter_interface=dbmodels.HORIZONTAL)
186
187 name_field = 'name'
188
189 class Meta:
190 db_table = 'test_labels'
191
192
193class SavedQuery(dbmodels.Model, model_logic.ModelExtensions):
194 # TODO: change this to foreign key once DBs are merged
195 owner = dbmodels.CharField(maxlength=80)
196 name = dbmodels.CharField(maxlength=100)
197 url_token = dbmodels.TextField()
198
199 class Meta:
200 db_table = 'saved_queries'
201
202
showardce12f552008-09-19 00:48:59 +0000203class EmbeddedGraphingQuery(dbmodels.Model, model_logic.ModelExtensions):
204 url_token = dbmodels.TextField(null=False, blank=False)
205 graph_type = dbmodels.CharField(maxlength=16, null=False, blank=False)
206 params = dbmodels.TextField(null=False, blank=False)
207 last_updated = dbmodels.DateTimeField(null=False, blank=False,
208 editable=False)
209 # refresh_time shows the time at which a thread is updating the cached
210 # image, or NULL if no one is updating the image. This is used so that only
211 # one thread is updating the cached image at a time (see
212 # graphing_utils.handle_plot_request)
213 refresh_time = dbmodels.DateTimeField(editable=False)
214 cached_png = dbmodels.TextField(editable=False)
215
216 class Meta:
217 db_table = 'embedded_graphing_queries'
218
219
showard35444862008-08-07 22:35:30 +0000220# views
221
222class TestViewManager(TempManager):
showardd50ffb42008-09-04 02:47:45 +0000223 class _CustomSqlQ(dbmodels.Q):
showard35444862008-08-07 22:35:30 +0000224 def __init__(self):
225 self._joins = datastructures.SortedDict()
showardd50ffb42008-09-04 02:47:45 +0000226 self._where, self._params = [], []
showard35444862008-08-07 22:35:30 +0000227
228
229 def add_join(self, table, condition, join_type, alias=None):
230 if alias is None:
231 alias = table
232 self._joins[alias] = (table, join_type, condition)
233
234
showardd50ffb42008-09-04 02:47:45 +0000235 def add_where(self, where, params=[]):
236 self._where.append(where)
237 self._params.extend(params)
238
239
showard35444862008-08-07 22:35:30 +0000240 def get_sql(self, opts):
showardd50ffb42008-09-04 02:47:45 +0000241 return self._joins, self._where, self._params
showard35444862008-08-07 22:35:30 +0000242
243
244 def get_query_set(self):
245 query = super(TestViewManager, self).get_query_set()
showard5bf7c502008-08-20 01:22:22 +0000246
showard35444862008-08-07 22:35:30 +0000247 # add extra fields to selects, using the SQL itself as the "alias"
248 extra_select = dict((sql, sql)
249 for sql in self.model.extra_fields.iterkeys())
250 return query.extra(select=extra_select)
251
252
showard64aeecd2008-09-19 21:32:58 +0000253 def _add_join(self, query_set, join_table, join_condition='',
254 join_key='test_idx', suffix='', exclude=False,
255 force_left_join=False):
showard02813502008-08-20 20:52:56 +0000256 table_name = self.model._meta.db_table
showard64aeecd2008-09-19 21:32:58 +0000257 join_alias = join_table + suffix
258 full_join_key = join_alias + '.' + join_key
259 full_join_condition = '%s = %s.test_idx' % (full_join_key, table_name)
showardd50ffb42008-09-04 02:47:45 +0000260 if join_condition:
showard64aeecd2008-09-19 21:32:58 +0000261 full_join_condition += ' AND (' + join_condition + ')'
262 if exclude or force_left_join:
263 join_type = 'LEFT JOIN'
264 else:
265 join_type = 'INNER JOIN'
266
showardd50ffb42008-09-04 02:47:45 +0000267 filter_object = self._CustomSqlQ()
showard64aeecd2008-09-19 21:32:58 +0000268 filter_object.add_join(join_table,
269 full_join_condition,
270 join_type,
271 alias=join_alias)
272 if exclude:
273 filter_object.add_where(full_join_key + ' IS NULL')
274 return query_set.filter(filter_object).distinct()
275
276
277 def _add_label_joins(self, query_set, suffix=''):
278 query_set = self._add_join(query_set, 'test_labels_tests',
279 join_key='test_id', suffix=suffix,
280 force_left_join=True)
showardd50ffb42008-09-04 02:47:45 +0000281
282 second_join_alias = 'test_labels' + suffix
283 second_join_condition = ('%s.id = %s.testlabel_id' %
showard64aeecd2008-09-19 21:32:58 +0000284 (second_join_alias,
285 'test_labels_tests' + suffix))
286 filter_object = self._CustomSqlQ()
showardd50ffb42008-09-04 02:47:45 +0000287 filter_object.add_join('test_labels',
288 second_join_condition,
289 'LEFT JOIN',
290 alias=second_join_alias)
showard64aeecd2008-09-19 21:32:58 +0000291 return query_set.filter(filter_object)
showardd50ffb42008-09-04 02:47:45 +0000292
showard64aeecd2008-09-19 21:32:58 +0000293
294 def _add_attribute_join(self, query_set, suffix='', join_condition='',
295 exclude=False):
296 return self._add_join(query_set, 'test_attributes',
297 join_condition=join_condition,
298 suffix=suffix, exclude=exclude)
showardd50ffb42008-09-04 02:47:45 +0000299
300
301 def _get_label_ids_from_names(self, label_names):
showard64aeecd2008-09-19 21:32:58 +0000302 if not label_names:
303 return []
showardd50ffb42008-09-04 02:47:45 +0000304 query = TestLabel.objects.filter(name__in=label_names).values('id')
305 return [label['id'] for label in query]
showard02813502008-08-20 20:52:56 +0000306
307
showard64aeecd2008-09-19 21:32:58 +0000308 def get_query_set_with_joins(self, filter_data):
showardd50ffb42008-09-04 02:47:45 +0000309 exclude_labels = filter_data.pop('exclude_labels', [])
showard35444862008-08-07 22:35:30 +0000310 query_set = self.get_query_set()
showardd50ffb42008-09-04 02:47:45 +0000311 joined = False
showard35444862008-08-07 22:35:30 +0000312 # TODO: make this check more thorough if necessary
313 if 'test_labels' in filter_data.get('extra_where', ''):
showard02813502008-08-20 20:52:56 +0000314 query_set = self._add_label_joins(query_set)
showardd50ffb42008-09-04 02:47:45 +0000315 joined = True
316
showard64aeecd2008-09-19 21:32:58 +0000317 exclude_label_ids = self._get_label_ids_from_names(exclude_labels)
318 if exclude_label_ids:
319 condition = ('test_labels_tests_exclude.testlabel_id IN (%s)' %
320 ','.join(str(label_id)
321 for label_id in exclude_label_ids))
322 query_set = self._add_join(query_set, 'test_labels_tests',
323 join_key='test_id',
324 suffix='_exclude',
325 join_condition=condition,
326 exclude=True)
327 joined = True
328
329 include_attributes_where = filter_data.pop('include_attributes_where',
330 '')
331 exclude_attributes_where = filter_data.pop('exclude_attributes_where',
332 '')
333 if include_attributes_where:
334 query_set = self._add_attribute_join(
335 query_set, suffix='_include',
336 join_condition=include_attributes_where)
337 joined = True
338 if exclude_attributes_where:
339 query_set = self._add_attribute_join(
340 query_set, suffix='_exclude',
341 join_condition=exclude_attributes_where,
342 exclude=True)
343 joined = True
showardd50ffb42008-09-04 02:47:45 +0000344
345 if not joined:
showard35444862008-08-07 22:35:30 +0000346 filter_data['no_distinct'] = True
showardd50ffb42008-09-04 02:47:45 +0000347
showard35444862008-08-07 22:35:30 +0000348 return query_set
349
350
showard02813502008-08-20 20:52:56 +0000351 def query_test_ids(self, filter_data):
352 dicts = self.model.query_objects(filter_data).values('test_idx')
353 return [item['test_idx'] for item in dicts]
354
355
356 def _custom_select_query(self, query_set, selects):
357 query_selects, where, params = query_set._get_sql_clause()
358 if query_set._distinct:
359 distinct = 'DISTINCT '
360 else:
361 distinct = ''
362 sql_query = 'SELECT ' + distinct + ','.join(selects) + where
363 cursor = readonly_connection.connection.cursor()
364 cursor.execute(sql_query, params)
365 return cursor.fetchall()
366
367
368 def query_test_label_ids(self, filter_data):
showardd50ffb42008-09-04 02:47:45 +0000369 query_set = self.model.query_objects(filter_data)
370 query_set = self._add_label_joins(query_set, suffix='_list')
371 rows = self._custom_select_query(query_set, ['test_labels_list.id'])
372 return [row[0] for row in rows if row[0] is not None]
showard02813502008-08-20 20:52:56 +0000373
374
showard35444862008-08-07 22:35:30 +0000375class TestView(dbmodels.Model, model_logic.ModelExtensions):
376 extra_fields = {
377 'DATE(test_finished_time)' : 'test finished day',
378 }
379
380 group_fields = [
381 'test_name',
382 'status',
383 'kernel',
384 'hostname',
385 'job_tag',
386 'job_name',
387 'platform',
388 'reason',
389 'job_owner',
390 'test_finished_time',
391 'DATE(test_finished_time)',
392 ]
393
394 test_idx = dbmodels.IntegerField('test index', primary_key=True)
395 job_idx = dbmodels.IntegerField('job index', null=True, blank=True)
396 test_name = dbmodels.CharField(blank=True, maxlength=90)
397 subdir = dbmodels.CharField('subdirectory', blank=True, maxlength=180)
398 kernel_idx = dbmodels.IntegerField('kernel index')
399 status_idx = dbmodels.IntegerField('status index')
400 reason = dbmodels.CharField(blank=True, maxlength=3072)
401 machine_idx = dbmodels.IntegerField('host index')
402 test_started_time = dbmodels.DateTimeField(null=True, blank=True)
403 test_finished_time = dbmodels.DateTimeField(null=True, blank=True)
404 job_tag = dbmodels.CharField(blank=True, maxlength=300)
405 job_name = dbmodels.CharField(blank=True, maxlength=300)
406 job_owner = dbmodels.CharField('owner', blank=True, maxlength=240)
407 job_queued_time = dbmodels.DateTimeField(null=True, blank=True)
408 job_started_time = dbmodels.DateTimeField(null=True, blank=True)
409 job_finished_time = dbmodels.DateTimeField(null=True, blank=True)
410 hostname = dbmodels.CharField(blank=True, maxlength=300)
411 platform = dbmodels.CharField(blank=True, maxlength=240)
412 machine_owner = dbmodels.CharField(blank=True, maxlength=240)
413 kernel_hash = dbmodels.CharField(blank=True, maxlength=105)
414 kernel_base = dbmodels.CharField(blank=True, maxlength=90)
415 kernel = dbmodels.CharField(blank=True, maxlength=300)
416 status = dbmodels.CharField(blank=True, maxlength=30)
417
418 objects = TestViewManager()
419
420 def save(self):
421 raise NotImplementedError('TestView is read-only')
422
423
424 def delete(self):
425 raise NotImplementedError('TestView is read-only')
426
427
428 @classmethod
429 def query_objects(cls, filter_data, initial_query=None):
430 if initial_query is None:
showard64aeecd2008-09-19 21:32:58 +0000431 initial_query = cls.objects.get_query_set_with_joins(filter_data)
showard35444862008-08-07 22:35:30 +0000432 return super(TestView, cls).query_objects(filter_data,
433 initial_query=initial_query)
434
435
436 @classmethod
showarde732ee72008-09-23 19:15:43 +0000437 def list_objects(cls, filter_data, initial_query=None, fields=None):
438 # include extra fields
439 if fields is None:
440 fields = cls.get_field_dict().keys() + cls.extra_fields.keys()
441 return super(TestView, cls).list_objects(filter_data, initial_query,
442 fields)
showard35444862008-08-07 22:35:30 +0000443
444
445 class Meta:
446 db_table = 'test_view_2'