| 1 |
from types import FunctionType |
|---|
| 2 |
|
|---|
| 3 |
from geniusql import logic |
|---|
| 4 |
|
|---|
| 5 |
|
|---|
| 6 |
__all__ = ['Join', 'Query', 'Statement'] |
|---|
| 7 |
|
|---|
| 8 |
|
|---|
| 9 |
|
|---|
| 10 |
class Query(object): |
|---|
| 11 |
"""A query with relation, attributes, and restriction expressions. |
|---|
| 12 |
|
|---|
| 13 |
relation: Either a single Table object, or a Join (of Tables). |
|---|
| 14 |
attributes: |
|---|
| 15 |
SELECT: |
|---|
| 16 |
If the relation is a single Table, this value must be a |
|---|
| 17 |
sequence of column names for that Table. If the relation |
|---|
| 18 |
is a Join, this must be a sequence of sequences of column |
|---|
| 19 |
names. That is: |
|---|
| 20 |
[(table1.col1.name, table1.col2.name, ...), |
|---|
| 21 |
(table2.col1.name, ...), |
|---|
| 22 |
...] |
|---|
| 23 |
The order of sequences must match the order of tables given in |
|---|
| 24 |
the relation (and therefore the restriction args, if applicable). |
|---|
| 25 |
A final option is to pass a lambda (or Expression) which returns |
|---|
| 26 |
the attributes as a tuple or list; e.g.: |
|---|
| 27 |
lambda t1, t2: (t1.a, t1.b - now(), t1.c + t2.a) |
|---|
| 28 |
This allows access to binary operations and builtin functions. |
|---|
| 29 |
INSERT/UPDATE: |
|---|
| 30 |
If the relation is a single Table, this value must be a |
|---|
| 31 |
dict whose keys are column names for that Table, and whose |
|---|
| 32 |
values or either scalars or Expressions (or lambdas) |
|---|
| 33 |
representing the new values for those columns. For example: |
|---|
| 34 |
{'Name': 'Ali Bayan', |
|---|
| 35 |
'Age': (lambda t: now() - t.Birthdate), |
|---|
| 36 |
... |
|---|
| 37 |
} |
|---|
| 38 |
If the relation is a Join, this must be a list of such dicts, |
|---|
| 39 |
one per table, in the same order as the tables in the relation. |
|---|
| 40 |
DELETE: |
|---|
| 41 |
This should be a single empty list. |
|---|
| 42 |
restriction: an Expression (or lambda) to restrict the rows returned; |
|---|
| 43 |
if given, this will be used to construct a WHERE clause. The args |
|---|
| 44 |
must be in the same order as the tables in the relation. |
|---|
| 45 |
""" |
|---|
| 46 |
|
|---|
| 47 |
def __init__(self, relation, attributes, restriction=None): |
|---|
| 48 |
self.relation = relation |
|---|
| 49 |
|
|---|
| 50 |
if isinstance(attributes, FunctionType): |
|---|
| 51 |
attributes = logic.Expression(attributes) |
|---|
| 52 |
self.attributes = attributes |
|---|
| 53 |
|
|---|
| 54 |
if restriction is None: |
|---|
| 55 |
restriction = logic.Expression(lambda *args: True) |
|---|
| 56 |
elif not isinstance(restriction, logic.Expression): |
|---|
| 57 |
restriction = logic.Expression(restriction) |
|---|
| 58 |
self.restriction = restriction |
|---|
| 59 |
|
|---|
| 60 |
def from_genexp(cls, expr): |
|---|
| 61 |
|
|---|
| 62 |
|
|---|
| 63 |
from geniusql import genexp |
|---|
| 64 |
dep = genexp.GenexpParser(expr) |
|---|
| 65 |
dep.verbose = True |
|---|
| 66 |
dep.walk() |
|---|
| 67 |
|
|---|
| 68 |
newq = cls(dep.relation, dep.attributes, dep.restriction) |
|---|
| 69 |
return newq |
|---|
| 70 |
from_genexp = classmethod(from_genexp) |
|---|
| 71 |
|
|---|
| 72 |
|
|---|
| 73 |
class Join(object): |
|---|
| 74 |
"""A join between two tables.""" |
|---|
| 75 |
|
|---|
| 76 |
def __init__(self, table1, table2, leftbiased=None): |
|---|
| 77 |
self.table1 = table1 |
|---|
| 78 |
self.table2 = table2 |
|---|
| 79 |
self.leftbiased = leftbiased |
|---|
| 80 |
self.path = None |
|---|
| 81 |
|
|---|
| 82 |
def __str__(self): |
|---|
| 83 |
if self.leftbiased is None: |
|---|
| 84 |
op = "&" |
|---|
| 85 |
elif self.leftbiased is True: |
|---|
| 86 |
op = "<<" |
|---|
| 87 |
else: |
|---|
| 88 |
op = ">>" |
|---|
| 89 |
if isinstance(self.table1, Join): |
|---|
| 90 |
name1 = str(self.table1) |
|---|
| 91 |
elif isinstance(self.table1, type): |
|---|
| 92 |
name1 = self.table1.__name__ |
|---|
| 93 |
else: |
|---|
| 94 |
name1 = repr(self.table1) |
|---|
| 95 |
|
|---|
| 96 |
if isinstance(self.table2, Join): |
|---|
| 97 |
name2 = str(self.table2) |
|---|
| 98 |
elif isinstance(self.table2, type): |
|---|
| 99 |
name2 = self.table2.__name__ |
|---|
| 100 |
else: |
|---|
| 101 |
name2 = repr(self.table2) |
|---|
| 102 |
|
|---|
| 103 |
return "(%s %s %s)" % (name1, op, name2) |
|---|
| 104 |
__repr__ = __str__ |
|---|
| 105 |
|
|---|
| 106 |
def __iter__(self): |
|---|
| 107 |
return JoinIterator(self) |
|---|
| 108 |
|
|---|
| 109 |
def __lshift__(self, other): |
|---|
| 110 |
return Join(self, other, leftbiased=True) |
|---|
| 111 |
__rrshift__ = __lshift__ |
|---|
| 112 |
|
|---|
| 113 |
def __rshift__(self, other): |
|---|
| 114 |
return Join(self, other, leftbiased=False) |
|---|
| 115 |
__rlshift__ = __rshift__ |
|---|
| 116 |
|
|---|
| 117 |
def __add__(self, other): |
|---|
| 118 |
return Join(self, other) |
|---|
| 119 |
__and__ = __add__ |
|---|
| 120 |
|
|---|
| 121 |
def __radd__(self, other): |
|---|
| 122 |
return Join(other, self) |
|---|
| 123 |
__rand__ = __radd__ |
|---|
| 124 |
|
|---|
| 125 |
def __eq__(self, other): |
|---|
| 126 |
return (self.table1 == other.table1 and |
|---|
| 127 |
self.table2 == other.table2 and |
|---|
| 128 |
self.leftbiased == other.leftbiased and |
|---|
| 129 |
self.path == other.path) |
|---|
| 130 |
|
|---|
| 131 |
|
|---|
| 132 |
class JoinIterator(object): |
|---|
| 133 |
|
|---|
| 134 |
def __init__(self, join): |
|---|
| 135 |
if isinstance(join.table1, Join): |
|---|
| 136 |
t1 = list(join.table1) |
|---|
| 137 |
else: |
|---|
| 138 |
t1 = [join.table1] |
|---|
| 139 |
|
|---|
| 140 |
if isinstance(join.table2, Join): |
|---|
| 141 |
t2 = list(join.table2) |
|---|
| 142 |
else: |
|---|
| 143 |
t2 = [join.table2] |
|---|
| 144 |
|
|---|
| 145 |
self.tableiter = iter(t1 + t2) |
|---|
| 146 |
|
|---|
| 147 |
def __iter__(self): |
|---|
| 148 |
return self |
|---|
| 149 |
|
|---|
| 150 |
def next(self): |
|---|
| 151 |
return self.tableiter.next() |
|---|
| 152 |
|
|---|
| 153 |
|
|---|
| 154 |
class Statement(object): |
|---|
| 155 |
"""A relational statement, including query, order, limit, offset, and distinct. |
|---|
| 156 |
|
|---|
| 157 |
query: a Query instance, or a tuple of arguments to form a Query. |
|---|
| 158 |
|
|---|
| 159 |
order: if given, this will be used to construct an ORDER BY clause. |
|---|
| 160 |
If the relation is a single Table, this value may be a sequence |
|---|
| 161 |
of column names for the Table. If the relation is a Join, this must |
|---|
| 162 |
be an Expression (or lambda) which returns a tuple or list of |
|---|
| 163 |
attributes; the args must be in the same order as the tables in the |
|---|
| 164 |
relation. |
|---|
| 165 |
""" |
|---|
| 166 |
|
|---|
| 167 |
def __init__(self, query, order=None, limit=None, offset=None, distinct=None): |
|---|
| 168 |
if not isinstance(query, Query): |
|---|
| 169 |
query = Query(*query) |
|---|
| 170 |
self.query = query |
|---|
| 171 |
|
|---|
| 172 |
self.order = order |
|---|
| 173 |
self.limit = limit |
|---|
| 174 |
self.offset = offset |
|---|
| 175 |
self.distinct = distinct |
|---|
| 176 |
|
|---|