blob: 9b0d408fe16086f4e02a2c349b64a49442f2f4ec [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):
Dale Curtis74a314b2011-06-23 14:55:46 -070027 compiler = query.query.get_compiler(using=query.db)
28 sql, params = compiler.as_sql()
29
showardd50ffb42008-09-04 02:47:45 +000030
31 # insert GROUP BY clause into query
showard8bfb5cb2009-10-07 20:49:15 +000032 group_fields = self._get_field_names(group_by, query.query.extra_select)
showard7c199df2008-10-03 10:17:15 +000033 group_by_clause = ' GROUP BY ' + ', '.join(group_fields)
showarda5288b42009-07-28 20:06:08 +000034 group_by_position = sql.rfind('ORDER BY')
showardd50ffb42008-09-04 02:47:45 +000035 if group_by_position == -1:
showarda5288b42009-07-28 20:06:08 +000036 group_by_position = len(sql)
37 sql = (sql[:group_by_position] +
38 group_by_clause + ' ' +
39 sql[group_by_position:])
showard35444862008-08-07 22:35:30 +000040
showarda5288b42009-07-28 20:06:08 +000041 return sql, params
showard35444862008-08-07 22:35:30 +000042
43
showard06b82fc2009-06-30 01:59:42 +000044 def _get_column_names(self, cursor):
showard8bfb5cb2009-10-07 20:49:15 +000045 """
showard06b82fc2009-06-30 01:59:42 +000046 Gets the column names from the cursor description. This method exists
showard8bfb5cb2009-10-07 20:49:15 +000047 so that it can be mocked in the unit test for sqlite3 compatibility.
showard06b82fc2009-06-30 01:59:42 +000048 """
49 return [column_info[0] for column_info in cursor.description]
50
51
showard8bfb5cb2009-10-07 20:49:15 +000052 def execute_group_query(self, query, group_by):
showard35444862008-08-07 22:35:30 +000053 """
showard8a6eb0c2008-10-01 11:38:59 +000054 Performs the given query grouped by the fields in group_by with the
showard8bfb5cb2009-10-07 20:49:15 +000055 given query's extra select fields added. Returns a list of dicts, where
56 each dict corresponds to single row and contains a key for each grouped
57 field as well as all of the extra select fields.
showard35444862008-08-07 22:35:30 +000058 """
showard8bfb5cb2009-10-07 20:49:15 +000059 sql, params = self._get_group_query_sql(query, group_by)
showard56e93772008-10-06 10:06:22 +000060 cursor = readonly_connection.connection().cursor()
showard8a6eb0c2008-10-01 11:38:59 +000061 cursor.execute(sql, params)
showard06b82fc2009-06-30 01:59:42 +000062 field_names = self._get_column_names(cursor)
showard8a6eb0c2008-10-01 11:38:59 +000063 row_dicts = [dict(zip(field_names, row)) for row in cursor.fetchall()]
64 return row_dicts
65
66
67 def get_count_sql(self, query):
68 """
69 Get the SQL to properly select a per-group count of unique matches for
showard7c199df2008-10-03 10:17:15 +000070 a grouped query. Returns a tuple (field alias, field SQL)
showard8a6eb0c2008-10-01 11:38:59 +000071 """
showarda5288b42009-07-28 20:06:08 +000072 if query.query.distinct:
showard7c199df2008-10-03 10:17:15 +000073 pk_field = self.get_key_on_this_table()
showard8a6eb0c2008-10-01 11:38:59 +000074 count_sql = 'COUNT(DISTINCT %s)' % pk_field
75 else:
76 count_sql = 'COUNT(1)'
showard7c199df2008-10-03 10:17:15 +000077 return self._GROUP_COUNT_NAME, count_sql
showard35444862008-08-07 22:35:30 +000078
79
80 def _get_num_groups_sql(self, query, group_by):
showardd2b0c882009-10-19 18:34:11 +000081 group_fields = self._get_field_names(group_by, query.query.extra_select)
showarda5288b42009-07-28 20:06:08 +000082 query = query.order_by() # this can mess up the query and isn't needed
83
Dale Curtis74a314b2011-06-23 14:55:46 -070084 compiler = query.query.get_compiler(using=query.db)
85 sql, params = compiler.as_sql()
showarda5288b42009-07-28 20:06:08 +000086 from_ = sql[sql.find(' FROM'):]
showardd2b0c882009-10-19 18:34:11 +000087 return ('SELECT DISTINCT %s %s' % (','.join(group_fields),
showarda5288b42009-07-28 20:06:08 +000088 from_),
showard35444862008-08-07 22:35:30 +000089 params)
90
91
showardd2b0c882009-10-19 18:34:11 +000092 def _cursor_rowcount(self, cursor):
93 """To be stubbed by tests"""
94 return cursor.rowcount
95
96
showard35444862008-08-07 22:35:30 +000097 def get_num_groups(self, query, group_by):
98 """
99 Returns the number of distinct groups for the given query grouped by the
100 fields in group_by.
101 """
102 sql, params = self._get_num_groups_sql(query, group_by)
showard56e93772008-10-06 10:06:22 +0000103 cursor = readonly_connection.connection().cursor()
showard35444862008-08-07 22:35:30 +0000104 cursor.execute(sql, params)
showardd2b0c882009-10-19 18:34:11 +0000105 return self._cursor_rowcount(cursor)
showard35444862008-08-07 22:35:30 +0000106
107
108class Machine(dbmodels.Model):
showardf8b19042009-05-12 17:22:49 +0000109 machine_idx = dbmodels.AutoField(primary_key=True)
showard356d2372009-11-10 01:29:53 +0000110 hostname = dbmodels.CharField(unique=True, max_length=255)
showarda5288b42009-07-28 20:06:08 +0000111 machine_group = dbmodels.CharField(blank=True, max_length=240)
112 owner = dbmodels.CharField(blank=True, max_length=240)
showard35444862008-08-07 22:35:30 +0000113
114 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000115 db_table = 'tko_machines'
showard35444862008-08-07 22:35:30 +0000116
117
118class Kernel(dbmodels.Model):
showardf8b19042009-05-12 17:22:49 +0000119 kernel_idx = dbmodels.AutoField(primary_key=True)
showarda5288b42009-07-28 20:06:08 +0000120 kernel_hash = dbmodels.CharField(max_length=105, editable=False)
121 base = dbmodels.CharField(max_length=90)
122 printable = dbmodels.CharField(max_length=300)
showard35444862008-08-07 22:35:30 +0000123
124 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000125 db_table = 'tko_kernels'
showard35444862008-08-07 22:35:30 +0000126
127
128class Patch(dbmodels.Model):
129 kernel = dbmodels.ForeignKey(Kernel, db_column='kernel_idx')
showarda5288b42009-07-28 20:06:08 +0000130 name = dbmodels.CharField(blank=True, max_length=240)
131 url = dbmodels.CharField(blank=True, max_length=900)
132 the_hash = dbmodels.CharField(blank=True, max_length=105, db_column='hash')
showard35444862008-08-07 22:35:30 +0000133
134 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000135 db_table = 'tko_patches'
showard35444862008-08-07 22:35:30 +0000136
137
138class Status(dbmodels.Model):
showardf8b19042009-05-12 17:22:49 +0000139 status_idx = dbmodels.AutoField(primary_key=True)
showarda5288b42009-07-28 20:06:08 +0000140 word = dbmodels.CharField(max_length=30)
showard35444862008-08-07 22:35:30 +0000141
142 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000143 db_table = 'tko_status'
showard35444862008-08-07 22:35:30 +0000144
145
jamesren708f1c02010-03-31 21:43:57 +0000146class Job(dbmodels.Model, model_logic.ModelExtensions):
showardf8b19042009-05-12 17:22:49 +0000147 job_idx = dbmodels.AutoField(primary_key=True)
showard356d2372009-11-10 01:29:53 +0000148 tag = dbmodels.CharField(unique=True, max_length=100)
showarda5288b42009-07-28 20:06:08 +0000149 label = dbmodels.CharField(max_length=300)
150 username = dbmodels.CharField(max_length=240)
showard35444862008-08-07 22:35:30 +0000151 machine = dbmodels.ForeignKey(Machine, db_column='machine_idx')
152 queued_time = dbmodels.DateTimeField(null=True, blank=True)
153 started_time = dbmodels.DateTimeField(null=True, blank=True)
154 finished_time = dbmodels.DateTimeField(null=True, blank=True)
showardc1c1caf2009-09-08 16:26:50 +0000155 afe_job_id = dbmodels.IntegerField(null=True, default=None)
showard35444862008-08-07 22:35:30 +0000156
jamesren708f1c02010-03-31 21:43:57 +0000157 objects = model_logic.ExtendedManager()
158
showard35444862008-08-07 22:35:30 +0000159 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000160 db_table = 'tko_jobs'
showard35444862008-08-07 22:35:30 +0000161
162
showardc1a98d12010-01-15 00:22:22 +0000163class JobKeyval(dbmodels.Model):
164 job = dbmodels.ForeignKey(Job)
165 key = dbmodels.CharField(max_length=90)
166 value = dbmodels.CharField(blank=True, max_length=300)
167
168
169 class Meta:
170 db_table = 'tko_job_keyvals'
171
172
showardf8b19042009-05-12 17:22:49 +0000173class Test(dbmodels.Model, model_logic.ModelExtensions,
174 model_logic.ModelWithAttributes):
175 test_idx = dbmodels.AutoField(primary_key=True)
showard35444862008-08-07 22:35:30 +0000176 job = dbmodels.ForeignKey(Job, db_column='job_idx')
lmr79697282010-03-31 23:18:02 +0000177 test = dbmodels.CharField(max_length=300)
178 subdir = dbmodels.CharField(blank=True, max_length=300)
showard35444862008-08-07 22:35:30 +0000179 kernel = dbmodels.ForeignKey(Kernel, db_column='kernel_idx')
180 status = dbmodels.ForeignKey(Status, db_column='status')
showarda5288b42009-07-28 20:06:08 +0000181 reason = dbmodels.CharField(blank=True, max_length=3072)
showard35444862008-08-07 22:35:30 +0000182 machine = dbmodels.ForeignKey(Machine, db_column='machine_idx')
183 finished_time = dbmodels.DateTimeField(null=True, blank=True)
184 started_time = dbmodels.DateTimeField(null=True, blank=True)
185
showardf8b19042009-05-12 17:22:49 +0000186 objects = model_logic.ExtendedManager()
187
188 def _get_attribute_model_and_args(self, attribute):
189 return TestAttribute, dict(test=self, attribute=attribute,
190 user_created=True)
191
192
193 def set_attribute(self, attribute, value):
194 # ensure non-user-created attributes remain immutable
195 try:
196 TestAttribute.objects.get(test=self, attribute=attribute,
197 user_created=False)
198 raise ValueError('Attribute %s already exists for test %s and is '
199 'immutable' % (attribute, self.test_idx))
200 except TestAttribute.DoesNotExist:
201 super(Test, self).set_attribute(attribute, value)
202
203
showard35444862008-08-07 22:35:30 +0000204 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000205 db_table = 'tko_tests'
showard35444862008-08-07 22:35:30 +0000206
207
showarde732ee72008-09-23 19:15:43 +0000208class TestAttribute(dbmodels.Model, model_logic.ModelExtensions):
showardf8b19042009-05-12 17:22:49 +0000209 test = dbmodels.ForeignKey(Test, db_column='test_idx')
showarda5288b42009-07-28 20:06:08 +0000210 attribute = dbmodels.CharField(max_length=90)
211 value = dbmodels.CharField(blank=True, max_length=300)
showardf8b19042009-05-12 17:22:49 +0000212 user_created = dbmodels.BooleanField(default=False)
213
214 objects = model_logic.ExtendedManager()
showard35444862008-08-07 22:35:30 +0000215
216 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000217 db_table = 'tko_test_attributes'
showard35444862008-08-07 22:35:30 +0000218
219
jadmanski430dca92008-12-16 20:56:53 +0000220class IterationAttribute(dbmodels.Model, model_logic.ModelExtensions):
showardf8b19042009-05-12 17:22:49 +0000221 # this isn't really a primary key, but it's necessary to appease Django
222 # and is harmless as long as we're careful
showarde732ee72008-09-23 19:15:43 +0000223 test = dbmodels.ForeignKey(Test, db_column='test_idx', primary_key=True)
showard35444862008-08-07 22:35:30 +0000224 iteration = dbmodels.IntegerField()
showarda5288b42009-07-28 20:06:08 +0000225 attribute = dbmodels.CharField(max_length=90)
226 value = dbmodels.CharField(blank=True, max_length=300)
showard35444862008-08-07 22:35:30 +0000227
showardf8b19042009-05-12 17:22:49 +0000228 objects = model_logic.ExtendedManager()
229
showard35444862008-08-07 22:35:30 +0000230 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000231 db_table = 'tko_iteration_attributes'
showard35444862008-08-07 22:35:30 +0000232
233
jadmanski430dca92008-12-16 20:56:53 +0000234class IterationResult(dbmodels.Model, model_logic.ModelExtensions):
showardf8b19042009-05-12 17:22:49 +0000235 # see comment on IterationAttribute regarding primary_key=True
jadmanski430dca92008-12-16 20:56:53 +0000236 test = dbmodels.ForeignKey(Test, db_column='test_idx', primary_key=True)
showard35444862008-08-07 22:35:30 +0000237 iteration = dbmodels.IntegerField()
showarda5288b42009-07-28 20:06:08 +0000238 attribute = dbmodels.CharField(max_length=90)
jamesren26f9a9f2010-04-02 17:44:55 +0000239 value = dbmodels.FloatField(null=True, blank=True)
showard35444862008-08-07 22:35:30 +0000240
showardf8b19042009-05-12 17:22:49 +0000241 objects = model_logic.ExtendedManager()
242
showard35444862008-08-07 22:35:30 +0000243 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000244 db_table = 'tko_iteration_result'
showard35444862008-08-07 22:35:30 +0000245
246
247class TestLabel(dbmodels.Model, model_logic.ModelExtensions):
showarda5288b42009-07-28 20:06:08 +0000248 name = dbmodels.CharField(max_length=80, unique=True)
showard35444862008-08-07 22:35:30 +0000249 description = dbmodels.TextField(blank=True)
showardeab66ce2009-12-23 00:03:56 +0000250 tests = dbmodels.ManyToManyField(Test, blank=True,
251 db_table='tko_test_labels_tests')
showard35444862008-08-07 22:35:30 +0000252
253 name_field = 'name'
showardf8b19042009-05-12 17:22:49 +0000254 objects = model_logic.ExtendedManager()
showard35444862008-08-07 22:35:30 +0000255
256 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000257 db_table = 'tko_test_labels'
showard35444862008-08-07 22:35:30 +0000258
259
260class SavedQuery(dbmodels.Model, model_logic.ModelExtensions):
261 # TODO: change this to foreign key once DBs are merged
showarda5288b42009-07-28 20:06:08 +0000262 owner = dbmodels.CharField(max_length=80)
263 name = dbmodels.CharField(max_length=100)
showard35444862008-08-07 22:35:30 +0000264 url_token = dbmodels.TextField()
265
266 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000267 db_table = 'tko_saved_queries'
showard35444862008-08-07 22:35:30 +0000268
269
showardce12f552008-09-19 00:48:59 +0000270class EmbeddedGraphingQuery(dbmodels.Model, model_logic.ModelExtensions):
271 url_token = dbmodels.TextField(null=False, blank=False)
showarda5288b42009-07-28 20:06:08 +0000272 graph_type = dbmodels.CharField(max_length=16, null=False, blank=False)
showardce12f552008-09-19 00:48:59 +0000273 params = dbmodels.TextField(null=False, blank=False)
274 last_updated = dbmodels.DateTimeField(null=False, blank=False,
275 editable=False)
276 # refresh_time shows the time at which a thread is updating the cached
277 # image, or NULL if no one is updating the image. This is used so that only
278 # one thread is updating the cached image at a time (see
279 # graphing_utils.handle_plot_request)
280 refresh_time = dbmodels.DateTimeField(editable=False)
281 cached_png = dbmodels.TextField(editable=False)
282
283 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000284 db_table = 'tko_embedded_graphing_queries'
showardce12f552008-09-19 00:48:59 +0000285
286
showard35444862008-08-07 22:35:30 +0000287# views
288
289class TestViewManager(TempManager):
showard35444862008-08-07 22:35:30 +0000290 def get_query_set(self):
291 query = super(TestViewManager, self).get_query_set()
showard5bf7c502008-08-20 01:22:22 +0000292
showard35444862008-08-07 22:35:30 +0000293 # add extra fields to selects, using the SQL itself as the "alias"
294 extra_select = dict((sql, sql)
295 for sql in self.model.extra_fields.iterkeys())
296 return query.extra(select=extra_select)
297
298
showardf2489522008-10-23 23:08:00 +0000299 def _get_include_exclude_suffix(self, exclude):
300 if exclude:
showard2aa318e2009-08-20 23:43:10 +0000301 return '_exclude'
302 return '_include'
303
304
305 def _add_attribute_join(self, query_set, join_condition,
306 suffix=None, exclude=False):
307 if suffix is None:
308 suffix = self._get_include_exclude_suffix(exclude)
showardeab66ce2009-12-23 00:03:56 +0000309 return self.add_join(query_set, 'tko_test_attributes',
310 join_key='test_idx',
showard2aa318e2009-08-20 23:43:10 +0000311 join_condition=join_condition,
312 suffix=suffix, exclude=exclude)
313
314
315 def _add_label_pivot_table_join(self, query_set, suffix, join_condition='',
316 exclude=False, force_left_join=False):
showardeab66ce2009-12-23 00:03:56 +0000317 return self.add_join(query_set, 'tko_test_labels_tests',
318 join_key='test_id',
showard2aa318e2009-08-20 23:43:10 +0000319 join_condition=join_condition,
320 suffix=suffix, exclude=exclude,
321 force_left_join=force_left_join)
showardf2489522008-10-23 23:08:00 +0000322
323
showard64aeecd2008-09-19 21:32:58 +0000324 def _add_label_joins(self, query_set, suffix=''):
showard2aa318e2009-08-20 23:43:10 +0000325 query_set = self._add_label_pivot_table_join(
326 query_set, suffix=suffix, force_left_join=True)
showardd50ffb42008-09-04 02:47:45 +0000327
showard2aa318e2009-08-20 23:43:10 +0000328 # since we're not joining from the original table, we can't use
329 # self.add_join() again
showardeab66ce2009-12-23 00:03:56 +0000330 second_join_alias = 'tko_test_labels' + suffix
showardd50ffb42008-09-04 02:47:45 +0000331 second_join_condition = ('%s.id = %s.testlabel_id' %
showard64aeecd2008-09-19 21:32:58 +0000332 (second_join_alias,
showardeab66ce2009-12-23 00:03:56 +0000333 'tko_test_labels_tests' + suffix))
showard7e67b432010-01-20 01:13:04 +0000334 query_set.query.add_custom_join('tko_test_labels',
335 second_join_condition,
336 query_set.query.LOUTER,
337 alias=second_join_alias)
338 return query_set
showardd50ffb42008-09-04 02:47:45 +0000339
showard64aeecd2008-09-19 21:32:58 +0000340
showardd50ffb42008-09-04 02:47:45 +0000341 def _get_label_ids_from_names(self, label_names):
showard2aa318e2009-08-20 23:43:10 +0000342 label_ids = list( # listifying avoids a double query below
showard8b0ea222009-12-23 19:23:03 +0000343 TestLabel.objects.filter(name__in=label_names)
344 .values_list('name', 'id'))
showard2aa318e2009-08-20 23:43:10 +0000345 if len(label_ids) < len(set(label_names)):
lmr79697282010-03-31 23:18:02 +0000346 raise ValueError('Not all labels found: %s' %
347 ', '.join(label_names))
showard8b0ea222009-12-23 19:23:03 +0000348 return dict(name_and_id for name_and_id in label_ids)
showard2aa318e2009-08-20 23:43:10 +0000349
350
351 def _include_or_exclude_labels(self, query_set, label_names, exclude=False):
showard8b0ea222009-12-23 19:23:03 +0000352 label_ids = self._get_label_ids_from_names(label_names).itervalues()
showard2aa318e2009-08-20 23:43:10 +0000353 suffix = self._get_include_exclude_suffix(exclude)
showardeab66ce2009-12-23 00:03:56 +0000354 condition = ('tko_test_labels_tests%s.testlabel_id IN (%s)' %
showard8b0ea222009-12-23 19:23:03 +0000355 (suffix,
356 ','.join(str(label_id) for label_id in label_ids)))
showard2aa318e2009-08-20 23:43:10 +0000357 return self._add_label_pivot_table_join(query_set,
358 join_condition=condition,
359 suffix=suffix,
360 exclude=exclude)
showard02813502008-08-20 20:52:56 +0000361
362
showard8b0ea222009-12-23 19:23:03 +0000363 def _add_custom_select(self, query_set, select_name, select_sql):
364 return query_set.extra(select={select_name: select_sql})
365
366
367 def _add_select_value(self, query_set, alias):
368 return self._add_custom_select(query_set, alias,
369 _quote_name(alias) + '.value')
370
371
372 def _add_select_ifnull(self, query_set, alias, non_null_value):
373 select_sql = "IF(%s.id IS NOT NULL, '%s', NULL)" % (_quote_name(alias),
374 non_null_value)
375 return self._add_custom_select(query_set, alias, select_sql)
376
377
jamesren708f1c02010-03-31 21:43:57 +0000378 def _join_test_label_column(self, query_set, label_name, label_id):
379 alias = 'test_label_' + label_name
showard7e67b432010-01-20 01:13:04 +0000380 label_query = TestLabel.objects.filter(name=label_name)
381 query_set = Test.objects.join_custom_field(query_set, label_query,
382 alias)
showard8b0ea222009-12-23 19:23:03 +0000383
384 query_set = self._add_select_ifnull(query_set, alias, label_name)
385 return query_set
386
387
jamesren708f1c02010-03-31 21:43:57 +0000388 def _join_test_label_columns(self, query_set, label_names):
showard8b0ea222009-12-23 19:23:03 +0000389 label_id_map = self._get_label_ids_from_names(label_names)
390 for label_name in label_names:
jamesren708f1c02010-03-31 21:43:57 +0000391 query_set = self._join_test_label_column(query_set, label_name,
392 label_id_map[label_name])
showard8b0ea222009-12-23 19:23:03 +0000393 return query_set
394
395
jamesren708f1c02010-03-31 21:43:57 +0000396 def _join_test_attribute(self, query_set, attribute, alias=None,
397 extra_join_condition=None):
showard8b0ea222009-12-23 19:23:03 +0000398 """
399 Join the given TestView QuerySet to TestAttribute. The resulting query
400 has an additional column for the given attribute named
401 "attribute_<attribute name>".
402 """
showard8b0ea222009-12-23 19:23:03 +0000403 if not alias:
jamesren708f1c02010-03-31 21:43:57 +0000404 alias = 'test_attribute_' + attribute
showard7e67b432010-01-20 01:13:04 +0000405 attribute_query = TestAttribute.objects.filter(attribute=attribute)
showard8b0ea222009-12-23 19:23:03 +0000406 if extra_join_condition:
showard7e67b432010-01-20 01:13:04 +0000407 attribute_query = attribute_query.extra(
408 where=[extra_join_condition])
409 query_set = Test.objects.join_custom_field(query_set, attribute_query,
410 alias)
showard8b0ea222009-12-23 19:23:03 +0000411
412 query_set = self._add_select_value(query_set, alias)
413 return query_set
414
415
416 def _join_machine_label_columns(self, query_set, machine_label_names):
417 for label_name in machine_label_names:
418 alias = 'machine_label_' + label_name
419 condition = "FIND_IN_SET('%s', %s)" % (
420 label_name, _quote_name(alias) + '.value')
jamesren708f1c02010-03-31 21:43:57 +0000421 query_set = self._join_test_attribute(
422 query_set, 'host-labels',
423 alias=alias, extra_join_condition=condition)
showard8b0ea222009-12-23 19:23:03 +0000424 query_set = self._add_select_ifnull(query_set, alias, label_name)
425 return query_set
426
427
428 def _join_one_iteration_key(self, query_set, result_key, first_alias=None):
jamesren708f1c02010-03-31 21:43:57 +0000429 alias = 'iteration_result_' + result_key
showard7e67b432010-01-20 01:13:04 +0000430 iteration_query = IterationResult.objects.filter(attribute=result_key)
showard8b0ea222009-12-23 19:23:03 +0000431 if first_alias:
432 # after the first join, we need to match up iteration indices,
433 # otherwise each join will expand the query by the number of
434 # iterations and we'll have extraneous rows
showard7e67b432010-01-20 01:13:04 +0000435 iteration_query = iteration_query.extra(
436 where=['%s.iteration = %s.iteration'
437 % (_quote_name(alias), _quote_name(first_alias))])
showard8b0ea222009-12-23 19:23:03 +0000438
showard7e67b432010-01-20 01:13:04 +0000439 query_set = Test.objects.join_custom_field(query_set, iteration_query,
440 alias, left_join=False)
showard8b0ea222009-12-23 19:23:03 +0000441 # select the iteration value and index for this join
442 query_set = self._add_select_value(query_set, alias)
443 if not first_alias:
444 # for first join, add iteration index select too
445 query_set = self._add_custom_select(
446 query_set, 'iteration_index',
447 _quote_name(alias) + '.iteration')
448
449 return query_set, alias
450
451
jamesren708f1c02010-03-31 21:43:57 +0000452 def _join_iteration_results(self, test_view_query_set, result_keys):
showard8b0ea222009-12-23 19:23:03 +0000453 """Join the given TestView QuerySet to IterationResult for one result.
454
455 The resulting query looks like a TestView query but has one row per
456 iteration. Each row includes all the attributes of TestView, an
457 attribute for each key in result_keys and an iteration_index attribute.
458
459 We accomplish this by joining the TestView query to IterationResult
460 once per result key. Each join is restricted on the result key (and on
461 the test index, like all one-to-many joins). For the first join, this
462 is the only restriction, so each TestView row expands to a row per
463 iteration (per iteration that includes the key, of course). For each
464 subsequent join, we also restrict the iteration index to match that of
465 the initial join. This makes each subsequent join produce exactly one
466 result row for each input row. (This assumes each iteration contains
467 the same set of keys. Results are undefined if that's not true.)
468 """
469 if not result_keys:
470 return test_view_query_set
471
472 query_set, first_alias = self._join_one_iteration_key(
473 test_view_query_set, result_keys[0])
474 for result_key in result_keys[1:]:
475 query_set, _ = self._join_one_iteration_key(query_set, result_key,
476 first_alias=first_alias)
477 return query_set
478
479
jamesren708f1c02010-03-31 21:43:57 +0000480 def _join_job_keyvals(self, query_set, job_keyvals):
481 for job_keyval in job_keyvals:
482 alias = 'job_keyval_' + job_keyval
483 keyval_query = JobKeyval.objects.filter(key=job_keyval)
484 query_set = Job.objects.join_custom_field(query_set, keyval_query,
485 alias)
486 query_set = self._add_select_value(query_set, alias)
487 return query_set
488
489
490 def _join_iteration_attributes(self, query_set, iteration_attributes):
491 for attribute in iteration_attributes:
492 alias = 'iteration_attribute_' + attribute
493 attribute_query = IterationAttribute.objects.filter(
494 attribute=attribute)
495 query_set = Test.objects.join_custom_field(query_set,
496 attribute_query, alias)
497 query_set = self._add_select_value(query_set, alias)
498 return query_set
499
500
showard8b0ea222009-12-23 19:23:03 +0000501 def get_query_set_with_joins(self, filter_data):
502 """
503 Add joins for querying over test-related items.
504
505 These parameters are supported going forward:
506 * test_attribute_fields: list of attribute names. Each attribute will
507 be available as a column attribute_<name>.value.
508 * test_label_fields: list of label names. Each label will be available
509 as a column label_<name>.id, non-null iff the label is present.
jamesren708f1c02010-03-31 21:43:57 +0000510 * iteration_result_fields: list of iteration result names. Each
showard8b0ea222009-12-23 19:23:03 +0000511 result will be available as a column iteration_<name>.value.
512 Note that this changes the semantics to return iterations
513 instead of tests -- if a test has multiple iterations, a row
514 will be returned for each one. The iteration index is also
515 available as iteration_<name>.iteration.
516 * machine_label_fields: list of machine label names. Each will be
517 available as a column machine_label_<name>.id, non-null iff the
518 label is present on the machine used in the test.
jamesren708f1c02010-03-31 21:43:57 +0000519 * job_keyval_fields: list of job keyval names. Each value will be
520 available as a column job_keyval_<name>.id, non-null iff the
521 keyval is present in the AFE job.
522 * iteration_attribute_fields: list of iteration attribute names. Each
523 attribute will be available as a column
524 iteration_attribute<name>.id, non-null iff the attribute is
525 present.
showard8b0ea222009-12-23 19:23:03 +0000526
527 These parameters are deprecated:
528 * include_labels
529 * exclude_labels
530 * include_attributes_where
531 * exclude_attributes_where
532
533 Additionally, this method adds joins if the following strings are
534 present in extra_where (this is also deprecated):
535 * test_labels
536 * test_attributes_host_labels
537 """
showard35444862008-08-07 22:35:30 +0000538 query_set = self.get_query_set()
showard8b0ea222009-12-23 19:23:03 +0000539
540 test_attributes = filter_data.pop('test_attribute_fields', [])
541 for attribute in test_attributes:
jamesren708f1c02010-03-31 21:43:57 +0000542 query_set = self._join_test_attribute(query_set, attribute)
showard8b0ea222009-12-23 19:23:03 +0000543
544 test_labels = filter_data.pop('test_label_fields', [])
jamesren708f1c02010-03-31 21:43:57 +0000545 query_set = self._join_test_label_columns(query_set, test_labels)
showard8b0ea222009-12-23 19:23:03 +0000546
547 machine_labels = filter_data.pop('machine_label_fields', [])
548 query_set = self._join_machine_label_columns(query_set, machine_labels)
549
jamesren708f1c02010-03-31 21:43:57 +0000550 iteration_keys = filter_data.pop('iteration_result_fields', [])
551 query_set = self._join_iteration_results(query_set, iteration_keys)
552
553 job_keyvals = filter_data.pop('job_keyval_fields', [])
554 query_set = self._join_job_keyvals(query_set, job_keyvals)
555
556 iteration_attributes = filter_data.pop('iteration_attribute_fields', [])
557 query_set = self._join_iteration_attributes(query_set,
558 iteration_attributes)
showard8b0ea222009-12-23 19:23:03 +0000559
560 # everything that follows is deprecated behavior
561
showardd50ffb42008-09-04 02:47:45 +0000562 joined = False
showard2aa318e2009-08-20 23:43:10 +0000563
showardf2489522008-10-23 23:08:00 +0000564 extra_where = filter_data.get('extra_where', '')
showardeab66ce2009-12-23 00:03:56 +0000565 if 'tko_test_labels' in extra_where:
showard02813502008-08-20 20:52:56 +0000566 query_set = self._add_label_joins(query_set)
showardd50ffb42008-09-04 02:47:45 +0000567 joined = True
568
showard8b0ea222009-12-23 19:23:03 +0000569 include_labels = filter_data.pop('include_labels', [])
570 exclude_labels = filter_data.pop('exclude_labels', [])
showard2aa318e2009-08-20 23:43:10 +0000571 if include_labels:
572 query_set = self._include_or_exclude_labels(query_set,
573 include_labels)
showardfc8c6ae2008-11-11 19:06:01 +0000574 joined = True
showard2aa318e2009-08-20 23:43:10 +0000575 if exclude_labels:
576 query_set = self._include_or_exclude_labels(query_set,
577 exclude_labels,
578 exclude=True)
showard64aeecd2008-09-19 21:32:58 +0000579 joined = True
580
581 include_attributes_where = filter_data.pop('include_attributes_where',
582 '')
583 exclude_attributes_where = filter_data.pop('exclude_attributes_where',
584 '')
585 if include_attributes_where:
586 query_set = self._add_attribute_join(
showard2aa318e2009-08-20 23:43:10 +0000587 query_set,
588 join_condition=self.escape_user_sql(include_attributes_where))
showard64aeecd2008-09-19 21:32:58 +0000589 joined = True
590 if exclude_attributes_where:
591 query_set = self._add_attribute_join(
showard2aa318e2009-08-20 23:43:10 +0000592 query_set,
593 join_condition=self.escape_user_sql(exclude_attributes_where),
showard64aeecd2008-09-19 21:32:58 +0000594 exclude=True)
595 joined = True
showardd50ffb42008-09-04 02:47:45 +0000596
597 if not joined:
showard35444862008-08-07 22:35:30 +0000598 filter_data['no_distinct'] = True
showardd50ffb42008-09-04 02:47:45 +0000599
showard8b0ea222009-12-23 19:23:03 +0000600 if 'tko_test_attributes_host_labels' in extra_where:
showardf2489522008-10-23 23:08:00 +0000601 query_set = self._add_attribute_join(
602 query_set, suffix='_host_labels',
showardeab66ce2009-12-23 00:03:56 +0000603 join_condition='tko_test_attributes_host_labels.attribute = '
showardf2489522008-10-23 23:08:00 +0000604 '"host-labels"')
605
showard35444862008-08-07 22:35:30 +0000606 return query_set
607
608
showard8bfb5cb2009-10-07 20:49:15 +0000609 def query_test_ids(self, filter_data, apply_presentation=True):
610 query = self.model.query_objects(filter_data,
611 apply_presentation=apply_presentation)
612 dicts = query.values('test_idx')
showard02813502008-08-20 20:52:56 +0000613 return [item['test_idx'] for item in dicts]
614
615
showard02813502008-08-20 20:52:56 +0000616 def query_test_label_ids(self, filter_data):
showardd50ffb42008-09-04 02:47:45 +0000617 query_set = self.model.query_objects(filter_data)
618 query_set = self._add_label_joins(query_set, suffix='_list')
showardeab66ce2009-12-23 00:03:56 +0000619 rows = self._custom_select_query(query_set, ['tko_test_labels_list.id'])
showardd50ffb42008-09-04 02:47:45 +0000620 return [row[0] for row in rows if row[0] is not None]
showard02813502008-08-20 20:52:56 +0000621
622
showardeaccf8f2009-04-16 03:11:33 +0000623 def escape_user_sql(self, sql):
624 sql = super(TestViewManager, self).escape_user_sql(sql)
625 return sql.replace('test_idx', self.get_key_on_this_table('test_idx'))
626
627
showard35444862008-08-07 22:35:30 +0000628class TestView(dbmodels.Model, model_logic.ModelExtensions):
629 extra_fields = {
showardf4c702e2009-07-08 21:14:27 +0000630 'DATE(job_queued_time)': 'job queued day',
631 'DATE(test_finished_time)': 'test finished day',
showard35444862008-08-07 22:35:30 +0000632 }
633
634 group_fields = [
showardf4c702e2009-07-08 21:14:27 +0000635 'test_name',
636 'status',
637 'kernel',
638 'hostname',
639 'job_tag',
640 'job_name',
641 'platform',
642 'reason',
643 'job_owner',
644 'job_queued_time',
645 'DATE(job_queued_time)',
646 'test_started_time',
647 'test_finished_time',
648 'DATE(test_finished_time)',
showard35444862008-08-07 22:35:30 +0000649 ]
650
651 test_idx = dbmodels.IntegerField('test index', primary_key=True)
652 job_idx = dbmodels.IntegerField('job index', null=True, blank=True)
showarda5288b42009-07-28 20:06:08 +0000653 test_name = dbmodels.CharField(blank=True, max_length=90)
654 subdir = dbmodels.CharField('subdirectory', blank=True, max_length=180)
showard35444862008-08-07 22:35:30 +0000655 kernel_idx = dbmodels.IntegerField('kernel index')
656 status_idx = dbmodels.IntegerField('status index')
showarda5288b42009-07-28 20:06:08 +0000657 reason = dbmodels.CharField(blank=True, max_length=3072)
showard35444862008-08-07 22:35:30 +0000658 machine_idx = dbmodels.IntegerField('host index')
659 test_started_time = dbmodels.DateTimeField(null=True, blank=True)
660 test_finished_time = dbmodels.DateTimeField(null=True, blank=True)
showarda5288b42009-07-28 20:06:08 +0000661 job_tag = dbmodels.CharField(blank=True, max_length=300)
662 job_name = dbmodels.CharField(blank=True, max_length=300)
663 job_owner = dbmodels.CharField('owner', blank=True, max_length=240)
showard35444862008-08-07 22:35:30 +0000664 job_queued_time = dbmodels.DateTimeField(null=True, blank=True)
665 job_started_time = dbmodels.DateTimeField(null=True, blank=True)
666 job_finished_time = dbmodels.DateTimeField(null=True, blank=True)
showardc1c1caf2009-09-08 16:26:50 +0000667 afe_job_id = dbmodels.IntegerField(null=True)
showarda5288b42009-07-28 20:06:08 +0000668 hostname = dbmodels.CharField(blank=True, max_length=300)
669 platform = dbmodels.CharField(blank=True, max_length=240)
670 machine_owner = dbmodels.CharField(blank=True, max_length=240)
671 kernel_hash = dbmodels.CharField(blank=True, max_length=105)
672 kernel_base = dbmodels.CharField(blank=True, max_length=90)
673 kernel = dbmodels.CharField(blank=True, max_length=300)
674 status = dbmodels.CharField(blank=True, max_length=30)
showard35444862008-08-07 22:35:30 +0000675
676 objects = TestViewManager()
677
678 def save(self):
679 raise NotImplementedError('TestView is read-only')
680
681
682 def delete(self):
683 raise NotImplementedError('TestView is read-only')
684
685
686 @classmethod
showard8bfb5cb2009-10-07 20:49:15 +0000687 def query_objects(cls, filter_data, initial_query=None,
688 apply_presentation=True):
showard35444862008-08-07 22:35:30 +0000689 if initial_query is None:
showard64aeecd2008-09-19 21:32:58 +0000690 initial_query = cls.objects.get_query_set_with_joins(filter_data)
showard8bfb5cb2009-10-07 20:49:15 +0000691 return super(TestView, cls).query_objects(
692 filter_data, initial_query=initial_query,
693 apply_presentation=apply_presentation)
showard35444862008-08-07 22:35:30 +0000694
695
696 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000697 db_table = 'tko_test_view_2'