blob: ca7e4b11c6235d661394f1afe9074333909132db [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
showard8b0ea222009-12-23 19:23:03 +00005_quote_name = connection.ops.quote_name
6
showard35444862008-08-07 22:35:30 +00007class TempManager(model_logic.ExtendedManager):
8 _GROUP_COUNT_NAME = 'group_count'
9
10 def _get_key_unless_is_function(self, field):
11 if '(' in field:
12 return field
showard7c199df2008-10-03 10:17:15 +000013 return self.get_key_on_this_table(field)
showard35444862008-08-07 22:35:30 +000014
15
showardf2489522008-10-23 23:08:00 +000016 def _get_field_names(self, fields, extra_select_fields={}):
17 field_names = []
18 for field in fields:
19 if field in extra_select_fields:
showardd2b0c882009-10-19 18:34:11 +000020 field_names.append(extra_select_fields[field][0])
showardf2489522008-10-23 23:08:00 +000021 else:
22 field_names.append(self._get_key_unless_is_function(field))
23 return field_names
showard35444862008-08-07 22:35:30 +000024
25
showard8bfb5cb2009-10-07 20:49:15 +000026 def _get_group_query_sql(self, query, group_by):
showarda5288b42009-07-28 20:06:08 +000027 sql, params = query.query.as_sql()
showardd50ffb42008-09-04 02:47:45 +000028
29 # insert GROUP BY clause into query
showard8bfb5cb2009-10-07 20:49:15 +000030 group_fields = self._get_field_names(group_by, query.query.extra_select)
showard7c199df2008-10-03 10:17:15 +000031 group_by_clause = ' GROUP BY ' + ', '.join(group_fields)
showarda5288b42009-07-28 20:06:08 +000032 group_by_position = sql.rfind('ORDER BY')
showardd50ffb42008-09-04 02:47:45 +000033 if group_by_position == -1:
showarda5288b42009-07-28 20:06:08 +000034 group_by_position = len(sql)
35 sql = (sql[:group_by_position] +
36 group_by_clause + ' ' +
37 sql[group_by_position:])
showard35444862008-08-07 22:35:30 +000038
showarda5288b42009-07-28 20:06:08 +000039 return sql, params
showard35444862008-08-07 22:35:30 +000040
41
showard06b82fc2009-06-30 01:59:42 +000042 def _get_column_names(self, cursor):
showard8bfb5cb2009-10-07 20:49:15 +000043 """
showard06b82fc2009-06-30 01:59:42 +000044 Gets the column names from the cursor description. This method exists
showard8bfb5cb2009-10-07 20:49:15 +000045 so that it can be mocked in the unit test for sqlite3 compatibility.
showard06b82fc2009-06-30 01:59:42 +000046 """
47 return [column_info[0] for column_info in cursor.description]
48
49
showard8bfb5cb2009-10-07 20:49:15 +000050 def execute_group_query(self, query, group_by):
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
showard8bfb5cb2009-10-07 20:49:15 +000053 given query's extra select fields added. Returns a list of dicts, where
54 each dict corresponds to single row and contains a key for each grouped
55 field as well as all of the extra select fields.
showard35444862008-08-07 22:35:30 +000056 """
showard8bfb5cb2009-10-07 20:49:15 +000057 sql, params = self._get_group_query_sql(query, group_by)
showard56e93772008-10-06 10:06:22 +000058 cursor = readonly_connection.connection().cursor()
showard8a6eb0c2008-10-01 11:38:59 +000059 cursor.execute(sql, params)
showard06b82fc2009-06-30 01:59:42 +000060 field_names = self._get_column_names(cursor)
showard8a6eb0c2008-10-01 11:38:59 +000061 row_dicts = [dict(zip(field_names, row)) for row in cursor.fetchall()]
62 return row_dicts
63
64
65 def get_count_sql(self, query):
66 """
67 Get the SQL to properly select a per-group count of unique matches for
showard7c199df2008-10-03 10:17:15 +000068 a grouped query. Returns a tuple (field alias, field SQL)
showard8a6eb0c2008-10-01 11:38:59 +000069 """
showarda5288b42009-07-28 20:06:08 +000070 if query.query.distinct:
showard7c199df2008-10-03 10:17:15 +000071 pk_field = self.get_key_on_this_table()
showard8a6eb0c2008-10-01 11:38:59 +000072 count_sql = 'COUNT(DISTINCT %s)' % pk_field
73 else:
74 count_sql = 'COUNT(1)'
showard7c199df2008-10-03 10:17:15 +000075 return self._GROUP_COUNT_NAME, count_sql
showard35444862008-08-07 22:35:30 +000076
77
78 def _get_num_groups_sql(self, query, group_by):
showardd2b0c882009-10-19 18:34:11 +000079 group_fields = self._get_field_names(group_by, query.query.extra_select)
showarda5288b42009-07-28 20:06:08 +000080 query = query.order_by() # this can mess up the query and isn't needed
81
82 sql, params = query.query.as_sql()
83 from_ = sql[sql.find(' FROM'):]
showardd2b0c882009-10-19 18:34:11 +000084 return ('SELECT DISTINCT %s %s' % (','.join(group_fields),
showarda5288b42009-07-28 20:06:08 +000085 from_),
showard35444862008-08-07 22:35:30 +000086 params)
87
88
showardd2b0c882009-10-19 18:34:11 +000089 def _cursor_rowcount(self, cursor):
90 """To be stubbed by tests"""
91 return cursor.rowcount
92
93
showard35444862008-08-07 22:35:30 +000094 def get_num_groups(self, query, group_by):
95 """
96 Returns the number of distinct groups for the given query grouped by the
97 fields in group_by.
98 """
99 sql, params = self._get_num_groups_sql(query, group_by)
showard56e93772008-10-06 10:06:22 +0000100 cursor = readonly_connection.connection().cursor()
showard35444862008-08-07 22:35:30 +0000101 cursor.execute(sql, params)
showardd2b0c882009-10-19 18:34:11 +0000102 return self._cursor_rowcount(cursor)
showard35444862008-08-07 22:35:30 +0000103
104
105class Machine(dbmodels.Model):
showardf8b19042009-05-12 17:22:49 +0000106 machine_idx = dbmodels.AutoField(primary_key=True)
showard356d2372009-11-10 01:29:53 +0000107 hostname = dbmodels.CharField(unique=True, max_length=255)
showarda5288b42009-07-28 20:06:08 +0000108 machine_group = dbmodels.CharField(blank=True, max_length=240)
109 owner = dbmodels.CharField(blank=True, max_length=240)
showard35444862008-08-07 22:35:30 +0000110
111 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000112 db_table = 'tko_machines'
showard35444862008-08-07 22:35:30 +0000113
114
115class Kernel(dbmodels.Model):
showardf8b19042009-05-12 17:22:49 +0000116 kernel_idx = dbmodels.AutoField(primary_key=True)
showarda5288b42009-07-28 20:06:08 +0000117 kernel_hash = dbmodels.CharField(max_length=105, editable=False)
118 base = dbmodels.CharField(max_length=90)
119 printable = dbmodels.CharField(max_length=300)
showard35444862008-08-07 22:35:30 +0000120
121 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000122 db_table = 'tko_kernels'
showard35444862008-08-07 22:35:30 +0000123
124
125class Patch(dbmodels.Model):
126 kernel = dbmodels.ForeignKey(Kernel, db_column='kernel_idx')
showarda5288b42009-07-28 20:06:08 +0000127 name = dbmodels.CharField(blank=True, max_length=240)
128 url = dbmodels.CharField(blank=True, max_length=900)
129 the_hash = dbmodels.CharField(blank=True, max_length=105, db_column='hash')
showard35444862008-08-07 22:35:30 +0000130
131 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000132 db_table = 'tko_patches'
showard35444862008-08-07 22:35:30 +0000133
134
135class Status(dbmodels.Model):
showardf8b19042009-05-12 17:22:49 +0000136 status_idx = dbmodels.AutoField(primary_key=True)
showarda5288b42009-07-28 20:06:08 +0000137 word = dbmodels.CharField(max_length=30)
showard35444862008-08-07 22:35:30 +0000138
139 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000140 db_table = 'tko_status'
showard35444862008-08-07 22:35:30 +0000141
142
jamesren708f1c02010-03-31 21:43:57 +0000143class Job(dbmodels.Model, model_logic.ModelExtensions):
showardf8b19042009-05-12 17:22:49 +0000144 job_idx = dbmodels.AutoField(primary_key=True)
showard356d2372009-11-10 01:29:53 +0000145 tag = dbmodels.CharField(unique=True, max_length=100)
showarda5288b42009-07-28 20:06:08 +0000146 label = dbmodels.CharField(max_length=300)
147 username = dbmodels.CharField(max_length=240)
showard35444862008-08-07 22:35:30 +0000148 machine = dbmodels.ForeignKey(Machine, db_column='machine_idx')
149 queued_time = dbmodels.DateTimeField(null=True, blank=True)
150 started_time = dbmodels.DateTimeField(null=True, blank=True)
151 finished_time = dbmodels.DateTimeField(null=True, blank=True)
showardc1c1caf2009-09-08 16:26:50 +0000152 afe_job_id = dbmodels.IntegerField(null=True, default=None)
showard35444862008-08-07 22:35:30 +0000153
jamesren708f1c02010-03-31 21:43:57 +0000154 objects = model_logic.ExtendedManager()
155
showard35444862008-08-07 22:35:30 +0000156 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000157 db_table = 'tko_jobs'
showard35444862008-08-07 22:35:30 +0000158
159
showardc1a98d12010-01-15 00:22:22 +0000160class JobKeyval(dbmodels.Model):
161 job = dbmodels.ForeignKey(Job)
162 key = dbmodels.CharField(max_length=90)
163 value = dbmodels.CharField(blank=True, max_length=300)
164
165
166 class Meta:
167 db_table = 'tko_job_keyvals'
168
169
showardf8b19042009-05-12 17:22:49 +0000170class Test(dbmodels.Model, model_logic.ModelExtensions,
171 model_logic.ModelWithAttributes):
172 test_idx = dbmodels.AutoField(primary_key=True)
showard35444862008-08-07 22:35:30 +0000173 job = dbmodels.ForeignKey(Job, db_column='job_idx')
showarda5288b42009-07-28 20:06:08 +0000174 test = dbmodels.CharField(max_length=90)
175 subdir = dbmodels.CharField(blank=True, max_length=180)
showard35444862008-08-07 22:35:30 +0000176 kernel = dbmodels.ForeignKey(Kernel, db_column='kernel_idx')
177 status = dbmodels.ForeignKey(Status, db_column='status')
showarda5288b42009-07-28 20:06:08 +0000178 reason = dbmodels.CharField(blank=True, max_length=3072)
showard35444862008-08-07 22:35:30 +0000179 machine = dbmodels.ForeignKey(Machine, db_column='machine_idx')
180 finished_time = dbmodels.DateTimeField(null=True, blank=True)
181 started_time = dbmodels.DateTimeField(null=True, blank=True)
182
showardf8b19042009-05-12 17:22:49 +0000183 objects = model_logic.ExtendedManager()
184
185 def _get_attribute_model_and_args(self, attribute):
186 return TestAttribute, dict(test=self, attribute=attribute,
187 user_created=True)
188
189
190 def set_attribute(self, attribute, value):
191 # ensure non-user-created attributes remain immutable
192 try:
193 TestAttribute.objects.get(test=self, attribute=attribute,
194 user_created=False)
195 raise ValueError('Attribute %s already exists for test %s and is '
196 'immutable' % (attribute, self.test_idx))
197 except TestAttribute.DoesNotExist:
198 super(Test, self).set_attribute(attribute, value)
199
200
showard35444862008-08-07 22:35:30 +0000201 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000202 db_table = 'tko_tests'
showard35444862008-08-07 22:35:30 +0000203
204
showarde732ee72008-09-23 19:15:43 +0000205class TestAttribute(dbmodels.Model, model_logic.ModelExtensions):
showardf8b19042009-05-12 17:22:49 +0000206 test = dbmodels.ForeignKey(Test, db_column='test_idx')
showarda5288b42009-07-28 20:06:08 +0000207 attribute = dbmodels.CharField(max_length=90)
208 value = dbmodels.CharField(blank=True, max_length=300)
showardf8b19042009-05-12 17:22:49 +0000209 user_created = dbmodels.BooleanField(default=False)
210
211 objects = model_logic.ExtendedManager()
showard35444862008-08-07 22:35:30 +0000212
213 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000214 db_table = 'tko_test_attributes'
showard35444862008-08-07 22:35:30 +0000215
216
jadmanski430dca92008-12-16 20:56:53 +0000217class IterationAttribute(dbmodels.Model, model_logic.ModelExtensions):
showardf8b19042009-05-12 17:22:49 +0000218 # this isn't really a primary key, but it's necessary to appease Django
219 # and is harmless as long as we're careful
showarde732ee72008-09-23 19:15:43 +0000220 test = dbmodels.ForeignKey(Test, db_column='test_idx', primary_key=True)
showard35444862008-08-07 22:35:30 +0000221 iteration = dbmodels.IntegerField()
showarda5288b42009-07-28 20:06:08 +0000222 attribute = dbmodels.CharField(max_length=90)
223 value = dbmodels.CharField(blank=True, max_length=300)
showard35444862008-08-07 22:35:30 +0000224
showardf8b19042009-05-12 17:22:49 +0000225 objects = model_logic.ExtendedManager()
226
showard35444862008-08-07 22:35:30 +0000227 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000228 db_table = 'tko_iteration_attributes'
showard35444862008-08-07 22:35:30 +0000229
230
jadmanski430dca92008-12-16 20:56:53 +0000231class IterationResult(dbmodels.Model, model_logic.ModelExtensions):
showardf8b19042009-05-12 17:22:49 +0000232 # see comment on IterationAttribute regarding primary_key=True
jadmanski430dca92008-12-16 20:56:53 +0000233 test = dbmodels.ForeignKey(Test, db_column='test_idx', primary_key=True)
showard35444862008-08-07 22:35:30 +0000234 iteration = dbmodels.IntegerField()
showarda5288b42009-07-28 20:06:08 +0000235 attribute = dbmodels.CharField(max_length=90)
236 value = dbmodels.DecimalField(null=True, max_digits=12, decimal_places=31,
237 blank=True)
showard35444862008-08-07 22:35:30 +0000238
showardf8b19042009-05-12 17:22:49 +0000239 objects = model_logic.ExtendedManager()
240
showard35444862008-08-07 22:35:30 +0000241 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000242 db_table = 'tko_iteration_result'
showard35444862008-08-07 22:35:30 +0000243
244
245class TestLabel(dbmodels.Model, model_logic.ModelExtensions):
showarda5288b42009-07-28 20:06:08 +0000246 name = dbmodels.CharField(max_length=80, unique=True)
showard35444862008-08-07 22:35:30 +0000247 description = dbmodels.TextField(blank=True)
showardeab66ce2009-12-23 00:03:56 +0000248 tests = dbmodels.ManyToManyField(Test, blank=True,
249 db_table='tko_test_labels_tests')
showard35444862008-08-07 22:35:30 +0000250
251 name_field = 'name'
showardf8b19042009-05-12 17:22:49 +0000252 objects = model_logic.ExtendedManager()
showard35444862008-08-07 22:35:30 +0000253
254 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000255 db_table = 'tko_test_labels'
showard35444862008-08-07 22:35:30 +0000256
257
258class SavedQuery(dbmodels.Model, model_logic.ModelExtensions):
259 # TODO: change this to foreign key once DBs are merged
showarda5288b42009-07-28 20:06:08 +0000260 owner = dbmodels.CharField(max_length=80)
261 name = dbmodels.CharField(max_length=100)
showard35444862008-08-07 22:35:30 +0000262 url_token = dbmodels.TextField()
263
264 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000265 db_table = 'tko_saved_queries'
showard35444862008-08-07 22:35:30 +0000266
267
showardce12f552008-09-19 00:48:59 +0000268class EmbeddedGraphingQuery(dbmodels.Model, model_logic.ModelExtensions):
269 url_token = dbmodels.TextField(null=False, blank=False)
showarda5288b42009-07-28 20:06:08 +0000270 graph_type = dbmodels.CharField(max_length=16, null=False, blank=False)
showardce12f552008-09-19 00:48:59 +0000271 params = dbmodels.TextField(null=False, blank=False)
272 last_updated = dbmodels.DateTimeField(null=False, blank=False,
273 editable=False)
274 # refresh_time shows the time at which a thread is updating the cached
275 # image, or NULL if no one is updating the image. This is used so that only
276 # one thread is updating the cached image at a time (see
277 # graphing_utils.handle_plot_request)
278 refresh_time = dbmodels.DateTimeField(editable=False)
279 cached_png = dbmodels.TextField(editable=False)
280
281 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000282 db_table = 'tko_embedded_graphing_queries'
showardce12f552008-09-19 00:48:59 +0000283
284
showard35444862008-08-07 22:35:30 +0000285# views
286
287class TestViewManager(TempManager):
showard35444862008-08-07 22:35:30 +0000288 def get_query_set(self):
289 query = super(TestViewManager, self).get_query_set()
showard5bf7c502008-08-20 01:22:22 +0000290
showard35444862008-08-07 22:35:30 +0000291 # add extra fields to selects, using the SQL itself as the "alias"
292 extra_select = dict((sql, sql)
293 for sql in self.model.extra_fields.iterkeys())
294 return query.extra(select=extra_select)
295
296
showardf2489522008-10-23 23:08:00 +0000297 def _get_include_exclude_suffix(self, exclude):
298 if exclude:
showard2aa318e2009-08-20 23:43:10 +0000299 return '_exclude'
300 return '_include'
301
302
303 def _add_attribute_join(self, query_set, join_condition,
304 suffix=None, exclude=False):
305 if suffix is None:
306 suffix = self._get_include_exclude_suffix(exclude)
showardeab66ce2009-12-23 00:03:56 +0000307 return self.add_join(query_set, 'tko_test_attributes',
308 join_key='test_idx',
showard2aa318e2009-08-20 23:43:10 +0000309 join_condition=join_condition,
310 suffix=suffix, exclude=exclude)
311
312
313 def _add_label_pivot_table_join(self, query_set, suffix, join_condition='',
314 exclude=False, force_left_join=False):
showardeab66ce2009-12-23 00:03:56 +0000315 return self.add_join(query_set, 'tko_test_labels_tests',
316 join_key='test_id',
showard2aa318e2009-08-20 23:43:10 +0000317 join_condition=join_condition,
318 suffix=suffix, exclude=exclude,
319 force_left_join=force_left_join)
showardf2489522008-10-23 23:08:00 +0000320
321
showard64aeecd2008-09-19 21:32:58 +0000322 def _add_label_joins(self, query_set, suffix=''):
showard2aa318e2009-08-20 23:43:10 +0000323 query_set = self._add_label_pivot_table_join(
324 query_set, suffix=suffix, force_left_join=True)
showardd50ffb42008-09-04 02:47:45 +0000325
showard2aa318e2009-08-20 23:43:10 +0000326 # since we're not joining from the original table, we can't use
327 # self.add_join() again
showardeab66ce2009-12-23 00:03:56 +0000328 second_join_alias = 'tko_test_labels' + suffix
showardd50ffb42008-09-04 02:47:45 +0000329 second_join_condition = ('%s.id = %s.testlabel_id' %
showard64aeecd2008-09-19 21:32:58 +0000330 (second_join_alias,
showardeab66ce2009-12-23 00:03:56 +0000331 'tko_test_labels_tests' + suffix))
showard7e67b432010-01-20 01:13:04 +0000332 query_set.query.add_custom_join('tko_test_labels',
333 second_join_condition,
334 query_set.query.LOUTER,
335 alias=second_join_alias)
336 return query_set
showardd50ffb42008-09-04 02:47:45 +0000337
showard64aeecd2008-09-19 21:32:58 +0000338
showardd50ffb42008-09-04 02:47:45 +0000339 def _get_label_ids_from_names(self, label_names):
showard2aa318e2009-08-20 23:43:10 +0000340 label_ids = list( # listifying avoids a double query below
showard8b0ea222009-12-23 19:23:03 +0000341 TestLabel.objects.filter(name__in=label_names)
342 .values_list('name', 'id'))
showard2aa318e2009-08-20 23:43:10 +0000343 if len(label_ids) < len(set(label_names)):
showard250d84d2010-01-12 21:59:48 +0000344 raise ValueError('Not all labels found: %s' %
345 ', '.join(label_names))
showard8b0ea222009-12-23 19:23:03 +0000346 return dict(name_and_id for name_and_id in label_ids)
showard2aa318e2009-08-20 23:43:10 +0000347
348
349 def _include_or_exclude_labels(self, query_set, label_names, exclude=False):
showard8b0ea222009-12-23 19:23:03 +0000350 label_ids = self._get_label_ids_from_names(label_names).itervalues()
showard2aa318e2009-08-20 23:43:10 +0000351 suffix = self._get_include_exclude_suffix(exclude)
showardeab66ce2009-12-23 00:03:56 +0000352 condition = ('tko_test_labels_tests%s.testlabel_id IN (%s)' %
showard8b0ea222009-12-23 19:23:03 +0000353 (suffix,
354 ','.join(str(label_id) for label_id in label_ids)))
showard2aa318e2009-08-20 23:43:10 +0000355 return self._add_label_pivot_table_join(query_set,
356 join_condition=condition,
357 suffix=suffix,
358 exclude=exclude)
showard02813502008-08-20 20:52:56 +0000359
360
showard8b0ea222009-12-23 19:23:03 +0000361 def _add_custom_select(self, query_set, select_name, select_sql):
362 return query_set.extra(select={select_name: select_sql})
363
364
365 def _add_select_value(self, query_set, alias):
366 return self._add_custom_select(query_set, alias,
367 _quote_name(alias) + '.value')
368
369
370 def _add_select_ifnull(self, query_set, alias, non_null_value):
371 select_sql = "IF(%s.id IS NOT NULL, '%s', NULL)" % (_quote_name(alias),
372 non_null_value)
373 return self._add_custom_select(query_set, alias, select_sql)
374
375
jamesren708f1c02010-03-31 21:43:57 +0000376 def _join_test_label_column(self, query_set, label_name, label_id):
377 alias = 'test_label_' + label_name
showard7e67b432010-01-20 01:13:04 +0000378 label_query = TestLabel.objects.filter(name=label_name)
379 query_set = Test.objects.join_custom_field(query_set, label_query,
380 alias)
showard8b0ea222009-12-23 19:23:03 +0000381
382 query_set = self._add_select_ifnull(query_set, alias, label_name)
383 return query_set
384
385
jamesren708f1c02010-03-31 21:43:57 +0000386 def _join_test_label_columns(self, query_set, label_names):
showard8b0ea222009-12-23 19:23:03 +0000387 label_id_map = self._get_label_ids_from_names(label_names)
388 for label_name in label_names:
jamesren708f1c02010-03-31 21:43:57 +0000389 query_set = self._join_test_label_column(query_set, label_name,
390 label_id_map[label_name])
showard8b0ea222009-12-23 19:23:03 +0000391 return query_set
392
393
jamesren708f1c02010-03-31 21:43:57 +0000394 def _join_test_attribute(self, query_set, attribute, alias=None,
395 extra_join_condition=None):
showard8b0ea222009-12-23 19:23:03 +0000396 """
397 Join the given TestView QuerySet to TestAttribute. The resulting query
398 has an additional column for the given attribute named
399 "attribute_<attribute name>".
400 """
showard8b0ea222009-12-23 19:23:03 +0000401 if not alias:
jamesren708f1c02010-03-31 21:43:57 +0000402 alias = 'test_attribute_' + attribute
showard7e67b432010-01-20 01:13:04 +0000403 attribute_query = TestAttribute.objects.filter(attribute=attribute)
showard8b0ea222009-12-23 19:23:03 +0000404 if extra_join_condition:
showard7e67b432010-01-20 01:13:04 +0000405 attribute_query = attribute_query.extra(
406 where=[extra_join_condition])
407 query_set = Test.objects.join_custom_field(query_set, attribute_query,
408 alias)
showard8b0ea222009-12-23 19:23:03 +0000409
410 query_set = self._add_select_value(query_set, alias)
411 return query_set
412
413
414 def _join_machine_label_columns(self, query_set, machine_label_names):
415 for label_name in machine_label_names:
416 alias = 'machine_label_' + label_name
417 condition = "FIND_IN_SET('%s', %s)" % (
418 label_name, _quote_name(alias) + '.value')
jamesren708f1c02010-03-31 21:43:57 +0000419 query_set = self._join_test_attribute(
420 query_set, 'host-labels',
421 alias=alias, extra_join_condition=condition)
showard8b0ea222009-12-23 19:23:03 +0000422 query_set = self._add_select_ifnull(query_set, alias, label_name)
423 return query_set
424
425
426 def _join_one_iteration_key(self, query_set, result_key, first_alias=None):
jamesren708f1c02010-03-31 21:43:57 +0000427 alias = 'iteration_result_' + result_key
showard7e67b432010-01-20 01:13:04 +0000428 iteration_query = IterationResult.objects.filter(attribute=result_key)
showard8b0ea222009-12-23 19:23:03 +0000429 if first_alias:
430 # after the first join, we need to match up iteration indices,
431 # otherwise each join will expand the query by the number of
432 # iterations and we'll have extraneous rows
showard7e67b432010-01-20 01:13:04 +0000433 iteration_query = iteration_query.extra(
434 where=['%s.iteration = %s.iteration'
435 % (_quote_name(alias), _quote_name(first_alias))])
showard8b0ea222009-12-23 19:23:03 +0000436
showard7e67b432010-01-20 01:13:04 +0000437 query_set = Test.objects.join_custom_field(query_set, iteration_query,
438 alias, left_join=False)
showard8b0ea222009-12-23 19:23:03 +0000439 # select the iteration value and index for this join
440 query_set = self._add_select_value(query_set, alias)
441 if not first_alias:
442 # for first join, add iteration index select too
443 query_set = self._add_custom_select(
444 query_set, 'iteration_index',
445 _quote_name(alias) + '.iteration')
446
447 return query_set, alias
448
449
jamesren708f1c02010-03-31 21:43:57 +0000450 def _join_iteration_results(self, test_view_query_set, result_keys):
showard8b0ea222009-12-23 19:23:03 +0000451 """Join the given TestView QuerySet to IterationResult for one result.
452
453 The resulting query looks like a TestView query but has one row per
454 iteration. Each row includes all the attributes of TestView, an
455 attribute for each key in result_keys and an iteration_index attribute.
456
457 We accomplish this by joining the TestView query to IterationResult
458 once per result key. Each join is restricted on the result key (and on
459 the test index, like all one-to-many joins). For the first join, this
460 is the only restriction, so each TestView row expands to a row per
461 iteration (per iteration that includes the key, of course). For each
462 subsequent join, we also restrict the iteration index to match that of
463 the initial join. This makes each subsequent join produce exactly one
464 result row for each input row. (This assumes each iteration contains
465 the same set of keys. Results are undefined if that's not true.)
466 """
467 if not result_keys:
468 return test_view_query_set
469
470 query_set, first_alias = self._join_one_iteration_key(
471 test_view_query_set, result_keys[0])
472 for result_key in result_keys[1:]:
473 query_set, _ = self._join_one_iteration_key(query_set, result_key,
474 first_alias=first_alias)
475 return query_set
476
477
jamesren708f1c02010-03-31 21:43:57 +0000478 def _join_job_keyvals(self, query_set, job_keyvals):
479 for job_keyval in job_keyvals:
480 alias = 'job_keyval_' + job_keyval
481 keyval_query = JobKeyval.objects.filter(key=job_keyval)
482 query_set = Job.objects.join_custom_field(query_set, keyval_query,
483 alias)
484 query_set = self._add_select_value(query_set, alias)
485 return query_set
486
487
488 def _join_iteration_attributes(self, query_set, iteration_attributes):
489 for attribute in iteration_attributes:
490 alias = 'iteration_attribute_' + attribute
491 attribute_query = IterationAttribute.objects.filter(
492 attribute=attribute)
493 query_set = Test.objects.join_custom_field(query_set,
494 attribute_query, alias)
495 query_set = self._add_select_value(query_set, alias)
496 return query_set
497
498
showard8b0ea222009-12-23 19:23:03 +0000499 def get_query_set_with_joins(self, filter_data):
500 """
501 Add joins for querying over test-related items.
502
503 These parameters are supported going forward:
504 * test_attribute_fields: list of attribute names. Each attribute will
505 be available as a column attribute_<name>.value.
506 * test_label_fields: list of label names. Each label will be available
507 as a column label_<name>.id, non-null iff the label is present.
jamesren708f1c02010-03-31 21:43:57 +0000508 * iteration_result_fields: list of iteration result names. Each
showard8b0ea222009-12-23 19:23:03 +0000509 result will be available as a column iteration_<name>.value.
510 Note that this changes the semantics to return iterations
511 instead of tests -- if a test has multiple iterations, a row
512 will be returned for each one. The iteration index is also
513 available as iteration_<name>.iteration.
514 * machine_label_fields: list of machine label names. Each will be
515 available as a column machine_label_<name>.id, non-null iff the
516 label is present on the machine used in the test.
jamesren708f1c02010-03-31 21:43:57 +0000517 * job_keyval_fields: list of job keyval names. Each value will be
518 available as a column job_keyval_<name>.id, non-null iff the
519 keyval is present in the AFE job.
520 * iteration_attribute_fields: list of iteration attribute names. Each
521 attribute will be available as a column
522 iteration_attribute<name>.id, non-null iff the attribute is
523 present.
showard8b0ea222009-12-23 19:23:03 +0000524
525 These parameters are deprecated:
526 * include_labels
527 * exclude_labels
528 * include_attributes_where
529 * exclude_attributes_where
530
531 Additionally, this method adds joins if the following strings are
532 present in extra_where (this is also deprecated):
533 * test_labels
534 * test_attributes_host_labels
535 """
showard35444862008-08-07 22:35:30 +0000536 query_set = self.get_query_set()
showard8b0ea222009-12-23 19:23:03 +0000537
538 test_attributes = filter_data.pop('test_attribute_fields', [])
539 for attribute in test_attributes:
jamesren708f1c02010-03-31 21:43:57 +0000540 query_set = self._join_test_attribute(query_set, attribute)
showard8b0ea222009-12-23 19:23:03 +0000541
542 test_labels = filter_data.pop('test_label_fields', [])
jamesren708f1c02010-03-31 21:43:57 +0000543 query_set = self._join_test_label_columns(query_set, test_labels)
showard8b0ea222009-12-23 19:23:03 +0000544
545 machine_labels = filter_data.pop('machine_label_fields', [])
546 query_set = self._join_machine_label_columns(query_set, machine_labels)
547
jamesren708f1c02010-03-31 21:43:57 +0000548 iteration_keys = filter_data.pop('iteration_result_fields', [])
549 query_set = self._join_iteration_results(query_set, iteration_keys)
550
551 job_keyvals = filter_data.pop('job_keyval_fields', [])
552 query_set = self._join_job_keyvals(query_set, job_keyvals)
553
554 iteration_attributes = filter_data.pop('iteration_attribute_fields', [])
555 query_set = self._join_iteration_attributes(query_set,
556 iteration_attributes)
showard8b0ea222009-12-23 19:23:03 +0000557
558 # everything that follows is deprecated behavior
559
showardd50ffb42008-09-04 02:47:45 +0000560 joined = False
showard2aa318e2009-08-20 23:43:10 +0000561
showardf2489522008-10-23 23:08:00 +0000562 extra_where = filter_data.get('extra_where', '')
showardeab66ce2009-12-23 00:03:56 +0000563 if 'tko_test_labels' in extra_where:
showard02813502008-08-20 20:52:56 +0000564 query_set = self._add_label_joins(query_set)
showardd50ffb42008-09-04 02:47:45 +0000565 joined = True
566
showard8b0ea222009-12-23 19:23:03 +0000567 include_labels = filter_data.pop('include_labels', [])
568 exclude_labels = filter_data.pop('exclude_labels', [])
showard2aa318e2009-08-20 23:43:10 +0000569 if include_labels:
570 query_set = self._include_or_exclude_labels(query_set,
571 include_labels)
showardfc8c6ae2008-11-11 19:06:01 +0000572 joined = True
showard2aa318e2009-08-20 23:43:10 +0000573 if exclude_labels:
574 query_set = self._include_or_exclude_labels(query_set,
575 exclude_labels,
576 exclude=True)
showard64aeecd2008-09-19 21:32:58 +0000577 joined = True
578
579 include_attributes_where = filter_data.pop('include_attributes_where',
580 '')
581 exclude_attributes_where = filter_data.pop('exclude_attributes_where',
582 '')
583 if include_attributes_where:
584 query_set = self._add_attribute_join(
showard2aa318e2009-08-20 23:43:10 +0000585 query_set,
586 join_condition=self.escape_user_sql(include_attributes_where))
showard64aeecd2008-09-19 21:32:58 +0000587 joined = True
588 if exclude_attributes_where:
589 query_set = self._add_attribute_join(
showard2aa318e2009-08-20 23:43:10 +0000590 query_set,
591 join_condition=self.escape_user_sql(exclude_attributes_where),
showard64aeecd2008-09-19 21:32:58 +0000592 exclude=True)
593 joined = True
showardd50ffb42008-09-04 02:47:45 +0000594
595 if not joined:
showard35444862008-08-07 22:35:30 +0000596 filter_data['no_distinct'] = True
showardd50ffb42008-09-04 02:47:45 +0000597
showard8b0ea222009-12-23 19:23:03 +0000598 if 'tko_test_attributes_host_labels' in extra_where:
showardf2489522008-10-23 23:08:00 +0000599 query_set = self._add_attribute_join(
600 query_set, suffix='_host_labels',
showardeab66ce2009-12-23 00:03:56 +0000601 join_condition='tko_test_attributes_host_labels.attribute = '
showardf2489522008-10-23 23:08:00 +0000602 '"host-labels"')
603
showard35444862008-08-07 22:35:30 +0000604 return query_set
605
606
showard8bfb5cb2009-10-07 20:49:15 +0000607 def query_test_ids(self, filter_data, apply_presentation=True):
608 query = self.model.query_objects(filter_data,
609 apply_presentation=apply_presentation)
610 dicts = query.values('test_idx')
showard02813502008-08-20 20:52:56 +0000611 return [item['test_idx'] for item in dicts]
612
613
showard02813502008-08-20 20:52:56 +0000614 def query_test_label_ids(self, filter_data):
showardd50ffb42008-09-04 02:47:45 +0000615 query_set = self.model.query_objects(filter_data)
616 query_set = self._add_label_joins(query_set, suffix='_list')
showardeab66ce2009-12-23 00:03:56 +0000617 rows = self._custom_select_query(query_set, ['tko_test_labels_list.id'])
showardd50ffb42008-09-04 02:47:45 +0000618 return [row[0] for row in rows if row[0] is not None]
showard02813502008-08-20 20:52:56 +0000619
620
showardeaccf8f2009-04-16 03:11:33 +0000621 def escape_user_sql(self, sql):
622 sql = super(TestViewManager, self).escape_user_sql(sql)
623 return sql.replace('test_idx', self.get_key_on_this_table('test_idx'))
624
625
showard35444862008-08-07 22:35:30 +0000626class TestView(dbmodels.Model, model_logic.ModelExtensions):
627 extra_fields = {
showardf4c702e2009-07-08 21:14:27 +0000628 'DATE(job_queued_time)': 'job queued day',
629 'DATE(test_finished_time)': 'test finished day',
showard35444862008-08-07 22:35:30 +0000630 }
631
632 group_fields = [
showardf4c702e2009-07-08 21:14:27 +0000633 'test_name',
634 'status',
635 'kernel',
636 'hostname',
637 'job_tag',
638 'job_name',
639 'platform',
640 'reason',
641 'job_owner',
642 'job_queued_time',
643 'DATE(job_queued_time)',
644 'test_started_time',
645 'test_finished_time',
646 'DATE(test_finished_time)',
showard35444862008-08-07 22:35:30 +0000647 ]
648
649 test_idx = dbmodels.IntegerField('test index', primary_key=True)
650 job_idx = dbmodels.IntegerField('job index', null=True, blank=True)
showarda5288b42009-07-28 20:06:08 +0000651 test_name = dbmodels.CharField(blank=True, max_length=90)
652 subdir = dbmodels.CharField('subdirectory', blank=True, max_length=180)
showard35444862008-08-07 22:35:30 +0000653 kernel_idx = dbmodels.IntegerField('kernel index')
654 status_idx = dbmodels.IntegerField('status index')
showarda5288b42009-07-28 20:06:08 +0000655 reason = dbmodels.CharField(blank=True, max_length=3072)
showard35444862008-08-07 22:35:30 +0000656 machine_idx = dbmodels.IntegerField('host index')
657 test_started_time = dbmodels.DateTimeField(null=True, blank=True)
658 test_finished_time = dbmodels.DateTimeField(null=True, blank=True)
showarda5288b42009-07-28 20:06:08 +0000659 job_tag = dbmodels.CharField(blank=True, max_length=300)
660 job_name = dbmodels.CharField(blank=True, max_length=300)
661 job_owner = dbmodels.CharField('owner', blank=True, max_length=240)
showard35444862008-08-07 22:35:30 +0000662 job_queued_time = dbmodels.DateTimeField(null=True, blank=True)
663 job_started_time = dbmodels.DateTimeField(null=True, blank=True)
664 job_finished_time = dbmodels.DateTimeField(null=True, blank=True)
showardc1c1caf2009-09-08 16:26:50 +0000665 afe_job_id = dbmodels.IntegerField(null=True)
showarda5288b42009-07-28 20:06:08 +0000666 hostname = dbmodels.CharField(blank=True, max_length=300)
667 platform = dbmodels.CharField(blank=True, max_length=240)
668 machine_owner = dbmodels.CharField(blank=True, max_length=240)
669 kernel_hash = dbmodels.CharField(blank=True, max_length=105)
670 kernel_base = dbmodels.CharField(blank=True, max_length=90)
671 kernel = dbmodels.CharField(blank=True, max_length=300)
672 status = dbmodels.CharField(blank=True, max_length=30)
showard35444862008-08-07 22:35:30 +0000673
674 objects = TestViewManager()
675
676 def save(self):
677 raise NotImplementedError('TestView is read-only')
678
679
680 def delete(self):
681 raise NotImplementedError('TestView is read-only')
682
683
684 @classmethod
showard8bfb5cb2009-10-07 20:49:15 +0000685 def query_objects(cls, filter_data, initial_query=None,
686 apply_presentation=True):
showard35444862008-08-07 22:35:30 +0000687 if initial_query is None:
showard64aeecd2008-09-19 21:32:58 +0000688 initial_query = cls.objects.get_query_set_with_joins(filter_data)
showard8bfb5cb2009-10-07 20:49:15 +0000689 return super(TestView, cls).query_objects(
690 filter_data, initial_query=initial_query,
691 apply_presentation=apply_presentation)
showard35444862008-08-07 22:35:30 +0000692
693
694 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000695 db_table = 'tko_test_view_2'