blob: d97501030c7e03e2d8fee7dfea1b11cfe56067c4 [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
showardf8b19042009-05-12 17:22:49 +0000158class Test(dbmodels.Model, model_logic.ModelExtensions,
159 model_logic.ModelWithAttributes):
160 test_idx = dbmodels.AutoField(primary_key=True)
showard35444862008-08-07 22:35:30 +0000161 job = dbmodels.ForeignKey(Job, db_column='job_idx')
showarda5288b42009-07-28 20:06:08 +0000162 test = dbmodels.CharField(max_length=90)
163 subdir = dbmodels.CharField(blank=True, max_length=180)
showard35444862008-08-07 22:35:30 +0000164 kernel = dbmodels.ForeignKey(Kernel, db_column='kernel_idx')
165 status = dbmodels.ForeignKey(Status, db_column='status')
showarda5288b42009-07-28 20:06:08 +0000166 reason = dbmodels.CharField(blank=True, max_length=3072)
showard35444862008-08-07 22:35:30 +0000167 machine = dbmodels.ForeignKey(Machine, db_column='machine_idx')
168 finished_time = dbmodels.DateTimeField(null=True, blank=True)
169 started_time = dbmodels.DateTimeField(null=True, blank=True)
170
showardf8b19042009-05-12 17:22:49 +0000171 objects = model_logic.ExtendedManager()
172
173 def _get_attribute_model_and_args(self, attribute):
174 return TestAttribute, dict(test=self, attribute=attribute,
175 user_created=True)
176
177
178 def set_attribute(self, attribute, value):
179 # ensure non-user-created attributes remain immutable
180 try:
181 TestAttribute.objects.get(test=self, attribute=attribute,
182 user_created=False)
183 raise ValueError('Attribute %s already exists for test %s and is '
184 'immutable' % (attribute, self.test_idx))
185 except TestAttribute.DoesNotExist:
186 super(Test, self).set_attribute(attribute, value)
187
188
showard35444862008-08-07 22:35:30 +0000189 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000190 db_table = 'tko_tests'
showard35444862008-08-07 22:35:30 +0000191
192
showarde732ee72008-09-23 19:15:43 +0000193class TestAttribute(dbmodels.Model, model_logic.ModelExtensions):
showardf8b19042009-05-12 17:22:49 +0000194 test = dbmodels.ForeignKey(Test, db_column='test_idx')
showarda5288b42009-07-28 20:06:08 +0000195 attribute = dbmodels.CharField(max_length=90)
196 value = dbmodels.CharField(blank=True, max_length=300)
showardf8b19042009-05-12 17:22:49 +0000197 user_created = dbmodels.BooleanField(default=False)
198
199 objects = model_logic.ExtendedManager()
showard35444862008-08-07 22:35:30 +0000200
201 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000202 db_table = 'tko_test_attributes'
showard35444862008-08-07 22:35:30 +0000203
204
jadmanski430dca92008-12-16 20:56:53 +0000205class IterationAttribute(dbmodels.Model, model_logic.ModelExtensions):
showardf8b19042009-05-12 17:22:49 +0000206 # this isn't really a primary key, but it's necessary to appease Django
207 # and is harmless as long as we're careful
showarde732ee72008-09-23 19:15:43 +0000208 test = dbmodels.ForeignKey(Test, db_column='test_idx', primary_key=True)
showard35444862008-08-07 22:35:30 +0000209 iteration = dbmodels.IntegerField()
showarda5288b42009-07-28 20:06:08 +0000210 attribute = dbmodels.CharField(max_length=90)
211 value = dbmodels.CharField(blank=True, max_length=300)
showard35444862008-08-07 22:35:30 +0000212
showardf8b19042009-05-12 17:22:49 +0000213 objects = model_logic.ExtendedManager()
214
showard35444862008-08-07 22:35:30 +0000215 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000216 db_table = 'tko_iteration_attributes'
showard35444862008-08-07 22:35:30 +0000217
218
jadmanski430dca92008-12-16 20:56:53 +0000219class IterationResult(dbmodels.Model, model_logic.ModelExtensions):
showardf8b19042009-05-12 17:22:49 +0000220 # see comment on IterationAttribute regarding primary_key=True
jadmanski430dca92008-12-16 20:56:53 +0000221 test = dbmodels.ForeignKey(Test, db_column='test_idx', primary_key=True)
showard35444862008-08-07 22:35:30 +0000222 iteration = dbmodels.IntegerField()
showarda5288b42009-07-28 20:06:08 +0000223 attribute = dbmodels.CharField(max_length=90)
224 value = dbmodels.DecimalField(null=True, max_digits=12, decimal_places=31,
225 blank=True)
showard35444862008-08-07 22:35:30 +0000226
showardf8b19042009-05-12 17:22:49 +0000227 objects = model_logic.ExtendedManager()
228
showard35444862008-08-07 22:35:30 +0000229 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000230 db_table = 'tko_iteration_result'
showard35444862008-08-07 22:35:30 +0000231
232
233class TestLabel(dbmodels.Model, model_logic.ModelExtensions):
showarda5288b42009-07-28 20:06:08 +0000234 name = dbmodels.CharField(max_length=80, unique=True)
showard35444862008-08-07 22:35:30 +0000235 description = dbmodels.TextField(blank=True)
showardeab66ce2009-12-23 00:03:56 +0000236 tests = dbmodels.ManyToManyField(Test, blank=True,
237 db_table='tko_test_labels_tests')
showard35444862008-08-07 22:35:30 +0000238
239 name_field = 'name'
showardf8b19042009-05-12 17:22:49 +0000240 objects = model_logic.ExtendedManager()
showard35444862008-08-07 22:35:30 +0000241
242 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000243 db_table = 'tko_test_labels'
showard35444862008-08-07 22:35:30 +0000244
245
246class SavedQuery(dbmodels.Model, model_logic.ModelExtensions):
247 # TODO: change this to foreign key once DBs are merged
showarda5288b42009-07-28 20:06:08 +0000248 owner = dbmodels.CharField(max_length=80)
249 name = dbmodels.CharField(max_length=100)
showard35444862008-08-07 22:35:30 +0000250 url_token = dbmodels.TextField()
251
252 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000253 db_table = 'tko_saved_queries'
showard35444862008-08-07 22:35:30 +0000254
255
showardce12f552008-09-19 00:48:59 +0000256class EmbeddedGraphingQuery(dbmodels.Model, model_logic.ModelExtensions):
257 url_token = dbmodels.TextField(null=False, blank=False)
showarda5288b42009-07-28 20:06:08 +0000258 graph_type = dbmodels.CharField(max_length=16, null=False, blank=False)
showardce12f552008-09-19 00:48:59 +0000259 params = dbmodels.TextField(null=False, blank=False)
260 last_updated = dbmodels.DateTimeField(null=False, blank=False,
261 editable=False)
262 # refresh_time shows the time at which a thread is updating the cached
263 # image, or NULL if no one is updating the image. This is used so that only
264 # one thread is updating the cached image at a time (see
265 # graphing_utils.handle_plot_request)
266 refresh_time = dbmodels.DateTimeField(editable=False)
267 cached_png = dbmodels.TextField(editable=False)
268
269 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000270 db_table = 'tko_embedded_graphing_queries'
showardce12f552008-09-19 00:48:59 +0000271
272
showard35444862008-08-07 22:35:30 +0000273# views
274
275class TestViewManager(TempManager):
showard35444862008-08-07 22:35:30 +0000276 def get_query_set(self):
277 query = super(TestViewManager, self).get_query_set()
showard5bf7c502008-08-20 01:22:22 +0000278
showard35444862008-08-07 22:35:30 +0000279 # add extra fields to selects, using the SQL itself as the "alias"
280 extra_select = dict((sql, sql)
281 for sql in self.model.extra_fields.iterkeys())
282 return query.extra(select=extra_select)
283
284
showardf2489522008-10-23 23:08:00 +0000285 def _get_include_exclude_suffix(self, exclude):
286 if exclude:
showard2aa318e2009-08-20 23:43:10 +0000287 return '_exclude'
288 return '_include'
289
290
291 def _add_attribute_join(self, query_set, join_condition,
292 suffix=None, exclude=False):
293 if suffix is None:
294 suffix = self._get_include_exclude_suffix(exclude)
showardeab66ce2009-12-23 00:03:56 +0000295 return self.add_join(query_set, 'tko_test_attributes',
296 join_key='test_idx',
showard2aa318e2009-08-20 23:43:10 +0000297 join_condition=join_condition,
298 suffix=suffix, exclude=exclude)
299
300
301 def _add_label_pivot_table_join(self, query_set, suffix, join_condition='',
302 exclude=False, force_left_join=False):
showardeab66ce2009-12-23 00:03:56 +0000303 return self.add_join(query_set, 'tko_test_labels_tests',
304 join_key='test_id',
showard2aa318e2009-08-20 23:43:10 +0000305 join_condition=join_condition,
306 suffix=suffix, exclude=exclude,
307 force_left_join=force_left_join)
showardf2489522008-10-23 23:08:00 +0000308
309
showard64aeecd2008-09-19 21:32:58 +0000310 def _add_label_joins(self, query_set, suffix=''):
showard2aa318e2009-08-20 23:43:10 +0000311 query_set = self._add_label_pivot_table_join(
312 query_set, suffix=suffix, force_left_join=True)
showardd50ffb42008-09-04 02:47:45 +0000313
showard2aa318e2009-08-20 23:43:10 +0000314 # since we're not joining from the original table, we can't use
315 # self.add_join() again
showardeab66ce2009-12-23 00:03:56 +0000316 second_join_alias = 'tko_test_labels' + suffix
showardd50ffb42008-09-04 02:47:45 +0000317 second_join_condition = ('%s.id = %s.testlabel_id' %
showard64aeecd2008-09-19 21:32:58 +0000318 (second_join_alias,
showardeab66ce2009-12-23 00:03:56 +0000319 'tko_test_labels_tests' + suffix))
showard64aeecd2008-09-19 21:32:58 +0000320 filter_object = self._CustomSqlQ()
showardeab66ce2009-12-23 00:03:56 +0000321 filter_object.add_join('tko_test_labels',
showardd50ffb42008-09-04 02:47:45 +0000322 second_join_condition,
showarda5288b42009-07-28 20:06:08 +0000323 query_set.query.LOUTER,
showardd50ffb42008-09-04 02:47:45 +0000324 alias=second_join_alias)
showarda5288b42009-07-28 20:06:08 +0000325 return self._add_customSqlQ(query_set, filter_object)
showardd50ffb42008-09-04 02:47:45 +0000326
showard64aeecd2008-09-19 21:32:58 +0000327
showardd50ffb42008-09-04 02:47:45 +0000328 def _get_label_ids_from_names(self, label_names):
showard2aa318e2009-08-20 23:43:10 +0000329 label_ids = list( # listifying avoids a double query below
showard8b0ea222009-12-23 19:23:03 +0000330 TestLabel.objects.filter(name__in=label_names)
331 .values_list('name', 'id'))
showard2aa318e2009-08-20 23:43:10 +0000332 if len(label_ids) < len(set(label_names)):
lmr12b45582010-01-11 21:22:02 +0000333 raise ValueError('Not all labels found: %s' %
334 ', '.join(label_names))
showard8b0ea222009-12-23 19:23:03 +0000335 return dict(name_and_id for name_and_id in label_ids)
showard2aa318e2009-08-20 23:43:10 +0000336
337
338 def _include_or_exclude_labels(self, query_set, label_names, exclude=False):
showard8b0ea222009-12-23 19:23:03 +0000339 label_ids = self._get_label_ids_from_names(label_names).itervalues()
showard2aa318e2009-08-20 23:43:10 +0000340 suffix = self._get_include_exclude_suffix(exclude)
showardeab66ce2009-12-23 00:03:56 +0000341 condition = ('tko_test_labels_tests%s.testlabel_id IN (%s)' %
showard8b0ea222009-12-23 19:23:03 +0000342 (suffix,
343 ','.join(str(label_id) for label_id in label_ids)))
showard2aa318e2009-08-20 23:43:10 +0000344 return self._add_label_pivot_table_join(query_set,
345 join_condition=condition,
346 suffix=suffix,
347 exclude=exclude)
showard02813502008-08-20 20:52:56 +0000348
349
showard8b0ea222009-12-23 19:23:03 +0000350 def _add_custom_select(self, query_set, select_name, select_sql):
351 return query_set.extra(select={select_name: select_sql})
352
353
354 def _add_select_value(self, query_set, alias):
355 return self._add_custom_select(query_set, alias,
356 _quote_name(alias) + '.value')
357
358
359 def _add_select_ifnull(self, query_set, alias, non_null_value):
360 select_sql = "IF(%s.id IS NOT NULL, '%s', NULL)" % (_quote_name(alias),
361 non_null_value)
362 return self._add_custom_select(query_set, alias, select_sql)
363
364
365 def _join_label_column(self, query_set, label_name, label_id):
366 table_name = TestLabel.tests.field.m2m_db_table()
367 alias = 'label_' + label_name
368 condition = "%s.testlabel_id = %s" % (_quote_name(alias), label_id)
369 query_set = self.add_join(query_set, table_name,
370 join_key='test_id', join_condition=condition,
371 alias=alias, force_left_join=True)
372
373 query_set = self._add_select_ifnull(query_set, alias, label_name)
374 return query_set
375
376
377 def _join_label_columns(self, query_set, label_names):
378 label_id_map = self._get_label_ids_from_names(label_names)
379 for label_name in label_names:
380 query_set = self._join_label_column(query_set, label_name,
381 label_id_map[label_name])
382 return query_set
383
384
385 def _join_attribute(self, test_view_query_set, attribute,
386 alias=None, extra_join_condition=None):
387 """
388 Join the given TestView QuerySet to TestAttribute. The resulting query
389 has an additional column for the given attribute named
390 "attribute_<attribute name>".
391 """
392 table_name = TestAttribute._meta.db_table
393 if not alias:
394 alias = 'attribute_' + attribute
395 condition = "%s.attribute = '%s'" % (_quote_name(alias),
396 self.escape_user_sql(attribute))
397 if extra_join_condition:
398 condition += ' AND (%s)' % extra_join_condition
399 query_set = self.add_join(test_view_query_set, table_name,
400 join_key='test_idx', join_condition=condition,
401 alias=alias, force_left_join=True)
402
403 query_set = self._add_select_value(query_set, alias)
404 return query_set
405
406
407 def _join_machine_label_columns(self, query_set, machine_label_names):
408 for label_name in machine_label_names:
409 alias = 'machine_label_' + label_name
410 condition = "FIND_IN_SET('%s', %s)" % (
411 label_name, _quote_name(alias) + '.value')
412 query_set = self._join_attribute(query_set, 'host-labels',
413 alias=alias,
414 extra_join_condition=condition)
415 query_set = self._add_select_ifnull(query_set, alias, label_name)
416 return query_set
417
418
419 def _join_one_iteration_key(self, query_set, result_key, first_alias=None):
420 table_name = IterationResult._meta.db_table
421 alias = 'iteration_' + result_key
422 condition_parts = ["%s.attribute = '%s'" %
423 (_quote_name(alias),
424 self.escape_user_sql(result_key))]
425 if first_alias:
426 # after the first join, we need to match up iteration indices,
427 # otherwise each join will expand the query by the number of
428 # iterations and we'll have extraneous rows
429 condition_parts.append('%s.iteration = %s.iteration' %
430 (_quote_name(alias),
431 _quote_name(first_alias)))
432
433 condition = ' and '.join(condition_parts)
434 # add a join to IterationResult
435 query_set = self.add_join(query_set, table_name, join_key='test_idx',
436 join_condition=condition, alias=alias)
437 # 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'