Bug 611 Fix unicode parsing at database
[osm/RO.git] / osm_ro / db_base.py
1 # -*- coding: utf-8 -*-
2
3 ##
4 # Copyright 2015 Telefonica Investigacion y Desarrollo, S.A.U.
5 # This file is part of openmano
6 # All Rights Reserved.
7 #
8 # Licensed under the Apache License, Version 2.0 (the "License"); you may
9 # not use this file except in compliance with the License. You may obtain
10 # a copy of the License at
11 #
12 # http://www.apache.org/licenses/LICENSE-2.0
13 #
14 # Unless required by applicable law or agreed to in writing, software
15 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17 # License for the specific language governing permissions and limitations
18 # under the License.
19 #
20 # For those usages not covered by the Apache License, Version 2.0 please
21 # contact with: nfvlabs@tid.es
22 ##
23
24 '''
25 Base class for openmano database manipulation
26 '''
27 __author__="Alfonso Tierno"
28 __date__ ="$4-Apr-2016 10:05:01$"
29
30 import MySQLdb as mdb
31 import uuid as myUuid
32 import utils as af
33 import json
34 #import yaml
35 import time
36 import logging
37 import datetime
38 from jsonschema import validate as js_v, exceptions as js_e
39
40 from .http_tools import errors as httperrors
41
42 def _check_valid_uuid(uuid):
43 id_schema = {"type" : "string", "pattern": "^[a-fA-F0-9]{8}(-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}$"}
44 id_schema2 = {"type" : "string", "pattern": "^[a-fA-F0-9]{32}$"}
45 try:
46 js_v(uuid, id_schema)
47 return True
48 except js_e.ValidationError:
49 try:
50 js_v(uuid, id_schema2)
51 return True
52 except js_e.ValidationError:
53 return False
54 return False
55
56 def _convert_datetime2str(var):
57 '''Converts a datetime variable to a string with the format '%Y-%m-%dT%H:%i:%s'
58 It enters recursively in the dict var finding this kind of variables
59 '''
60 if type(var) is dict:
61 for k,v in var.items():
62 if type(v) is datetime.datetime:
63 var[k]= v.strftime('%Y-%m-%dT%H:%M:%S')
64 elif type(v) is dict or type(v) is list or type(v) is tuple:
65 _convert_datetime2str(v)
66 if len(var) == 0: return True
67 elif type(var) is list or type(var) is tuple:
68 for v in var:
69 _convert_datetime2str(v)
70
71 def _convert_bandwidth(data, reverse=False, logger=None):
72 '''Check the field bandwidth recursivelly and when found, it removes units and convert to number
73 It assumes that bandwidth is well formed
74 Attributes:
75 'data': dictionary bottle.FormsDict variable to be checked. None or empty is consideted valid
76 'reverse': by default convert form str to int (Mbps), if True it convert from number to units
77 Return:
78 None
79 '''
80 if type(data) is dict:
81 for k in data.keys():
82 if type(data[k]) is dict or type(data[k]) is tuple or type(data[k]) is list:
83 _convert_bandwidth(data[k], reverse, logger)
84 if "bandwidth" in data:
85 try:
86 value=str(data["bandwidth"])
87 if not reverse:
88 pos = value.find("bps")
89 if pos>0:
90 if value[pos-1]=="G": data["bandwidth"] = int(data["bandwidth"][:pos-1]) * 1000
91 elif value[pos-1]=="k": data["bandwidth"]= int(data["bandwidth"][:pos-1]) / 1000
92 else: data["bandwidth"]= int(data["bandwidth"][:pos-1])
93 else:
94 value = int(data["bandwidth"])
95 if value % 1000 == 0: data["bandwidth"]=str(value/1000) + " Gbps"
96 else: data["bandwidth"]=str(value) + " Mbps"
97 except:
98 if logger:
99 logger.error("convert_bandwidth exception for type '%s' data '%s'", type(data["bandwidth"]), data["bandwidth"])
100 return
101 if type(data) is tuple or type(data) is list:
102 for k in data:
103 if type(k) is dict or type(k) is tuple or type(k) is list:
104 _convert_bandwidth(k, reverse, logger)
105
106 def _convert_str2boolean(data, items):
107 '''Check recursively the content of data, and if there is an key contained in items, convert value from string to boolean
108 Done recursively
109 Attributes:
110 'data': dictionary variable to be checked. None or empty is considered valid
111 'items': tuple of keys to convert
112 Return:
113 None
114 '''
115 if type(data) is dict:
116 for k in data.keys():
117 if type(data[k]) is dict or type(data[k]) is tuple or type(data[k]) is list:
118 _convert_str2boolean(data[k], items)
119 if k in items:
120 if type(data[k]) is str:
121 if data[k]=="false" or data[k]=="False" or data[k]=="0": data[k]=False
122 elif data[k]=="true" or data[k]=="True" or data[k]=="1": data[k]=True
123 elif type(data[k]) is int:
124 if data[k]==0: data[k]=False
125 elif data[k]==1: data[k]=True
126 if type(data) is tuple or type(data) is list:
127 for k in data:
128 if type(k) is dict or type(k) is tuple or type(k) is list:
129 _convert_str2boolean(k, items)
130
131 class db_base_Exception(httperrors.HttpMappedError):
132 '''Common Exception for all database exceptions'''
133
134 def __init__(self, message, http_code=httperrors.Bad_Request):
135 super(db_base_Exception, self).__init__(message, http_code)
136
137 class db_base():
138 tables_with_created_field=()
139
140 def __init__(self, host=None, user=None, passwd=None, database=None, log_name='db', log_level=None):
141 self.host = host
142 self.user = user
143 self.passwd = passwd
144 self.database = database
145 self.con = None
146 self.log_level=log_level
147 self.logger = logging.getLogger(log_name)
148 if self.log_level:
149 self.logger.setLevel( getattr(logging, log_level) )
150
151 def connect(self, host=None, user=None, passwd=None, database=None):
152 '''Connect to specific data base.
153 The first time a valid host, user, passwd and database must be provided,
154 Following calls can skip this parameters
155 '''
156 try:
157 if host: self.host = host
158 if user: self.user = user
159 if passwd: self.passwd = passwd
160 if database: self.database = database
161
162 self.con = mdb.connect(self.host, self.user, self.passwd, self.database)
163 self.logger.debug("DB: connected to '%s' at '%s@%s'", self.database, self.user, self.host)
164 except mdb.Error as e:
165 raise db_base_Exception("Cannot connect to DataBase '{}' at '{}@{}' Error {}: {}".format(
166 self.database, self.user, self.host, e.args[0], e.args[1]),
167 http_code = httperrors.Unauthorized )
168
169 def escape(self, value):
170 return self.con.escape(value)
171
172 def escape_string(self, value):
173 if isinstance(value, "unicode"):
174 value = value.encode("utf8")
175 return self.con.escape_string(value)
176
177 def get_db_version(self):
178 ''' Obtain the database schema version.
179 Return: (negative, text) if error or version 0.0 where schema_version table is missing
180 (version_int, version_text) if ok
181 '''
182 cmd = "SELECT version_int,version FROM schema_version"
183 tries = 2
184 while tries:
185 try:
186 with self.con:
187 self.cur = self.con.cursor()
188 self.logger.debug(cmd)
189 self.cur.execute(cmd)
190 rows = self.cur.fetchall()
191 highest_version_int=0
192 highest_version=""
193 for row in rows: #look for the latest version
194 if row[0]>highest_version_int:
195 highest_version_int, highest_version = row[0:2]
196 return highest_version_int, highest_version
197 except (mdb.Error, AttributeError) as e:
198 self.logger.error("Exception '{}' with command '{}'".format(e, cmd))
199 #self.logger.error("get_db_version DB Exception %d: %s. Command %s",e.args[0], e.args[1], cmd)
200 self._format_error(e, tries)
201 tries -= 1
202
203 def disconnect(self):
204 '''disconnect from specific data base'''
205 try:
206 self.con.close()
207 self.con = None
208 except mdb.Error as e:
209 self.logger.error("while disconnecting from DB: Error %d: %s",e.args[0], e.args[1])
210 return
211 except AttributeError as e: #self.con not defined
212 if e[0][-5:] == "'con'":
213 self.logger.warn("while disconnecting from DB: Error %d: %s",e.args[0], e.args[1])
214 return
215 else:
216 raise
217
218 def _format_error(self, e, tries=1, command=None, extra=None, table=None):
219 '''Creates a text error base on the produced exception
220 Params:
221 e: mdb exception
222 retry: in case of timeout, if reconnecting to database and retry, or raise and exception
223 cmd: database command that produce the exception
224 command: if the intention is update or delete
225 extra: extra information to add to some commands
226 Return
227 HTTP error in negative, formatted error text
228 '''
229 if isinstance(e,AttributeError ):
230 self.logger.debug(str(e), exc_info=True)
231 raise db_base_Exception("DB Exception " + str(e), httperrors.Internal_Server_Error)
232 if e.args[0]==2006 or e.args[0]==2013 : #MySQL server has gone away (((or))) Exception 2013: Lost connection to MySQL server during query
233 if tries>1:
234 self.logger.warn("DB Exception '%s'. Retry", str(e))
235 #reconnect
236 self.connect()
237 return
238 else:
239 raise db_base_Exception("Database connection timeout Try Again", httperrors.Request_Timeout)
240
241 fk=e.args[1].find("foreign key constraint fails")
242 if fk>=0:
243 if command=="update":
244 raise db_base_Exception("tenant_id '{}' not found.".format(extra), httperrors.Not_Found)
245 elif command=="delete":
246 raise db_base_Exception("Resource is not free. There are {} that prevent deleting it.".format(extra), httperrors.Conflict)
247 de = e.args[1].find("Duplicate entry")
248 fk = e.args[1].find("for key")
249 uk = e.args[1].find("Unknown column")
250 wc = e.args[1].find("in 'where clause'")
251 fl = e.args[1].find("in 'field list'")
252 #print de, fk, uk, wc,fl
253 table_info = ' (table `{}`)'.format(table) if table else ''
254 if de>=0:
255 if fk>=0: #error 1062
256 raise db_base_Exception(
257 "Value {} already in use for {}{}".format(
258 e.args[1][de+15:fk], e.args[1][fk+7:], table_info),
259 httperrors.Conflict)
260 if uk>=0:
261 if wc>=0:
262 raise db_base_Exception(
263 "Field {} can not be used for filtering{}".format(
264 e.args[1][uk+14:wc], table_info),
265 httperrors.Bad_Request)
266 if fl>=0:
267 raise db_base_Exception(
268 "Field {} does not exist{}".format(
269 e.args[1][uk+14:wc], table_info),
270 httperrors.Bad_Request)
271 raise db_base_Exception(
272 "Database internal Error{} {}: {}".format(
273 table_info, e.args[0], e.args[1]),
274 httperrors.Internal_Server_Error)
275
276 def __str2db_format(self, data):
277 """Convert string data to database format.
278 If data is None it returns the 'Null' text,
279 otherwise it returns the text surrounded by quotes ensuring internal quotes are escaped.
280 """
281 if data is None:
282 return 'Null'
283 elif isinstance(data[1], (str, unicode)):
284 return json.dumps(data)
285 else:
286 return json.dumps(str(data))
287
288 def __tuple2db_format_set(self, data):
289 """Compose the needed text for a SQL SET, parameter 'data' is a pair tuple (A,B),
290 and it returns the text 'A="B"', where A is a field of a table and B is the value
291 If B is None it returns the 'A=Null' text, without surrounding Null by quotes
292 If B is not None it returns the text "A='B'" or 'A="B"' where B is surrounded by quotes,
293 and it ensures internal quotes of B are escaped.
294 B can be also a dict with special keys:
295 {"INCREMENT": NUMBER}, then it produce "A=A+NUMBER"
296 """
297 if data[1] is None:
298 return str(data[0]) + "=Null"
299 elif isinstance(data[1], (str, unicode)):
300 return str(data[0]) + '=' + json.dumps(data[1])
301 elif isinstance(data[1], dict):
302 if "INCREMENT" in data[1]:
303 return "{A}={A}{N:+d}".format(A=data[0], N=data[1]["INCREMENT"])
304 raise db_base_Exception("Format error for UPDATE field: {!r}".format(data[0]))
305 else:
306 return str(data[0]) + '=' + json.dumps(str(data[1]))
307
308 def __create_where(self, data, use_or=None):
309 """
310 Compose the needed text for a SQL WHERE, parameter 'data' can be a dict or a list of dict. By default lists are
311 concatenated with OR and dict with AND, unless parameter 'use_or' indicates other thing.
312 If a dict it will generate 'key1="value1" AND key2="value2" AND ...'.
313 If value is None, it will produce 'key is null'
314 If value is a list or tuple, it will produce 'key="value[0]" OR key="value[1]" OR ...'
315 keys can be suffixed by >,<,<>,>=,<=,' LIKE ' so that this is used to compare key and value instead of "="
316 The special keys "OR", "AND" with a dict value is used to create a nested WHERE
317 If a list, each item will be a dictionary that will be concatenated with OR by default
318 :param data: dict or list of dicts
319 :param use_or: Can be None (use default behaviour), True (use OR) or False (use AND)
320 :return: a string with the content to send to mysql
321 """
322 cmd = []
323 if isinstance(data, dict):
324 for k, v in data.items():
325 if k == "OR":
326 cmd.append("(" + self.__create_where(v, use_or=True) + ")")
327 continue
328 elif k == "AND":
329 cmd.append("(" + self.__create_where(v, use_or=False) + ")")
330 continue
331
332 if k.endswith(">") or k.endswith("<") or k.endswith("=") or k.endswith(" LIKE "):
333 pass
334 else:
335 k += "="
336
337 if v is None:
338 cmd.append(k.replace("=", " is").replace("<>", " is not") + " Null")
339 elif isinstance(v, (tuple, list)):
340 cmd2 = []
341 for v2 in v:
342 if v2 is None:
343 cmd2.append(k.replace("=", " is").replace("<>", " is not") + " Null")
344 elif isinstance(v2, (str, unicode)):
345 cmd2.append(k + json.dumps(v2))
346 else:
347 cmd2.append(k + json.dumps(str(v2)))
348 cmd.append("(" + " OR ".join(cmd2) + ")")
349 elif isinstance(v, (str, unicode)):
350 cmd.append(k + json.dumps(v))
351 else:
352 cmd.append(k + json.dumps(str(v)))
353 elif isinstance(data, (tuple, list)):
354 if use_or is None:
355 use_or = True
356 for k in data:
357 cmd.append("(" + self.__create_where(k) + ")")
358 else:
359 raise db_base_Exception("invalid WHERE clause at '{}'".format(data))
360 if use_or:
361 return " OR ".join(cmd)
362 return " AND ".join(cmd)
363
364 def __remove_quotes(self, data):
365 '''remove single quotes ' of any string content of data dictionary'''
366 for k,v in data.items():
367 if type(v) == str:
368 if "'" in v:
369 data[k] = data[k].replace("'","_")
370
371 def _update_rows(self, table, UPDATE, WHERE, modified_time=0):
372 """ Update one or several rows of a table.
373 :param UPDATE: dictionary with the changes. dict keys are database columns that will be set with the dict values
374 :param table: database table to update
375 :param WHERE: dict or list of dicts to compose the SQL WHERE clause.
376 If a dict it will generate 'key1="value1" AND key2="value2" AND ...'.
377 If value is None, it will produce 'key is null'
378 If value is a list or tuple, it will produce 'key="value[0]" OR key="value[1]" OR ...'
379 keys can be suffixed by >,<,<>,>=,<= so that this is used to compare key and value instead of "="
380 The special keys "OR", "AND" with a dict value is used to create a nested WHERE
381 If a list, each item will be a dictionary that will be concatenated with OR
382 :return: the number of updated rows, raises exception upon error
383 """
384 # gettting uuid
385 values = ",".join(map(self.__tuple2db_format_set, UPDATE.iteritems() ))
386 if modified_time:
387 values += ",modified_at={:f}".format(modified_time)
388 cmd= "UPDATE " + table + " SET " + values + " WHERE " + self.__create_where(WHERE)
389 self.logger.debug(cmd)
390 self.cur.execute(cmd)
391 return self.cur.rowcount
392
393 def _new_uuid(self, root_uuid=None, used_table=None, created_time=0):
394 """
395 Generate a new uuid. It DOES NOT begin or end the transaction, so self.con.cursor must be created
396 :param root_uuid: master uuid of the transaction
397 :param used_table: the table this uuid is intended for
398 :param created_time: time of creation
399 :return: the created uuid
400 """
401
402 uuid = str(myUuid.uuid1())
403 # defining root_uuid if not provided
404 if root_uuid is None:
405 root_uuid = uuid
406 if created_time:
407 created_at = created_time
408 else:
409 created_at = time.time()
410 # inserting new uuid
411 cmd = "INSERT INTO uuids (uuid, root_uuid, used_at, created_at) VALUES ('{:s}','{:s}','{:s}', {:f})".format(
412 uuid, root_uuid, used_table, created_at)
413 self.logger.debug(cmd)
414 self.cur.execute(cmd)
415 return uuid
416
417 def _new_row_internal(self, table, INSERT, add_uuid=False, root_uuid=None, created_time=0, confidential_data=False):
418 ''' Add one row into a table. It DOES NOT begin or end the transaction, so self.con.cursor must be created
419 Attribute
420 INSERT: dictionary with the key:value to insert
421 table: table where to insert
422 add_uuid: if True, it will create an uuid key entry at INSERT if not provided
423 created_time: time to add to the created_time column
424 It checks presence of uuid and add one automatically otherwise
425 Return: uuid
426 '''
427
428 if add_uuid:
429 #create uuid if not provided
430 if 'uuid' not in INSERT:
431 uuid = INSERT['uuid'] = str(myUuid.uuid1()) # create_uuid
432 else:
433 uuid = str(INSERT['uuid'])
434 else:
435 uuid=None
436 if add_uuid:
437 #defining root_uuid if not provided
438 if root_uuid is None:
439 root_uuid = uuid
440 if created_time:
441 created_at = created_time
442 else:
443 created_at=time.time()
444 #inserting new uuid
445 cmd = "INSERT INTO uuids (uuid, root_uuid, used_at, created_at) VALUES ('{:s}','{:s}','{:s}', {:f})".format(uuid, root_uuid, table, created_at)
446 self.logger.debug(cmd)
447 self.cur.execute(cmd)
448 #insertion
449 cmd= "INSERT INTO " + table +" SET " + \
450 ",".join(map(self.__tuple2db_format_set, INSERT.iteritems() ))
451 if created_time:
452 cmd += ",created_at=%f" % created_time
453 if confidential_data:
454 index = cmd.find("SET")
455 subcmd = cmd[:index] + 'SET...'
456 self.logger.debug(subcmd)
457 else:
458 self.logger.debug(cmd)
459 self.cur.execute(cmd)
460 self.cur.rowcount
461 return uuid
462
463 def _get_rows(self,table,uuid):
464 cmd = "SELECT * FROM {} WHERE uuid='{}'".format(str(table), str(uuid))
465 self.logger.debug(cmd)
466 self.cur.execute(cmd)
467 rows = self.cur.fetchall()
468 return rows
469
470 def new_row(self, table, INSERT, add_uuid=False, created_time=0, confidential_data=False):
471 ''' Add one row into a table.
472 Attribute
473 INSERT: dictionary with the key: value to insert
474 table: table where to insert
475 tenant_id: only useful for logs. If provided, logs will use this tenant_id
476 add_uuid: if True, it will create an uuid key entry at INSERT if not provided
477 It checks presence of uuid and add one automatically otherwise
478 Return: uuid
479 '''
480 if table in self.tables_with_created_field and created_time==0:
481 created_time=time.time()
482 tries = 2
483 while tries:
484 try:
485 with self.con:
486 self.cur = self.con.cursor()
487 return self._new_row_internal(table, INSERT, add_uuid, None, created_time, confidential_data)
488
489 except (mdb.Error, AttributeError) as e:
490 self._format_error(e, tries, table=table)
491 tries -= 1
492
493 def update_rows(self, table, UPDATE, WHERE, modified_time=0):
494 """ Update one or several rows of a table.
495 :param UPDATE: dictionary with the changes. dict keys are database columns that will be set with the dict values
496 :param table: database table to update
497 :param WHERE: dict or list of dicts to compose the SQL WHERE clause.
498 If a dict it will generate 'key1="value1" AND key2="value2" AND ...'.
499 If value is None, it will produce 'key is null'
500 If value is a list or tuple, it will produce 'key="value[0]" OR key="value[1]" OR ...'
501 keys can be suffixed by >,<,<>,>=,<= so that this is used to compare key and value instead of "="
502 The special keys "OR", "AND" with a dict value is used to create a nested WHERE
503 If a list, each item will be a dictionary that will be concatenated with OR
504 :param modified_time: Can contain the time to be set to the table row
505 :return: the number of updated rows, raises exception upon error
506 """
507 if table in self.tables_with_created_field and modified_time==0:
508 modified_time=time.time()
509 tries = 2
510 while tries:
511 try:
512 with self.con:
513 self.cur = self.con.cursor()
514 return self._update_rows(
515 table, UPDATE, WHERE, modified_time)
516 except (mdb.Error, AttributeError) as e:
517 self._format_error(e, tries, table=table)
518 tries -= 1
519
520 def _delete_row_by_id_internal(self, table, uuid):
521 cmd = "DELETE FROM {} WHERE uuid = '{}'".format(table, uuid)
522 self.logger.debug(cmd)
523 self.cur.execute(cmd)
524 deleted = self.cur.rowcount
525 # delete uuid
526 self.cur = self.con.cursor()
527 cmd = "DELETE FROM uuids WHERE root_uuid = '{}'".format(uuid)
528 self.logger.debug(cmd)
529 self.cur.execute(cmd)
530 return deleted
531
532 def delete_row_by_id(self, table, uuid):
533 tries = 2
534 while tries:
535 try:
536 with self.con:
537 self.cur = self.con.cursor()
538 return self._delete_row_by_id_internal(table, uuid)
539 except (mdb.Error, AttributeError) as e:
540 self._format_error(
541 e, tries, "delete", "dependencies", table=table)
542 tries -= 1
543
544 def delete_row(self, **sql_dict):
545 """ Deletes rows from a table.
546 :param UPDATE: dictionary with the changes. dict keys are database columns that will be set with the dict values
547 :param FROM: string with table name (Mandatory)
548 :param WHERE: dict or list of dicts to compose the SQL WHERE clause. (Optional)
549 If a dict it will generate 'key1="value1" AND key2="value2" AND ...'.
550 If value is None, it will produce 'key is null'
551 If value is a list or tuple, it will produce 'key="value[0]" OR key="value[1]" OR ...'
552 keys can be suffixed by >,<,<>,>=,<= so that this is used to compare key and value instead of "="
553 The special keys "OR", "AND" with a dict value is used to create a nested WHERE
554 If a list, each item will be a dictionary that will be concatenated with OR
555 :return: the number of deleted rows, raises exception upon error
556 """
557 # print sql_dict
558 cmd = "DELETE FROM " + str(sql_dict['FROM'])
559 if sql_dict.get('WHERE'):
560 cmd += " WHERE " + self.__create_where(sql_dict['WHERE'])
561 if sql_dict.get('LIMIT'):
562 cmd += " LIMIT " + str(sql_dict['LIMIT'])
563 tries = 2
564 while tries:
565 try:
566 with self.con:
567 self.cur = self.con.cursor()
568 self.logger.debug(cmd)
569 self.cur.execute(cmd)
570 deleted = self.cur.rowcount
571 return deleted
572 except (mdb.Error, AttributeError) as e:
573 self._format_error(e, tries)
574 tries -= 1
575
576 def get_rows_by_id(self, table, uuid):
577 '''get row from a table based on uuid'''
578 tries = 2
579 while tries:
580 try:
581 with self.con:
582 self.cur = self.con.cursor(mdb.cursors.DictCursor)
583 cmd="SELECT * FROM {} where uuid='{}'".format(str(table), str(uuid))
584 self.logger.debug(cmd)
585 self.cur.execute(cmd)
586 rows = self.cur.fetchall()
587 return rows
588 except (mdb.Error, AttributeError) as e:
589 self._format_error(e, tries, table=table)
590 tries -= 1
591
592 def get_rows(self, **sql_dict):
593 """ Obtain rows from a table.
594 :param SELECT: list or tuple of fields to retrieve) (by default all)
595 :param FROM: string with table name (Mandatory)
596 :param WHERE: dict or list of dicts to compose the SQL WHERE clause. (Optional)
597 If a dict it will generate 'key1="value1" AND key2="value2" AND ...'.
598 If value is None, it will produce 'key is null'
599 If value is a list or tuple, it will produce 'key="value[0]" OR key="value[1]" OR ...'
600 keys can be suffixed by >,<,<>,>=,<= so that this is used to compare key and value instead of "="
601 The special keys "OR", "AND" with a dict value is used to create a nested WHERE
602 If a list, each item will be a dictionary that will be concatenated with OR
603 :param LIMIT: limit the number of obtained entries (Optional)
604 :param ORDER_BY: list or tuple of fields to order, add ' DESC' to each item if inverse order is required
605 :return: a list with dictionaries at each row, raises exception upon error
606 """
607 # print sql_dict
608 cmd = "SELECT "
609 if 'SELECT' in sql_dict:
610 if isinstance(sql_dict['SELECT'], (tuple, list)):
611 cmd += ",".join(map(str, sql_dict['SELECT']))
612 else:
613 cmd += sql_dict['SELECT']
614 else:
615 cmd += "*"
616
617 cmd += " FROM " + str(sql_dict['FROM'])
618 if sql_dict.get('WHERE'):
619 cmd += " WHERE " + self.__create_where(sql_dict['WHERE'])
620
621 if 'ORDER_BY' in sql_dict:
622 cmd += " ORDER BY "
623 if isinstance(sql_dict['ORDER_BY'], (tuple, list)):
624 cmd += ",".join(map(str, sql_dict['ORDER_BY']))
625 else:
626 cmd += str(sql_dict['ORDER_BY'])
627
628 if 'LIMIT' in sql_dict:
629 cmd += " LIMIT " + str(sql_dict['LIMIT'])
630
631 tries = 2
632 while tries:
633 try:
634 with self.con:
635 self.cur = self.con.cursor(mdb.cursors.DictCursor)
636 self.logger.debug(cmd)
637 self.cur.execute(cmd)
638 rows = self.cur.fetchall()
639 return rows
640 except (mdb.Error, AttributeError) as e:
641 self.logger.error("Exception '{}' with command '{}'".format(e, cmd))
642 self._format_error(e, tries)
643 tries -= 1
644
645 def get_table_by_uuid_name(self, table, uuid_name, error_item_text=None, allow_serveral=False, WHERE_OR={}, WHERE_AND_OR="OR"):
646 ''' Obtain One row from a table based on name or uuid.
647 Attribute:
648 table: string of table name
649 uuid_name: name or uuid. If not uuid format is found, it is considered a name
650 allow_severeral: if False return ERROR if more than one row are founded
651 error_item_text: in case of error it identifies the 'item' name for a proper output text
652 'WHERE_OR': dict of key:values, translated to key=value OR ... (Optional)
653 'WHERE_AND_OR: str 'AND' or 'OR'(by default) mark the priority to 'WHERE AND (WHERE_OR)' or (WHERE) OR WHERE_OR' (Optional
654 Return: if allow_several==False, a dictionary with this row, or error if no item is found or more than one is found
655 if allow_several==True, a list of dictionaries with the row or rows, error if no item is found
656 '''
657
658 if error_item_text==None:
659 error_item_text = table
660 what = 'uuid' if af.check_valid_uuid(uuid_name) else 'name'
661 cmd = " SELECT * FROM {} WHERE {}='{}'".format(table, what, uuid_name)
662 if WHERE_OR:
663 where_or = self.__create_where(WHERE_OR, use_or=True)
664 if WHERE_AND_OR == "AND":
665 cmd += " AND (" + where_or + ")"
666 else:
667 cmd += " OR " + where_or
668
669 tries = 2
670 while tries:
671 try:
672 with self.con:
673 self.cur = self.con.cursor(mdb.cursors.DictCursor)
674 self.logger.debug(cmd)
675 self.cur.execute(cmd)
676 number = self.cur.rowcount
677 if number == 0:
678 raise db_base_Exception("No {} found with {} '{}'".format(error_item_text, what, uuid_name), http_code=httperrors.Not_Found)
679 elif number > 1 and not allow_serveral:
680 raise db_base_Exception("More than one {} found with {} '{}'".format(error_item_text, what, uuid_name), http_code=httperrors.Conflict)
681 if allow_serveral:
682 rows = self.cur.fetchall()
683 else:
684 rows = self.cur.fetchone()
685 return rows
686 except (mdb.Error, AttributeError) as e:
687 self._format_error(e, tries, table=table)
688 tries -= 1
689
690 def get_uuid(self, uuid):
691 '''check in the database if this uuid is already present'''
692 tries = 2
693 while tries:
694 try:
695 with self.con:
696 self.cur = self.con.cursor(mdb.cursors.DictCursor)
697 self.cur.execute("SELECT * FROM uuids where uuid='" + str(uuid) + "'")
698 rows = self.cur.fetchall()
699 return self.cur.rowcount, rows
700 except (mdb.Error, AttributeError) as e:
701 self._format_error(e, tries)
702 tries -= 1
703
704 def get_uuid_from_name(self, table, name):
705 '''Searchs in table the name and returns the uuid
706 '''
707 tries = 2
708 while tries:
709 try:
710 with self.con:
711 self.cur = self.con.cursor(mdb.cursors.DictCursor)
712 where_text = "name='" + name +"'"
713 self.cur.execute("SELECT * FROM " + table + " WHERE "+ where_text)
714 rows = self.cur.fetchall()
715 if self.cur.rowcount==0:
716 return 0, "Name %s not found in table %s" %(name, table)
717 elif self.cur.rowcount>1:
718 return self.cur.rowcount, "More than one VNF with name %s found in table %s" %(name, table)
719 return self.cur.rowcount, rows[0]["uuid"]
720 except (mdb.Error, AttributeError) as e:
721 self._format_error(e, tries, table=table)
722 tries -= 1
723