blob: 7348b07ee7960330f6a82cf434319eb4dae60f5c [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
143class Job(dbmodels.Model):
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
154 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000155 db_table = 'tko_jobs'
showard35444862008-08-07 22:35:30 +0000156
157
showardc1a98d12010-01-15 00:22:22 +0000158class JobKeyval(dbmodels.Model):
159 job = dbmodels.ForeignKey(Job)
160 key = dbmodels.CharField(max_length=90)
161 value = dbmodels.CharField(blank=True, max_length=300)
162
163
164 class Meta:
165 db_table = 'tko_job_keyvals'
166
167
showardf8b19042009-05-12 17:22:49 +0000168class Test(dbmodels.Model, model_logic.ModelExtensions,
169 model_logic.ModelWithAttributes):
170 test_idx = dbmodels.AutoField(primary_key=True)
showard35444862008-08-07 22:35:30 +0000171 job = dbmodels.ForeignKey(Job, db_column='job_idx')
showarda5288b42009-07-28 20:06:08 +0000172 test = dbmodels.CharField(max_length=90)
173 subdir = dbmodels.CharField(blank=True, max_length=180)
showard35444862008-08-07 22:35:30 +0000174 kernel = dbmodels.ForeignKey(Kernel, db_column='kernel_idx')
175 status = dbmodels.ForeignKey(Status, db_column='status')
showarda5288b42009-07-28 20:06:08 +0000176 reason = dbmodels.CharField(blank=True, max_length=3072)
showard35444862008-08-07 22:35:30 +0000177 machine = dbmodels.ForeignKey(Machine, db_column='machine_idx')
178 finished_time = dbmodels.DateTimeField(null=True, blank=True)
179 started_time = dbmodels.DateTimeField(null=True, blank=True)
180
showardf8b19042009-05-12 17:22:49 +0000181 objects = model_logic.ExtendedManager()
182
183 def _get_attribute_model_and_args(self, attribute):
184 return TestAttribute, dict(test=self, attribute=attribute,
185 user_created=True)
186
187
188 def set_attribute(self, attribute, value):
189 # ensure non-user-created attributes remain immutable
190 try:
191 TestAttribute.objects.get(test=self, attribute=attribute,
192 user_created=False)
193 raise ValueError('Attribute %s already exists for test %s and is '
194 'immutable' % (attribute, self.test_idx))
195 except TestAttribute.DoesNotExist:
196 super(Test, self).set_attribute(attribute, value)
197
198
showard35444862008-08-07 22:35:30 +0000199 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000200 db_table = 'tko_tests'
showard35444862008-08-07 22:35:30 +0000201
202
showarde732ee72008-09-23 19:15:43 +0000203class TestAttribute(dbmodels.Model, model_logic.ModelExtensions):
showardf8b19042009-05-12 17:22:49 +0000204 test = dbmodels.ForeignKey(Test, db_column='test_idx')
showarda5288b42009-07-28 20:06:08 +0000205 attribute = dbmodels.CharField(max_length=90)
206 value = dbmodels.CharField(blank=True, max_length=300)
showardf8b19042009-05-12 17:22:49 +0000207 user_created = dbmodels.BooleanField(default=False)
208
209 objects = model_logic.ExtendedManager()
showard35444862008-08-07 22:35:30 +0000210
211 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000212 db_table = 'tko_test_attributes'
showard35444862008-08-07 22:35:30 +0000213
214
jadmanski430dca92008-12-16 20:56:53 +0000215class IterationAttribute(dbmodels.Model, model_logic.ModelExtensions):
showardf8b19042009-05-12 17:22:49 +0000216 # this isn't really a primary key, but it's necessary to appease Django
217 # and is harmless as long as we're careful
showarde732ee72008-09-23 19:15:43 +0000218 test = dbmodels.ForeignKey(Test, db_column='test_idx', primary_key=True)
showard35444862008-08-07 22:35:30 +0000219 iteration = dbmodels.IntegerField()
showarda5288b42009-07-28 20:06:08 +0000220 attribute = dbmodels.CharField(max_length=90)
221 value = dbmodels.CharField(blank=True, max_length=300)
showard35444862008-08-07 22:35:30 +0000222
showardf8b19042009-05-12 17:22:49 +0000223 objects = model_logic.ExtendedManager()
224
showard35444862008-08-07 22:35:30 +0000225 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000226 db_table = 'tko_iteration_attributes'
showard35444862008-08-07 22:35:30 +0000227
228
jadmanski430dca92008-12-16 20:56:53 +0000229class IterationResult(dbmodels.Model, model_logic.ModelExtensions):
showardf8b19042009-05-12 17:22:49 +0000230 # see comment on IterationAttribute regarding primary_key=True
jadmanski430dca92008-12-16 20:56:53 +0000231 test = dbmodels.ForeignKey(Test, db_column='test_idx', primary_key=True)
showard35444862008-08-07 22:35:30 +0000232 iteration = dbmodels.IntegerField()
showarda5288b42009-07-28 20:06:08 +0000233 attribute = dbmodels.CharField(max_length=90)
234 value = dbmodels.DecimalField(null=True, max_digits=12, decimal_places=31,
235 blank=True)
showard35444862008-08-07 22:35:30 +0000236
showardf8b19042009-05-12 17:22:49 +0000237 objects = model_logic.ExtendedManager()
238
showard35444862008-08-07 22:35:30 +0000239 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000240 db_table = 'tko_iteration_result'
showard35444862008-08-07 22:35:30 +0000241
242
243class TestLabel(dbmodels.Model, model_logic.ModelExtensions):
showarda5288b42009-07-28 20:06:08 +0000244 name = dbmodels.CharField(max_length=80, unique=True)
showard35444862008-08-07 22:35:30 +0000245 description = dbmodels.TextField(blank=True)
showardeab66ce2009-12-23 00:03:56 +0000246 tests = dbmodels.ManyToManyField(Test, blank=True,
247 db_table='tko_test_labels_tests')
showard35444862008-08-07 22:35:30 +0000248
249 name_field = 'name'
showardf8b19042009-05-12 17:22:49 +0000250 objects = model_logic.ExtendedManager()
showard35444862008-08-07 22:35:30 +0000251
252 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000253 db_table = 'tko_test_labels'
showard35444862008-08-07 22:35:30 +0000254
255
256class SavedQuery(dbmodels.Model, model_logic.ModelExtensions):
257 # TODO: change this to foreign key once DBs are merged
showarda5288b42009-07-28 20:06:08 +0000258 owner = dbmodels.CharField(max_length=80)
259 name = dbmodels.CharField(max_length=100)
showard35444862008-08-07 22:35:30 +0000260 url_token = dbmodels.TextField()
261
262 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000263 db_table = 'tko_saved_queries'
showard35444862008-08-07 22:35:30 +0000264
265
showardce12f552008-09-19 00:48:59 +0000266class EmbeddedGraphingQuery(dbmodels.Model, model_logic.ModelExtensions):
267 url_token = dbmodels.TextField(null=False, blank=False)
showarda5288b42009-07-28 20:06:08 +0000268 graph_type = dbmodels.CharField(max_length=16, null=False, blank=False)
showardce12f552008-09-19 00:48:59 +0000269 params = dbmodels.TextField(null=False, blank=False)
270 last_updated = dbmodels.DateTimeField(null=False, blank=False,
271 editable=False)
272 # refresh_time shows the time at which a thread is updating the cached
273 # image, or NULL if no one is updating the image. This is used so that only
274 # one thread is updating the cached image at a time (see
275 # graphing_utils.handle_plot_request)
276 refresh_time = dbmodels.DateTimeField(editable=False)
277 cached_png = dbmodels.TextField(editable=False)
278
279 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000280 db_table = 'tko_embedded_graphing_queries'
showardce12f552008-09-19 00:48:59 +0000281
282
showard35444862008-08-07 22:35:30 +0000283# views
284
285class TestViewManager(TempManager):
showard35444862008-08-07 22:35:30 +0000286 def get_query_set(self):
287 query = super(TestViewManager, self).get_query_set()
showard5bf7c502008-08-20 01:22:22 +0000288
showard35444862008-08-07 22:35:30 +0000289 # add extra fields to selects, using the SQL itself as the "alias"
290 extra_select = dict((sql, sql)
291 for sql in self.model.extra_fields.iterkeys())
292 return query.extra(select=extra_select)
293
294
showardf2489522008-10-23 23:08:00 +0000295 def _get_include_exclude_suffix(self, exclude):
296 if exclude:
showard2aa318e2009-08-20 23:43:10 +0000297 return '_exclude'
298 return '_include'
299
300
301 def _add_attribute_join(self, query_set, join_condition,
302 suffix=None, exclude=False):
303 if suffix is None:
304 suffix = self._get_include_exclude_suffix(exclude)
showardeab66ce2009-12-23 00:03:56 +0000305 return self.add_join(query_set, 'tko_test_attributes',
306 join_key='test_idx',
showard2aa318e2009-08-20 23:43:10 +0000307 join_condition=join_condition,
308 suffix=suffix, exclude=exclude)
309
310
311 def _add_label_pivot_table_join(self, query_set, suffix, join_condition='',
312 exclude=False, force_left_join=False):
showardeab66ce2009-12-23 00:03:56 +0000313 return self.add_join(query_set, 'tko_test_labels_tests',
314 join_key='test_id',
showard2aa318e2009-08-20 23:43:10 +0000315 join_condition=join_condition,
316 suffix=suffix, exclude=exclude,
317 force_left_join=force_left_join)
showardf2489522008-10-23 23:08:00 +0000318
319
showard64aeecd2008-09-19 21:32:58 +0000320 def _add_label_joins(self, query_set, suffix=''):
showard2aa318e2009-08-20 23:43:10 +0000321 query_set = self._add_label_pivot_table_join(
322 query_set, suffix=suffix, force_left_join=True)
showardd50ffb42008-09-04 02:47:45 +0000323
showard2aa318e2009-08-20 23:43:10 +0000324 # since we're not joining from the original table, we can't use
325 # self.add_join() again
showardeab66ce2009-12-23 00:03:56 +0000326 second_join_alias = 'tko_test_labels' + suffix
showardd50ffb42008-09-04 02:47:45 +0000327 second_join_condition = ('%s.id = %s.testlabel_id' %
showard64aeecd2008-09-19 21:32:58 +0000328 (second_join_alias,
showardeab66ce2009-12-23 00:03:56 +0000329 'tko_test_labels_tests' + suffix))
showard7e67b432010-01-20 01:13:04 +0000330 query_set.query.add_custom_join('tko_test_labels',
331 second_join_condition,
332 query_set.query.LOUTER,
333 alias=second_join_alias)
334 return query_set
showardd50ffb42008-09-04 02:47:45 +0000335
showard64aeecd2008-09-19 21:32:58 +0000336
showardd50ffb42008-09-04 02:47:45 +0000337 def _get_label_ids_from_names(self, label_names):
showard2aa318e2009-08-20 23:43:10 +0000338 label_ids = list( # listifying avoids a double query below
showard8b0ea222009-12-23 19:23:03 +0000339 TestLabel.objects.filter(name__in=label_names)
340 .values_list('name', 'id'))
showard2aa318e2009-08-20 23:43:10 +0000341 if len(label_ids) < len(set(label_names)):
showard250d84d2010-01-12 21:59:48 +0000342 raise ValueError('Not all labels found: %s' %
343 ', '.join(label_names))
showard8b0ea222009-12-23 19:23:03 +0000344 return dict(name_and_id for name_and_id in label_ids)
showard2aa318e2009-08-20 23:43:10 +0000345
346
347 def _include_or_exclude_labels(self, query_set, label_names, exclude=False):
showard8b0ea222009-12-23 19:23:03 +0000348 label_ids = self._get_label_ids_from_names(label_names).itervalues()
showard2aa318e2009-08-20 23:43:10 +0000349 suffix = self._get_include_exclude_suffix(exclude)
showardeab66ce2009-12-23 00:03:56 +0000350 condition = ('tko_test_labels_tests%s.testlabel_id IN (%s)' %
showard8b0ea222009-12-23 19:23:03 +0000351 (suffix,
352 ','.join(str(label_id) for label_id in label_ids)))
showard2aa318e2009-08-20 23:43:10 +0000353 return self._add_label_pivot_table_join(query_set,
354 join_condition=condition,
355 suffix=suffix,
356 exclude=exclude)
showard02813502008-08-20 20:52:56 +0000357
358
showard8b0ea222009-12-23 19:23:03 +0000359 def _add_custom_select(self, query_set, select_name, select_sql):
360 return query_set.extra(select={select_name: select_sql})
361
362
363 def _add_select_value(self, query_set, alias):
364 return self._add_custom_select(query_set, alias,
365 _quote_name(alias) + '.value')
366
367
368 def _add_select_ifnull(self, query_set, alias, non_null_value):
369 select_sql = "IF(%s.id IS NOT NULL, '%s', NULL)" % (_quote_name(alias),
370 non_null_value)
371 return self._add_custom_select(query_set, alias, select_sql)
372
373
374 def _join_label_column(self, query_set, label_name, label_id):
showard8b0ea222009-12-23 19:23:03 +0000375 alias = 'label_' + label_name
showard7e67b432010-01-20 01:13:04 +0000376 label_query = TestLabel.objects.filter(name=label_name)
377 query_set = Test.objects.join_custom_field(query_set, label_query,
378 alias)
showard8b0ea222009-12-23 19:23:03 +0000379
380 query_set = self._add_select_ifnull(query_set, alias, label_name)
381 return query_set
382
383
384 def _join_label_columns(self, query_set, label_names):
385 label_id_map = self._get_label_ids_from_names(label_names)
386 for label_name in label_names:
387 query_set = self._join_label_column(query_set, label_name,
388 label_id_map[label_name])
389 return query_set
390
391
showard7e67b432010-01-20 01:13:04 +0000392 def _join_attribute(self, query_set, attribute, alias=None,
393 extra_join_condition=None):
showard8b0ea222009-12-23 19:23:03 +0000394 """
395 Join the given TestView QuerySet to TestAttribute. The resulting query
396 has an additional column for the given attribute named
397 "attribute_<attribute name>".
398 """
showard8b0ea222009-12-23 19:23:03 +0000399 if not alias:
400 alias = 'attribute_' + attribute
showard7e67b432010-01-20 01:13:04 +0000401 attribute_query = TestAttribute.objects.filter(attribute=attribute)
showard8b0ea222009-12-23 19:23:03 +0000402 if extra_join_condition:
showard7e67b432010-01-20 01:13:04 +0000403 attribute_query = attribute_query.extra(
404 where=[extra_join_condition])
405 query_set = Test.objects.join_custom_field(query_set, attribute_query,
406 alias)
showard8b0ea222009-12-23 19:23:03 +0000407
408 query_set = self._add_select_value(query_set, alias)
409 return query_set
410
411
412 def _join_machine_label_columns(self, query_set, machine_label_names):
413 for label_name in machine_label_names:
414 alias = 'machine_label_' + label_name
415 condition = "FIND_IN_SET('%s', %s)" % (
416 label_name, _quote_name(alias) + '.value')
417 query_set = self._join_attribute(query_set, 'host-labels',
418 alias=alias,
419 extra_join_condition=condition)
420 query_set = self._add_select_ifnull(query_set, alias, label_name)
421 return query_set
422
423
424 def _join_one_iteration_key(self, query_set, result_key, first_alias=None):
showard8b0ea222009-12-23 19:23:03 +0000425 alias = 'iteration_' + result_key
showard7e67b432010-01-20 01:13:04 +0000426 iteration_query = IterationResult.objects.filter(attribute=result_key)
showard8b0ea222009-12-23 19:23:03 +0000427 if first_alias:
428 # after the first join, we need to match up iteration indices,
429 # otherwise each join will expand the query by the number of
430 # iterations and we'll have extraneous rows
showard7e67b432010-01-20 01:13:04 +0000431 iteration_query = iteration_query.extra(
432 where=['%s.iteration = %s.iteration'
433 % (_quote_name(alias), _quote_name(first_alias))])
showard8b0ea222009-12-23 19:23:03 +0000434
showard7e67b432010-01-20 01:13:04 +0000435 query_set = Test.objects.join_custom_field(query_set, iteration_query,
436 alias, left_join=False)
showard8b0ea222009-12-23 19:23:03 +0000437 # select the iteration value and index for this join
438 query_set = self._add_select_value(query_set, alias)
439 if not first_alias:
440 # for first join, add iteration index select too
441 query_set = self._add_custom_select(
442 query_set, 'iteration_index',
443 _quote_name(alias) + '.iteration')
444
445 return query_set, alias
446
447
448 def _join_iterations(self, test_view_query_set, result_keys):
449 """Join the given TestView QuerySet to IterationResult for one result.
450
451 The resulting query looks like a TestView query but has one row per
452 iteration. Each row includes all the attributes of TestView, an
453 attribute for each key in result_keys and an iteration_index attribute.
454
455 We accomplish this by joining the TestView query to IterationResult
456 once per result key. Each join is restricted on the result key (and on
457 the test index, like all one-to-many joins). For the first join, this
458 is the only restriction, so each TestView row expands to a row per
459 iteration (per iteration that includes the key, of course). For each
460 subsequent join, we also restrict the iteration index to match that of
461 the initial join. This makes each subsequent join produce exactly one
462 result row for each input row. (This assumes each iteration contains
463 the same set of keys. Results are undefined if that's not true.)
464 """
465 if not result_keys:
466 return test_view_query_set
467
468 query_set, first_alias = self._join_one_iteration_key(
469 test_view_query_set, result_keys[0])
470 for result_key in result_keys[1:]:
471 query_set, _ = self._join_one_iteration_key(query_set, result_key,
472 first_alias=first_alias)
473 return query_set
474
475
476 def get_query_set_with_joins(self, filter_data):
477 """
478 Add joins for querying over test-related items.
479
480 These parameters are supported going forward:
481 * test_attribute_fields: list of attribute names. Each attribute will
482 be available as a column attribute_<name>.value.
483 * test_label_fields: list of label names. Each label will be available
484 as a column label_<name>.id, non-null iff the label is present.
485 * iteration_fields: list of iteration result names. Each
486 result will be available as a column iteration_<name>.value.
487 Note that this changes the semantics to return iterations
488 instead of tests -- if a test has multiple iterations, a row
489 will be returned for each one. The iteration index is also
490 available as iteration_<name>.iteration.
491 * machine_label_fields: list of machine label names. Each will be
492 available as a column machine_label_<name>.id, non-null iff the
493 label is present on the machine used in the test.
494
495 These parameters are deprecated:
496 * include_labels
497 * exclude_labels
498 * include_attributes_where
499 * exclude_attributes_where
500
501 Additionally, this method adds joins if the following strings are
502 present in extra_where (this is also deprecated):
503 * test_labels
504 * test_attributes_host_labels
505 """
showard35444862008-08-07 22:35:30 +0000506 query_set = self.get_query_set()
showard8b0ea222009-12-23 19:23:03 +0000507
508 test_attributes = filter_data.pop('test_attribute_fields', [])
509 for attribute in test_attributes:
510 query_set = self._join_attribute(query_set, attribute)
511
512 test_labels = filter_data.pop('test_label_fields', [])
513 query_set = self._join_label_columns(query_set, test_labels)
514
515 machine_labels = filter_data.pop('machine_label_fields', [])
516 query_set = self._join_machine_label_columns(query_set, machine_labels)
517
518 iteration_keys = filter_data.pop('iteration_fields', [])
519 query_set = self._join_iterations(query_set, iteration_keys)
520
521 # everything that follows is deprecated behavior
522
showardd50ffb42008-09-04 02:47:45 +0000523 joined = False
showard2aa318e2009-08-20 23:43:10 +0000524
showardf2489522008-10-23 23:08:00 +0000525 extra_where = filter_data.get('extra_where', '')
showardeab66ce2009-12-23 00:03:56 +0000526 if 'tko_test_labels' in extra_where:
showard02813502008-08-20 20:52:56 +0000527 query_set = self._add_label_joins(query_set)
showardd50ffb42008-09-04 02:47:45 +0000528 joined = True
529
showard8b0ea222009-12-23 19:23:03 +0000530 include_labels = filter_data.pop('include_labels', [])
531 exclude_labels = filter_data.pop('exclude_labels', [])
showard2aa318e2009-08-20 23:43:10 +0000532 if include_labels:
533 query_set = self._include_or_exclude_labels(query_set,
534 include_labels)
showardfc8c6ae2008-11-11 19:06:01 +0000535 joined = True
showard2aa318e2009-08-20 23:43:10 +0000536 if exclude_labels:
537 query_set = self._include_or_exclude_labels(query_set,
538 exclude_labels,
539 exclude=True)
showard64aeecd2008-09-19 21:32:58 +0000540 joined = True
541
542 include_attributes_where = filter_data.pop('include_attributes_where',
543 '')
544 exclude_attributes_where = filter_data.pop('exclude_attributes_where',
545 '')
546 if include_attributes_where:
547 query_set = self._add_attribute_join(
showard2aa318e2009-08-20 23:43:10 +0000548 query_set,
549 join_condition=self.escape_user_sql(include_attributes_where))
showard64aeecd2008-09-19 21:32:58 +0000550 joined = True
551 if exclude_attributes_where:
552 query_set = self._add_attribute_join(
showard2aa318e2009-08-20 23:43:10 +0000553 query_set,
554 join_condition=self.escape_user_sql(exclude_attributes_where),
showard64aeecd2008-09-19 21:32:58 +0000555 exclude=True)
556 joined = True
showardd50ffb42008-09-04 02:47:45 +0000557
558 if not joined:
showard35444862008-08-07 22:35:30 +0000559 filter_data['no_distinct'] = True
showardd50ffb42008-09-04 02:47:45 +0000560
showard8b0ea222009-12-23 19:23:03 +0000561 if 'tko_test_attributes_host_labels' in extra_where:
showardf2489522008-10-23 23:08:00 +0000562 query_set = self._add_attribute_join(
563 query_set, suffix='_host_labels',
showardeab66ce2009-12-23 00:03:56 +0000564 join_condition='tko_test_attributes_host_labels.attribute = '
showardf2489522008-10-23 23:08:00 +0000565 '"host-labels"')
566
showard35444862008-08-07 22:35:30 +0000567 return query_set
568
569
showard8bfb5cb2009-10-07 20:49:15 +0000570 def query_test_ids(self, filter_data, apply_presentation=True):
571 query = self.model.query_objects(filter_data,
572 apply_presentation=apply_presentation)
573 dicts = query.values('test_idx')
showard02813502008-08-20 20:52:56 +0000574 return [item['test_idx'] for item in dicts]
575
576
showard02813502008-08-20 20:52:56 +0000577 def query_test_label_ids(self, filter_data):
showardd50ffb42008-09-04 02:47:45 +0000578 query_set = self.model.query_objects(filter_data)
579 query_set = self._add_label_joins(query_set, suffix='_list')
showardeab66ce2009-12-23 00:03:56 +0000580 rows = self._custom_select_query(query_set, ['tko_test_labels_list.id'])
showardd50ffb42008-09-04 02:47:45 +0000581 return [row[0] for row in rows if row[0] is not None]
showard02813502008-08-20 20:52:56 +0000582
583
showardeaccf8f2009-04-16 03:11:33 +0000584 def escape_user_sql(self, sql):
585 sql = super(TestViewManager, self).escape_user_sql(sql)
586 return sql.replace('test_idx', self.get_key_on_this_table('test_idx'))
587
588
showard35444862008-08-07 22:35:30 +0000589class TestView(dbmodels.Model, model_logic.ModelExtensions):
590 extra_fields = {
showardf4c702e2009-07-08 21:14:27 +0000591 'DATE(job_queued_time)': 'job queued day',
592 'DATE(test_finished_time)': 'test finished day',
showard35444862008-08-07 22:35:30 +0000593 }
594
595 group_fields = [
showardf4c702e2009-07-08 21:14:27 +0000596 'test_name',
597 'status',
598 'kernel',
599 'hostname',
600 'job_tag',
601 'job_name',
602 'platform',
603 'reason',
604 'job_owner',
605 'job_queued_time',
606 'DATE(job_queued_time)',
607 'test_started_time',
608 'test_finished_time',
609 'DATE(test_finished_time)',
showard35444862008-08-07 22:35:30 +0000610 ]
611
612 test_idx = dbmodels.IntegerField('test index', primary_key=True)
613 job_idx = dbmodels.IntegerField('job index', null=True, blank=True)
showarda5288b42009-07-28 20:06:08 +0000614 test_name = dbmodels.CharField(blank=True, max_length=90)
615 subdir = dbmodels.CharField('subdirectory', blank=True, max_length=180)
showard35444862008-08-07 22:35:30 +0000616 kernel_idx = dbmodels.IntegerField('kernel index')
617 status_idx = dbmodels.IntegerField('status index')
showarda5288b42009-07-28 20:06:08 +0000618 reason = dbmodels.CharField(blank=True, max_length=3072)
showard35444862008-08-07 22:35:30 +0000619 machine_idx = dbmodels.IntegerField('host index')
620 test_started_time = dbmodels.DateTimeField(null=True, blank=True)
621 test_finished_time = dbmodels.DateTimeField(null=True, blank=True)
showarda5288b42009-07-28 20:06:08 +0000622 job_tag = dbmodels.CharField(blank=True, max_length=300)
623 job_name = dbmodels.CharField(blank=True, max_length=300)
624 job_owner = dbmodels.CharField('owner', blank=True, max_length=240)
showard35444862008-08-07 22:35:30 +0000625 job_queued_time = dbmodels.DateTimeField(null=True, blank=True)
626 job_started_time = dbmodels.DateTimeField(null=True, blank=True)
627 job_finished_time = dbmodels.DateTimeField(null=True, blank=True)
showardc1c1caf2009-09-08 16:26:50 +0000628 afe_job_id = dbmodels.IntegerField(null=True)
showarda5288b42009-07-28 20:06:08 +0000629 hostname = dbmodels.CharField(blank=True, max_length=300)
630 platform = dbmodels.CharField(blank=True, max_length=240)
631 machine_owner = dbmodels.CharField(blank=True, max_length=240)
632 kernel_hash = dbmodels.CharField(blank=True, max_length=105)
633 kernel_base = dbmodels.CharField(blank=True, max_length=90)
634 kernel = dbmodels.CharField(blank=True, max_length=300)
635 status = dbmodels.CharField(blank=True, max_length=30)
showard35444862008-08-07 22:35:30 +0000636
637 objects = TestViewManager()
638
639 def save(self):
640 raise NotImplementedError('TestView is read-only')
641
642
643 def delete(self):
644 raise NotImplementedError('TestView is read-only')
645
646
647 @classmethod
showard8bfb5cb2009-10-07 20:49:15 +0000648 def query_objects(cls, filter_data, initial_query=None,
649 apply_presentation=True):
showard35444862008-08-07 22:35:30 +0000650 if initial_query is None:
showard64aeecd2008-09-19 21:32:58 +0000651 initial_query = cls.objects.get_query_set_with_joins(filter_data)
showard8bfb5cb2009-10-07 20:49:15 +0000652 return super(TestView, cls).query_objects(
653 filter_data, initial_query=initial_query,
654 apply_presentation=apply_presentation)
showard35444862008-08-07 22:35:30 +0000655
656
657 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000658 db_table = 'tko_test_view_2'