blob: 63ce262176d9a742f54a4a4ca8d843ea83e82e2a [file] [log] [blame]
showardf828c772010-01-25 21:49:42 +00001from autotest_lib.frontend.shared import exceptions
2
3class ConstraintError(Exception):
4 """Raised when an error occurs applying a Constraint."""
5
6
jamesren3981f442010-02-16 19:27:59 +00007class QueryProcessor(object):
8 def __init__(self):
9 # maps selector name to (selector, constraint)
10 self._selectors = {}
11 self._alias_counter = 0
showardf828c772010-01-25 21:49:42 +000012
13
jamesren3981f442010-02-16 19:27:59 +000014 def add_field_selector(self, name, field=None, value_transform=None,
15 doc=None):
showardf828c772010-01-25 21:49:42 +000016 if not field:
17 field = name
jamesren3981f442010-02-16 19:27:59 +000018 self.add_selector(Selector(name, doc),
showardf828c772010-01-25 21:49:42 +000019 _FieldConstraint(field, value_transform))
20
21
jamesren3981f442010-02-16 19:27:59 +000022 def add_related_existence_selector(self, name, model, field, doc=None):
23 self.add_selector(
24 Selector(name, doc),
25 _RelatedExistenceConstraint(model, field, self.make_alias))
showardf828c772010-01-25 21:49:42 +000026
27
jamesren3981f442010-02-16 19:27:59 +000028 def add_selector(self, selector, constraint):
29 if self._selectors is None:
30 self._selectors = {}
31 self._selectors[selector.name] = (selector, constraint)
showardf828c772010-01-25 21:49:42 +000032
33
jamesren3981f442010-02-16 19:27:59 +000034 def make_alias(self):
35 self._alias_counter += 1
36 return 'alias%s' % self._alias_counter
showardf828c772010-01-25 21:49:42 +000037
38
jamesren3981f442010-02-16 19:27:59 +000039 def selectors(self):
showardf828c772010-01-25 21:49:42 +000040 return tuple(selector for selector, constraint
jamesren3981f442010-02-16 19:27:59 +000041 in self._selectors.itervalues())
showardf828c772010-01-25 21:49:42 +000042
43
jamesren3981f442010-02-16 19:27:59 +000044 def has_selector(self, selector_name):
45 return selector_name in self._selectors
showardf828c772010-01-25 21:49:42 +000046
47
48 def apply_selector(self, queryset, selector_name, value,
49 comparison_type='equals', is_inverse=False):
50 _, constraint = self._selectors[selector_name]
51 try:
52 return constraint.apply_constraint(queryset, value, comparison_type,
53 is_inverse)
54 except ConstraintError, exc:
55 raise exceptions.BadRequest('Selector %s: %s'
56 % (selector_name, exc))
57
58
59 # common value conversions
60
jamesren3981f442010-02-16 19:27:59 +000061 def read_boolean(self, boolean_input):
showardf828c772010-01-25 21:49:42 +000062 if boolean_input.lower() == 'true':
63 return True
64 if boolean_input.lower() == 'false':
65 return False
66 raise exceptions.BadRequest('Invalid input for boolean: %r'
67 % boolean_input)
68
69
70class Selector(object):
71 def __init__(self, name, doc):
72 self.name = name
73 self.doc = doc
74
75
76class Constraint(object):
77 def apply_constraint(self, queryset, value, comparison_type, is_inverse):
78 raise NotImplementedError
79
80
81class _FieldConstraint(Constraint):
82 def __init__(self, field, value_transform=None):
83 self._field = field
84 self._value_transform = value_transform
85
86
87 _COMPARISON_MAP = {
88 'equals': 'exact',
89 'lt': 'lt',
90 'le': 'lte',
91 'gt': 'gt',
92 'ge': 'gte',
93 'contains': 'contains',
94 'startswith': 'startswith',
95 'endswith': 'endswith',
96 'in': 'in',
97 }
98
99
100 def apply_constraint(self, queryset, value, comparison_type, is_inverse):
101 if self._value_transform:
102 value = self._value_transform(value)
103
104 kwarg_name = str(self._field + '__' +
105 self._COMPARISON_MAP[comparison_type])
106
107 if is_inverse:
108 return queryset.exclude(**{kwarg_name: value})
109 else:
110 return queryset.filter(**{kwarg_name: value})
111
112
113class _RelatedExistenceConstraint(Constraint):
114 def __init__(self, model, field, make_alias_fn):
115 self._model = model
116 self._field = field
117 self._make_alias_fn = make_alias_fn
118
119
120 def apply_constraint(self, queryset, value, comparison_type, is_inverse):
121 if comparison_type not in (None, 'equals'):
122 raise ConstraintError('Can only use equals or not equals with '
123 'this selector')
124 related_query = self._model.objects.filter(**{self._field: value})
125 if not related_query:
126 raise ConstraintError('%s %s not found' % (self._model_name, value))
127 alias = self._make_alias_fn()
128 queryset = queryset.model.objects.join_custom_field(queryset,
129 related_query,
130 alias)
131 if is_inverse:
132 condition = '%s.%s IS NULL'
133 else:
134 condition = '%s.%s IS NOT NULL'
135 condition %= (alias,
136 queryset.model.objects.key_on_joined_table(related_query))
137
138 queryset = queryset.model.objects.add_where(queryset, condition)
139
140 return queryset