blob: 429473d3230916cdd293923af67673f3599a5c2e [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))
showard64aeecd2008-09-19 21:32:58 +0000330 filter_object = self._CustomSqlQ()
showardeab66ce2009-12-23 00:03:56 +0000331 filter_object.add_join('tko_test_labels',
showardd50ffb42008-09-04 02:47:45 +0000332 second_join_condition,
showarda5288b42009-07-28 20:06:08 +0000333 query_set.query.LOUTER,
showardd50ffb42008-09-04 02:47:45 +0000334 alias=second_join_alias)
showarda5288b42009-07-28 20:06:08 +0000335 return self._add_customSqlQ(query_set, filter_object)
showardd50ffb42008-09-04 02:47:45 +0000336
showard64aeecd2008-09-19 21:32:58 +0000337
showardd50ffb42008-09-04 02:47:45 +0000338 def _get_label_ids_from_names(self, label_names):
showard2aa318e2009-08-20 23:43:10 +0000339 label_ids = list( # listifying avoids a double query below
showard8b0ea222009-12-23 19:23:03 +0000340 TestLabel.objects.filter(name__in=label_names)
341 .values_list('name', 'id'))
showard2aa318e2009-08-20 23:43:10 +0000342 if len(label_ids) < len(set(label_names)):
showard250d84d2010-01-12 21:59:48 +0000343 raise ValueError('Not all labels found: %s' %
344 ', '.join(label_names))
showard8b0ea222009-12-23 19:23:03 +0000345 return dict(name_and_id for name_and_id in label_ids)
showard2aa318e2009-08-20 23:43:10 +0000346
347
348 def _include_or_exclude_labels(self, query_set, label_names, exclude=False):
showard8b0ea222009-12-23 19:23:03 +0000349 label_ids = self._get_label_ids_from_names(label_names).itervalues()
showard2aa318e2009-08-20 23:43:10 +0000350 suffix = self._get_include_exclude_suffix(exclude)
showardeab66ce2009-12-23 00:03:56 +0000351 condition = ('tko_test_labels_tests%s.testlabel_id IN (%s)' %
showard8b0ea222009-12-23 19:23:03 +0000352 (suffix,
353 ','.join(str(label_id) for label_id in label_ids)))
showard2aa318e2009-08-20 23:43:10 +0000354 return self._add_label_pivot_table_join(query_set,
355 join_condition=condition,
356 suffix=suffix,
357 exclude=exclude)
showard02813502008-08-20 20:52:56 +0000358
359
showard8b0ea222009-12-23 19:23:03 +0000360 def _add_custom_select(self, query_set, select_name, select_sql):
361 return query_set.extra(select={select_name: select_sql})
362
363
364 def _add_select_value(self, query_set, alias):
365 return self._add_custom_select(query_set, alias,
366 _quote_name(alias) + '.value')
367
368
369 def _add_select_ifnull(self, query_set, alias, non_null_value):
370 select_sql = "IF(%s.id IS NOT NULL, '%s', NULL)" % (_quote_name(alias),
371 non_null_value)
372 return self._add_custom_select(query_set, alias, select_sql)
373
374
375 def _join_label_column(self, query_set, label_name, label_id):
376 table_name = TestLabel.tests.field.m2m_db_table()
377 alias = 'label_' + label_name
378 condition = "%s.testlabel_id = %s" % (_quote_name(alias), label_id)
379 query_set = self.add_join(query_set, table_name,
380 join_key='test_id', join_condition=condition,
381 alias=alias, force_left_join=True)
382
383 query_set = self._add_select_ifnull(query_set, alias, label_name)
384 return query_set
385
386
387 def _join_label_columns(self, query_set, label_names):
388 label_id_map = self._get_label_ids_from_names(label_names)
389 for label_name in label_names:
390 query_set = self._join_label_column(query_set, label_name,
391 label_id_map[label_name])
392 return query_set
393
394
395 def _join_attribute(self, test_view_query_set, attribute,
396 alias=None, extra_join_condition=None):
397 """
398 Join the given TestView QuerySet to TestAttribute. The resulting query
399 has an additional column for the given attribute named
400 "attribute_<attribute name>".
401 """
402 table_name = TestAttribute._meta.db_table
403 if not alias:
404 alias = 'attribute_' + attribute
405 condition = "%s.attribute = '%s'" % (_quote_name(alias),
406 self.escape_user_sql(attribute))
407 if extra_join_condition:
408 condition += ' AND (%s)' % extra_join_condition
409 query_set = self.add_join(test_view_query_set, table_name,
410 join_key='test_idx', join_condition=condition,
411 alias=alias, force_left_join=True)
412
413 query_set = self._add_select_value(query_set, alias)
414 return query_set
415
416
417 def _join_machine_label_columns(self, query_set, machine_label_names):
418 for label_name in machine_label_names:
419 alias = 'machine_label_' + label_name
420 condition = "FIND_IN_SET('%s', %s)" % (
421 label_name, _quote_name(alias) + '.value')
422 query_set = self._join_attribute(query_set, 'host-labels',
423 alias=alias,
424 extra_join_condition=condition)
425 query_set = self._add_select_ifnull(query_set, alias, label_name)
426 return query_set
427
428
429 def _join_one_iteration_key(self, query_set, result_key, first_alias=None):
430 table_name = IterationResult._meta.db_table
431 alias = 'iteration_' + result_key
432 condition_parts = ["%s.attribute = '%s'" %
433 (_quote_name(alias),
434 self.escape_user_sql(result_key))]
435 if first_alias:
436 # after the first join, we need to match up iteration indices,
437 # otherwise each join will expand the query by the number of
438 # iterations and we'll have extraneous rows
439 condition_parts.append('%s.iteration = %s.iteration' %
440 (_quote_name(alias),
441 _quote_name(first_alias)))
442
443 condition = ' and '.join(condition_parts)
444 # add a join to IterationResult
445 query_set = self.add_join(query_set, table_name, join_key='test_idx',
446 join_condition=condition, alias=alias)
447 # select the iteration value and index for this join
448 query_set = self._add_select_value(query_set, alias)
449 if not first_alias:
450 # for first join, add iteration index select too
451 query_set = self._add_custom_select(
452 query_set, 'iteration_index',
453 _quote_name(alias) + '.iteration')
454
455 return query_set, alias
456
457
458 def _join_iterations(self, test_view_query_set, result_keys):
459 """Join the given TestView QuerySet to IterationResult for one result.
460
461 The resulting query looks like a TestView query but has one row per
462 iteration. Each row includes all the attributes of TestView, an
463 attribute for each key in result_keys and an iteration_index attribute.
464
465 We accomplish this by joining the TestView query to IterationResult
466 once per result key. Each join is restricted on the result key (and on
467 the test index, like all one-to-many joins). For the first join, this
468 is the only restriction, so each TestView row expands to a row per
469 iteration (per iteration that includes the key, of course). For each
470 subsequent join, we also restrict the iteration index to match that of
471 the initial join. This makes each subsequent join produce exactly one
472 result row for each input row. (This assumes each iteration contains
473 the same set of keys. Results are undefined if that's not true.)
474 """
475 if not result_keys:
476 return test_view_query_set
477
478 query_set, first_alias = self._join_one_iteration_key(
479 test_view_query_set, result_keys[0])
480 for result_key in result_keys[1:]:
481 query_set, _ = self._join_one_iteration_key(query_set, result_key,
482 first_alias=first_alias)
483 return query_set
484
485
486 def get_query_set_with_joins(self, filter_data):
487 """
488 Add joins for querying over test-related items.
489
490 These parameters are supported going forward:
491 * test_attribute_fields: list of attribute names. Each attribute will
492 be available as a column attribute_<name>.value.
493 * test_label_fields: list of label names. Each label will be available
494 as a column label_<name>.id, non-null iff the label is present.
495 * iteration_fields: list of iteration result names. Each
496 result will be available as a column iteration_<name>.value.
497 Note that this changes the semantics to return iterations
498 instead of tests -- if a test has multiple iterations, a row
499 will be returned for each one. The iteration index is also
500 available as iteration_<name>.iteration.
501 * machine_label_fields: list of machine label names. Each will be
502 available as a column machine_label_<name>.id, non-null iff the
503 label is present on the machine used in the test.
504
505 These parameters are deprecated:
506 * include_labels
507 * exclude_labels
508 * include_attributes_where
509 * exclude_attributes_where
510
511 Additionally, this method adds joins if the following strings are
512 present in extra_where (this is also deprecated):
513 * test_labels
514 * test_attributes_host_labels
515 """
showard35444862008-08-07 22:35:30 +0000516 query_set = self.get_query_set()
showard8b0ea222009-12-23 19:23:03 +0000517
518 test_attributes = filter_data.pop('test_attribute_fields', [])
519 for attribute in test_attributes:
520 query_set = self._join_attribute(query_set, attribute)
521
522 test_labels = filter_data.pop('test_label_fields', [])
523 query_set = self._join_label_columns(query_set, test_labels)
524
525 machine_labels = filter_data.pop('machine_label_fields', [])
526 query_set = self._join_machine_label_columns(query_set, machine_labels)
527
528 iteration_keys = filter_data.pop('iteration_fields', [])
529 query_set = self._join_iterations(query_set, iteration_keys)
530
531 # everything that follows is deprecated behavior
532
showardd50ffb42008-09-04 02:47:45 +0000533 joined = False
showard2aa318e2009-08-20 23:43:10 +0000534
showardf2489522008-10-23 23:08:00 +0000535 extra_where = filter_data.get('extra_where', '')
showardeab66ce2009-12-23 00:03:56 +0000536 if 'tko_test_labels' in extra_where:
showard02813502008-08-20 20:52:56 +0000537 query_set = self._add_label_joins(query_set)
showardd50ffb42008-09-04 02:47:45 +0000538 joined = True
539
showard8b0ea222009-12-23 19:23:03 +0000540 include_labels = filter_data.pop('include_labels', [])
541 exclude_labels = filter_data.pop('exclude_labels', [])
showard2aa318e2009-08-20 23:43:10 +0000542 if include_labels:
543 query_set = self._include_or_exclude_labels(query_set,
544 include_labels)
showardfc8c6ae2008-11-11 19:06:01 +0000545 joined = True
showard2aa318e2009-08-20 23:43:10 +0000546 if exclude_labels:
547 query_set = self._include_or_exclude_labels(query_set,
548 exclude_labels,
549 exclude=True)
showard64aeecd2008-09-19 21:32:58 +0000550 joined = True
551
552 include_attributes_where = filter_data.pop('include_attributes_where',
553 '')
554 exclude_attributes_where = filter_data.pop('exclude_attributes_where',
555 '')
556 if include_attributes_where:
557 query_set = self._add_attribute_join(
showard2aa318e2009-08-20 23:43:10 +0000558 query_set,
559 join_condition=self.escape_user_sql(include_attributes_where))
showard64aeecd2008-09-19 21:32:58 +0000560 joined = True
561 if exclude_attributes_where:
562 query_set = self._add_attribute_join(
showard2aa318e2009-08-20 23:43:10 +0000563 query_set,
564 join_condition=self.escape_user_sql(exclude_attributes_where),
showard64aeecd2008-09-19 21:32:58 +0000565 exclude=True)
566 joined = True
showardd50ffb42008-09-04 02:47:45 +0000567
568 if not joined:
showard35444862008-08-07 22:35:30 +0000569 filter_data['no_distinct'] = True
showardd50ffb42008-09-04 02:47:45 +0000570
showard8b0ea222009-12-23 19:23:03 +0000571 if 'tko_test_attributes_host_labels' in extra_where:
showardf2489522008-10-23 23:08:00 +0000572 query_set = self._add_attribute_join(
573 query_set, suffix='_host_labels',
showardeab66ce2009-12-23 00:03:56 +0000574 join_condition='tko_test_attributes_host_labels.attribute = '
showardf2489522008-10-23 23:08:00 +0000575 '"host-labels"')
576
showard35444862008-08-07 22:35:30 +0000577 return query_set
578
579
showard8bfb5cb2009-10-07 20:49:15 +0000580 def query_test_ids(self, filter_data, apply_presentation=True):
581 query = self.model.query_objects(filter_data,
582 apply_presentation=apply_presentation)
583 dicts = query.values('test_idx')
showard02813502008-08-20 20:52:56 +0000584 return [item['test_idx'] for item in dicts]
585
586
showard02813502008-08-20 20:52:56 +0000587 def query_test_label_ids(self, filter_data):
showardd50ffb42008-09-04 02:47:45 +0000588 query_set = self.model.query_objects(filter_data)
589 query_set = self._add_label_joins(query_set, suffix='_list')
showardeab66ce2009-12-23 00:03:56 +0000590 rows = self._custom_select_query(query_set, ['tko_test_labels_list.id'])
showardd50ffb42008-09-04 02:47:45 +0000591 return [row[0] for row in rows if row[0] is not None]
showard02813502008-08-20 20:52:56 +0000592
593
showardeaccf8f2009-04-16 03:11:33 +0000594 def escape_user_sql(self, sql):
595 sql = super(TestViewManager, self).escape_user_sql(sql)
596 return sql.replace('test_idx', self.get_key_on_this_table('test_idx'))
597
598
showard35444862008-08-07 22:35:30 +0000599class TestView(dbmodels.Model, model_logic.ModelExtensions):
600 extra_fields = {
showardf4c702e2009-07-08 21:14:27 +0000601 'DATE(job_queued_time)': 'job queued day',
602 'DATE(test_finished_time)': 'test finished day',
showard35444862008-08-07 22:35:30 +0000603 }
604
605 group_fields = [
showardf4c702e2009-07-08 21:14:27 +0000606 'test_name',
607 'status',
608 'kernel',
609 'hostname',
610 'job_tag',
611 'job_name',
612 'platform',
613 'reason',
614 'job_owner',
615 'job_queued_time',
616 'DATE(job_queued_time)',
617 'test_started_time',
618 'test_finished_time',
619 'DATE(test_finished_time)',
showard35444862008-08-07 22:35:30 +0000620 ]
621
622 test_idx = dbmodels.IntegerField('test index', primary_key=True)
623 job_idx = dbmodels.IntegerField('job index', null=True, blank=True)
showarda5288b42009-07-28 20:06:08 +0000624 test_name = dbmodels.CharField(blank=True, max_length=90)
625 subdir = dbmodels.CharField('subdirectory', blank=True, max_length=180)
showard35444862008-08-07 22:35:30 +0000626 kernel_idx = dbmodels.IntegerField('kernel index')
627 status_idx = dbmodels.IntegerField('status index')
showarda5288b42009-07-28 20:06:08 +0000628 reason = dbmodels.CharField(blank=True, max_length=3072)
showard35444862008-08-07 22:35:30 +0000629 machine_idx = dbmodels.IntegerField('host index')
630 test_started_time = dbmodels.DateTimeField(null=True, blank=True)
631 test_finished_time = dbmodels.DateTimeField(null=True, blank=True)
showarda5288b42009-07-28 20:06:08 +0000632 job_tag = dbmodels.CharField(blank=True, max_length=300)
633 job_name = dbmodels.CharField(blank=True, max_length=300)
634 job_owner = dbmodels.CharField('owner', blank=True, max_length=240)
showard35444862008-08-07 22:35:30 +0000635 job_queued_time = dbmodels.DateTimeField(null=True, blank=True)
636 job_started_time = dbmodels.DateTimeField(null=True, blank=True)
637 job_finished_time = dbmodels.DateTimeField(null=True, blank=True)
showardc1c1caf2009-09-08 16:26:50 +0000638 afe_job_id = dbmodels.IntegerField(null=True)
showarda5288b42009-07-28 20:06:08 +0000639 hostname = dbmodels.CharField(blank=True, max_length=300)
640 platform = dbmodels.CharField(blank=True, max_length=240)
641 machine_owner = dbmodels.CharField(blank=True, max_length=240)
642 kernel_hash = dbmodels.CharField(blank=True, max_length=105)
643 kernel_base = dbmodels.CharField(blank=True, max_length=90)
644 kernel = dbmodels.CharField(blank=True, max_length=300)
645 status = dbmodels.CharField(blank=True, max_length=30)
showard35444862008-08-07 22:35:30 +0000646
647 objects = TestViewManager()
648
649 def save(self):
650 raise NotImplementedError('TestView is read-only')
651
652
653 def delete(self):
654 raise NotImplementedError('TestView is read-only')
655
656
657 @classmethod
showard8bfb5cb2009-10-07 20:49:15 +0000658 def query_objects(cls, filter_data, initial_query=None,
659 apply_presentation=True):
showard35444862008-08-07 22:35:30 +0000660 if initial_query is None:
showard64aeecd2008-09-19 21:32:58 +0000661 initial_query = cls.objects.get_query_set_with_joins(filter_data)
showard8bfb5cb2009-10-07 20:49:15 +0000662 return super(TestView, cls).query_objects(
663 filter_data, initial_query=initial_query,
664 apply_presentation=apply_presentation)
showard35444862008-08-07 22:35:30 +0000665
666
667 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000668 db_table = 'tko_test_view_2'