blob: 89aba227594362ae92aff794f893f4a71d68e3e5 [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
showardf2489522008-10-23 23:08:00 +000014 def _get_field_names(self, fields, extra_select_fields={}):
15 field_names = []
16 for field in fields:
17 if field in extra_select_fields:
18 field_names.append(field)
19 else:
20 field_names.append(self._get_key_unless_is_function(field))
21 return field_names
showard35444862008-08-07 22:35:30 +000022
23
24 def _get_group_query_sql(self, query, group_by, extra_select_fields):
showardf2489522008-10-23 23:08:00 +000025 group_fields = self._get_field_names(group_by, extra_select_fields)
showard35444862008-08-07 22:35:30 +000026
showardf2489522008-10-23 23:08:00 +000027 select_fields = [field for field in group_fields
28 if field not in extra_select_fields]
showard7c199df2008-10-03 10:17:15 +000029 for field_name, field_sql in extra_select_fields.iteritems():
30 field_sql = self._get_key_unless_is_function(field_sql)
31 select_fields.append(field_sql + ' AS ' + field_name)
32 # add the extra fields to the query selects, so they'll be sortable
33 # and Django won't mess with any of them
showard8a6eb0c2008-10-01 11:38:59 +000034 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
showard7c199df2008-10-03 10:17:15 +000039 group_by_clause = ' GROUP BY ' + ', '.join(group_fields)
showardd50ffb42008-09-04 02:47:45 +000040 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
showard7c199df2008-10-03 10:17:15 +000053 given extra select fields added. extra_select_fields should be a dict
54 mapping field alias to field SQL. Usually, the extra fields will use
showard8a6eb0c2008-10-01 11:38:59 +000055 group aggregation functions. Returns a list of dicts, where each dict
56 corresponds to single row and contains a key for each grouped field as
57 well as all of the extra select fields.
showard35444862008-08-07 22:35:30 +000058 """
59 sql, params = self._get_group_query_sql(query, group_by,
60 extra_select_fields)
showard56e93772008-10-06 10:06:22 +000061 cursor = readonly_connection.connection().cursor()
showard8a6eb0c2008-10-01 11:38:59 +000062 cursor.execute(sql, params)
63 field_names = [column_info[0] for column_info in cursor.description]
64 row_dicts = [dict(zip(field_names, row)) for row in cursor.fetchall()]
65 return row_dicts
66
67
68 def get_count_sql(self, query):
69 """
70 Get the SQL to properly select a per-group count of unique matches for
showard7c199df2008-10-03 10:17:15 +000071 a grouped query. Returns a tuple (field alias, field SQL)
showard8a6eb0c2008-10-01 11:38:59 +000072 """
73 if query._distinct:
showard7c199df2008-10-03 10:17:15 +000074 pk_field = self.get_key_on_this_table()
showard8a6eb0c2008-10-01 11:38:59 +000075 count_sql = 'COUNT(DISTINCT %s)' % pk_field
76 else:
77 count_sql = 'COUNT(1)'
showard7c199df2008-10-03 10:17:15 +000078 return self._GROUP_COUNT_NAME, count_sql
showard35444862008-08-07 22:35:30 +000079
80
81 def _get_num_groups_sql(self, query, group_by):
82 group_fields = self._get_field_names(group_by)
showard8a6eb0c2008-10-01 11:38:59 +000083 query._order_by = None # this can mess up the query and isn't needed
showard35444862008-08-07 22:35:30 +000084 _, where, params = query._get_sql_clause()
85 return ('SELECT COUNT(DISTINCT %s) %s' % (','.join(group_fields),
86 where),
87 params)
88
89
90 def get_num_groups(self, query, group_by):
91 """
92 Returns the number of distinct groups for the given query grouped by the
93 fields in group_by.
94 """
95 sql, params = self._get_num_groups_sql(query, group_by)
showard56e93772008-10-06 10:06:22 +000096 cursor = readonly_connection.connection().cursor()
showard35444862008-08-07 22:35:30 +000097 cursor.execute(sql, params)
98 return cursor.fetchone()[0]
99
100
101class Machine(dbmodels.Model):
102 machine_idx = dbmodels.IntegerField(primary_key=True)
103 hostname = dbmodels.CharField(unique=True, maxlength=300)
104 machine_group = dbmodels.CharField(blank=True, maxlength=240)
105 owner = dbmodels.CharField(blank=True, maxlength=240)
106
107 class Meta:
108 db_table = 'machines'
109
110
111class Kernel(dbmodels.Model):
112 kernel_idx = dbmodels.IntegerField(primary_key=True)
113 kernel_hash = dbmodels.CharField(maxlength=105, editable=False)
114 base = dbmodels.CharField(maxlength=90)
115 printable = dbmodels.CharField(maxlength=300)
116
117 class Meta:
118 db_table = 'kernels'
119
120
121class Patch(dbmodels.Model):
122 kernel = dbmodels.ForeignKey(Kernel, db_column='kernel_idx')
123 name = dbmodels.CharField(blank=True, maxlength=240)
124 url = dbmodels.CharField(blank=True, maxlength=900)
125 hash_ = dbmodels.CharField(blank=True, maxlength=105, db_column='hash')
126
127 class Meta:
128 db_table = 'patches'
129
130
131class Status(dbmodels.Model):
132 status_idx = dbmodels.IntegerField(primary_key=True)
133 word = dbmodels.CharField(maxlength=30)
134
135 class Meta:
136 db_table = 'status'
137
138
139class Job(dbmodels.Model):
140 job_idx = dbmodels.IntegerField(primary_key=True)
141 tag = dbmodels.CharField(unique=True, maxlength=300)
142 label = dbmodels.CharField(maxlength=300)
143 username = dbmodels.CharField(maxlength=240)
144 machine = dbmodels.ForeignKey(Machine, db_column='machine_idx')
145 queued_time = dbmodels.DateTimeField(null=True, blank=True)
146 started_time = dbmodels.DateTimeField(null=True, blank=True)
147 finished_time = dbmodels.DateTimeField(null=True, blank=True)
148
149 class Meta:
150 db_table = 'jobs'
151
152
showarde732ee72008-09-23 19:15:43 +0000153class Test(dbmodels.Model, model_logic.ModelExtensions):
showard35444862008-08-07 22:35:30 +0000154 test_idx = dbmodels.IntegerField(primary_key=True)
155 job = dbmodels.ForeignKey(Job, db_column='job_idx')
156 test = dbmodels.CharField(maxlength=90)
157 subdir = dbmodels.CharField(blank=True, maxlength=180)
158 kernel = dbmodels.ForeignKey(Kernel, db_column='kernel_idx')
159 status = dbmodels.ForeignKey(Status, db_column='status')
160 reason = dbmodels.CharField(blank=True, maxlength=3072)
161 machine = dbmodels.ForeignKey(Machine, db_column='machine_idx')
162 finished_time = dbmodels.DateTimeField(null=True, blank=True)
163 started_time = dbmodels.DateTimeField(null=True, blank=True)
164
showard35444862008-08-07 22:35:30 +0000165 class Meta:
166 db_table = 'tests'
167
168
showarde732ee72008-09-23 19:15:43 +0000169class TestAttribute(dbmodels.Model, model_logic.ModelExtensions):
170 # this isn't really a primary key, but it's necessary to appease Django
171 # and is harmless as long as we're careful
172 test = dbmodels.ForeignKey(Test, db_column='test_idx', primary_key=True)
showard35444862008-08-07 22:35:30 +0000173 attribute = dbmodels.CharField(maxlength=90)
174 value = dbmodels.CharField(blank=True, maxlength=300)
175
176 class Meta:
177 db_table = 'test_attributes'
178
179
180class IterationAttribute(dbmodels.Model):
showarde732ee72008-09-23 19:15:43 +0000181 # see comment on TestAttribute regarding primary_key=True
182 test = dbmodels.ForeignKey(Test, db_column='test_idx', primary_key=True)
showard35444862008-08-07 22:35:30 +0000183 iteration = dbmodels.IntegerField()
184 attribute = dbmodels.CharField(maxlength=90)
185 value = dbmodels.CharField(blank=True, maxlength=300)
186
187 class Meta:
188 db_table = 'iteration_attributes'
189
190
191class IterationResult(dbmodels.Model):
192 test = dbmodels.ForeignKey(Test, db_column='test_idx')
193 iteration = dbmodels.IntegerField()
194 attribute = dbmodels.CharField(maxlength=90)
195 value = dbmodels.FloatField(null=True, max_digits=12, decimal_places=31,
196 blank=True)
197
198 class Meta:
199 db_table = 'iteration_result'
200
201
202class TestLabel(dbmodels.Model, model_logic.ModelExtensions):
showardd50ffb42008-09-04 02:47:45 +0000203 name = dbmodels.CharField(maxlength=80, unique=True)
showard35444862008-08-07 22:35:30 +0000204 description = dbmodels.TextField(blank=True)
205 tests = dbmodels.ManyToManyField(Test, blank=True,
206 filter_interface=dbmodels.HORIZONTAL)
207
208 name_field = 'name'
209
210 class Meta:
211 db_table = 'test_labels'
212
213
214class SavedQuery(dbmodels.Model, model_logic.ModelExtensions):
215 # TODO: change this to foreign key once DBs are merged
216 owner = dbmodels.CharField(maxlength=80)
217 name = dbmodels.CharField(maxlength=100)
218 url_token = dbmodels.TextField()
219
220 class Meta:
221 db_table = 'saved_queries'
222
223
showardce12f552008-09-19 00:48:59 +0000224class EmbeddedGraphingQuery(dbmodels.Model, model_logic.ModelExtensions):
225 url_token = dbmodels.TextField(null=False, blank=False)
226 graph_type = dbmodels.CharField(maxlength=16, null=False, blank=False)
227 params = dbmodels.TextField(null=False, blank=False)
228 last_updated = dbmodels.DateTimeField(null=False, blank=False,
229 editable=False)
230 # refresh_time shows the time at which a thread is updating the cached
231 # image, or NULL if no one is updating the image. This is used so that only
232 # one thread is updating the cached image at a time (see
233 # graphing_utils.handle_plot_request)
234 refresh_time = dbmodels.DateTimeField(editable=False)
235 cached_png = dbmodels.TextField(editable=False)
236
237 class Meta:
238 db_table = 'embedded_graphing_queries'
239
240
showard35444862008-08-07 22:35:30 +0000241# views
242
243class TestViewManager(TempManager):
showardd50ffb42008-09-04 02:47:45 +0000244 class _CustomSqlQ(dbmodels.Q):
showard35444862008-08-07 22:35:30 +0000245 def __init__(self):
246 self._joins = datastructures.SortedDict()
showardd50ffb42008-09-04 02:47:45 +0000247 self._where, self._params = [], []
showard35444862008-08-07 22:35:30 +0000248
249
250 def add_join(self, table, condition, join_type, alias=None):
251 if alias is None:
252 alias = table
showard7c199df2008-10-03 10:17:15 +0000253 condition = model_logic.ModelExtensions.escape_user_sql(condition)
showard35444862008-08-07 22:35:30 +0000254 self._joins[alias] = (table, join_type, condition)
255
256
showardd50ffb42008-09-04 02:47:45 +0000257 def add_where(self, where, params=[]):
258 self._where.append(where)
259 self._params.extend(params)
260
261
showard35444862008-08-07 22:35:30 +0000262 def get_sql(self, opts):
showardd50ffb42008-09-04 02:47:45 +0000263 return self._joins, self._where, self._params
showard35444862008-08-07 22:35:30 +0000264
265
266 def get_query_set(self):
267 query = super(TestViewManager, self).get_query_set()
showard5bf7c502008-08-20 01:22:22 +0000268
showard35444862008-08-07 22:35:30 +0000269 # add extra fields to selects, using the SQL itself as the "alias"
270 extra_select = dict((sql, sql)
271 for sql in self.model.extra_fields.iterkeys())
272 return query.extra(select=extra_select)
273
274
showard64aeecd2008-09-19 21:32:58 +0000275 def _add_join(self, query_set, join_table, join_condition='',
276 join_key='test_idx', suffix='', exclude=False,
277 force_left_join=False):
showard02813502008-08-20 20:52:56 +0000278 table_name = self.model._meta.db_table
showard64aeecd2008-09-19 21:32:58 +0000279 join_alias = join_table + suffix
280 full_join_key = join_alias + '.' + join_key
281 full_join_condition = '%s = %s.test_idx' % (full_join_key, table_name)
showardd50ffb42008-09-04 02:47:45 +0000282 if join_condition:
showard64aeecd2008-09-19 21:32:58 +0000283 full_join_condition += ' AND (' + join_condition + ')'
284 if exclude or force_left_join:
285 join_type = 'LEFT JOIN'
286 else:
287 join_type = 'INNER JOIN'
288
showardd50ffb42008-09-04 02:47:45 +0000289 filter_object = self._CustomSqlQ()
showard64aeecd2008-09-19 21:32:58 +0000290 filter_object.add_join(join_table,
291 full_join_condition,
292 join_type,
293 alias=join_alias)
294 if exclude:
295 filter_object.add_where(full_join_key + ' IS NULL')
296 return query_set.filter(filter_object).distinct()
297
298
showardf2489522008-10-23 23:08:00 +0000299 def _get_include_exclude_suffix(self, exclude):
300 if exclude:
301 suffix = '_exclude'
302 else:
303 suffix = '_include'
304 return suffix
305
306
showard64aeecd2008-09-19 21:32:58 +0000307 def _add_label_joins(self, query_set, suffix=''):
308 query_set = self._add_join(query_set, 'test_labels_tests',
309 join_key='test_id', suffix=suffix,
310 force_left_join=True)
showardd50ffb42008-09-04 02:47:45 +0000311
312 second_join_alias = 'test_labels' + suffix
313 second_join_condition = ('%s.id = %s.testlabel_id' %
showard64aeecd2008-09-19 21:32:58 +0000314 (second_join_alias,
315 'test_labels_tests' + suffix))
316 filter_object = self._CustomSqlQ()
showardd50ffb42008-09-04 02:47:45 +0000317 filter_object.add_join('test_labels',
318 second_join_condition,
319 'LEFT JOIN',
320 alias=second_join_alias)
showard64aeecd2008-09-19 21:32:58 +0000321 return query_set.filter(filter_object)
showardd50ffb42008-09-04 02:47:45 +0000322
showard64aeecd2008-09-19 21:32:58 +0000323
showardf2489522008-10-23 23:08:00 +0000324 def _add_attribute_join(self, query_set, join_condition='', suffix=None,
showard64aeecd2008-09-19 21:32:58 +0000325 exclude=False):
showardf2489522008-10-23 23:08:00 +0000326 if suffix is None:
327 suffix = self._get_include_exclude_suffix(exclude)
showard64aeecd2008-09-19 21:32:58 +0000328 return self._add_join(query_set, 'test_attributes',
329 join_condition=join_condition,
330 suffix=suffix, exclude=exclude)
showardd50ffb42008-09-04 02:47:45 +0000331
332
333 def _get_label_ids_from_names(self, label_names):
showard64aeecd2008-09-19 21:32:58 +0000334 if not label_names:
335 return []
showardd50ffb42008-09-04 02:47:45 +0000336 query = TestLabel.objects.filter(name__in=label_names).values('id')
337 return [label['id'] for label in query]
showard02813502008-08-20 20:52:56 +0000338
339
showardf2489522008-10-23 23:08:00 +0000340 def get_query_set_with_joins(self, filter_data, include_host_labels=False):
showardd50ffb42008-09-04 02:47:45 +0000341 exclude_labels = filter_data.pop('exclude_labels', [])
showard35444862008-08-07 22:35:30 +0000342 query_set = self.get_query_set()
showardd50ffb42008-09-04 02:47:45 +0000343 joined = False
showard35444862008-08-07 22:35:30 +0000344 # TODO: make this check more thorough if necessary
showardf2489522008-10-23 23:08:00 +0000345 extra_where = filter_data.get('extra_where', '')
346 if 'test_labels' in extra_where:
showard02813502008-08-20 20:52:56 +0000347 query_set = self._add_label_joins(query_set)
showardd50ffb42008-09-04 02:47:45 +0000348 joined = True
349
showard64aeecd2008-09-19 21:32:58 +0000350 exclude_label_ids = self._get_label_ids_from_names(exclude_labels)
351 if exclude_label_ids:
352 condition = ('test_labels_tests_exclude.testlabel_id IN (%s)' %
353 ','.join(str(label_id)
354 for label_id in exclude_label_ids))
355 query_set = self._add_join(query_set, 'test_labels_tests',
356 join_key='test_id',
357 suffix='_exclude',
358 join_condition=condition,
359 exclude=True)
360 joined = True
361
362 include_attributes_where = filter_data.pop('include_attributes_where',
363 '')
364 exclude_attributes_where = filter_data.pop('exclude_attributes_where',
365 '')
366 if include_attributes_where:
367 query_set = self._add_attribute_join(
showardf2489522008-10-23 23:08:00 +0000368 query_set, join_condition=include_attributes_where)
showard64aeecd2008-09-19 21:32:58 +0000369 joined = True
370 if exclude_attributes_where:
371 query_set = self._add_attribute_join(
showardf2489522008-10-23 23:08:00 +0000372 query_set, join_condition=exclude_attributes_where,
showard64aeecd2008-09-19 21:32:58 +0000373 exclude=True)
374 joined = True
showardd50ffb42008-09-04 02:47:45 +0000375
376 if not joined:
showard35444862008-08-07 22:35:30 +0000377 filter_data['no_distinct'] = True
showardd50ffb42008-09-04 02:47:45 +0000378
showardf2489522008-10-23 23:08:00 +0000379 if include_host_labels or 'test_attributes_host_labels' in extra_where:
380 query_set = self._add_attribute_join(
381 query_set, suffix='_host_labels',
382 join_condition='test_attributes_host_labels.attribute = '
383 '"host-labels"')
384
showard35444862008-08-07 22:35:30 +0000385 return query_set
386
387
showard02813502008-08-20 20:52:56 +0000388 def query_test_ids(self, filter_data):
389 dicts = self.model.query_objects(filter_data).values('test_idx')
390 return [item['test_idx'] for item in dicts]
391
392
393 def _custom_select_query(self, query_set, selects):
394 query_selects, where, params = query_set._get_sql_clause()
395 if query_set._distinct:
396 distinct = 'DISTINCT '
397 else:
398 distinct = ''
399 sql_query = 'SELECT ' + distinct + ','.join(selects) + where
showard56e93772008-10-06 10:06:22 +0000400 cursor = readonly_connection.connection().cursor()
showard02813502008-08-20 20:52:56 +0000401 cursor.execute(sql_query, params)
402 return cursor.fetchall()
403
404
405 def query_test_label_ids(self, filter_data):
showardd50ffb42008-09-04 02:47:45 +0000406 query_set = self.model.query_objects(filter_data)
407 query_set = self._add_label_joins(query_set, suffix='_list')
408 rows = self._custom_select_query(query_set, ['test_labels_list.id'])
409 return [row[0] for row in rows if row[0] is not None]
showard02813502008-08-20 20:52:56 +0000410
411
showard35444862008-08-07 22:35:30 +0000412class TestView(dbmodels.Model, model_logic.ModelExtensions):
413 extra_fields = {
414 'DATE(test_finished_time)' : 'test finished day',
415 }
416
417 group_fields = [
418 'test_name',
419 'status',
420 'kernel',
421 'hostname',
422 'job_tag',
423 'job_name',
424 'platform',
425 'reason',
426 'job_owner',
showard35bcba22008-10-07 11:07:27 +0000427 'test_started_time',
showard35444862008-08-07 22:35:30 +0000428 'test_finished_time',
429 'DATE(test_finished_time)',
430 ]
431
432 test_idx = dbmodels.IntegerField('test index', primary_key=True)
433 job_idx = dbmodels.IntegerField('job index', null=True, blank=True)
434 test_name = dbmodels.CharField(blank=True, maxlength=90)
435 subdir = dbmodels.CharField('subdirectory', blank=True, maxlength=180)
436 kernel_idx = dbmodels.IntegerField('kernel index')
437 status_idx = dbmodels.IntegerField('status index')
438 reason = dbmodels.CharField(blank=True, maxlength=3072)
439 machine_idx = dbmodels.IntegerField('host index')
440 test_started_time = dbmodels.DateTimeField(null=True, blank=True)
441 test_finished_time = dbmodels.DateTimeField(null=True, blank=True)
442 job_tag = dbmodels.CharField(blank=True, maxlength=300)
443 job_name = dbmodels.CharField(blank=True, maxlength=300)
444 job_owner = dbmodels.CharField('owner', blank=True, maxlength=240)
445 job_queued_time = dbmodels.DateTimeField(null=True, blank=True)
446 job_started_time = dbmodels.DateTimeField(null=True, blank=True)
447 job_finished_time = dbmodels.DateTimeField(null=True, blank=True)
448 hostname = dbmodels.CharField(blank=True, maxlength=300)
449 platform = dbmodels.CharField(blank=True, maxlength=240)
450 machine_owner = dbmodels.CharField(blank=True, maxlength=240)
451 kernel_hash = dbmodels.CharField(blank=True, maxlength=105)
452 kernel_base = dbmodels.CharField(blank=True, maxlength=90)
453 kernel = dbmodels.CharField(blank=True, maxlength=300)
454 status = dbmodels.CharField(blank=True, maxlength=30)
455
456 objects = TestViewManager()
457
458 def save(self):
459 raise NotImplementedError('TestView is read-only')
460
461
462 def delete(self):
463 raise NotImplementedError('TestView is read-only')
464
465
466 @classmethod
467 def query_objects(cls, filter_data, initial_query=None):
468 if initial_query is None:
showard64aeecd2008-09-19 21:32:58 +0000469 initial_query = cls.objects.get_query_set_with_joins(filter_data)
showard35444862008-08-07 22:35:30 +0000470 return super(TestView, cls).query_objects(filter_data,
471 initial_query=initial_query)
472
473
474 @classmethod
showarde732ee72008-09-23 19:15:43 +0000475 def list_objects(cls, filter_data, initial_query=None, fields=None):
476 # include extra fields
477 if fields is None:
478 fields = cls.get_field_dict().keys() + cls.extra_fields.keys()
479 return super(TestView, cls).list_objects(filter_data, initial_query,
480 fields)
showard35444862008-08-07 22:35:30 +0000481
482
483 class Meta:
484 db_table = 'test_view_2'