blob: 483eb1352a8428cedc048310ea5a8490634210fd [file] [log] [blame]
showard35444862008-08-07 22:35:30 +00001from django.db import models as dbmodels, connection
2from django.utils import datastructures
3from autotest_lib.frontend.afe import model_logic, readonly_connection
4
showard8b0ea222009-12-23 19:23:03 +00005_quote_name = connection.ops.quote_name
6
showard35444862008-08-07 22:35:30 +00007class TempManager(model_logic.ExtendedManager):
8 _GROUP_COUNT_NAME = 'group_count'
9
10 def _get_key_unless_is_function(self, field):
11 if '(' in field:
12 return field
showard7c199df2008-10-03 10:17:15 +000013 return self.get_key_on_this_table(field)
showard35444862008-08-07 22:35:30 +000014
15
showardf2489522008-10-23 23:08:00 +000016 def _get_field_names(self, fields, extra_select_fields={}):
17 field_names = []
18 for field in fields:
19 if field in extra_select_fields:
showardd2b0c882009-10-19 18:34:11 +000020 field_names.append(extra_select_fields[field][0])
showardf2489522008-10-23 23:08:00 +000021 else:
22 field_names.append(self._get_key_unless_is_function(field))
23 return field_names
showard35444862008-08-07 22:35:30 +000024
25
showard8bfb5cb2009-10-07 20:49:15 +000026 def _get_group_query_sql(self, query, group_by):
showarda5288b42009-07-28 20:06:08 +000027 sql, params = query.query.as_sql()
showardd50ffb42008-09-04 02:47:45 +000028
29 # insert GROUP BY clause into query
showard8bfb5cb2009-10-07 20:49:15 +000030 group_fields = self._get_field_names(group_by, query.query.extra_select)
showard7c199df2008-10-03 10:17:15 +000031 group_by_clause = ' GROUP BY ' + ', '.join(group_fields)
showarda5288b42009-07-28 20:06:08 +000032 group_by_position = sql.rfind('ORDER BY')
showardd50ffb42008-09-04 02:47:45 +000033 if group_by_position == -1:
showarda5288b42009-07-28 20:06:08 +000034 group_by_position = len(sql)
35 sql = (sql[:group_by_position] +
36 group_by_clause + ' ' +
37 sql[group_by_position:])
showard35444862008-08-07 22:35:30 +000038
showarda5288b42009-07-28 20:06:08 +000039 return sql, params
showard35444862008-08-07 22:35:30 +000040
41
showard06b82fc2009-06-30 01:59:42 +000042 def _get_column_names(self, cursor):
showard8bfb5cb2009-10-07 20:49:15 +000043 """
showard06b82fc2009-06-30 01:59:42 +000044 Gets the column names from the cursor description. This method exists
showard8bfb5cb2009-10-07 20:49:15 +000045 so that it can be mocked in the unit test for sqlite3 compatibility.
showard06b82fc2009-06-30 01:59:42 +000046 """
47 return [column_info[0] for column_info in cursor.description]
48
49
showard8bfb5cb2009-10-07 20:49:15 +000050 def execute_group_query(self, query, group_by):
showard35444862008-08-07 22:35:30 +000051 """
showard8a6eb0c2008-10-01 11:38:59 +000052 Performs the given query grouped by the fields in group_by with the
showard8bfb5cb2009-10-07 20:49:15 +000053 given query's extra select fields added. Returns a list of dicts, where
54 each dict corresponds to single row and contains a key for each grouped
55 field as well as all of the extra select fields.
showard35444862008-08-07 22:35:30 +000056 """
showard8bfb5cb2009-10-07 20:49:15 +000057 sql, params = self._get_group_query_sql(query, group_by)
showard56e93772008-10-06 10:06:22 +000058 cursor = readonly_connection.connection().cursor()
showard8a6eb0c2008-10-01 11:38:59 +000059 cursor.execute(sql, params)
showard06b82fc2009-06-30 01:59:42 +000060 field_names = self._get_column_names(cursor)
showard8a6eb0c2008-10-01 11:38:59 +000061 row_dicts = [dict(zip(field_names, row)) for row in cursor.fetchall()]
62 return row_dicts
63
64
65 def get_count_sql(self, query):
66 """
67 Get the SQL to properly select a per-group count of unique matches for
showard7c199df2008-10-03 10:17:15 +000068 a grouped query. Returns a tuple (field alias, field SQL)
showard8a6eb0c2008-10-01 11:38:59 +000069 """
showarda5288b42009-07-28 20:06:08 +000070 if query.query.distinct:
showard7c199df2008-10-03 10:17:15 +000071 pk_field = self.get_key_on_this_table()
showard8a6eb0c2008-10-01 11:38:59 +000072 count_sql = 'COUNT(DISTINCT %s)' % pk_field
73 else:
74 count_sql = 'COUNT(1)'
showard7c199df2008-10-03 10:17:15 +000075 return self._GROUP_COUNT_NAME, count_sql
showard35444862008-08-07 22:35:30 +000076
77
78 def _get_num_groups_sql(self, query, group_by):
showardd2b0c882009-10-19 18:34:11 +000079 group_fields = self._get_field_names(group_by, query.query.extra_select)
showarda5288b42009-07-28 20:06:08 +000080 query = query.order_by() # this can mess up the query and isn't needed
81
82 sql, params = query.query.as_sql()
83 from_ = sql[sql.find(' FROM'):]
showardd2b0c882009-10-19 18:34:11 +000084 return ('SELECT DISTINCT %s %s' % (','.join(group_fields),
showarda5288b42009-07-28 20:06:08 +000085 from_),
showard35444862008-08-07 22:35:30 +000086 params)
87
88
showardd2b0c882009-10-19 18:34:11 +000089 def _cursor_rowcount(self, cursor):
90 """To be stubbed by tests"""
91 return cursor.rowcount
92
93
showard35444862008-08-07 22:35:30 +000094 def get_num_groups(self, query, group_by):
95 """
96 Returns the number of distinct groups for the given query grouped by the
97 fields in group_by.
98 """
99 sql, params = self._get_num_groups_sql(query, group_by)
showard56e93772008-10-06 10:06:22 +0000100 cursor = readonly_connection.connection().cursor()
showard35444862008-08-07 22:35:30 +0000101 cursor.execute(sql, params)
showardd2b0c882009-10-19 18:34:11 +0000102 return self._cursor_rowcount(cursor)
showard35444862008-08-07 22:35:30 +0000103
104
105class Machine(dbmodels.Model):
showardf8b19042009-05-12 17:22:49 +0000106 machine_idx = dbmodels.AutoField(primary_key=True)
showard356d2372009-11-10 01:29:53 +0000107 hostname = dbmodels.CharField(unique=True, max_length=255)
showarda5288b42009-07-28 20:06:08 +0000108 machine_group = dbmodels.CharField(blank=True, max_length=240)
109 owner = dbmodels.CharField(blank=True, max_length=240)
showard35444862008-08-07 22:35:30 +0000110
111 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000112 db_table = 'tko_machines'
showard35444862008-08-07 22:35:30 +0000113
114
115class Kernel(dbmodels.Model):
showardf8b19042009-05-12 17:22:49 +0000116 kernel_idx = dbmodels.AutoField(primary_key=True)
showarda5288b42009-07-28 20:06:08 +0000117 kernel_hash = dbmodels.CharField(max_length=105, editable=False)
118 base = dbmodels.CharField(max_length=90)
119 printable = dbmodels.CharField(max_length=300)
showard35444862008-08-07 22:35:30 +0000120
121 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000122 db_table = 'tko_kernels'
showard35444862008-08-07 22:35:30 +0000123
124
125class Patch(dbmodels.Model):
126 kernel = dbmodels.ForeignKey(Kernel, db_column='kernel_idx')
showarda5288b42009-07-28 20:06:08 +0000127 name = dbmodels.CharField(blank=True, max_length=240)
128 url = dbmodels.CharField(blank=True, max_length=900)
129 the_hash = dbmodels.CharField(blank=True, max_length=105, db_column='hash')
showard35444862008-08-07 22:35:30 +0000130
131 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000132 db_table = 'tko_patches'
showard35444862008-08-07 22:35:30 +0000133
134
135class Status(dbmodels.Model):
showardf8b19042009-05-12 17:22:49 +0000136 status_idx = dbmodels.AutoField(primary_key=True)
showarda5288b42009-07-28 20:06:08 +0000137 word = dbmodels.CharField(max_length=30)
showard35444862008-08-07 22:35:30 +0000138
139 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000140 db_table = 'tko_status'
showard35444862008-08-07 22:35:30 +0000141
142
jamesren708f1c02010-03-31 21:43:57 +0000143class Job(dbmodels.Model, model_logic.ModelExtensions):
showardf8b19042009-05-12 17:22:49 +0000144 job_idx = dbmodels.AutoField(primary_key=True)
showard356d2372009-11-10 01:29:53 +0000145 tag = dbmodels.CharField(unique=True, max_length=100)
showarda5288b42009-07-28 20:06:08 +0000146 label = dbmodels.CharField(max_length=300)
147 username = dbmodels.CharField(max_length=240)
showard35444862008-08-07 22:35:30 +0000148 machine = dbmodels.ForeignKey(Machine, db_column='machine_idx')
149 queued_time = dbmodels.DateTimeField(null=True, blank=True)
150 started_time = dbmodels.DateTimeField(null=True, blank=True)
151 finished_time = dbmodels.DateTimeField(null=True, blank=True)
showardc1c1caf2009-09-08 16:26:50 +0000152 afe_job_id = dbmodels.IntegerField(null=True, default=None)
showard35444862008-08-07 22:35:30 +0000153
jamesren708f1c02010-03-31 21:43:57 +0000154 objects = model_logic.ExtendedManager()
155
showard35444862008-08-07 22:35:30 +0000156 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000157 db_table = 'tko_jobs'
showard35444862008-08-07 22:35:30 +0000158
159
showardc1a98d12010-01-15 00:22:22 +0000160class JobKeyval(dbmodels.Model):
161 job = dbmodels.ForeignKey(Job)
162 key = dbmodels.CharField(max_length=90)
163 value = dbmodels.CharField(blank=True, max_length=300)
164
165
166 class Meta:
167 db_table = 'tko_job_keyvals'
168
169
showardf8b19042009-05-12 17:22:49 +0000170class Test(dbmodels.Model, model_logic.ModelExtensions,
171 model_logic.ModelWithAttributes):
172 test_idx = dbmodels.AutoField(primary_key=True)
showard35444862008-08-07 22:35:30 +0000173 job = dbmodels.ForeignKey(Job, db_column='job_idx')
lmr79697282010-03-31 23:18:02 +0000174 test = dbmodels.CharField(max_length=300)
175 subdir = dbmodels.CharField(blank=True, max_length=300)
showard35444862008-08-07 22:35:30 +0000176 kernel = dbmodels.ForeignKey(Kernel, db_column='kernel_idx')
177 status = dbmodels.ForeignKey(Status, db_column='status')
showarda5288b42009-07-28 20:06:08 +0000178 reason = dbmodels.CharField(blank=True, max_length=3072)
showard35444862008-08-07 22:35:30 +0000179 machine = dbmodels.ForeignKey(Machine, db_column='machine_idx')
180 finished_time = dbmodels.DateTimeField(null=True, blank=True)
181 started_time = dbmodels.DateTimeField(null=True, blank=True)
182
showardf8b19042009-05-12 17:22:49 +0000183 objects = model_logic.ExtendedManager()
184
185 def _get_attribute_model_and_args(self, attribute):
186 return TestAttribute, dict(test=self, attribute=attribute,
187 user_created=True)
188
189
190 def set_attribute(self, attribute, value):
191 # ensure non-user-created attributes remain immutable
192 try:
193 TestAttribute.objects.get(test=self, attribute=attribute,
194 user_created=False)
195 raise ValueError('Attribute %s already exists for test %s and is '
196 'immutable' % (attribute, self.test_idx))
197 except TestAttribute.DoesNotExist:
198 super(Test, self).set_attribute(attribute, value)
199
200
showard35444862008-08-07 22:35:30 +0000201 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000202 db_table = 'tko_tests'
showard35444862008-08-07 22:35:30 +0000203
204
showarde732ee72008-09-23 19:15:43 +0000205class TestAttribute(dbmodels.Model, model_logic.ModelExtensions):
showardf8b19042009-05-12 17:22:49 +0000206 test = dbmodels.ForeignKey(Test, db_column='test_idx')
showarda5288b42009-07-28 20:06:08 +0000207 attribute = dbmodels.CharField(max_length=90)
208 value = dbmodels.CharField(blank=True, max_length=300)
showardf8b19042009-05-12 17:22:49 +0000209 user_created = dbmodels.BooleanField(default=False)
210
211 objects = model_logic.ExtendedManager()
showard35444862008-08-07 22:35:30 +0000212
213 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000214 db_table = 'tko_test_attributes'
showard35444862008-08-07 22:35:30 +0000215
216
jadmanski430dca92008-12-16 20:56:53 +0000217class IterationAttribute(dbmodels.Model, model_logic.ModelExtensions):
showardf8b19042009-05-12 17:22:49 +0000218 # this isn't really a primary key, but it's necessary to appease Django
219 # and is harmless as long as we're careful
showarde732ee72008-09-23 19:15:43 +0000220 test = dbmodels.ForeignKey(Test, db_column='test_idx', primary_key=True)
showard35444862008-08-07 22:35:30 +0000221 iteration = dbmodels.IntegerField()
showarda5288b42009-07-28 20:06:08 +0000222 attribute = dbmodels.CharField(max_length=90)
223 value = dbmodels.CharField(blank=True, max_length=300)
showard35444862008-08-07 22:35:30 +0000224
showardf8b19042009-05-12 17:22:49 +0000225 objects = model_logic.ExtendedManager()
226
showard35444862008-08-07 22:35:30 +0000227 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000228 db_table = 'tko_iteration_attributes'
showard35444862008-08-07 22:35:30 +0000229
230
jadmanski430dca92008-12-16 20:56:53 +0000231class IterationResult(dbmodels.Model, model_logic.ModelExtensions):
showardf8b19042009-05-12 17:22:49 +0000232 # see comment on IterationAttribute regarding primary_key=True
jadmanski430dca92008-12-16 20:56:53 +0000233 test = dbmodels.ForeignKey(Test, db_column='test_idx', primary_key=True)
showard35444862008-08-07 22:35:30 +0000234 iteration = dbmodels.IntegerField()
showarda5288b42009-07-28 20:06:08 +0000235 attribute = dbmodels.CharField(max_length=90)
jamesren26f9a9f2010-04-02 17:44:55 +0000236 value = dbmodels.FloatField(null=True, blank=True)
showard35444862008-08-07 22:35:30 +0000237
showardf8b19042009-05-12 17:22:49 +0000238 objects = model_logic.ExtendedManager()
239
showard35444862008-08-07 22:35:30 +0000240 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000241 db_table = 'tko_iteration_result'
showard35444862008-08-07 22:35:30 +0000242
243
244class TestLabel(dbmodels.Model, model_logic.ModelExtensions):
showarda5288b42009-07-28 20:06:08 +0000245 name = dbmodels.CharField(max_length=80, unique=True)
showard35444862008-08-07 22:35:30 +0000246 description = dbmodels.TextField(blank=True)
showardeab66ce2009-12-23 00:03:56 +0000247 tests = dbmodels.ManyToManyField(Test, blank=True,
248 db_table='tko_test_labels_tests')
showard35444862008-08-07 22:35:30 +0000249
250 name_field = 'name'
showardf8b19042009-05-12 17:22:49 +0000251 objects = model_logic.ExtendedManager()
showard35444862008-08-07 22:35:30 +0000252
253 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000254 db_table = 'tko_test_labels'
showard35444862008-08-07 22:35:30 +0000255
256
257class SavedQuery(dbmodels.Model, model_logic.ModelExtensions):
258 # TODO: change this to foreign key once DBs are merged
showarda5288b42009-07-28 20:06:08 +0000259 owner = dbmodels.CharField(max_length=80)
260 name = dbmodels.CharField(max_length=100)
showard35444862008-08-07 22:35:30 +0000261 url_token = dbmodels.TextField()
262
263 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000264 db_table = 'tko_saved_queries'
showard35444862008-08-07 22:35:30 +0000265
266
showardce12f552008-09-19 00:48:59 +0000267class EmbeddedGraphingQuery(dbmodels.Model, model_logic.ModelExtensions):
268 url_token = dbmodels.TextField(null=False, blank=False)
showarda5288b42009-07-28 20:06:08 +0000269 graph_type = dbmodels.CharField(max_length=16, null=False, blank=False)
showardce12f552008-09-19 00:48:59 +0000270 params = dbmodels.TextField(null=False, blank=False)
271 last_updated = dbmodels.DateTimeField(null=False, blank=False,
272 editable=False)
273 # refresh_time shows the time at which a thread is updating the cached
274 # image, or NULL if no one is updating the image. This is used so that only
275 # one thread is updating the cached image at a time (see
276 # graphing_utils.handle_plot_request)
277 refresh_time = dbmodels.DateTimeField(editable=False)
278 cached_png = dbmodels.TextField(editable=False)
279
280 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000281 db_table = 'tko_embedded_graphing_queries'
showardce12f552008-09-19 00:48:59 +0000282
283
showard35444862008-08-07 22:35:30 +0000284# views
285
286class TestViewManager(TempManager):
showard35444862008-08-07 22:35:30 +0000287 def get_query_set(self):
288 query = super(TestViewManager, self).get_query_set()
showard5bf7c502008-08-20 01:22:22 +0000289
showard35444862008-08-07 22:35:30 +0000290 # add extra fields to selects, using the SQL itself as the "alias"
291 extra_select = dict((sql, sql)
292 for sql in self.model.extra_fields.iterkeys())
293 return query.extra(select=extra_select)
294
295
showardf2489522008-10-23 23:08:00 +0000296 def _get_include_exclude_suffix(self, exclude):
297 if exclude:
showard2aa318e2009-08-20 23:43:10 +0000298 return '_exclude'
299 return '_include'
300
301
302 def _add_attribute_join(self, query_set, join_condition,
303 suffix=None, exclude=False):
304 if suffix is None:
305 suffix = self._get_include_exclude_suffix(exclude)
showardeab66ce2009-12-23 00:03:56 +0000306 return self.add_join(query_set, 'tko_test_attributes',
307 join_key='test_idx',
showard2aa318e2009-08-20 23:43:10 +0000308 join_condition=join_condition,
309 suffix=suffix, exclude=exclude)
310
311
312 def _add_label_pivot_table_join(self, query_set, suffix, join_condition='',
313 exclude=False, force_left_join=False):
showardeab66ce2009-12-23 00:03:56 +0000314 return self.add_join(query_set, 'tko_test_labels_tests',
315 join_key='test_id',
showard2aa318e2009-08-20 23:43:10 +0000316 join_condition=join_condition,
317 suffix=suffix, exclude=exclude,
318 force_left_join=force_left_join)
showardf2489522008-10-23 23:08:00 +0000319
320
showard64aeecd2008-09-19 21:32:58 +0000321 def _add_label_joins(self, query_set, suffix=''):
showard2aa318e2009-08-20 23:43:10 +0000322 query_set = self._add_label_pivot_table_join(
323 query_set, suffix=suffix, force_left_join=True)
showardd50ffb42008-09-04 02:47:45 +0000324
showard2aa318e2009-08-20 23:43:10 +0000325 # since we're not joining from the original table, we can't use
326 # self.add_join() again
showardeab66ce2009-12-23 00:03:56 +0000327 second_join_alias = 'tko_test_labels' + suffix
showardd50ffb42008-09-04 02:47:45 +0000328 second_join_condition = ('%s.id = %s.testlabel_id' %
showard64aeecd2008-09-19 21:32:58 +0000329 (second_join_alias,
showardeab66ce2009-12-23 00:03:56 +0000330 'tko_test_labels_tests' + suffix))
showard7e67b432010-01-20 01:13:04 +0000331 query_set.query.add_custom_join('tko_test_labels',
332 second_join_condition,
333 query_set.query.LOUTER,
334 alias=second_join_alias)
335 return query_set
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)):
lmr79697282010-03-31 23:18:02 +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
jamesren708f1c02010-03-31 21:43:57 +0000375 def _join_test_label_column(self, query_set, label_name, label_id):
376 alias = 'test_label_' + label_name
showard7e67b432010-01-20 01:13:04 +0000377 label_query = TestLabel.objects.filter(name=label_name)
378 query_set = Test.objects.join_custom_field(query_set, label_query,
379 alias)
showard8b0ea222009-12-23 19:23:03 +0000380
381 query_set = self._add_select_ifnull(query_set, alias, label_name)
382 return query_set
383
384
jamesren708f1c02010-03-31 21:43:57 +0000385 def _join_test_label_columns(self, query_set, label_names):
showard8b0ea222009-12-23 19:23:03 +0000386 label_id_map = self._get_label_ids_from_names(label_names)
387 for label_name in label_names:
jamesren708f1c02010-03-31 21:43:57 +0000388 query_set = self._join_test_label_column(query_set, label_name,
389 label_id_map[label_name])
showard8b0ea222009-12-23 19:23:03 +0000390 return query_set
391
392
jamesren708f1c02010-03-31 21:43:57 +0000393 def _join_test_attribute(self, query_set, attribute, alias=None,
394 extra_join_condition=None):
showard8b0ea222009-12-23 19:23:03 +0000395 """
396 Join the given TestView QuerySet to TestAttribute. The resulting query
397 has an additional column for the given attribute named
398 "attribute_<attribute name>".
399 """
showard8b0ea222009-12-23 19:23:03 +0000400 if not alias:
jamesren708f1c02010-03-31 21:43:57 +0000401 alias = 'test_attribute_' + attribute
showard7e67b432010-01-20 01:13:04 +0000402 attribute_query = TestAttribute.objects.filter(attribute=attribute)
showard8b0ea222009-12-23 19:23:03 +0000403 if extra_join_condition:
showard7e67b432010-01-20 01:13:04 +0000404 attribute_query = attribute_query.extra(
405 where=[extra_join_condition])
406 query_set = Test.objects.join_custom_field(query_set, attribute_query,
407 alias)
showard8b0ea222009-12-23 19:23:03 +0000408
409 query_set = self._add_select_value(query_set, alias)
410 return query_set
411
412
413 def _join_machine_label_columns(self, query_set, machine_label_names):
414 for label_name in machine_label_names:
415 alias = 'machine_label_' + label_name
416 condition = "FIND_IN_SET('%s', %s)" % (
417 label_name, _quote_name(alias) + '.value')
jamesren708f1c02010-03-31 21:43:57 +0000418 query_set = self._join_test_attribute(
419 query_set, 'host-labels',
420 alias=alias, extra_join_condition=condition)
showard8b0ea222009-12-23 19:23:03 +0000421 query_set = self._add_select_ifnull(query_set, alias, label_name)
422 return query_set
423
424
425 def _join_one_iteration_key(self, query_set, result_key, first_alias=None):
jamesren708f1c02010-03-31 21:43:57 +0000426 alias = 'iteration_result_' + result_key
showard7e67b432010-01-20 01:13:04 +0000427 iteration_query = IterationResult.objects.filter(attribute=result_key)
showard8b0ea222009-12-23 19:23:03 +0000428 if first_alias:
429 # after the first join, we need to match up iteration indices,
430 # otherwise each join will expand the query by the number of
431 # iterations and we'll have extraneous rows
showard7e67b432010-01-20 01:13:04 +0000432 iteration_query = iteration_query.extra(
433 where=['%s.iteration = %s.iteration'
434 % (_quote_name(alias), _quote_name(first_alias))])
showard8b0ea222009-12-23 19:23:03 +0000435
showard7e67b432010-01-20 01:13:04 +0000436 query_set = Test.objects.join_custom_field(query_set, iteration_query,
437 alias, left_join=False)
showard8b0ea222009-12-23 19:23:03 +0000438 # select the iteration value and index for this join
439 query_set = self._add_select_value(query_set, alias)
440 if not first_alias:
441 # for first join, add iteration index select too
442 query_set = self._add_custom_select(
443 query_set, 'iteration_index',
444 _quote_name(alias) + '.iteration')
445
446 return query_set, alias
447
448
jamesren708f1c02010-03-31 21:43:57 +0000449 def _join_iteration_results(self, test_view_query_set, result_keys):
showard8b0ea222009-12-23 19:23:03 +0000450 """Join the given TestView QuerySet to IterationResult for one result.
451
452 The resulting query looks like a TestView query but has one row per
453 iteration. Each row includes all the attributes of TestView, an
454 attribute for each key in result_keys and an iteration_index attribute.
455
456 We accomplish this by joining the TestView query to IterationResult
457 once per result key. Each join is restricted on the result key (and on
458 the test index, like all one-to-many joins). For the first join, this
459 is the only restriction, so each TestView row expands to a row per
460 iteration (per iteration that includes the key, of course). For each
461 subsequent join, we also restrict the iteration index to match that of
462 the initial join. This makes each subsequent join produce exactly one
463 result row for each input row. (This assumes each iteration contains
464 the same set of keys. Results are undefined if that's not true.)
465 """
466 if not result_keys:
467 return test_view_query_set
468
469 query_set, first_alias = self._join_one_iteration_key(
470 test_view_query_set, result_keys[0])
471 for result_key in result_keys[1:]:
472 query_set, _ = self._join_one_iteration_key(query_set, result_key,
473 first_alias=first_alias)
474 return query_set
475
476
jamesren708f1c02010-03-31 21:43:57 +0000477 def _join_job_keyvals(self, query_set, job_keyvals):
478 for job_keyval in job_keyvals:
479 alias = 'job_keyval_' + job_keyval
480 keyval_query = JobKeyval.objects.filter(key=job_keyval)
481 query_set = Job.objects.join_custom_field(query_set, keyval_query,
482 alias)
483 query_set = self._add_select_value(query_set, alias)
484 return query_set
485
486
487 def _join_iteration_attributes(self, query_set, iteration_attributes):
488 for attribute in iteration_attributes:
489 alias = 'iteration_attribute_' + attribute
490 attribute_query = IterationAttribute.objects.filter(
491 attribute=attribute)
492 query_set = Test.objects.join_custom_field(query_set,
493 attribute_query, alias)
494 query_set = self._add_select_value(query_set, alias)
495 return query_set
496
497
showard8b0ea222009-12-23 19:23:03 +0000498 def get_query_set_with_joins(self, filter_data):
499 """
500 Add joins for querying over test-related items.
501
502 These parameters are supported going forward:
503 * test_attribute_fields: list of attribute names. Each attribute will
504 be available as a column attribute_<name>.value.
505 * test_label_fields: list of label names. Each label will be available
506 as a column label_<name>.id, non-null iff the label is present.
jamesren708f1c02010-03-31 21:43:57 +0000507 * iteration_result_fields: list of iteration result names. Each
showard8b0ea222009-12-23 19:23:03 +0000508 result will be available as a column iteration_<name>.value.
509 Note that this changes the semantics to return iterations
510 instead of tests -- if a test has multiple iterations, a row
511 will be returned for each one. The iteration index is also
512 available as iteration_<name>.iteration.
513 * machine_label_fields: list of machine label names. Each will be
514 available as a column machine_label_<name>.id, non-null iff the
515 label is present on the machine used in the test.
jamesren708f1c02010-03-31 21:43:57 +0000516 * job_keyval_fields: list of job keyval names. Each value will be
517 available as a column job_keyval_<name>.id, non-null iff the
518 keyval is present in the AFE job.
519 * iteration_attribute_fields: list of iteration attribute names. Each
520 attribute will be available as a column
521 iteration_attribute<name>.id, non-null iff the attribute is
522 present.
showard8b0ea222009-12-23 19:23:03 +0000523
524 These parameters are deprecated:
525 * include_labels
526 * exclude_labels
527 * include_attributes_where
528 * exclude_attributes_where
529
530 Additionally, this method adds joins if the following strings are
531 present in extra_where (this is also deprecated):
532 * test_labels
533 * test_attributes_host_labels
534 """
showard35444862008-08-07 22:35:30 +0000535 query_set = self.get_query_set()
showard8b0ea222009-12-23 19:23:03 +0000536
537 test_attributes = filter_data.pop('test_attribute_fields', [])
538 for attribute in test_attributes:
jamesren708f1c02010-03-31 21:43:57 +0000539 query_set = self._join_test_attribute(query_set, attribute)
showard8b0ea222009-12-23 19:23:03 +0000540
541 test_labels = filter_data.pop('test_label_fields', [])
jamesren708f1c02010-03-31 21:43:57 +0000542 query_set = self._join_test_label_columns(query_set, test_labels)
showard8b0ea222009-12-23 19:23:03 +0000543
544 machine_labels = filter_data.pop('machine_label_fields', [])
545 query_set = self._join_machine_label_columns(query_set, machine_labels)
546
jamesren708f1c02010-03-31 21:43:57 +0000547 iteration_keys = filter_data.pop('iteration_result_fields', [])
548 query_set = self._join_iteration_results(query_set, iteration_keys)
549
550 job_keyvals = filter_data.pop('job_keyval_fields', [])
551 query_set = self._join_job_keyvals(query_set, job_keyvals)
552
553 iteration_attributes = filter_data.pop('iteration_attribute_fields', [])
554 query_set = self._join_iteration_attributes(query_set,
555 iteration_attributes)
showard8b0ea222009-12-23 19:23:03 +0000556
557 # everything that follows is deprecated behavior
558
showardd50ffb42008-09-04 02:47:45 +0000559 joined = False
showard2aa318e2009-08-20 23:43:10 +0000560
showardf2489522008-10-23 23:08:00 +0000561 extra_where = filter_data.get('extra_where', '')
showardeab66ce2009-12-23 00:03:56 +0000562 if 'tko_test_labels' in extra_where:
showard02813502008-08-20 20:52:56 +0000563 query_set = self._add_label_joins(query_set)
showardd50ffb42008-09-04 02:47:45 +0000564 joined = True
565
showard8b0ea222009-12-23 19:23:03 +0000566 include_labels = filter_data.pop('include_labels', [])
567 exclude_labels = filter_data.pop('exclude_labels', [])
showard2aa318e2009-08-20 23:43:10 +0000568 if include_labels:
569 query_set = self._include_or_exclude_labels(query_set,
570 include_labels)
showardfc8c6ae2008-11-11 19:06:01 +0000571 joined = True
showard2aa318e2009-08-20 23:43:10 +0000572 if exclude_labels:
573 query_set = self._include_or_exclude_labels(query_set,
574 exclude_labels,
575 exclude=True)
showard64aeecd2008-09-19 21:32:58 +0000576 joined = True
577
578 include_attributes_where = filter_data.pop('include_attributes_where',
579 '')
580 exclude_attributes_where = filter_data.pop('exclude_attributes_where',
581 '')
582 if include_attributes_where:
583 query_set = self._add_attribute_join(
showard2aa318e2009-08-20 23:43:10 +0000584 query_set,
585 join_condition=self.escape_user_sql(include_attributes_where))
showard64aeecd2008-09-19 21:32:58 +0000586 joined = True
587 if exclude_attributes_where:
588 query_set = self._add_attribute_join(
showard2aa318e2009-08-20 23:43:10 +0000589 query_set,
590 join_condition=self.escape_user_sql(exclude_attributes_where),
showard64aeecd2008-09-19 21:32:58 +0000591 exclude=True)
592 joined = True
showardd50ffb42008-09-04 02:47:45 +0000593
594 if not joined:
showard35444862008-08-07 22:35:30 +0000595 filter_data['no_distinct'] = True
showardd50ffb42008-09-04 02:47:45 +0000596
showard8b0ea222009-12-23 19:23:03 +0000597 if 'tko_test_attributes_host_labels' in extra_where:
showardf2489522008-10-23 23:08:00 +0000598 query_set = self._add_attribute_join(
599 query_set, suffix='_host_labels',
showardeab66ce2009-12-23 00:03:56 +0000600 join_condition='tko_test_attributes_host_labels.attribute = '
showardf2489522008-10-23 23:08:00 +0000601 '"host-labels"')
602
showard35444862008-08-07 22:35:30 +0000603 return query_set
604
605
showard8bfb5cb2009-10-07 20:49:15 +0000606 def query_test_ids(self, filter_data, apply_presentation=True):
607 query = self.model.query_objects(filter_data,
608 apply_presentation=apply_presentation)
609 dicts = query.values('test_idx')
showard02813502008-08-20 20:52:56 +0000610 return [item['test_idx'] for item in dicts]
611
612
showard02813502008-08-20 20:52:56 +0000613 def query_test_label_ids(self, filter_data):
showardd50ffb42008-09-04 02:47:45 +0000614 query_set = self.model.query_objects(filter_data)
615 query_set = self._add_label_joins(query_set, suffix='_list')
showardeab66ce2009-12-23 00:03:56 +0000616 rows = self._custom_select_query(query_set, ['tko_test_labels_list.id'])
showardd50ffb42008-09-04 02:47:45 +0000617 return [row[0] for row in rows if row[0] is not None]
showard02813502008-08-20 20:52:56 +0000618
619
showardeaccf8f2009-04-16 03:11:33 +0000620 def escape_user_sql(self, sql):
621 sql = super(TestViewManager, self).escape_user_sql(sql)
622 return sql.replace('test_idx', self.get_key_on_this_table('test_idx'))
623
624
showard35444862008-08-07 22:35:30 +0000625class TestView(dbmodels.Model, model_logic.ModelExtensions):
626 extra_fields = {
showardf4c702e2009-07-08 21:14:27 +0000627 'DATE(job_queued_time)': 'job queued day',
628 'DATE(test_finished_time)': 'test finished day',
showard35444862008-08-07 22:35:30 +0000629 }
630
631 group_fields = [
showardf4c702e2009-07-08 21:14:27 +0000632 'test_name',
633 'status',
634 'kernel',
635 'hostname',
636 'job_tag',
637 'job_name',
638 'platform',
639 'reason',
640 'job_owner',
641 'job_queued_time',
642 'DATE(job_queued_time)',
643 'test_started_time',
644 'test_finished_time',
645 'DATE(test_finished_time)',
showard35444862008-08-07 22:35:30 +0000646 ]
647
648 test_idx = dbmodels.IntegerField('test index', primary_key=True)
649 job_idx = dbmodels.IntegerField('job index', null=True, blank=True)
showarda5288b42009-07-28 20:06:08 +0000650 test_name = dbmodels.CharField(blank=True, max_length=90)
651 subdir = dbmodels.CharField('subdirectory', blank=True, max_length=180)
showard35444862008-08-07 22:35:30 +0000652 kernel_idx = dbmodels.IntegerField('kernel index')
653 status_idx = dbmodels.IntegerField('status index')
showarda5288b42009-07-28 20:06:08 +0000654 reason = dbmodels.CharField(blank=True, max_length=3072)
showard35444862008-08-07 22:35:30 +0000655 machine_idx = dbmodels.IntegerField('host index')
656 test_started_time = dbmodels.DateTimeField(null=True, blank=True)
657 test_finished_time = dbmodels.DateTimeField(null=True, blank=True)
showarda5288b42009-07-28 20:06:08 +0000658 job_tag = dbmodels.CharField(blank=True, max_length=300)
659 job_name = dbmodels.CharField(blank=True, max_length=300)
660 job_owner = dbmodels.CharField('owner', blank=True, max_length=240)
showard35444862008-08-07 22:35:30 +0000661 job_queued_time = dbmodels.DateTimeField(null=True, blank=True)
662 job_started_time = dbmodels.DateTimeField(null=True, blank=True)
663 job_finished_time = dbmodels.DateTimeField(null=True, blank=True)
showardc1c1caf2009-09-08 16:26:50 +0000664 afe_job_id = dbmodels.IntegerField(null=True)
showarda5288b42009-07-28 20:06:08 +0000665 hostname = dbmodels.CharField(blank=True, max_length=300)
666 platform = dbmodels.CharField(blank=True, max_length=240)
667 machine_owner = dbmodels.CharField(blank=True, max_length=240)
668 kernel_hash = dbmodels.CharField(blank=True, max_length=105)
669 kernel_base = dbmodels.CharField(blank=True, max_length=90)
670 kernel = dbmodels.CharField(blank=True, max_length=300)
671 status = dbmodels.CharField(blank=True, max_length=30)
showard35444862008-08-07 22:35:30 +0000672
673 objects = TestViewManager()
674
675 def save(self):
676 raise NotImplementedError('TestView is read-only')
677
678
679 def delete(self):
680 raise NotImplementedError('TestView is read-only')
681
682
683 @classmethod
showard8bfb5cb2009-10-07 20:49:15 +0000684 def query_objects(cls, filter_data, initial_query=None,
685 apply_presentation=True):
showard35444862008-08-07 22:35:30 +0000686 if initial_query is None:
showard64aeecd2008-09-19 21:32:58 +0000687 initial_query = cls.objects.get_query_set_with_joins(filter_data)
showard8bfb5cb2009-10-07 20:49:15 +0000688 return super(TestView, cls).query_objects(
689 filter_data, initial_query=initial_query,
690 apply_presentation=apply_presentation)
showard35444862008-08-07 22:35:30 +0000691
692
693 class Meta:
showardeab66ce2009-12-23 00:03:56 +0000694 db_table = 'tko_test_view_2'