|
@@ -0,0 +1,481 @@
|
|
1
|
+# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+from sqlalchemy import * # TODO ajuster les classes à importer
|
|
4
|
+from sql_settings import SqlSettings as sqlsettings
|
|
5
|
+from django.conf import settings
|
|
6
|
+import re
|
|
7
|
+
|
|
8
|
+class SqlWrapper(object):
|
|
9
|
+
|
|
10
|
+ def __init__(self):
|
|
11
|
+ # TODO Passer ces deux éléments dans les settings django (au niveau de l'init de l'appli)
|
|
12
|
+ self.read_engine = self.get_engine(sqlsettings.DB_READ_CONNECTION_NAME)
|
|
13
|
+ self.write_engine = self.get_engine(sqlsettings.DB_WRITE_CONNECTION_NAME)
|
|
14
|
+
|
|
15
|
+ def get_engine(self, connection_name):
|
|
16
|
+ """Crée un moteur logique sur une base de données
|
|
17
|
+
|
|
18
|
+ Args:
|
|
19
|
+ connection_name (str): Nom de la connexion telle que définie dans les settings django
|
|
20
|
+
|
|
21
|
+ Returns:
|
|
22
|
+ Engine.
|
|
23
|
+ """
|
|
24
|
+
|
|
25
|
+ connection_params = settings.DATABASES[connection_name]
|
|
26
|
+
|
|
27
|
+ dialect = None
|
|
28
|
+ for dbms in sqlsettings.dbms_list:
|
|
29
|
+ if dbms in connection_params['ENGINE']:
|
|
30
|
+ dialect=dbms
|
|
31
|
+ break
|
|
32
|
+
|
|
33
|
+ driver = sqlsettings.dbms_list[dialect]['driver']
|
|
34
|
+ username = connection_params['USER']
|
|
35
|
+ password = connection_params['PASSWORD']
|
|
36
|
+ hostname = connection_params['HOST'] if connection_params['HOST']!='' else sqlsettings.DEFAULT_HOSTNAME
|
|
37
|
+ port = connection_params['PORT']
|
|
38
|
+ host = hostname if port=='' else '%s:%s' % (hostname, port)
|
|
39
|
+ database = connection_params['NAME']
|
|
40
|
+
|
|
41
|
+ connection_string = '%s+%s://%s:%s@%s/%s' % (dialect, driver, username, password, host, database)
|
|
42
|
+ engine = create_engine(connection_string, encoding=sqlsettings.dbms_list[dialect]['encoding'], echo=True, poolclass=NullPool)
|
|
43
|
+ return engine
|
|
44
|
+
|
|
45
|
+ def get_read_engine(self):
|
|
46
|
+ return self.read_engine
|
|
47
|
+
|
|
48
|
+ def get_write_engine(self):
|
|
49
|
+ return self.write_engine
|
|
50
|
+
|
|
51
|
+ def execute(self, queries, action_type):
|
|
52
|
+ """Exécute une série de requêtes en base
|
|
53
|
+
|
|
54
|
+ Args:
|
|
55
|
+ queries (list): une liste de requêtes
|
|
56
|
+ action_type (str): le type d'action ('read'|'write')
|
|
57
|
+
|
|
58
|
+ Returns:
|
|
59
|
+ List. Tableau de résultats sous forme de tuples (requête, résultat)
|
|
60
|
+ """
|
|
61
|
+
|
|
62
|
+ db = self.get_write_engine() if action_type==sqlsettings.ACTION_TYPE_WRITE else self.get_read_engine()
|
|
63
|
+ connection = db.connect()
|
|
64
|
+ transaction= connection.begin()
|
|
65
|
+ result = []
|
|
66
|
+
|
|
67
|
+ queries = queries if isinstance(queries, list) else [queries]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+ try:
|
|
71
|
+ for query in queries:
|
|
72
|
+ result.append((query, connection.execute(query)))
|
|
73
|
+ transaction.commit()
|
|
74
|
+ connection.close()
|
|
75
|
+ except:
|
|
76
|
+ transaction.rollback()
|
|
77
|
+ connection.close()
|
|
78
|
+ result = None
|
|
79
|
+ raise
|
|
80
|
+
|
|
81
|
+ return result
|
|
82
|
+
|
|
83
|
+ def create_foreign_key_constraint_object(self, localcol, remotecol, constraint_name=None):
|
|
84
|
+ """Génère une contrainte Clé étrangère
|
|
85
|
+
|
|
86
|
+ Args:
|
|
87
|
+ localcol (str) : Colonne locale
|
|
88
|
+ remotecol (str) : Colonne distante (syntaxe : Table.col)
|
|
89
|
+ constraint_name (str) : Nom de la contrainte (par défaut : fk_localcol_remotecol)
|
|
90
|
+
|
|
91
|
+ Returns:
|
|
92
|
+ ForeignKeyConstraint.
|
|
93
|
+ """
|
|
94
|
+
|
|
95
|
+ foreignkeyobj = ForeignKeyConstraint([localcol], [remotecol], name=constraint_name)
|
|
96
|
+ return foreignkeyobj
|
|
97
|
+
|
|
98
|
+ def create_column_object(self, column_name, column_type, column_extra=None):
|
|
99
|
+ """Génère un objet colonne
|
|
100
|
+
|
|
101
|
+ Args:
|
|
102
|
+ column_name (str): Nom de la colonne
|
|
103
|
+ column_name (str): Type de la colonne
|
|
104
|
+ column_extra (objet) : Objet json contenant les paramètres optionnels comme PRIMARY KEY, NOT NULL, etc ...
|
|
105
|
+
|
|
106
|
+ Returns:
|
|
107
|
+ Column. La méthode renvoie "None" si l'opération échoue
|
|
108
|
+
|
|
109
|
+ Ex d'objet json "extra" :
|
|
110
|
+ {
|
|
111
|
+ "primarykey":True,
|
|
112
|
+ "nullable":False,
|
|
113
|
+ "default":"test" ...
|
|
114
|
+ }
|
|
115
|
+ """
|
|
116
|
+
|
|
117
|
+ # Traitement du type (mapping avec les types SQLAlchemy)
|
|
118
|
+ # TODO créer une méthode qui fait un mapping plus complet
|
|
119
|
+ column = None
|
|
120
|
+ if column_type=='INTEGER':
|
|
121
|
+ column = Column(column_name, INTEGER)
|
|
122
|
+ elif 'VARCHAR' in column_type:
|
|
123
|
+ check_length = re.search(re.compile('VARCHAR\(([\d]+)\)', re.IGNORECASE), column_type)
|
|
124
|
+ column_length = int(check_length.groups()[0]) if check_length else None
|
|
125
|
+ column = Column(column_name, VARCHAR(length=column_length))
|
|
126
|
+ elif column_type=='TEXT':
|
|
127
|
+ column = Column(column_name, TEXT)
|
|
128
|
+ elif column_type=='DATE':
|
|
129
|
+ column = Column(column_name, DATE)
|
|
130
|
+ elif column_type=='BOOLEAN':
|
|
131
|
+ column = Column(column_name, BOOLEAN)
|
|
132
|
+
|
|
133
|
+ if column and column_extra:
|
|
134
|
+ if 'nullable' in column_extra:
|
|
135
|
+ column.nullable = column_extra['nullable']
|
|
136
|
+ if 'primarykey' in column_extra:
|
|
137
|
+ column.primary_key = column_extra['primarykey']
|
|
138
|
+ if 'default' in column_extra:
|
|
139
|
+ column.default = column_extra['default']
|
|
140
|
+ if 'foreingkey' in column_extra:
|
|
141
|
+ column.append_foreign_key(ForeignKey(column_extra['foreignkey']))
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+ return column
|
|
145
|
+
|
|
146
|
+ def create_table(self, tableparams):
|
|
147
|
+ """Crée une nouvelle table
|
|
148
|
+ Args:
|
|
149
|
+ tableparams (object): Objet Json contenant les paramétrages de la table
|
|
150
|
+
|
|
151
|
+ Returns:
|
|
152
|
+ bool. True si le process est allé au bout, False si on a rencontré une erreur.
|
|
153
|
+
|
|
154
|
+ Le json de paramètres de table est construit suivant le modèle :
|
|
155
|
+ {
|
|
156
|
+ 'name':'<nom de la table>',
|
|
157
|
+ 'columns':[
|
|
158
|
+ {
|
|
159
|
+ 'name':"<nom de la colonne 1>",
|
|
160
|
+ 'type':"<type de la colonne 1, ex: VARCHAR(50)>",
|
|
161
|
+ 'extra': "indications supplémentaires sous forme d'objet JSON"
|
|
162
|
+ }
|
|
163
|
+ ],
|
|
164
|
+ 'constraints':{},
|
|
165
|
+ ...
|
|
166
|
+ }
|
|
167
|
+
|
|
168
|
+ Les deux champs "name" et "columns" seulement sont obligatoires.
|
|
169
|
+ Le champ "extra" de la définition des colonnes est facultatif.
|
|
170
|
+ """
|
|
171
|
+
|
|
172
|
+ metadata = MetaData()
|
|
173
|
+ table = Table(tableparams['name'], metadata)
|
|
174
|
+ columns = tableparams['columns']
|
|
175
|
+ for column in columns:
|
|
176
|
+ column_extra = column['extra'] if 'extra' in column else None
|
|
177
|
+ table.append_column(self.create_column_object(column['name'], column['type'], column_extra))
|
|
178
|
+
|
|
179
|
+ try:
|
|
180
|
+ table.create(self.get_write_engine())
|
|
181
|
+ return True
|
|
182
|
+ except:
|
|
183
|
+ # TODO Ajuster le code d'erreur à retourner
|
|
184
|
+ return False
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+ def get_table(self, table_name, action_type=sqlsettings.ACTION_TYPE_WRITE):
|
|
188
|
+ """Récupère une table dans un objet Table
|
|
189
|
+
|
|
190
|
+ Args:
|
|
191
|
+ table_name (str): Nom de la table
|
|
192
|
+ action_type (str): Type d'action (read|write) (par défaut : "write")
|
|
193
|
+ Returns:
|
|
194
|
+ Table.
|
|
195
|
+ """
|
|
196
|
+ db = self.get_write_engine() if action_type == sqlsettings.ACTION_TYPE_WRITE else self.get_read_engine()
|
|
197
|
+ metadata = MetaData()
|
|
198
|
+
|
|
199
|
+ return Table(table_name, metadata, autoload=True, autoload_with=db)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+ def drop_table(self, table_name):
|
|
203
|
+ """Supprime une table
|
|
204
|
+
|
|
205
|
+ Args:
|
|
206
|
+ table_name (str): Nom de la table
|
|
207
|
+
|
|
208
|
+ Returns:
|
|
209
|
+ bool. True si le process est allé au bout. False si on a rencontré une erreur
|
|
210
|
+ """
|
|
211
|
+
|
|
212
|
+ try:
|
|
213
|
+ db = self.get_write_engine().connect()
|
|
214
|
+ metadata = MetaData()
|
|
215
|
+ table = Table(table_name, metadata, autoload=True, autoload_with=db)
|
|
216
|
+ # Pour le drop, on utilise checkfirst qui permet de demander la suppression préalable des contraintes liées à la table
|
|
217
|
+ table.drop(db, checkfirst=True)
|
|
218
|
+ db.close()
|
|
219
|
+ return True
|
|
220
|
+ except:
|
|
221
|
+ # TODO ajuster le code d'erreur
|
|
222
|
+ return False
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+ def get_querystring(self, action, dialect):
|
|
226
|
+ string_dialect = dialect if dialect in sqlsettings.querystrings[action] else 'default'
|
|
227
|
+ querystring = sqlsettings.querystrings[action][string_dialect]
|
|
228
|
+ return querystring
|
|
229
|
+
|
|
230
|
+ def add_column(self, table_name, column):
|
|
231
|
+ """Ajoute une colonne à une table existante
|
|
232
|
+
|
|
233
|
+ Args:
|
|
234
|
+ table_name (str): nom de la table
|
|
235
|
+ column (object): colonne à rajouter sous forme d'objet python - {"name":"<nom de la colonne>", "type":"<type de la colonne>"}
|
|
236
|
+
|
|
237
|
+ Returns:
|
|
238
|
+ bool. True si le process est allé au bout, False si on a rencontré une erreur
|
|
239
|
+ """
|
|
240
|
+
|
|
241
|
+ sqlquery = self.get_querystring('add_column', self.get_write_engine().dialect) % (table_name, column['name'], column['type'])
|
|
242
|
+ sqlresult = self.execute(sqlquery, sqlsettings.ACTION_TYPE_WRITE)
|
|
243
|
+ return True if sqlresult else False
|
|
244
|
+
|
|
245
|
+ def alter_column(self, table_name, column):
|
|
246
|
+ """Modifie le type d'une colonne
|
|
247
|
+
|
|
248
|
+ Args:
|
|
249
|
+ table_name (str): nom de la table
|
|
250
|
+ column_name (object): colonne passée sous forme d'objet python - {"name":"<nom de la colonne>","type":"<nouveau type de la colonne>"}
|
|
251
|
+
|
|
252
|
+ Returns:
|
|
253
|
+ bool. True si le process est allé au bout. False si on a rencontré une erreur
|
|
254
|
+ """
|
|
255
|
+
|
|
256
|
+ sqlquery = self.get_querystring('alter_column', self.get_write_engine().dialect) % (table_name, column['name'], column['type'])
|
|
257
|
+ sqlresult = self.execute(sqlquery, sqlsettings.ACTION_TYPE_WRITE)
|
|
258
|
+ return True if sqlresult else False
|
|
259
|
+
|
|
260
|
+ def insert(self, table_name, newrecord):
|
|
261
|
+ """Insère un nouvel enregistrement
|
|
262
|
+
|
|
263
|
+ Args:
|
|
264
|
+ table_name (str): nom de la table
|
|
265
|
+ newrecord (dict): objet python contenant le nouvel enregistrement à insérer - {"column1":"value1", "column2":"value2", etc ...)
|
|
266
|
+
|
|
267
|
+ Returns:
|
|
268
|
+ int. Nombre de lignes insérées
|
|
269
|
+ """
|
|
270
|
+ sqlresult = self.execute(self.get_table(table_name).insert().values(newrecord), sqlsettings.ACTION_TYPE_WRITE)
|
|
271
|
+ return sqlresult.rowcount
|
|
272
|
+
|
|
273
|
+ def delete(self, table_name, whereclauses):
|
|
274
|
+ """Supprime un enregistrement
|
|
275
|
+
|
|
276
|
+ Args:
|
|
277
|
+ table_name (str): nom de la table
|
|
278
|
+ whereclauses (list): liste des conditions sur les enregistrements (sous forme de textes SQL)
|
|
279
|
+
|
|
280
|
+ Returns:
|
|
281
|
+ int. Nombre de lignes supprimées
|
|
282
|
+ """
|
|
283
|
+
|
|
284
|
+ deleteobject = self.get_table(table_name).delete()
|
|
285
|
+
|
|
286
|
+ for whereclause in whereclauses:
|
|
287
|
+ deleteobject = deleteobject.where(whereclause)
|
|
288
|
+
|
|
289
|
+ sqlresult = self.execute(deleteobject, sqlsettings.ACTION_TYPE_WRITE)
|
|
290
|
+ return sqlresult.rowcount
|
|
291
|
+
|
|
292
|
+ def update(self, table_name, whereclauses, newvalues):
|
|
293
|
+ """Met à jour des enregistrements
|
|
294
|
+
|
|
295
|
+ Args:
|
|
296
|
+ table_name (str): nom de la table
|
|
297
|
+ whereclauses (list): liste des conditions sur les enregistrements (sous forme de textes SQL)
|
|
298
|
+ newvalues (dict): objet python contenant les nouvelles valeurs à insérer - {"colonne1":"valeur1", "colonne2":"valeur2", etc ...}
|
|
299
|
+
|
|
300
|
+ Returns:
|
|
301
|
+ int. Nombre de lignes modifiées
|
|
302
|
+
|
|
303
|
+ Raises:
|
|
304
|
+ DataError. Incompatibilité entre la valeur insérée et le type de colonne dans la table
|
|
305
|
+ """
|
|
306
|
+
|
|
307
|
+ table = self.get_table(table_name)
|
|
308
|
+ update_object = table.update()
|
|
309
|
+
|
|
310
|
+ updated_lines_count = 0
|
|
311
|
+
|
|
312
|
+ try:
|
|
313
|
+ for whereclause in whereclauses:
|
|
314
|
+ update_object = update_object.where(whereclause)
|
|
315
|
+
|
|
316
|
+ update_object = update_object.values(newvalues)
|
|
317
|
+
|
|
318
|
+ sqlresult = self.execute(update_object, sqlsettings.ACTION_TYPE_WRITE)
|
|
319
|
+ updated_lines_count = sqlresult.rowcount
|
|
320
|
+
|
|
321
|
+ except DataError:
|
|
322
|
+ # TODO Voir si on garde "-1" ou si on place un "None" ou un "False"
|
|
323
|
+ updated_lines_count = -1
|
|
324
|
+
|
|
325
|
+ return updated_lines_count
|
|
326
|
+
|
|
327
|
+ def select(self, select_params):
|
|
328
|
+ """Récupère un jeu d'enregistrements
|
|
329
|
+
|
|
330
|
+ Args:
|
|
331
|
+ select_params (list): liste d'objets permettant de caractériser la requête.
|
|
332
|
+ {
|
|
333
|
+ "what":"<liste des colonnes>",
|
|
334
|
+ "from":"<liste des tables>",
|
|
335
|
+ "where":"<liste des conditions>",
|
|
336
|
+ "distinct":True|False,
|
|
337
|
+ "join":{"left":"table_de_gauche.champ","right":"table_de_droite.champ", "options":{"outer":True|False}}
|
|
338
|
+ ...
|
|
339
|
+ }
|
|
340
|
+
|
|
341
|
+ Les listes sont supportées sous deux formats :
|
|
342
|
+ - texte SQL : "colonne1, colonne2, ..."
|
|
343
|
+ - liste Python : ["colonne1", "colonne2", ...]
|
|
344
|
+
|
|
345
|
+ Returns:
|
|
346
|
+ list. Liste des dictionnaires représentant les différents enregistrements.
|
|
347
|
+ """
|
|
348
|
+
|
|
349
|
+ if not 'what' in select_params or not 'from' in select_params:
|
|
350
|
+ # TODO Lever une exception à ce niveau
|
|
351
|
+ raise
|
|
352
|
+
|
|
353
|
+ query_what = select_params['what'] if isinstance(select_params['what'], list) else select_params['what'].replace(' ', '').split(',')
|
|
354
|
+ query_from = select_params['from'] if isinstance(select_params['from'], list) else select_params['from'].replace(' ', '').split(',')
|
|
355
|
+ query_where= select_params['where'] if isinstance(select_params['where'], list) else select_params['where'].replace(' ', '').split(',')
|
|
356
|
+ query_distinct = select_params['distinct'] if 'distinct' in select_params else False
|
|
357
|
+ query_limit = select_params['limit'] if 'limit' in select_params else False
|
|
358
|
+ query_offset = select_params['offset'] if 'offset' in select_params else False
|
|
359
|
+ query_orderby = select_params['order_by'] if 'order_by' in select_params else False
|
|
360
|
+ query_groupby = select_params['group_by'] if 'group_by' in select_params else False
|
|
361
|
+ query_having = select_params['having'] if 'having' in select_params else False
|
|
362
|
+
|
|
363
|
+ columns_list = []
|
|
364
|
+ if len(query_from) > 1:
|
|
365
|
+ for column_id in query_what:
|
|
366
|
+ # Il y a plusieurs tables, les colonnes sont donc nommées sur le format suivant : <nom de la table>.<nom du champ>
|
|
367
|
+ column_id_array = column_id.split('.')
|
|
368
|
+ columns_list.append(getattr(self.get_table(column_id_array[0]).c, column_id_array[1]))
|
|
369
|
+ else:
|
|
370
|
+ table = self.get_table(query_from[0])
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+ select_object = select(columns=columnslist)
|
|
374
|
+
|
|
375
|
+ selected_lines = []
|
|
376
|
+
|
|
377
|
+ try:
|
|
378
|
+ # Traitement des différents paramètres de la requête
|
|
379
|
+ for query_where_item in query_where:
|
|
380
|
+ select_object = select_object.where(query_where_item)
|
|
381
|
+
|
|
382
|
+ select_object = select_object.distinct(True) if query_distinct else select_object
|
|
383
|
+ select_object = select_object.limit(query_limit) if query_limit else select_object
|
|
384
|
+ select_object = select_object.offset(query_offset) if query_offset else select_object
|
|
385
|
+
|
|
386
|
+ if query_orderby:
|
|
387
|
+ for query_orderby_column, query_orderby_direction in query_orderby:
|
|
388
|
+ select_object = select_object.order_by("%s %s" % (query_orderby_column, query_orderby_direction))
|
|
389
|
+
|
|
390
|
+ #Traitement de la jointure
|
|
391
|
+ if 'join' in select_params:
|
|
392
|
+ select_join = self.create_join_object(select_params['join'])
|
|
393
|
+ if select_join:
|
|
394
|
+ select_object = select_object.select_from(select_join)
|
|
395
|
+ else:
|
|
396
|
+ raise # TODO Ajuster l'exception à lever (ici :"Données de jointure invalides ou manquantes")
|
|
397
|
+
|
|
398
|
+ #Traitement du group_by
|
|
399
|
+ if query_groupby:
|
|
400
|
+ for group_by_clause in query_groupby:
|
|
401
|
+ select_object = select_object.group_by(self.get_table_column_from_sql_string(group_by_clause))
|
|
402
|
+
|
|
403
|
+ # Traitement du having
|
|
404
|
+ if query_groupby and query_having:
|
|
405
|
+ for having_clause in query_having:
|
|
406
|
+ select_object = select_object.having(having_clause)
|
|
407
|
+
|
|
408
|
+ # Exécution de la requête
|
|
409
|
+ sqlresult = self.execute(select_object, sqlsettings.ACTION_TYPE_READ)
|
|
410
|
+
|
|
411
|
+ # Transformation du résultat en une liste de dictionnaires
|
|
412
|
+ records = sqlresult.fetchall()
|
|
413
|
+ for record in records:
|
|
414
|
+ selected_lines.append(dict(zip(record.keys(), record)))
|
|
415
|
+ except:
|
|
416
|
+ selected_lines = None
|
|
417
|
+
|
|
418
|
+ return selected_lines
|
|
419
|
+
|
|
420
|
+ def get_table_column_from_sql_string(self,sql_string):
|
|
421
|
+
|
|
422
|
+ sql_string_array = sql_string.split('.')
|
|
423
|
+ table_name = sql_string_array[0]
|
|
424
|
+ column_name = sql_string_array[1]
|
|
425
|
+ table_object = self.get_table(table_name,sqlsettings.ACTION_TYPE_READ)
|
|
426
|
+ column_object = getattr(table_object.c, column_name)
|
|
427
|
+
|
|
428
|
+ return column_object
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+ def create_join_object(self, join_params):
|
|
432
|
+ """Génère un objet "Jointure" pour le greffer dans une requête
|
|
433
|
+
|
|
434
|
+ Args:
|
|
435
|
+ join_params (dict) : dictionnaire de données sur la jointure.
|
|
436
|
+ On peut avoir les deux formats suivants :
|
|
437
|
+ {
|
|
438
|
+ "left":"table.champ",
|
|
439
|
+ "right":"table.champ",
|
|
440
|
+ "options":{"outer":True|False}
|
|
441
|
+ }
|
|
442
|
+ ou
|
|
443
|
+ {
|
|
444
|
+ "left":{"table":"nom de la table","field":"nom du champ"},
|
|
445
|
+ "right":{"table":"nom de la table","field":"nom du champ"},
|
|
446
|
+ "options":{"outer":True|False}
|
|
447
|
+ }
|
|
448
|
+ Returns:
|
|
449
|
+ join.
|
|
450
|
+ """
|
|
451
|
+
|
|
452
|
+ join_object = None
|
|
453
|
+
|
|
454
|
+ if 'left' in join_params and 'right' in join_params:
|
|
455
|
+ if isinstance(join_params['left'], dict):
|
|
456
|
+ join_left_table_name = join_params['left']['table']
|
|
457
|
+ join_left_field_name = join_params['left']['field']
|
|
458
|
+ else:
|
|
459
|
+ join_left_array = join_params['left'].split('.')
|
|
460
|
+ join_left_table_name = join_left_array[0]
|
|
461
|
+ join_left_field_name = join_left_array[1]
|
|
462
|
+
|
|
463
|
+ if isinstance(join_params['right'], dict):
|
|
464
|
+ join_right_table_name = join_params['right']['table']
|
|
465
|
+ join_right_field_name = join_params['right']['field']
|
|
466
|
+ else:
|
|
467
|
+ join_right_array = join_params['right'].split('.')
|
|
468
|
+ join_right_table_name = join_right_array[0]
|
|
469
|
+ join_right_field_name = join_right_array[1]
|
|
470
|
+
|
|
471
|
+ left_table = self.get_table(join_left_table_name, sqlsettings.ACTION_TYPE_READ)
|
|
472
|
+ left_field = getattr(left_table.c, join_left_field_name)
|
|
473
|
+
|
|
474
|
+ right_table = self.get_table(join_right_table_name, sqlsettings.ACTION_TYPE_READ)
|
|
475
|
+ right_field = getattr(right_table.c, join_right_field_name)
|
|
476
|
+
|
|
477
|
+ join_option_isouter = True if 'options' in join_params and 'outer' in join_params and join_params['outer']==True else False
|
|
478
|
+
|
|
479
|
+ join_object = join(left_table, right_table, left_field == right_field,isouter=join_option_isouter)
|
|
480
|
+
|
|
481
|
+ return join_object
|