sqlobject 3.13.1__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sqlobject/__init__.py +15 -0
- sqlobject/__version__.py +8 -0
- sqlobject/boundattributes.py +124 -0
- sqlobject/cache.py +376 -0
- sqlobject/classregistry.py +138 -0
- sqlobject/col.py +2187 -0
- sqlobject/compat.py +59 -0
- sqlobject/conftest.py +64 -0
- sqlobject/constraints.py +77 -0
- sqlobject/converters.py +278 -0
- sqlobject/dbconnection.py +1137 -0
- sqlobject/dberrors.py +54 -0
- sqlobject/declarative.py +211 -0
- sqlobject/events.py +365 -0
- sqlobject/firebird/__init__.py +8 -0
- sqlobject/firebird/firebirdconnection.py +463 -0
- sqlobject/include/__init__.py +1 -0
- sqlobject/include/hashcol.py +109 -0
- sqlobject/include/tests/__init__.py +1 -0
- sqlobject/include/tests/test_hashcol.py +84 -0
- sqlobject/index.py +189 -0
- sqlobject/inheritance/__init__.py +551 -0
- sqlobject/inheritance/iteration.py +106 -0
- sqlobject/inheritance/tests/__init__.py +1 -0
- sqlobject/inheritance/tests/test_aggregates.py +23 -0
- sqlobject/inheritance/tests/test_asdict.py +48 -0
- sqlobject/inheritance/tests/test_deep_inheritance.py +114 -0
- sqlobject/inheritance/tests/test_destroy_cascade.py +23 -0
- sqlobject/inheritance/tests/test_foreignKey.py +94 -0
- sqlobject/inheritance/tests/test_indexes.py +42 -0
- sqlobject/inheritance/tests/test_inheritance.py +164 -0
- sqlobject/inheritance/tests/test_inheritance_tree.py +50 -0
- sqlobject/joins.py +656 -0
- sqlobject/main.py +1880 -0
- sqlobject/manager/__init__.py +1 -0
- sqlobject/manager/command.py +1309 -0
- sqlobject/maxdb/__init__.py +8 -0
- sqlobject/maxdb/maxdbconnection.py +320 -0
- sqlobject/maxdb/readme.txt +27 -0
- sqlobject/mssql/__init__.py +8 -0
- sqlobject/mssql/mssqlconnection.py +417 -0
- sqlobject/mysql/__init__.py +8 -0
- sqlobject/mysql/mysqlconnection.py +562 -0
- sqlobject/postgres/__init__.py +8 -0
- sqlobject/postgres/pgconnection.py +619 -0
- sqlobject/sqlbuilder.py +1655 -0
- sqlobject/sqlite/__init__.py +8 -0
- sqlobject/sqlite/sqliteconnection.py +418 -0
- sqlobject/sresults.py +377 -0
- sqlobject/styles.py +174 -0
- sqlobject/sybase/__init__.py +8 -0
- sqlobject/sybase/sybaseconnection.py +183 -0
- sqlobject/tests/__init__.py +1 -0
- sqlobject/tests/dbtest.py +363 -0
- sqlobject/tests/test_ForeignKey.py +133 -0
- sqlobject/tests/test_ForeignKey_cascade.py +150 -0
- sqlobject/tests/test_NoneValuedResultItem.py +33 -0
- sqlobject/tests/test_SQLMultipleJoin.py +65 -0
- sqlobject/tests/test_SQLRelatedJoin.py +114 -0
- sqlobject/tests/test_SingleJoin.py +30 -0
- sqlobject/tests/test_aggregates.py +75 -0
- sqlobject/tests/test_aliases.py +49 -0
- sqlobject/tests/test_asdict.py +18 -0
- sqlobject/tests/test_auto.py +218 -0
- sqlobject/tests/test_basic.py +359 -0
- sqlobject/tests/test_blob.py +34 -0
- sqlobject/tests/test_boundattributes.py +67 -0
- sqlobject/tests/test_cache.py +57 -0
- sqlobject/tests/test_class_hash.py +22 -0
- sqlobject/tests/test_columns_order.py +18 -0
- sqlobject/tests/test_combining_joins.py +47 -0
- sqlobject/tests/test_comparison.py +24 -0
- sqlobject/tests/test_compat.py +10 -0
- sqlobject/tests/test_complex_sorting.py +77 -0
- sqlobject/tests/test_constraints.py +43 -0
- sqlobject/tests/test_converters.py +276 -0
- sqlobject/tests/test_create_drop.py +29 -0
- sqlobject/tests/test_csvexport.py +101 -0
- sqlobject/tests/test_csvimport.py +21 -0
- sqlobject/tests/test_cyclic_reference.py +55 -0
- sqlobject/tests/test_datetime.py +171 -0
- sqlobject/tests/test_dbconnection.py +17 -0
- sqlobject/tests/test_decimal.py +63 -0
- sqlobject/tests/test_declarative.py +83 -0
- sqlobject/tests/test_default_style.py +65 -0
- sqlobject/tests/test_delete.py +74 -0
- sqlobject/tests/test_distinct.py +36 -0
- sqlobject/tests/test_empty.py +22 -0
- sqlobject/tests/test_enum.py +75 -0
- sqlobject/tests/test_events.py +142 -0
- sqlobject/tests/test_exceptions.py +40 -0
- sqlobject/tests/test_expire.py +29 -0
- sqlobject/tests/test_groupBy.py +45 -0
- sqlobject/tests/test_identity.py +29 -0
- sqlobject/tests/test_indexes.py +100 -0
- sqlobject/tests/test_inheritance.py +35 -0
- sqlobject/tests/test_joins.py +160 -0
- sqlobject/tests/test_joins_conditional.py +143 -0
- sqlobject/tests/test_jsonbcol.py +53 -0
- sqlobject/tests/test_jsoncol.py +56 -0
- sqlobject/tests/test_lazy.py +165 -0
- sqlobject/tests/test_md5.py +13 -0
- sqlobject/tests/test_mysql.py +50 -0
- sqlobject/tests/test_new_joins.py +165 -0
- sqlobject/tests/test_parse_uri.py +147 -0
- sqlobject/tests/test_paste.py +92 -0
- sqlobject/tests/test_perConnection.py +20 -0
- sqlobject/tests/test_pickle.py +42 -0
- sqlobject/tests/test_picklecol.py +44 -0
- sqlobject/tests/test_postgres.py +82 -0
- sqlobject/tests/test_reparent_sqlmeta.py +33 -0
- sqlobject/tests/test_schema.py +30 -0
- sqlobject/tests/test_select.py +218 -0
- sqlobject/tests/test_select_through.py +63 -0
- sqlobject/tests/test_setters.py +23 -0
- sqlobject/tests/test_slice.py +59 -0
- sqlobject/tests/test_sorting.py +80 -0
- sqlobject/tests/test_sqlbuilder.py +134 -0
- sqlobject/tests/test_sqlbuilder_dbspecific.py +66 -0
- sqlobject/tests/test_sqlbuilder_importproxy.py +55 -0
- sqlobject/tests/test_sqlbuilder_joins_instances.py +90 -0
- sqlobject/tests/test_sqlite.py +160 -0
- sqlobject/tests/test_sqlmeta_idName.py +52 -0
- sqlobject/tests/test_sqlobject_admin.py +38 -0
- sqlobject/tests/test_string_id.py +79 -0
- sqlobject/tests/test_style.py +36 -0
- sqlobject/tests/test_subqueries.py +142 -0
- sqlobject/tests/test_transactions.py +122 -0
- sqlobject/tests/test_unicode.py +84 -0
- sqlobject/tests/test_uuidcol.py +40 -0
- sqlobject/tests/test_validation.py +125 -0
- sqlobject/tests/test_views.py +159 -0
- sqlobject/util/__init__.py +1 -0
- sqlobject/util/csvexport.py +210 -0
- sqlobject/util/csvimport.py +366 -0
- sqlobject/util/moduleloader.py +35 -0
- sqlobject/util/threadinglocal.py +6 -0
- sqlobject/versioning/__init__.py +120 -0
- sqlobject/versioning/test/__init__.py +0 -0
- sqlobject/versioning/test/test_version.py +183 -0
- sqlobject/views.py +151 -0
- sqlobject/wsgi_middleware.py +109 -0
- sqlobject-3.13.1.data/scripts/sqlobject-admin +36 -0
- sqlobject-3.13.1.data/scripts/sqlobject-convertOldURI +19 -0
- sqlobject-3.13.1.dist-info/METADATA +177 -0
- sqlobject-3.13.1.dist-info/RECORD +149 -0
- sqlobject-3.13.1.dist-info/WHEEL +6 -0
- sqlobject-3.13.1.dist-info/entry_points.txt +4 -0
- sqlobject-3.13.1.dist-info/top_level.txt +1 -0
sqlobject/joins.py
ADDED
|
@@ -0,0 +1,656 @@
|
|
|
1
|
+
from itertools import count
|
|
2
|
+
from . import boundattributes
|
|
3
|
+
from . import classregistry
|
|
4
|
+
from . import events
|
|
5
|
+
from . import sresults
|
|
6
|
+
from . import styles
|
|
7
|
+
from . import sqlbuilder
|
|
8
|
+
from .styles import capword
|
|
9
|
+
|
|
10
|
+
__all__ = ['MultipleJoin', 'SQLMultipleJoin', 'RelatedJoin', 'SQLRelatedJoin',
|
|
11
|
+
'SingleJoin', 'ManyToMany', 'OneToMany']
|
|
12
|
+
|
|
13
|
+
creationOrder = count()
|
|
14
|
+
NoDefault = sqlbuilder.NoDefault
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def getID(obj):
|
|
18
|
+
try:
|
|
19
|
+
return obj.id
|
|
20
|
+
except AttributeError:
|
|
21
|
+
return int(obj)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Join(object):
|
|
25
|
+
|
|
26
|
+
def __init__(self, otherClass=None, **kw):
|
|
27
|
+
kw['otherClass'] = otherClass
|
|
28
|
+
self.kw = kw
|
|
29
|
+
self._joinMethodName = self.kw.pop('joinMethodName', None)
|
|
30
|
+
self.creationOrder = next(creationOrder)
|
|
31
|
+
|
|
32
|
+
def _set_joinMethodName(self, value):
|
|
33
|
+
assert self._joinMethodName == value or self._joinMethodName is None, \
|
|
34
|
+
"You have already given an explicit joinMethodName (%s), " \
|
|
35
|
+
"and you are now setting it to %s" % (self._joinMethodName, value)
|
|
36
|
+
self._joinMethodName = value
|
|
37
|
+
|
|
38
|
+
def _get_joinMethodName(self):
|
|
39
|
+
return self._joinMethodName
|
|
40
|
+
|
|
41
|
+
joinMethodName = property(_get_joinMethodName, _set_joinMethodName)
|
|
42
|
+
name = joinMethodName
|
|
43
|
+
|
|
44
|
+
def withClass(self, soClass):
|
|
45
|
+
if 'joinMethodName' in self.kw:
|
|
46
|
+
self._joinMethodName = self.kw['joinMethodName']
|
|
47
|
+
del self.kw['joinMethodName']
|
|
48
|
+
return self.baseClass(creationOrder=self.creationOrder,
|
|
49
|
+
soClass=soClass,
|
|
50
|
+
joinDef=self,
|
|
51
|
+
joinMethodName=self._joinMethodName,
|
|
52
|
+
**self.kw)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# A join is separate from a foreign key, i.e., it is
|
|
56
|
+
# many-to-many, or one-to-many where the *other* class
|
|
57
|
+
# has the foreign key.
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class SOJoin(object):
|
|
61
|
+
|
|
62
|
+
def __init__(self,
|
|
63
|
+
creationOrder,
|
|
64
|
+
soClass=None,
|
|
65
|
+
otherClass=None,
|
|
66
|
+
joinColumn=None,
|
|
67
|
+
joinMethodName=None,
|
|
68
|
+
orderBy=NoDefault,
|
|
69
|
+
joinDef=None):
|
|
70
|
+
self.creationOrder = creationOrder
|
|
71
|
+
self.soClass = soClass
|
|
72
|
+
self.joinDef = joinDef
|
|
73
|
+
self.otherClassName = otherClass
|
|
74
|
+
classregistry.registry(soClass.sqlmeta.registry).addClassCallback(
|
|
75
|
+
otherClass, self._setOtherClass)
|
|
76
|
+
self.joinColumn = joinColumn
|
|
77
|
+
self.joinMethodName = joinMethodName
|
|
78
|
+
self._orderBy = orderBy
|
|
79
|
+
if not self.joinColumn:
|
|
80
|
+
# Here we set up the basic join, which is
|
|
81
|
+
# one-to-many, where the other class points to
|
|
82
|
+
# us.
|
|
83
|
+
self.joinColumn = styles.getStyle(
|
|
84
|
+
self.soClass).tableReference(self.soClass.sqlmeta.table)
|
|
85
|
+
|
|
86
|
+
def orderBy(self):
|
|
87
|
+
if self._orderBy is NoDefault:
|
|
88
|
+
self._orderBy = self.otherClass.sqlmeta.defaultOrder
|
|
89
|
+
return self._orderBy
|
|
90
|
+
orderBy = property(orderBy)
|
|
91
|
+
|
|
92
|
+
def _setOtherClass(self, cls):
|
|
93
|
+
self.otherClass = cls
|
|
94
|
+
|
|
95
|
+
def hasIntermediateTable(self):
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
def _applyOrderBy(self, results, defaultSortClass):
|
|
99
|
+
if self.orderBy is not None:
|
|
100
|
+
doSort(results, self.orderBy)
|
|
101
|
+
return results
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class MinType(object):
|
|
105
|
+
"""Sort less than everything, for handling None's in the results"""
|
|
106
|
+
# functools.total_ordering would simplify this
|
|
107
|
+
|
|
108
|
+
def __lt__(self, other):
|
|
109
|
+
if self is other:
|
|
110
|
+
return False
|
|
111
|
+
return True
|
|
112
|
+
|
|
113
|
+
def __eq__(self, other):
|
|
114
|
+
return self is other
|
|
115
|
+
|
|
116
|
+
def __gt__(self, other):
|
|
117
|
+
return False
|
|
118
|
+
|
|
119
|
+
def __le__(self, other):
|
|
120
|
+
return True
|
|
121
|
+
|
|
122
|
+
def __ge__(self, other):
|
|
123
|
+
if self is other:
|
|
124
|
+
return True
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
Min = MinType()
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def doSort(results, orderBy):
|
|
132
|
+
if isinstance(orderBy, (tuple, list)):
|
|
133
|
+
if len(orderBy) == 1:
|
|
134
|
+
orderBy = orderBy[0]
|
|
135
|
+
else:
|
|
136
|
+
# Rely on stable sort results, since this is simpler
|
|
137
|
+
# than trying to munge everything into a single sort key
|
|
138
|
+
doSort(results, orderBy[0])
|
|
139
|
+
doSort(results, orderBy[1:])
|
|
140
|
+
return
|
|
141
|
+
if isinstance(orderBy, sqlbuilder.DESC) \
|
|
142
|
+
and isinstance(orderBy.expr, sqlbuilder.SQLObjectField):
|
|
143
|
+
orderBy = '-' + orderBy.expr.original
|
|
144
|
+
elif isinstance(orderBy, sqlbuilder.SQLObjectField):
|
|
145
|
+
orderBy = orderBy.original
|
|
146
|
+
# @@: but we don't handle more complex expressions for orderings
|
|
147
|
+
if orderBy.startswith('-'):
|
|
148
|
+
orderBy = orderBy[1:]
|
|
149
|
+
reverse = True
|
|
150
|
+
else:
|
|
151
|
+
reverse = False
|
|
152
|
+
|
|
153
|
+
def sortkey(x, attr=orderBy):
|
|
154
|
+
a = getattr(x, attr)
|
|
155
|
+
if a is None:
|
|
156
|
+
return Min
|
|
157
|
+
return a
|
|
158
|
+
results.sort(key=sortkey, reverse=reverse)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# This is a one-to-many
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class SOMultipleJoin(SOJoin):
|
|
165
|
+
|
|
166
|
+
def __init__(self, addRemoveName=None, **kw):
|
|
167
|
+
# addRemovePrefix is something like @@
|
|
168
|
+
SOJoin.__init__(self, **kw)
|
|
169
|
+
|
|
170
|
+
# Here we generate the method names
|
|
171
|
+
if not self.joinMethodName:
|
|
172
|
+
name = self.otherClassName[0].lower() + self.otherClassName[1:]
|
|
173
|
+
if name.endswith('s'):
|
|
174
|
+
name += "es"
|
|
175
|
+
else:
|
|
176
|
+
name += "s"
|
|
177
|
+
self.joinMethodName = name
|
|
178
|
+
if addRemoveName:
|
|
179
|
+
self.addRemoveName = addRemoveName
|
|
180
|
+
else:
|
|
181
|
+
self.addRemoveName = capword(self.otherClassName)
|
|
182
|
+
|
|
183
|
+
def performJoin(self, inst):
|
|
184
|
+
ids = inst._connection._SO_selectJoin(
|
|
185
|
+
self.otherClass,
|
|
186
|
+
self.joinColumn,
|
|
187
|
+
inst.id)
|
|
188
|
+
if inst.sqlmeta._perConnection:
|
|
189
|
+
conn = inst._connection
|
|
190
|
+
else:
|
|
191
|
+
conn = None
|
|
192
|
+
return self._applyOrderBy(
|
|
193
|
+
[self.otherClass.get(id, conn) for (id,) in ids if id is not None],
|
|
194
|
+
self.otherClass)
|
|
195
|
+
|
|
196
|
+
def _dbNameToPythonName(self):
|
|
197
|
+
for column in self.otherClass.sqlmeta.columns.values():
|
|
198
|
+
if column.dbName == self.joinColumn:
|
|
199
|
+
return column.name
|
|
200
|
+
return self.soClass.sqlmeta.style.dbColumnToPythonAttr(self.joinColumn)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class MultipleJoin(Join):
|
|
204
|
+
baseClass = SOMultipleJoin
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class SOSQLMultipleJoin(SOMultipleJoin):
|
|
208
|
+
|
|
209
|
+
def performJoin(self, inst):
|
|
210
|
+
if inst.sqlmeta._perConnection:
|
|
211
|
+
conn = inst._connection
|
|
212
|
+
else:
|
|
213
|
+
conn = None
|
|
214
|
+
pythonColumn = self._dbNameToPythonName()
|
|
215
|
+
results = self.otherClass.select(
|
|
216
|
+
getattr(self.otherClass.q, pythonColumn) == inst.id,
|
|
217
|
+
connection=conn)
|
|
218
|
+
return results.orderBy(self.orderBy)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class SQLMultipleJoin(Join):
|
|
222
|
+
baseClass = SOSQLMultipleJoin
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
# This is a many-to-many join, with an intermediary table
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class SORelatedJoin(SOMultipleJoin):
|
|
229
|
+
|
|
230
|
+
def __init__(self,
|
|
231
|
+
otherColumn=None,
|
|
232
|
+
intermediateTable=None,
|
|
233
|
+
createRelatedTable=True,
|
|
234
|
+
**kw):
|
|
235
|
+
self.intermediateTable = intermediateTable
|
|
236
|
+
self.otherColumn = otherColumn
|
|
237
|
+
self.createRelatedTable = createRelatedTable
|
|
238
|
+
SOMultipleJoin.__init__(self, **kw)
|
|
239
|
+
classregistry.registry(
|
|
240
|
+
self.soClass.sqlmeta.registry).addClassCallback(
|
|
241
|
+
self.otherClassName, self._setOtherRelatedClass)
|
|
242
|
+
|
|
243
|
+
def _setOtherRelatedClass(self, otherClass):
|
|
244
|
+
if not self.intermediateTable:
|
|
245
|
+
names = [self.soClass.sqlmeta.table,
|
|
246
|
+
otherClass.sqlmeta.table]
|
|
247
|
+
names.sort()
|
|
248
|
+
self.intermediateTable = '%s_%s' % (names[0], names[1])
|
|
249
|
+
if not self.otherColumn:
|
|
250
|
+
self.otherColumn = self.soClass.sqlmeta.style.tableReference(
|
|
251
|
+
otherClass.sqlmeta.table)
|
|
252
|
+
|
|
253
|
+
def hasIntermediateTable(self):
|
|
254
|
+
return True
|
|
255
|
+
|
|
256
|
+
def performJoin(self, inst):
|
|
257
|
+
ids = inst._connection._SO_intermediateJoin(
|
|
258
|
+
self.intermediateTable,
|
|
259
|
+
self.otherColumn,
|
|
260
|
+
self.joinColumn,
|
|
261
|
+
inst.id)
|
|
262
|
+
if inst.sqlmeta._perConnection:
|
|
263
|
+
conn = inst._connection
|
|
264
|
+
else:
|
|
265
|
+
conn = None
|
|
266
|
+
return self._applyOrderBy(
|
|
267
|
+
[self.otherClass.get(id, conn) for (id,) in ids if id is not None],
|
|
268
|
+
self.otherClass)
|
|
269
|
+
|
|
270
|
+
def remove(self, inst, other):
|
|
271
|
+
inst._connection._SO_intermediateDelete(
|
|
272
|
+
self.intermediateTable,
|
|
273
|
+
self.joinColumn,
|
|
274
|
+
getID(inst),
|
|
275
|
+
self.otherColumn,
|
|
276
|
+
getID(other))
|
|
277
|
+
|
|
278
|
+
def add(self, inst, other):
|
|
279
|
+
inst._connection._SO_intermediateInsert(
|
|
280
|
+
self.intermediateTable,
|
|
281
|
+
self.joinColumn,
|
|
282
|
+
getID(inst),
|
|
283
|
+
self.otherColumn,
|
|
284
|
+
getID(other))
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
class RelatedJoin(MultipleJoin):
|
|
288
|
+
baseClass = SORelatedJoin
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
# helper classes to SQLRelatedJoin
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
class OtherTableToJoin(sqlbuilder.SQLExpression):
|
|
295
|
+
def __init__(self, otherTable, otherIdName, interTable, joinColumn, alias):
|
|
296
|
+
self.otherTable = otherTable
|
|
297
|
+
self.otherIdName = otherIdName
|
|
298
|
+
self.interTable = interTable
|
|
299
|
+
self.joinColumn = joinColumn
|
|
300
|
+
self.alias = alias
|
|
301
|
+
|
|
302
|
+
def tablesUsedImmediate(self):
|
|
303
|
+
return [
|
|
304
|
+
'%s %s' % (self.otherTable, self.alias)
|
|
305
|
+
if self.alias else self.otherTable,
|
|
306
|
+
self.interTable,
|
|
307
|
+
]
|
|
308
|
+
|
|
309
|
+
def __sqlrepr__(self, db):
|
|
310
|
+
return '%s.%s = %s.%s' % (
|
|
311
|
+
self.alias if self.alias else self.otherTable,
|
|
312
|
+
self.otherIdName, self.interTable, self.joinColumn)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
class JoinToTable(sqlbuilder.SQLExpression):
|
|
316
|
+
def __init__(self, table, idName, interTable, joinColumn, alias):
|
|
317
|
+
self.table = table
|
|
318
|
+
self.idName = idName
|
|
319
|
+
self.interTable = interTable
|
|
320
|
+
self.joinColumn = joinColumn
|
|
321
|
+
self.alias = alias
|
|
322
|
+
|
|
323
|
+
def tablesUsedImmediate(self):
|
|
324
|
+
return [
|
|
325
|
+
'%s %s' % (self.table, self.alias)
|
|
326
|
+
if self.alias else self.table,
|
|
327
|
+
self.interTable,
|
|
328
|
+
]
|
|
329
|
+
|
|
330
|
+
def __sqlrepr__(self, db):
|
|
331
|
+
return '%s.%s = %s.%s' % (
|
|
332
|
+
self.interTable, self.joinColumn,
|
|
333
|
+
self.alias if self.alias else self.table, self.idName)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
class TableToId(sqlbuilder.SQLExpression):
|
|
337
|
+
def __init__(self, table, idName, idValue, alias):
|
|
338
|
+
self.table = table
|
|
339
|
+
self.idName = idName
|
|
340
|
+
self.idValue = idValue
|
|
341
|
+
self.alias = alias
|
|
342
|
+
|
|
343
|
+
def tablesUsedImmediate(self):
|
|
344
|
+
return [
|
|
345
|
+
'%s %s' % (self.table, self.alias)
|
|
346
|
+
if self.alias else self.table,
|
|
347
|
+
]
|
|
348
|
+
|
|
349
|
+
def __sqlrepr__(self, db):
|
|
350
|
+
return '%s.%s = %s' % (
|
|
351
|
+
self.alias if self.alias else self.table,
|
|
352
|
+
self.idName, self.idValue)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
class SQLJoinSelectResults(sresults.SelectResults):
|
|
356
|
+
def filter(self, filter_clause):
|
|
357
|
+
clause_tables = filter_clause.tablesUsed(None)
|
|
358
|
+
if self._SOSQLRelatedJoin_realSourceClass.sqlmeta.table \
|
|
359
|
+
in clause_tables:
|
|
360
|
+
tableClass = self._SOSQLRelatedJoin_realSourceClass.__name__
|
|
361
|
+
raise ValueError(
|
|
362
|
+
"Using table '%s' in the filter expression without an alias "
|
|
363
|
+
"could produce wrong SQL. Most probably you need "
|
|
364
|
+
"Alias(%s, '_SO_SQLRelatedJoin_OtherTable') instead."
|
|
365
|
+
% (tableClass, tableClass)
|
|
366
|
+
)
|
|
367
|
+
return sresults.SelectResults.filter(self, filter_clause)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
class SOSQLRelatedJoin(SORelatedJoin):
|
|
371
|
+
def performJoin(self, inst):
|
|
372
|
+
if inst.sqlmeta._perConnection:
|
|
373
|
+
conn = inst._connection
|
|
374
|
+
else:
|
|
375
|
+
conn = None
|
|
376
|
+
needAlias = self.soClass is self.otherClass
|
|
377
|
+
if needAlias:
|
|
378
|
+
source = sqlbuilder.Alias(
|
|
379
|
+
self.otherClass, '_SO_SQLRelatedJoin_OtherTable')
|
|
380
|
+
sresultsClass = SQLJoinSelectResults
|
|
381
|
+
else:
|
|
382
|
+
source = self.otherClass
|
|
383
|
+
sresultsClass = self.otherClass.SelectResultsClass
|
|
384
|
+
results = sresultsClass(
|
|
385
|
+
source,
|
|
386
|
+
sqlbuilder.AND(
|
|
387
|
+
OtherTableToJoin(
|
|
388
|
+
self.otherClass.sqlmeta.table,
|
|
389
|
+
self.otherClass.sqlmeta.idName,
|
|
390
|
+
self.intermediateTable, self.otherColumn,
|
|
391
|
+
'_SO_SQLRelatedJoin_OtherTable' if needAlias else '',
|
|
392
|
+
),
|
|
393
|
+
JoinToTable(
|
|
394
|
+
self.soClass.sqlmeta.table, self.soClass.sqlmeta.idName,
|
|
395
|
+
self.intermediateTable, self.joinColumn,
|
|
396
|
+
'_SO_SQLRelatedJoin_ThisTable' if needAlias else '',
|
|
397
|
+
),
|
|
398
|
+
TableToId(
|
|
399
|
+
self.soClass.sqlmeta.table, self.soClass.sqlmeta.idName,
|
|
400
|
+
inst.id,
|
|
401
|
+
'_SO_SQLRelatedJoin_ThisTable' if needAlias else '',
|
|
402
|
+
),
|
|
403
|
+
),
|
|
404
|
+
clauseTables=(
|
|
405
|
+
'%s _SO_SQLRelatedJoin_ThisTable' % self.soClass.sqlmeta.table
|
|
406
|
+
if needAlias else self.soClass.sqlmeta.table,
|
|
407
|
+
'%s _SO_SQLRelatedJoin_OtherTable' %
|
|
408
|
+
self.otherClass.sqlmeta.table
|
|
409
|
+
if needAlias else self.otherClass.sqlmeta.table,
|
|
410
|
+
self.intermediateTable,
|
|
411
|
+
),
|
|
412
|
+
connection=conn,
|
|
413
|
+
orderBy=self.orderBy,
|
|
414
|
+
)
|
|
415
|
+
results._SOSQLRelatedJoin_realSourceClass = self.otherClass
|
|
416
|
+
return results
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
class SQLRelatedJoin(RelatedJoin):
|
|
420
|
+
baseClass = SOSQLRelatedJoin
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
class SOSingleJoin(SOMultipleJoin):
|
|
424
|
+
|
|
425
|
+
def __init__(self, **kw):
|
|
426
|
+
self.makeDefault = kw.pop('makeDefault', False)
|
|
427
|
+
SOMultipleJoin.__init__(self, **kw)
|
|
428
|
+
|
|
429
|
+
def performJoin(self, inst):
|
|
430
|
+
if inst.sqlmeta._perConnection:
|
|
431
|
+
conn = inst._connection
|
|
432
|
+
else:
|
|
433
|
+
conn = None
|
|
434
|
+
pythonColumn = self._dbNameToPythonName()
|
|
435
|
+
results = self.otherClass.select(
|
|
436
|
+
getattr(self.otherClass.q, pythonColumn) == inst.id,
|
|
437
|
+
connection=conn
|
|
438
|
+
)
|
|
439
|
+
if results.count() == 0:
|
|
440
|
+
if not self.makeDefault:
|
|
441
|
+
return None
|
|
442
|
+
else:
|
|
443
|
+
kw = {self.soClass.sqlmeta.style.
|
|
444
|
+
instanceIDAttrToAttr(pythonColumn): inst}
|
|
445
|
+
# instanciating the otherClass with all
|
|
446
|
+
return self.otherClass(**kw)
|
|
447
|
+
else:
|
|
448
|
+
return results[0]
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
class SingleJoin(Join):
|
|
452
|
+
baseClass = SOSingleJoin
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
class SOManyToMany(object):
|
|
456
|
+
|
|
457
|
+
def __init__(self, soClass, name, join,
|
|
458
|
+
intermediateTable, joinColumn, otherColumn,
|
|
459
|
+
createJoinTable, **attrs):
|
|
460
|
+
self.name = name
|
|
461
|
+
self.intermediateTable = intermediateTable
|
|
462
|
+
self.joinColumn = joinColumn
|
|
463
|
+
self.otherColumn = otherColumn
|
|
464
|
+
self.createJoinTable = createJoinTable
|
|
465
|
+
self.soClass = self.otherClass = None
|
|
466
|
+
for name, value in attrs.items():
|
|
467
|
+
setattr(self, name, value)
|
|
468
|
+
classregistry.registry(
|
|
469
|
+
soClass.sqlmeta.registry).addClassCallback(
|
|
470
|
+
join, self._setOtherClass)
|
|
471
|
+
classregistry.registry(
|
|
472
|
+
soClass.sqlmeta.registry).addClassCallback(
|
|
473
|
+
soClass.__name__, self._setThisClass)
|
|
474
|
+
|
|
475
|
+
def _setThisClass(self, soClass):
|
|
476
|
+
self.soClass = soClass
|
|
477
|
+
if self.soClass and self.otherClass:
|
|
478
|
+
self._finishSet()
|
|
479
|
+
|
|
480
|
+
def _setOtherClass(self, otherClass):
|
|
481
|
+
self.otherClass = otherClass
|
|
482
|
+
if self.soClass and self.otherClass:
|
|
483
|
+
self._finishSet()
|
|
484
|
+
|
|
485
|
+
def _finishSet(self):
|
|
486
|
+
if self.intermediateTable is None:
|
|
487
|
+
names = [self.soClass.sqlmeta.table,
|
|
488
|
+
self.otherClass.sqlmeta.table]
|
|
489
|
+
names.sort()
|
|
490
|
+
self.intermediateTable = '%s_%s' % (names[0], names[1])
|
|
491
|
+
if not self.otherColumn:
|
|
492
|
+
self.otherColumn = self.soClass.sqlmeta.style.tableReference(
|
|
493
|
+
self.otherClass.sqlmeta.table)
|
|
494
|
+
if not self.joinColumn:
|
|
495
|
+
self.joinColumn = styles.getStyle(
|
|
496
|
+
self.soClass).tableReference(self.soClass.sqlmeta.table)
|
|
497
|
+
events.listen(self.event_CreateTableSignal,
|
|
498
|
+
self.soClass, events.CreateTableSignal)
|
|
499
|
+
events.listen(self.event_CreateTableSignal,
|
|
500
|
+
self.otherClass, events.CreateTableSignal)
|
|
501
|
+
self.clause = (
|
|
502
|
+
(self.otherClass.q.id
|
|
503
|
+
== sqlbuilder.Field(self.intermediateTable, self.otherColumn))
|
|
504
|
+
& (sqlbuilder.Field(self.intermediateTable, self.joinColumn)
|
|
505
|
+
== self.soClass.q.id))
|
|
506
|
+
|
|
507
|
+
def __get__(self, obj, type):
|
|
508
|
+
if obj is None:
|
|
509
|
+
return self
|
|
510
|
+
query = (
|
|
511
|
+
(self.otherClass.q.id
|
|
512
|
+
== sqlbuilder.Field(self.intermediateTable, self.otherColumn))
|
|
513
|
+
& (sqlbuilder.Field(self.intermediateTable, self.joinColumn)
|
|
514
|
+
== obj.id))
|
|
515
|
+
select = self.otherClass.select(query)
|
|
516
|
+
return _ManyToManySelectWrapper(obj, self, select)
|
|
517
|
+
|
|
518
|
+
def event_CreateTableSignal(self, soClass, connection, extra_sql,
|
|
519
|
+
post_funcs):
|
|
520
|
+
if self.createJoinTable:
|
|
521
|
+
post_funcs.append(self.event_CreateTableSignalPost)
|
|
522
|
+
|
|
523
|
+
def event_CreateTableSignalPost(self, soClass, connection):
|
|
524
|
+
if connection.tableExists(self.intermediateTable):
|
|
525
|
+
return
|
|
526
|
+
connection._SO_createJoinTable(self)
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
class ManyToMany(boundattributes.BoundFactory):
|
|
530
|
+
factory_class = SOManyToMany
|
|
531
|
+
__restrict_attributes__ = (
|
|
532
|
+
'join', 'intermediateTable',
|
|
533
|
+
'joinColumn', 'otherColumn', 'createJoinTable')
|
|
534
|
+
__unpackargs__ = ('join',)
|
|
535
|
+
|
|
536
|
+
# Default values:
|
|
537
|
+
intermediateTable = None
|
|
538
|
+
joinColumn = None
|
|
539
|
+
otherColumn = None
|
|
540
|
+
createJoinTable = True
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
class _ManyToManySelectWrapper(object):
|
|
544
|
+
|
|
545
|
+
def __init__(self, forObject, join, select):
|
|
546
|
+
self.forObject = forObject
|
|
547
|
+
self.join = join
|
|
548
|
+
self.select = select
|
|
549
|
+
|
|
550
|
+
def __getattr__(self, attr):
|
|
551
|
+
# @@: This passes through private variable access too... should it?
|
|
552
|
+
# Also magic methods, like __str__
|
|
553
|
+
return getattr(self.select, attr)
|
|
554
|
+
|
|
555
|
+
def __repr__(self):
|
|
556
|
+
return '<%s for: %s>' % (self.__class__.__name__, repr(self.select))
|
|
557
|
+
|
|
558
|
+
def __str__(self):
|
|
559
|
+
return str(self.select)
|
|
560
|
+
|
|
561
|
+
def __iter__(self):
|
|
562
|
+
return iter(self.select)
|
|
563
|
+
|
|
564
|
+
def __getitem__(self, key):
|
|
565
|
+
return self.select[key]
|
|
566
|
+
|
|
567
|
+
def add(self, obj):
|
|
568
|
+
obj._connection._SO_intermediateInsert(
|
|
569
|
+
self.join.intermediateTable,
|
|
570
|
+
self.join.joinColumn,
|
|
571
|
+
getID(self.forObject),
|
|
572
|
+
self.join.otherColumn,
|
|
573
|
+
getID(obj))
|
|
574
|
+
|
|
575
|
+
def remove(self, obj):
|
|
576
|
+
obj._connection._SO_intermediateDelete(
|
|
577
|
+
self.join.intermediateTable,
|
|
578
|
+
self.join.joinColumn,
|
|
579
|
+
getID(self.forObject),
|
|
580
|
+
self.join.otherColumn,
|
|
581
|
+
getID(obj))
|
|
582
|
+
|
|
583
|
+
def create(self, **kw):
|
|
584
|
+
obj = self.join.otherClass(**kw)
|
|
585
|
+
self.add(obj)
|
|
586
|
+
return obj
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
class SOOneToMany(object):
|
|
590
|
+
|
|
591
|
+
def __init__(self, soClass, name, join, joinColumn, **attrs):
|
|
592
|
+
self.soClass = soClass
|
|
593
|
+
self.name = name
|
|
594
|
+
self.joinColumn = joinColumn
|
|
595
|
+
for name, value in attrs.items():
|
|
596
|
+
setattr(self, name, value)
|
|
597
|
+
classregistry.registry(
|
|
598
|
+
soClass.sqlmeta.registry).addClassCallback(
|
|
599
|
+
join, self._setOtherClass)
|
|
600
|
+
|
|
601
|
+
def _setOtherClass(self, otherClass):
|
|
602
|
+
self.otherClass = otherClass
|
|
603
|
+
if not self.joinColumn:
|
|
604
|
+
self.joinColumn = styles.getStyle(
|
|
605
|
+
self.soClass).tableReference(self.soClass.sqlmeta.table)
|
|
606
|
+
self.clause = (
|
|
607
|
+
sqlbuilder.Field(self.otherClass.sqlmeta.table, self.joinColumn)
|
|
608
|
+
== self.soClass.q.id)
|
|
609
|
+
|
|
610
|
+
def __get__(self, obj, type):
|
|
611
|
+
if obj is None:
|
|
612
|
+
return self
|
|
613
|
+
query = (
|
|
614
|
+
sqlbuilder.Field(self.otherClass.sqlmeta.table, self.joinColumn)
|
|
615
|
+
== obj.id)
|
|
616
|
+
select = self.otherClass.select(query)
|
|
617
|
+
return _OneToManySelectWrapper(obj, self, select)
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
class OneToMany(boundattributes.BoundFactory):
|
|
621
|
+
factory_class = SOOneToMany
|
|
622
|
+
__restrict_attributes__ = (
|
|
623
|
+
'join', 'joinColumn')
|
|
624
|
+
__unpackargs__ = ('join',)
|
|
625
|
+
|
|
626
|
+
# Default values:
|
|
627
|
+
joinColumn = None
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
class _OneToManySelectWrapper(object):
|
|
631
|
+
|
|
632
|
+
def __init__(self, forObject, join, select):
|
|
633
|
+
self.forObject = forObject
|
|
634
|
+
self.join = join
|
|
635
|
+
self.select = select
|
|
636
|
+
|
|
637
|
+
def __getattr__(self, attr):
|
|
638
|
+
# @@: This passes through private variable access too... should it?
|
|
639
|
+
# Also magic methods, like __str__
|
|
640
|
+
return getattr(self.select, attr)
|
|
641
|
+
|
|
642
|
+
def __repr__(self):
|
|
643
|
+
return '<%s for: %s>' % (self.__class__.__name__, repr(self.select))
|
|
644
|
+
|
|
645
|
+
def __str__(self):
|
|
646
|
+
return str(self.select)
|
|
647
|
+
|
|
648
|
+
def __iter__(self):
|
|
649
|
+
return iter(self.select)
|
|
650
|
+
|
|
651
|
+
def __getitem__(self, key):
|
|
652
|
+
return self.select[key]
|
|
653
|
+
|
|
654
|
+
def create(self, **kw):
|
|
655
|
+
kw[self.join.joinColumn] = self.forObject.id
|
|
656
|
+
return self.join.otherClass(**kw)
|