|
@@ -1,469 +1,225 @@
|
1
|
1
|
# -*- coding: utf-8 -*-
|
|
2
|
+import os
|
|
3
|
+import logging as logger
|
2
|
4
|
|
3
|
|
-from sqlalchemy import * # TODO ajuster les classes à importer
|
4
|
|
-from Database.sqlsettings import SQLSettings as sqlsettings
|
|
5
|
+import sqlalchemy as sqla
|
5
|
6
|
from django.conf import settings
|
6
|
|
-import re
|
7
|
7
|
|
8
|
|
-import logging as logger #TODO hack to replace the standarts use of logging in django
|
|
8
|
+#Logger config
|
9
|
9
|
logger.getLogger().setLevel('DEBUG')
|
|
10
|
+#To be able to use dango confs
|
|
11
|
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Lodel.settings")
|
10
|
12
|
|
11
|
13
|
class SqlWrapper(object):
|
12
|
|
-
|
13
|
|
- def __init__(self, log=False):
|
|
14
|
+ """ A wrapper class to sqlalchemy
|
|
15
|
+ Usefull to provide a standart API
|
|
16
|
+ """
|
|
17
|
+
|
|
18
|
+ ##Read Engine
|
|
19
|
+ rengine = None
|
|
20
|
+ ##Write Engine
|
|
21
|
+ wengine = None
|
|
22
|
+
|
|
23
|
+ ##Read connection
|
|
24
|
+ rconn = None
|
|
25
|
+ ##Write connection
|
|
26
|
+ wconn = None
|
|
27
|
+
|
|
28
|
+ ##Configuration dict alias for class access
|
|
29
|
+ config=settings.LODEL2SQLWRAPPER
|
|
30
|
+ ##SqlAlchemy logging
|
|
31
|
+ sqla_logging = False
|
|
32
|
+
|
|
33
|
+ def __init__(self, alchemy_logs=None):
|
14
|
34
|
""" Instanciate a new SqlWrapper
|
15
|
|
- @param log bool: If true activate sqlalchemy logger
|
16
|
|
- """
|
17
|
|
- # TODO Passer ces deux éléments dans les settings django (au niveau de l'init de l'appli)
|
18
|
|
- self.read_engine = self.get_engine(sqlsettings.DB_READ_CONNECTION_NAME, log)
|
19
|
|
- self.write_engine = self.get_engine(sqlsettings.DB_WRITE_CONNECTION_NAME, log)
|
20
|
|
-
|
21
|
|
- def get_engine(self, connection_name, log=False):
|
22
|
|
- """ Crée un moteur logique sur une base de données
|
23
|
|
-
|
24
|
|
- @param connection_name str: Connection name as defined is django settings
|
25
|
|
- @param log bool: If true this engine will have logging enabled
|
26
|
|
- @return Engine
|
27
|
|
- """
|
28
|
|
-
|
29
|
|
- connection_params = settings.DATABASES[connection_name]
|
30
|
|
-
|
31
|
|
- if 'ENGINE' not in connection_params:
|
32
|
|
- raise NameError('Missing ENGINE in configuration file')
|
33
|
|
-
|
34
|
|
- if connection_params['ENGINE'] not in sqlsettings.dbms_list:
|
35
|
|
- raise NameError('Wrong ENGINE "'+connection_params['ENGINE']+'" in configuration files')
|
36
|
|
- dialect = connection_params['ENGINE']
|
37
|
|
-
|
38
|
|
- if 'driver' not in sqlsettings.dbms_list[dialect]:
|
39
|
|
- raise NameError('Wrong ENGINE "'+dialect+'" in configuration files')
|
40
|
|
- driver = sqlsettings.dbms_list[dialect]['driver']
|
41
|
|
-
|
42
|
|
- if 'NAME' not in connection_params:
|
43
|
|
- raise NameError('Missing database name in configuration files')
|
44
|
|
- database = connection_params['NAME']
|
45
|
|
-
|
46
|
|
- if 'USER' in connection_params:
|
47
|
|
- user = connection_params['USER']
|
48
|
|
- if 'PASSWORD' in connection_params:
|
49
|
|
- user += ':'+connection_params['PASSWORD']
|
|
35
|
+ @param alchemy_logs bool: If true activate sqlalchemy logger
|
|
36
|
+ """
|
|
37
|
+
|
|
38
|
+ if (alchemy_logs != None and \
|
|
39
|
+ bool(alchemy_logs) != self.__class__.sqla_logging):
|
|
40
|
+ #logging config changed for sqlalchemy
|
|
41
|
+ self.__class__.restart()
|
|
42
|
+
|
|
43
|
+ if not self.__class__.started():
|
|
44
|
+ self.__class__.start()
|
|
45
|
+
|
|
46
|
+ pass
|
|
47
|
+
|
|
48
|
+ @classmethod
|
|
49
|
+ def table(c, o):
|
|
50
|
+ """ Return a SqlAlchemy Table object
|
|
51
|
+ @param o str: Table name
|
|
52
|
+ """
|
|
53
|
+ if isinstance(o, str):
|
|
54
|
+ return sqla.Table(o, sqla.MetaData())
|
|
55
|
+ return None
|
|
56
|
+
|
|
57
|
+ @classmethod
|
|
58
|
+ def connect(c,read = None):
|
|
59
|
+
|
|
60
|
+ if read == None:
|
|
61
|
+ return c.connect(True) and c.coonect(False)
|
|
62
|
+ elif read:
|
|
63
|
+ c.rconn = c.rengine.connect()
|
50
|
64
|
else:
|
51
|
|
- user = ''
|
52
|
|
-
|
53
|
|
- hostname = connection_params['HOST'] if 'HOST' in connection_params else sqlsettings.DEFAULT_HOSTNAME
|
54
|
|
- port = connection_params['PORT'] if 'PORT' in connection_params else ''
|
55
|
|
- host = hostname if port=='' else '%s:%s' % (hostname, port)
|
|
65
|
+ c.wconn = c.wengine.connect()
|
|
66
|
+ return True #TODO attention c'est pas checké...
|
56
|
67
|
|
57
|
|
-
|
58
|
|
- if dialect == 'sqlite':
|
59
|
|
- connection_string = '%s+%s:///%s' % (dialect, driver, database)
|
|
68
|
+ @classmethod
|
|
69
|
+ def conn(c, read=True):
|
|
70
|
+ if read:
|
|
71
|
+ res = c.rconn
|
60
|
72
|
else:
|
61
|
|
- connection_string = '%s+%s://%s@%s/%s' % (dialect, driver, user, host, database)
|
62
|
|
-
|
63
|
|
- engine = create_engine(connection_string, encoding=sqlsettings.dbms_list[dialect]['encoding'], echo=log)
|
64
|
|
- return engine
|
65
|
|
-
|
66
|
|
- def get_read_engine(self):
|
67
|
|
- return self.read_engine
|
68
|
|
-
|
69
|
|
- def get_write_engine(self):
|
70
|
|
- return self.write_engine
|
71
|
|
-
|
72
|
|
- def execute(self, queries, action_type):
|
73
|
|
- """ Exécute une série de requêtes en base
|
74
|
|
-
|
75
|
|
- @param queries list: une liste de requêtes
|
76
|
|
- @param action_type str: le type d'action ( 'read'|'write' )
|
77
|
|
- @return List. Tableau de résultats sous forme de tuples (requête, résultat)
|
78
|
|
- """
|
79
|
|
-
|
80
|
|
- db = self.get_write_engine() if action_type==sqlsettings.ACTION_TYPE_WRITE else self.get_read_engine()
|
81
|
|
- connection = db.connect()
|
82
|
|
- transaction= connection.begin()
|
83
|
|
- result = []
|
84
|
|
-
|
85
|
|
- queries = queries if isinstance(queries, list) else [queries]
|
86
|
|
-
|
87
|
|
-
|
88
|
|
- try:
|
89
|
|
- for query in queries:
|
90
|
|
- result.append((query, connection.execute(query)))
|
91
|
|
- transaction.commit()
|
92
|
|
- connection.close()
|
93
|
|
- except:
|
94
|
|
- transaction.rollback()
|
95
|
|
- connection.close()
|
96
|
|
- result = None
|
97
|
|
- raise
|
98
|
|
-
|
99
|
|
- return result
|
100
|
|
-
|
101
|
|
- def create_foreign_key_constraint_object(self, localcol, remotecol, constraint_name=None):
|
102
|
|
- """ Génère une contrainte Clé étrangère
|
103
|
|
-
|
104
|
|
- @param localcol str: Colonne locale
|
105
|
|
- @param remotecol str: Colonne distante (syntaxe : Table.col)
|
106
|
|
- @param constraint_name str: Nom de la contrainte (par défaut : fk_localcol_remotecol)
|
107
|
|
-
|
108
|
|
- Returns:
|
109
|
|
- ForeignKeyConstraint.
|
110
|
|
- """
|
111
|
|
-
|
112
|
|
- foreignkeyobj = ForeignKeyConstraint([localcol], [remotecol], name=constraint_name)
|
113
|
|
- return foreignkeyobj
|
114
|
|
-
|
115
|
|
- def create_column_object(self, column_name, column_type, column_extra=None):
|
116
|
|
- """ Génère un objet colonne
|
117
|
|
-
|
118
|
|
- Exemple de dictionnaire pour column_extra : { "primarykey":True, "nullable":False, "default":"test" ... }
|
119
|
|
- @param column_name str: Nom de la colonne
|
120
|
|
- @param column_type str: Type de la colonne
|
121
|
|
- @param column_extra dict : Objet json contenant les paramètres optionnels comme PRIMARY KEY, NOT NULL, etc ...
|
122
|
|
-
|
123
|
|
- @return Column. La méthode renvoie "None" si l'opération échoue
|
124
|
|
-
|
125
|
|
- """
|
126
|
|
-
|
127
|
|
- # Traitement du type (mapping avec les types SQLAlchemy)
|
128
|
|
- # TODO créer une méthode qui fait un mapping plus complet
|
129
|
|
- column = None
|
130
|
|
- if column_type=='INTEGER':
|
131
|
|
- column = Column(column_name, INTEGER)
|
132
|
|
- elif 'VARCHAR' in column_type:
|
133
|
|
- check_length = re.search(re.compile('VARCHAR\(([\d]+)\)', re.IGNORECASE), column_type)
|
134
|
|
- column_length = int(check_length.groups()[0]) if check_length else None
|
135
|
|
- column = Column(column_name, VARCHAR(length=column_length))
|
136
|
|
- elif column_type=='TEXT':
|
137
|
|
- column = Column(column_name, TEXT)
|
138
|
|
- elif column_type=='DATE':
|
139
|
|
- column = Column(column_name, DATE)
|
140
|
|
- elif column_type=='BOOLEAN':
|
141
|
|
- column = Column(column_name, BOOLEAN)
|
142
|
|
-
|
143
|
|
- if column is not None and column_extra:
|
144
|
|
- if 'nullable' in column_extra:
|
145
|
|
- column.nullable = column_extra['nullable']
|
146
|
|
- if 'primarykey' in column_extra:
|
147
|
|
- column.primary_key = column_extra['primarykey']
|
148
|
|
- if 'default' in column_extra:
|
149
|
|
- column.default = ColumnDefault(column_extra['default'])
|
150
|
|
- if 'foreignkey' in column_extra:
|
151
|
|
- column.append_foreign_key(ForeignKey(column_extra['foreignkey']))
|
152
|
|
-
|
153
|
|
-
|
154
|
|
- return column
|
155
|
|
-
|
156
|
|
- def create_table(self, tableparams):
|
157
|
|
- """ Crée une nouvelle table
|
158
|
|
-
|
159
|
|
- Les paramètres de la tables sont passés via un dictionnaire dont voici la description :
|
160
|
|
- { 'name':'<nom_table>', 'columns': [ <col1>, <col2>, <coln> ], 'constraints': dict(...) }
|
161
|
|
- Avec <colX> : { 'name': '<name_colX>', 'type': '<type_colX>', 'extra': dict(...) }
|
162
|
|
- A noter que les types de col (<type_colX>) sont des chaines representant un type SQL (ex: "VARCHAR(50)")
|
163
|
|
- Les deux champs "name" et "columns" seulement sont obligatoires.
|
164
|
|
- Le champ "extra" de la définition des colonnes est facultatif.
|
165
|
|
-
|
166
|
|
- @args tableparams dict: Dictionnaire avec les paramétrages de la table
|
167
|
|
-
|
168
|
|
- @return bool. True si le process est allé au bout, False si on a rencontré une erreur.
|
169
|
|
-
|
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 Exception as e:
|
183
|
|
- # TODO Ajuster le code d'erreur à retourner
|
184
|
|
- logger.warning("Error creating table : "+str(e))
|
|
73
|
+ res = c.wconn
|
|
74
|
+
|
|
75
|
+ if res == None:
|
|
76
|
+ if not c.connect(read):
|
|
77
|
+ raise RuntimeError('Unable to connect to Db')
|
|
78
|
+ return self.conn(read)
|
|
79
|
+
|
|
80
|
+ return c.rconn
|
|
81
|
+
|
|
82
|
+ @classmethod
|
|
83
|
+ def rc(c): return c.conn(True)
|
|
84
|
+ @classmethod
|
|
85
|
+ def wc(c): return c.conn(False)
|
|
86
|
+
|
|
87
|
+ @classmethod
|
|
88
|
+ def start(c, sqlalogging = None):
|
|
89
|
+ """ Load engines
|
|
90
|
+ Open connections to databases
|
|
91
|
+ @param sqlalogging bool: overwrite class parameter about sqlalchemy logging
|
|
92
|
+ @return False if already started
|
|
93
|
+ """
|
|
94
|
+ c.checkConf()
|
|
95
|
+ if c.started():
|
|
96
|
+ logger.warning('Starting SqlWrapper but it is allready \
|
|
97
|
+started')
|
185
|
98
|
return False
|
186
|
99
|
|
|
100
|
+ c.rengine = c._getEngine(read=True, sqlaloggingi=None)
|
|
101
|
+ c.wengine = c._getEngine(read=False, sqlalogging=None)
|
|
102
|
+ return True
|
187
|
103
|
|
188
|
|
- def get_table(self, table_name, action_type=sqlsettings.ACTION_TYPE_WRITE):
|
189
|
|
- """ Récupère une table dans un objet Table
|
|
104
|
+ @classmethod
|
|
105
|
+ def stop(c): c.rengine = c.wengine = None; pass
|
|
106
|
+ @classmethod
|
|
107
|
+ def restart(c): c.stop(); c.start(); pass
|
|
108
|
+ @classmethod
|
|
109
|
+ def started(c): return (c.rengine != None and c.rengine != None)
|
|
110
|
+
|
|
111
|
+ @classmethod
|
|
112
|
+ def _sqllog(c,sqlalogging = None):
|
|
113
|
+ return bool(sqlalogging) if sqlalogging != None else c.sqlalogging
|
|
114
|
+
|
|
115
|
+ @classmethod
|
|
116
|
+ def _getEngine(c, read=True, sqlalogging = None):
|
|
117
|
+ """ Return a sqlalchemy engine
|
|
118
|
+ @param read bool: If True return the read engine, else
|
|
119
|
+ return the write one
|
|
120
|
+ @return a sqlachemy engine instance
|
|
121
|
+
|
|
122
|
+ @todo Put the check on db config in SqlWrapper.checkConf()
|
|
123
|
+ """
|
|
124
|
+ #Loading confs
|
|
125
|
+ connconf = 'dbread' if read else 'dbwrite'
|
|
126
|
+ dbconf = connconf if connconf in c.config['db'] else 'default'
|
|
127
|
+
|
|
128
|
+ if dbconf not in c.config['db']: #This check should not be here
|
|
129
|
+ raise NameError('Configuration error no db "'+dbconf+'" in \
|
|
130
|
+configuration files')
|
|
131
|
+
|
|
132
|
+ cfg = c.config['db'][dbconf] #Database config
|
|
133
|
+
|
|
134
|
+ edata = c.config['engines'][cfg['ENGINE']] #engine infos
|
|
135
|
+ conn_str = cfg['ENGINE']+'+'+edata['driver']+'://'
|
|
136
|
+
|
|
137
|
+ if cfg['ENGINE'] == 'sqlite':
|
|
138
|
+ #Sqlite connection string
|
|
139
|
+ conn_str = '%s+%s:///%s'%( cfg['ENGINE'],
|
|
140
|
+ edata['driver'],
|
|
141
|
+ cfg['NAME'])
|
|
142
|
+ else:
|
|
143
|
+ #Mysql and Postgres connection string
|
|
144
|
+ user = cfg['USER']
|
|
145
|
+ user += (':'+cfg['PASSWORD'] if 'PASSWORD' in cfg else '')
|
190
|
146
|
|
191
|
|
- @param table_name str: Nom de la table
|
192
|
|
- @param action_type str: Type d'action (read|write) (par défaut : "write")
|
193
|
|
- @return Table.
|
194
|
|
- """
|
195
|
|
- db = self.get_write_engine() if action_type == sqlsettings.ACTION_TYPE_WRITE else self.get_read_engine()
|
196
|
|
- metadata = MetaData()
|
197
|
|
-
|
198
|
|
- return Table(table_name, metadata, autoload=True, autoload_with=db)
|
199
|
|
-
|
|
147
|
+ if 'HOST' not in cfg:
|
|
148
|
+ logger.info('Not HOST in configuration, using \
|
|
149
|
+localhost')
|
|
150
|
+ host = 'localhost'
|
|
151
|
+ else:
|
|
152
|
+ host = cfg['HOST']
|
200
|
153
|
|
201
|
|
- def drop_table(self, table_name):
|
202
|
|
- """ Supprime une table
|
203
|
|
- @param table_name str: Nom de la table
|
204
|
|
- @return bool. True si le process est allé au bout. False si on a rencontré une erreur
|
205
|
|
- """
|
|
154
|
+ host += (':'+cfg['PORT'] if 'PORT' in cfg else '')
|
206
|
155
|
|
207
|
|
- try:
|
208
|
|
- db = self.get_write_engine().connect()
|
209
|
|
- metadata = MetaData()
|
210
|
|
- table = Table(table_name, metadata, autoload=True, autoload_with=db)
|
211
|
|
- # Pour le drop, on utilise checkfirst qui permet de demander la suppression préalable des contraintes liées à la table
|
212
|
|
- table.drop(db, checkfirst=True)
|
213
|
|
- db.close()
|
214
|
|
- return True
|
215
|
|
- except Exception as e:
|
216
|
|
- # TODO ajuster le code d'erreur
|
217
|
|
- logger.warning("Error droping table : "+str(e))
|
218
|
|
- return False
|
|
156
|
+ conn_str = '%s+%s://'%(cfg['ENGINE'], edata['driver'])
|
|
157
|
+ conn_str += '%s@%s/%s'%(user,host,cfg['NAME'])
|
219
|
158
|
|
|
159
|
+ return sqla.create_engine( conn_str, encoding=edata['encoding'],
|
|
160
|
+ echo=c._sqllog(sqlalogging))
|
220
|
161
|
|
221
|
|
- def get_querystring(self, action, dialect):
|
222
|
|
- """ Renvoit un modèle de requete selon un dialect et une action
|
|
162
|
+ @classmethod
|
|
163
|
+ def checkConf(c):
|
|
164
|
+ """ Class method that check the configuration
|
223
|
165
|
|
224
|
|
- @param action str: Peut être 'add_column' 'alter_column' 'drop_column'
|
225
|
|
- @param dialect str: Pour le moment seul 'default', 'mysql' et 'postgresql' sont supportés
|
226
|
|
-
|
227
|
|
- @return str: Un modèle de requète
|
228
|
|
- """
|
229
|
|
- string_dialect = dialect if dialect in sqlsettings.querystrings[action] else 'default'
|
230
|
|
- querystring = sqlsettings.querystrings[action][string_dialect]
|
231
|
|
- return querystring
|
232
|
|
-
|
233
|
|
- def add_column(self, table_name, column):
|
234
|
|
- """ Ajoute une colonne à une table existante
|
235
|
|
-
|
236
|
|
- @param table_name str: nom de la table
|
237
|
|
- @param column dict: la colonne - {"name":"<nom de la colonne>", "type":"<type de la colonne>"}
|
238
|
|
-
|
239
|
|
- @return bool. True si le process est allé au bout, False si on a rencontré une erreur
|
240
|
|
- """
|
241
|
|
-
|
242
|
|
- sqlquery = self.get_querystring('add_column', self.get_write_engine().dialect) % (table_name, column['name'], column['type'])
|
243
|
|
- sqlresult = self.execute(sqlquery, sqlsettings.ACTION_TYPE_WRITE)
|
244
|
|
- return True if sqlresult else False
|
245
|
|
-
|
246
|
|
- def alter_column(self, table_name, column):
|
247
|
|
- """ Modifie le type d'une colonne
|
248
|
|
-
|
249
|
|
- @param table_name str: nom de la table
|
250
|
|
- @param column_name dict: la colonne - {"name":"<nom de la colonne>","type":"<nouveau type de la colonne>"}
|
251
|
|
-
|
252
|
|
- @return bool. True si le process est allé au bout. False si on a rencontré une erreur
|
253
|
|
- """
|
254
|
|
-
|
255
|
|
- sqlquery = self.get_querystring('alter_column', self.get_write_engine().dialect) % (table_name, column['name'], column['type'])
|
256
|
|
- sqlresult = self.execute(sqlquery, sqlsettings.ACTION_TYPE_WRITE)
|
257
|
|
- return True if sqlresult else False
|
258
|
|
-
|
259
|
|
- def insert(self, table_name, newrecord):
|
260
|
|
- """ Insère un nouvel enregistrement
|
261
|
|
-
|
262
|
|
- @param table_name str: nom de la table
|
263
|
|
- @param newrecord dict: dictionnaire contenant les informations sur le nouvel enregistrement à insérer - {"column1":"value1", "column2":"value2", etc ...)
|
264
|
|
-
|
265
|
|
- @return int. Nombre de lignes insérées
|
266
|
|
- """
|
267
|
|
- sqlresult = self.execute(self.get_table(table_name).insert().values(newrecord), sqlsettings.ACTION_TYPE_WRITE)
|
268
|
|
- return sqlresult.rowcount
|
269
|
|
-
|
270
|
|
- def delete(self, table_name, whereclauses):
|
271
|
|
- """ Supprime un enregistrement
|
272
|
|
-
|
273
|
|
- @param table_name str: nom de la table
|
274
|
|
- @param whereclauses list: liste des conditions sur les enregistrements (sous forme de textes SQL)
|
275
|
|
-
|
276
|
|
- @return int. Nombre de lignes supprimées
|
277
|
|
- """
|
278
|
|
-
|
279
|
|
- deleteobject = self.get_table(table_name).delete()
|
280
|
|
-
|
281
|
|
- for whereclause in whereclauses:
|
282
|
|
- deleteobject = deleteobject.where(whereclause)
|
283
|
|
-
|
284
|
|
- sqlresult = self.execute(deleteobject, sqlsettings.ACTION_TYPE_WRITE)
|
285
|
|
- return sqlresult.rowcount
|
286
|
|
-
|
287
|
|
- def update(self, table_name, whereclauses, newvalues):
|
288
|
|
- """ Met à jour des enregistrements
|
289
|
|
-
|
290
|
|
- @param table_name str: nom de la table
|
291
|
|
- @param whereclauses list: liste des conditions sur les enregistrements (sous forme de textes SQL)
|
292
|
|
- @param newvalues dict: dictionnaire contenant les nouvelles valeurs à insérer - {"colonne1":"valeur1", "colonne2":"valeur2", etc ...}
|
293
|
|
-
|
294
|
|
- @return int. Nombre de lignes modifiées
|
295
|
|
-
|
296
|
|
- @throw DataError. Incompatibilité entre la valeur insérée et le type de colonne dans la table
|
297
|
|
- """
|
298
|
|
-
|
299
|
|
- table = self.get_table(table_name)
|
300
|
|
- update_object = table.update()
|
301
|
|
-
|
302
|
|
- updated_lines_count = 0
|
303
|
|
-
|
304
|
|
- try:
|
305
|
|
- for whereclause in whereclauses:
|
306
|
|
- update_object = update_object.where(whereclause)
|
307
|
|
-
|
308
|
|
- update_object = update_object.values(newvalues)
|
309
|
|
-
|
310
|
|
- sqlresult = self.execute(update_object, sqlsettings.ACTION_TYPE_WRITE)
|
311
|
|
- updated_lines_count = sqlresult.rowcount
|
312
|
|
-
|
313
|
|
- except DataError as e:
|
314
|
|
- # TODO Voir si on garde "-1" ou si on place un "None" ou un "False"
|
315
|
|
- logger.warning("Error updating database : "+str(e))
|
316
|
|
- updated_lines_count = -1
|
317
|
|
-
|
318
|
|
- return updated_lines_count
|
319
|
|
-
|
320
|
|
- def select(self, select_params):
|
321
|
|
- """ Récupère un jeu d'enregistrements
|
322
|
|
-
|
323
|
|
- Champs de select_params :
|
324
|
|
- - "what":"<liste des colonnes>",
|
325
|
|
- - "from":"<liste des tables>",
|
326
|
|
- - "where":"<liste des conditions>",
|
327
|
|
- - "distinct":True|False,
|
328
|
|
- - "join":{"left":"table_de_gauche.champ","right":"table_de_droite.champ", "options":{"outer":True|False}}
|
329
|
|
- - ...
|
330
|
|
-
|
331
|
|
- Les listes sont supportées sous deux formats :
|
332
|
|
- - texte SQL : "colonne1, colonne2, ..."
|
333
|
|
- - liste Python : ["colonne1", "colonne2", ...]
|
334
|
|
-
|
335
|
|
- @param select_params list: liste de dictionnaire permettant de caractériser la requête.
|
336
|
|
-
|
337
|
|
- @return list. Liste des dictionnaires représentant les différents enregistrements.
|
338
|
|
- """
|
339
|
|
-
|
340
|
|
- if not 'what' in select_params or not 'from' in select_params:
|
341
|
|
- # TODO Lever une exception à ce niveau
|
342
|
|
- raise
|
343
|
|
-
|
344
|
|
- query_what = select_params['what'] if isinstance(select_params['what'], list) else select_params['what'].replace(' ', '').split(',')
|
345
|
|
- query_from = select_params['from'] if isinstance(select_params['from'], list) else select_params['from'].replace(' ', '').split(',')
|
346
|
|
- query_where= select_params['where'] if isinstance(select_params['where'], list) else select_params['where'].replace(' ', '').split(',')
|
347
|
|
- query_distinct = select_params['distinct'] if 'distinct' in select_params else False
|
348
|
|
- query_limit = select_params['limit'] if 'limit' in select_params else False
|
349
|
|
- query_offset = select_params['offset'] if 'offset' in select_params else False
|
350
|
|
- query_orderby = select_params['order_by'] if 'order_by' in select_params else False
|
351
|
|
- query_groupby = select_params['group_by'] if 'group_by' in select_params else False
|
352
|
|
- query_having = select_params['having'] if 'having' in select_params else False
|
353
|
|
-
|
354
|
|
- columns_list = []
|
355
|
|
- if len(query_from) > 1:
|
356
|
|
- for column_id in query_what:
|
357
|
|
- # Il y a plusieurs tables, les colonnes sont donc nommées sur le format suivant : <nom de la table>.<nom du champ>
|
358
|
|
- column_id_array = column_id.split('.')
|
359
|
|
- columns_list.append(getattr(self.get_table(column_id_array[0]).c, column_id_array[1]))
|
|
166
|
+ Configuration looks like
|
|
167
|
+ - db (mandatory)
|
|
168
|
+ - ENGINE (mandatory)
|
|
169
|
+ - NAME (mandatory)
|
|
170
|
+ - USER
|
|
171
|
+ - PASSWORD
|
|
172
|
+ - engines (mandatory)
|
|
173
|
+ - driver (mandatory)
|
|
174
|
+ - encoding (mandatory)
|
|
175
|
+ - dbread (mandatory if no default db)
|
|
176
|
+ - dbwrite (mandatory if no default db)
|
|
177
|
+ """
|
|
178
|
+ return True #TODO desactivate because buggy
|
|
179
|
+ err = []
|
|
180
|
+ if 'db' not in c.config:
|
|
181
|
+ err.append('Missing "db" in configuration')
|
360
|
182
|
else:
|
361
|
|
- table = self.get_table(query_from[0])
|
362
|
|
-
|
363
|
|
-
|
364
|
|
- select_object = select(columns=columnslist)
|
365
|
|
-
|
366
|
|
- selected_lines = []
|
367
|
|
-
|
368
|
|
- try:
|
369
|
|
- # Traitement des différents paramètres de la requête
|
370
|
|
- for query_where_item in query_where:
|
371
|
|
- select_object = select_object.where(query_where_item)
|
372
|
|
-
|
373
|
|
- select_object = select_object.distinct(True) if query_distinct else select_object
|
374
|
|
- select_object = select_object.limit(query_limit) if query_limit else select_object
|
375
|
|
- select_object = select_object.offset(query_offset) if query_offset else select_object
|
376
|
|
-
|
377
|
|
- if query_orderby:
|
378
|
|
- for query_orderby_column, query_orderby_direction in query_orderby:
|
379
|
|
- select_object = select_object.order_by("%s %s" % (query_orderby_column, query_orderby_direction))
|
380
|
|
-
|
381
|
|
- #Traitement de la jointure
|
382
|
|
- if 'join' in select_params:
|
383
|
|
- select_join = self.create_join_object(select_params['join'])
|
384
|
|
- if select_join:
|
385
|
|
- select_object = select_object.select_from(select_join)
|
|
183
|
+ if 'default' not in c.config['db']:
|
|
184
|
+ if 'dbread' not in c.config:
|
|
185
|
+ err.append('Missing "dbread" in configuration and\
|
|
186
|
+ no "default" db declared')
|
|
187
|
+ if 'dbwrite' not in c.config:
|
|
188
|
+ err.append('Missing "dbwrite" in configuration and\
|
|
189
|
+ no "default" db declared')
|
|
190
|
+ for db in c.config['db']:
|
|
191
|
+ if 'ENGINE' not in db:
|
|
192
|
+ err.append('Missing "ENGINE" in database "'+db+'"')
|
386
|
193
|
else:
|
387
|
|
- raise # TODO Ajuster l'exception à lever (ici :"Données de jointure invalides ou manquantes")
|
388
|
|
-
|
389
|
|
- #Traitement du group_by
|
390
|
|
- if query_groupby:
|
391
|
|
- for group_by_clause in query_groupby:
|
392
|
|
- select_object = select_object.group_by(self.get_table_column_from_sql_string(group_by_clause))
|
393
|
|
-
|
394
|
|
- # Traitement du having
|
395
|
|
- if query_groupby and query_having:
|
396
|
|
- for having_clause in query_having:
|
397
|
|
- select_object = select_object.having(having_clause)
|
398
|
|
-
|
399
|
|
- # Exécution de la requête
|
400
|
|
- sqlresult = self.execute(select_object, sqlsettings.ACTION_TYPE_READ)
|
401
|
|
-
|
402
|
|
- # Transformation du résultat en une liste de dictionnaires
|
403
|
|
- records = sqlresult.fetchall()
|
404
|
|
- for record in records:
|
405
|
|
- selected_lines.append(dict(zip(record.keys(), record)))
|
406
|
|
- except Exception as e:
|
407
|
|
- logger.debug("Error selecting in database : "+str(e))
|
408
|
|
- selected_lines = None
|
409
|
|
-
|
410
|
|
- return selected_lines
|
411
|
|
-
|
412
|
|
- def get_table_column_from_sql_string(self,sql_string):
|
413
|
|
-
|
414
|
|
- sql_string_array = sql_string.split('.')
|
415
|
|
- table_name = sql_string_array[0]
|
416
|
|
- column_name = sql_string_array[1]
|
417
|
|
- table_object = self.get_table(table_name,sqlsettings.ACTION_TYPE_READ)
|
418
|
|
- column_object = getattr(table_object.c, column_name)
|
419
|
|
-
|
420
|
|
- return column_object
|
421
|
|
-
|
422
|
|
-
|
423
|
|
- def create_join_object(self, join_params):
|
424
|
|
- """Génère un objet "Jointure" pour le greffer dans une requête
|
425
|
|
-
|
426
|
|
- Format des dictionnaires de description des jointures :
|
427
|
|
- - Format1 liste de champs
|
428
|
|
- - "left":"table.champ",
|
429
|
|
- - "right":"table.champ",
|
430
|
|
- - "options":{"outer":True|False}
|
431
|
|
- - Format2 liste de champs
|
432
|
|
- - "left":{"table":"nom de la table","field":"nom du champ"},
|
433
|
|
- - "right":{"table":"nom de la table","field":"nom du champ"},
|
434
|
|
- - "options":{"outer":True|False}
|
435
|
|
-
|
436
|
|
- @param join_params (dict) : dictionnaire de données sur la jointure.
|
437
|
|
- @return join.
|
438
|
|
- """
|
439
|
|
-
|
440
|
|
- join_object = None
|
441
|
|
-
|
442
|
|
- if 'left' in join_params and 'right' in join_params:
|
443
|
|
- if isinstance(join_params['left'], dict):
|
444
|
|
- join_left_table_name = join_params['left']['table']
|
445
|
|
- join_left_field_name = join_params['left']['field']
|
446
|
|
- else:
|
447
|
|
- join_left_array = join_params['left'].split('.')
|
448
|
|
- join_left_table_name = join_left_array[0]
|
449
|
|
- join_left_field_name = join_left_array[1]
|
450
|
|
-
|
451
|
|
- if isinstance(join_params['right'], dict):
|
452
|
|
- join_right_table_name = join_params['right']['table']
|
453
|
|
- join_right_field_name = join_params['right']['field']
|
454
|
|
- else:
|
455
|
|
- join_right_array = join_params['right'].split('.')
|
456
|
|
- join_right_table_name = join_right_array[0]
|
457
|
|
- join_right_field_name = join_right_array[1]
|
458
|
|
-
|
459
|
|
- left_table = self.get_table(join_left_table_name, sqlsettings.ACTION_TYPE_READ)
|
460
|
|
- left_field = getattr(left_table.c, join_left_field_name)
|
461
|
|
-
|
462
|
|
- right_table = self.get_table(join_right_table_name, sqlsettings.ACTION_TYPE_READ)
|
463
|
|
- right_field = getattr(right_table.c, join_right_field_name)
|
464
|
|
-
|
465
|
|
- join_option_isouter = True if 'options' in join_params and 'outer' in join_params and join_params['outer']==True else False
|
466
|
|
-
|
467
|
|
- join_object = join(left_table, right_table, left_field == right_field,isouter=join_option_isouter)
|
|
194
|
+ if db['ENGINE'] != 'sqlite' and 'USER' not in db:
|
|
195
|
+ err.append('Missing "USER" in database "\
|
|
196
|
+'+db+'"')
|
|
197
|
+ if 'NAME' not in db:
|
|
198
|
+ err.append('Missing "NAME" in database "'+db+'"')
|
|
199
|
+
|
|
200
|
+ if len(c.config['engines']) == 0:
|
|
201
|
+ err.append('Missing "engines" in configuration')
|
|
202
|
+ for ename in c.config['engines']:
|
|
203
|
+ engine = c.config['engines'][ename]
|
|
204
|
+ if 'driver' not in engine:
|
|
205
|
+ err.append('Missing "driver" in database engine "\
|
|
206
|
+'+ename+'"')
|
|
207
|
+ if 'encoding' not in engine:
|
|
208
|
+ err.append('Missing "encoding" in database engine "\
|
|
209
|
+'+ename+'"')
|
|
210
|
+
|
|
211
|
+ if len(err)>0:
|
|
212
|
+ err_str = ""
|
|
213
|
+ for e in err:
|
|
214
|
+ err_str += e+"\n"
|
|
215
|
+ raise NameError('Configuration errors in LODEL2SQLWRAPPER\
|
|
216
|
+:'+err_str)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+ @property
|
|
220
|
+ def cfg(self):
|
|
221
|
+ """ Get the dict of options for the wrapper
|
|
222
|
+ Its an alias to the classes property SqlWrapper.config
|
|
223
|
+ @return a dict containing the Db settings"""
|
|
224
|
+ return self.__class__.config
|
468
|
225
|
|
469
|
|
- return join_object
|