Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" 

2@file 

3 

4@brief @see cl Database 

5""" 

6import os 

7import sys 

8import math 

9import re 

10import time 

11import decimal 

12import sqlite3 as SQLite 

13import datetime 

14import numpy 

15from .database_exception import ExceptionSQL, DBException 

16from .database_core2 import DatabaseCore2 

17 

18module_odbc = None 

19 

20 

21class DatabaseCore(DatabaseCore2): 

22 """ 

23 Core methods for class @see cl Database. 

24 

25 @var _engine engine type (SQLite is the only available) 

26 @var _sql_file database file, if it does not exist, it will be created. 

27 """ 

28 

29 _sql_keywords = ["order", "by", "select", "from", "group", "where", "as", "like", "upper", "collapse", "join", "union", 

30 "inner", "default", "id", "double", "text", "varchar", "float", "long", "Decimal"] 

31 

32 _SQL_conversion_types = {"": float, 

33 "TEXT": str, 

34 "text": str, 

35 "INTEGER": int, 

36 "FLOAT": float, 

37 "REAL": float, 

38 "float": float, 

39 "numeric": float, 

40 "LONG": int, 

41 "int": int, 

42 "varchar": str, 

43 "VARCHAR": str, 

44 "Decimal": decimal.Decimal, 

45 "DATETIME": datetime.datetime, 

46 "smallint": int, 

47 "bigint": float, } 

48 

49 _engines = ["SQLite", "MySQL", "ODBCMSSQL"] 

50 _field_option = ["PRIMARYKEY", "AUTOINCREMENT", "AUTOFILL"] 

51 

52 def __init__(self, sql_file, engine="SQLite", user=None, password=None, 

53 host="localhost", LOG=None, attach=None): 

54 """ 

55 Creates a database object. 

56 

57 @param sql_file database file database file (use ``:memory:`` to avoid creating a file and using only memory) 

58 it can also contain several files separated by ; 

59 ``name_file ; nickname,second_file ; ...`` 

60 @param engine SQLite or MySQL (if it is installed), ODBCMSSQL 

61 @param user user if needed 

62 @param host to connect to a MSSQL database 

63 @param password password if needed 

64 @param LOG LOG function, if None, choose ``print`` 

65 @param attach dictionary ``{nickname: filename}``, 

66 list of databases to attach 

67 

68 @warning If the folder does not exist, it will be created 

69 

70 Parameter *dbfile* can be of type 

71 `sqlite3.Connection <https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection>`_. 

72 """ 

73 

74 # attach cases 

75 if attach is None: 

76 attach = {} 

77 else: 

78 attach = attach.copy() 

79 

80 if isinstance(sql_file, str): 

81 

82 for e in DatabaseCore._engines: 

83 if sql_file.startswith(e + ":::"): 

84 engine = e 

85 sql_file = sql_file[len(e) + 3:] 

86 if "###" in sql_file: 

87 host, sql_file = sql_file.split("###") 

88 break 

89 

90 if ";" in sql_file: 

91 li = [s.strip() for s in sql_file.split(";")] 

92 sql_file = li[0] 

93 rest = li[1:] 

94 for s in rest: 

95 ok = s.split(",") 

96 if len(ok) != 2: 

97 raise DBException( # pragma: no cover 

98 "unable to find an alias in %r" % s) 

99 nick = ok[0].strip() 

100 file = ",".join(ok[1:]) 

101 attach[nick] = file.strip() 

102 elif sql_file.startswith(":"): 

103 if sql_file != ":memory:": 

104 raise FileNotFoundError( # pragma: no cover 

105 "unable to interpret file: %r" % sql_file) 

106 

107 # some initialization 

108 self._password = password 

109 self._user = user 

110 self._host = host 

111 

112 # the rest 

113 if LOG is None: 

114 def blind(*li, **p): # pragma: no cover 

115 pass 

116 LOG = blind # pragma: no cover 

117 self.LOG = LOG 

118 

119 if isinstance(LOG, dict): 

120 raise TypeError( # pragma: no cover 

121 "fLOG should be a function, not a dictionary") 

122 if isinstance(self.LOG, dict): 

123 raise TypeError( # pragma: no cover 

124 "LOG should be a function, not a dictionary") 

125 

126 if engine == "SQLite": 

127 self._sql_file = sql_file 

128 self._engine = engine 

129 

130 elif engine == "ODBCMSSQL": 

131 raise DBException( # pragma: no cover 

132 "Unable to connect to a SQL server.") 

133 

134 else: 

135 raise DBException( # pragma: no cover 

136 "unfounded engine %s in %s" % 

137 (engine, ", ".join( 

138 DatabaseCore._engines))) 

139 

140 # write a file able to build a database summary 

141 if isinstance(sql_file, str) and not self.isMemory(): 

142 folder = os.path.split(sql_file)[0] 

143 if len(folder) > 0 and not os.path.exists(folder): 

144 os.makedirs(folder) 

145 summary = os.path.join(folder, "temp_quick_look_up.py") 

146 if not os.path.exists(summary): 

147 #cwd = os.path.join (os.path.abspath (os.path.split (__file__) [0]), "..", "..") 

148 #fi = os.path.split (sql_file) [1] 

149 

150 if hasattr(DatabaseCore, "SCRIPT_LOOKUP"): 

151 script = DatabaseCore.SCRIPT_LOOKUP 

152 lines = script.split("\n") 

153 lines = [li if "__CWD__ =" not in li else 

154 li.replace( 

155 "(__file__)", 

156 "(r'%s')" % 

157 os.path.realpath(__file__)) 

158 for li in lines] 

159 script = "\n".join(lines) 

160 script = script.replace( 

161 "python quick_look_up.py", 

162 "%s quick_look_up.py" % 

163 sys.executable) 

164 self.LOG("creating script ", summary) 

165 try: 

166 f = open(summary, "w") 

167 f.write(script) 

168 f.close() 

169 except IOError: 

170 self.LOG("unable to write ", summary) 

171 

172 self._attach = attach 

173 self._buffer_insert = [] 

174 self._buffer_insert_s = 0 

175 

176 if isinstance(sql_file, str) and self.isMemory(): 

177 self._connection = SQLite.connect(self._sql_file) 

178 elif isinstance(sql_file, SQLite.Connection): 

179 self._connection = sql_file 

180 self._sql_file = ":memory:" 

181 

182 def isMSSQL(self): 

183 """ 

184 Says if the syntax is MS SQL Server. 

185 """ 

186 if self._engine == "ODBCMSSQL": 

187 return True 

188 return False 

189 

190 def isMemory(self): 

191 """ 

192 Tells if the Database takes place in memory (``:memory:``). 

193 """ 

194 return self._sql_file == ":memory:" 

195 

196 ########################################################################## 

197 # connection 

198 ########################################################################## 

199 

200 def SetBufferInsert(self, n): 

201 """ 

202 This function offers the possibility to postpone the insertion, 

203 they will be processed all at the time during when method commit is called. 

204 

205 @param n number of insertion to postpone 

206 """ 

207 self._buffer_insert_s = n 

208 

209 def is_connected(self): 

210 """ 

211 Says if the database is connected. 

212 

213 @return "_connection" in self.__dict__ 

214 """ 

215 return "_connection" in self.__dict__ 

216 

217 @staticmethod 

218 def regex_match(exp, st): 

219 "Applies a regular expression. Static method to insert in a SQL query." 

220 return 0 if re.compile(exp).search(st) is None else 1 

221 

222 @staticmethod 

223 def idaytodate(dayint, year, month, day): 

224 "Date conversion. Static method to insert in a SQL query." 

225 try: 

226 d = datetime.datetime(year, month, day) 

227 day = datetime.datetime(year, month, day + 1) - d 

228 cur = d + day * dayint 

229 return str(cur).split()[0] 

230 except Exception as e: 

231 return str(e) 

232 

233 @staticmethod 

234 def isectoday(sec): 

235 "Date conversion. Static method to insert in a SQL query." 

236 if sec < 0: 

237 return "negative time" 

238 elif sec >= 86400: 

239 return "out of day" 

240 else: 

241 s = int(sec) 

242 h = s / 3600 

243 m = (s % 3600) / 60 

244 s %= 60 

245 return "%02d:%02d:%02d" % (h, m, s) 

246 

247 @staticmethod 

248 def itimestamp(t, year, month, day): 

249 "Date conversion. Static method to insert in a SQL query." 

250 d = DatabaseCore.idaytodate(int(t / 86400), year, month, day) 

251 s = DatabaseCore.isectoday(int(t - 86400. * int(t / 86400))) 

252 return d + " " + s 

253 

254 @staticmethod 

255 def string_to_date(s): 

256 "Date conversion. Static method to insert in a SQL query." 

257 d = int(s[:2]) 

258 m = int(s[3:5]) 

259 y = int(s[6:]) 

260 return datetime.datetime(y, m, d) 

261 

262 @staticmethod 

263 def _special_function_init_(): 

264 _list_special_function = [ 

265 ("log", math.log, 1, "log(s) --> float", "log"), 

266 ("exp", math.exp, 1, "exp(s) --> float", "exp"), 

267 ("len", len, 1, "len(s) --> int", "string length"), 

268 ("lower", 

269 lambda s:s.lower(), 

270 1, 

271 "lower(s) --> string", 

272 "lower case"), 

273 ("upper", 

274 lambda s:s.upper(), 

275 1, 

276 "upper(s) --> string", 

277 "upper case"), 

278 ("isubstring", lambda sub, s: 1 if sub in s else 0, 

279 2, "isubstring(sub,str) --> {0,1}", "return 1 if str includes sub, 0 otherwise"), 

280 ("match", DatabaseCore.regex_match, 

281 2, "match(regex,str) --> {0,1}", "return 1 if str matches the regular expression exp, 0 otherwise"), 

282 ("idaytodate", DatabaseCore.idaytodate, 

283 4, "idaytodate (day, 1970, 1, 1) --> str", "date if day is the number of days since 01/01/1970"), 

284 ("itimestamp", DatabaseCore.itimestamp, 

285 4, "itimestamp (t, 1970, 1, 1) --> str", "date,time if t is the number of seconds since 01/01/1970"), 

286 ("isectoday", DatabaseCore.isectoday, 

287 1, "isectoday (isec) --> str", "time if isec is the number of seconds since midnight"), 

288 ] 

289 return _list_special_function 

290 

291 def connect(self, odbc_string=None): 

292 """ 

293 Opens a connection to the database. 

294 

295 @param odbc_string use a different odbc string 

296 """ 

297 if self.isMemory(): 

298 if "_connection" not in self.__dict__: 

299 raise DBException( # pragma: no cover 

300 "It is a database in memory, the database should already be connected.") 

301 else: 

302 if "_connection" in self.__dict__: 

303 raise RuntimeError("A previous connection was not closed.") 

304 

305 if self._engine == "SQLite": 

306 self._connection = SQLite.connect(self._sql_file) 

307 # elif self._engine == "MySQL" : self._connection = 

308 # MySQLdb.connect (self._host, self._user, self._password, 

309 # self._sql_file) 

310 elif self._engine == "ODBCMSSQL": # pragma: no cover 

311 

312 if odbc_string is None: 

313 temp = ["DRIVER={SQL Server Native Client 10.0}", # {SQL Server}", 

314 "SERVER=%s" % self._host, 

315 "DATABASE=%s" % self._sql_file, 

316 "Trusted_Connection=yes", 

317 "MARS_Connection=yes", 

318 # "MultipleActiveResultSets=True", 

319 #"Integrated Security=SSPI", 

320 ] 

321 #temp = ["DSN=%s" % self._sql_file ] 

322 if self._user is not None: 

323 temp.append("UID=%s" % self._user) 

324 if self._password is not None: 

325 temp.append("PASSWORD=%s" % self._password) 

326 st = ";".join(temp) 

327 self.LOG("connection string ", st) 

328 self._connection = module_odbc.connect(st) 

329 else: 

330 st = odbc_string 

331 self.LOG("connection string ", st) 

332 self._connection = module_odbc.connect(st) 

333 

334 else: 

335 raise DBException( # pragma: no cover 

336 "This engine does not exists (%r)" % self._engine) 

337 

338 for func in DatabaseCore._special_function_init_(): 

339 self.add_function(func[0], func[2], func[1]) 

340 

341 for k, v in self._attach.items(): 

342 self.attach_database(v, k) 

343 

344 def close(self): 

345 """ 

346 Closes the database. 

347 """ 

348 if self.isMemory(): 

349 # we should not close, otherwise, we lose the data 

350 pass 

351 else: 

352 self._check_connection() 

353 self._connection.close() 

354 del self._connection 

355 

356 def commit(self): 

357 """ 

358 Calls this function after any insert request. 

359 """ 

360 self._check_connection() 

361 

362 for s in self._buffer_insert: 

363 self._connection.execute(s) 

364 del self._buffer_insert[:] 

365 

366 self._connection.commit() 

367 

368 ########################################################################## 

369 # access part 

370 ########################################################################## 

371 

372 def get_file(self, attached_db=False): 

373 """ 

374 Gets database file. 

375 

376 @param attached_db if True, add the list of attached databases 

377 @return the database file 

378 """ 

379 if attached_db: 

380 files = [self._sql_file] 

381 att = self.get_attached_database_list(True) 

382 for alias, file in att: 

383 files.append("%s,%s" % (alias, file)) 

384 temp = ";".join(files) 

385 if self._engine != "SQLite": 

386 if self._host is None: 

387 temp = "%s:::%s" % (self._engine, temp) 

388 else: 

389 temp = "%s:::%s###%s" % (self._engine, self._host, temp) 

390 return temp 

391 else: 

392 return self._sql_file 

393 

394 def has_table(self, table): 

395 """ 

396 Says if the table belongs to the database. 

397 

398 @param table table name 

399 @return boolean 

400 """ 

401 return table in self.get_table_list("." in table) 

402 

403 def has_index(self, index): 

404 """ 

405 Says if the index belongs to the database. 

406 

407 @param index index name 

408 @return boolean""" 

409 return index in [s[0] for s in self.get_index_list()] 

410 

411 def get_index_on_table(self, table, full=False): 

412 """ 

413 Returns the list of indexes on a specific table. 

414 

415 @param table table 

416 @param full if True returns all fields, otherwise, returns only the index names 

417 @return list of the index on this table 

418 """ 

419 indexes = self.get_index_list() 

420 if full: 

421 return [la for la in indexes if la[1] == table] 

422 return [la[0] for la in indexes if la[1] == table] 

423 

424 def get_column_type(self, table, column): 

425 """ 

426 Returns the column type of a table. 

427 

428 @param table table name 

429 @param column column name 

430 @return type (python class) 

431 """ 

432 self._check_connection() 

433 cols = self.get_table_columns_list(table) 

434 for c in cols: 

435 if c[0] == column: 

436 return c[1] 

437 raise DBException( 

438 "column %s were not found in table %s" % 

439 (column, table)) 

440 

441 def get_index_list(self, attached="main"): 

442 """ 

443 Returns the list of indexes. 

444 

445 @param attached if main, returns the index for the main database, otherwise, for an attached database 

446 @return list of tuple (index_name, table, sql_request, fields) 

447 """ 

448 self._check_connection() 

449 if attached == "main": 

450 request = """ SELECT name,tbl_name,sql 

451 FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) AS temptbl 

452 WHERE type='index' ORDER BY name;""" 

453 else: 

454 request = """ SELECT name,tbl_name,sql 

455 FROM (SELECT * FROM %s.sqlite_master) AS temptbl 

456 WHERE type='index' ORDER BY name;""" % attached 

457 select = self._connection.execute(request) 

458 

459 exp = re.compile("[(]([a-zA-Z0-9_,]+)[)]") 

460 res = [] 

461 for a, b, c in select: 

462 fi = exp.findall(c) 

463 if len(fi) != 1: 

464 raise DBException( # pragma: no cover 

465 "Unable to extract index fields from %r" % c) 

466 fi = tuple(s.strip() for s in fi[0].split(",")) 

467 res.append((a, b, c, fi)) 

468 select.close() 

469 #self.LOG ("number of indices ", len (res)) 

470 select = res 

471 

472 res = [] 

473 if attached == "main": 

474 res = select 

475 else: 

476 for el in select: 

477 res.append((el[0], attached + "." + el[1], el[2], el[3])) 

478 #self.LOG ("number of indices ", len (res)) 

479 

480 if attached == "main": 

481 attach = self.get_attached_database_list() 

482 for a in attach: 

483 if a in ("main", "temp"): 

484 continue 

485 r = self.get_index_list(a) 

486 res.extend(r) 

487 

488 return res 

489 

490 def get_attached_database_list(self, file=False): 

491 """ 

492 Returns all the attached database (avoid the temporary ones and the main one). 

493 

494 @param file ask for file also 

495 @return a list of tuple (alias, file) 

496 """ 

497 if self.isMSSQL(): 

498 return [] # pragma: no cover 

499 else: 

500 cur = self._connection.cursor() 

501 cur.execute("PRAGMA database_list;") 

502 res = cur.fetchall() 

503 cur.close() 

504 res = [r for r in res if r[1] != "temp" and r[1] != "main"] 

505 if file: 

506 return [(r[1], r[2]) for r in res] 

507 else: 

508 return [r[1] for r in res] 

509 

510 def get_table_list(self, add_attached=False): 

511 """ 

512 Returns the list of tables. 

513 

514 @param add_attached if True, add the list of tables included in the attached databases 

515 @return the table list 

516 """ 

517 self._check_connection() 

518 if self.isMSSQL(): # pragma: no cover 

519 request = """ SELECT TABLE_NAME FROM ( 

520 SELECT TABLE_NAME, OBJECTPROPERTY(object_id(TABLE_NAME), N'IsUserTable') AS type 

521 FROM INFORMATION_SCHEMA.TABLES) AS temp_tbl 

522 WHERE type = 1 ORDER BY TABLE_NAME""" 

523 else: 

524 request = """ SELECT name 

525 FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) AS temptbl 

526 WHERE type in('table','temp') AND name != 'sqlite_sequence' ORDER BY name;""" 

527 

528 select = self._connection.execute(request) 

529 res = [] 

530 for el in select: 

531 res.append(el[0]) 

532 

533 if add_attached: 

534 att = self.get_attached_database_list() 

535 for at in att: 

536 if at == "temp": 

537 continue 

538 sql = "SELECT name FROM %s.sqlite_master" % at 

539 vie = self._connection.execute(sql) 

540 vie = ["%s.%s" % (at, v[0]) for v in vie] 

541 res.extend(vie) 

542 return res 

543 

544 def get_table_columns(self, table, dictionary=False): 

545 """ 

546 See @see me get_table_columns_list. 

547 

548 Example (`dictionary == False`): 

549 :: 

550 [('fid', <type 'int'>), ('fidp', <type 'int'>), ('field', <type 'str'>)] 

551 

552 Or (`dictionary = True`): 

553 :: 

554 {0: ('fid', <type 'int'>), 1: ('fidp', <type 'int'>), 2: ('field', <type 'str'>)} 

555 """ 

556 return self.get_table_columns_list(table, dictionary) 

557 

558 def get_table_columns_list(self, table, dictionary=False): 

559 """ 

560 Returns all the columns for a table. 

561 

562 @param table table name 

563 @param dictionary returns the list as a dictionary 

564 @return a list of tuple (column name, Python type) 

565 

566 Example (`dictionary == False`): 

567 

568 :: 

569 [('fid', <type 'int'>), ('fidp', <type 'int'>), ('field', <type 'str'>)] 

570 

571 Or (`dictionary = True`): 

572 

573 :: 

574 

575 {0: ('fid', <type 'int'>), 1: ('fidp', <type 'int'>), 2: ('field', <type 'str'>)} 

576 """ 

577 if "." in table: 

578 prefix = table.split(".")[0] + "." 

579 table = table.split(".")[1] 

580 else: 

581 # table = table 

582 prefix = "" 

583 cur = self._connection.cursor() 

584 

585 if self.isMSSQL(): # pragma: no cover 

586 prf = "" if len(prefix) == 0 else prefix + "." 

587 sql = """SELECT * FROM (SELECT OBJECT_NAME(c.OBJECT_ID) TableName,c.name AS ColumnName,t.name AS TypeName 

588 FROM sys.columns AS c 

589 JOIN sys.types AS t ON c.user_type_id=t.user_type_id 

590 ) AS ttt 

591 WHERE ttt.TableName = '%s%s'""" % (prf, table) 

592 cur.execute(sql) 

593 else: 

594 cur.execute("PRAGMA %stable_info(%s)" % (prefix, table) + ";") 

595 

596 res = cur.fetchall() 

597 cur.close() 

598 res = [(r[1], DatabaseCore._SQL_conversion_types[r[2]]) for r in res] 

599 if dictionary: 

600 dic = {} 

601 for i in range(0, len(res)): 

602 dic[i] = res[i] 

603 return dic 

604 else: 

605 return res 

606 

607 def get_table_nb_lines(self, table): 

608 """ 

609 Returns the number of lines in a table (or number of observations). 

610 

611 @param table table name 

612 @return integer 

613 """ 

614 sql = "SELECT COUNT(*) FROM " + table + ";" 

615 cur = self._connection.cursor() 

616 cur.execute(sql) 

617 res = cur.fetchall() 

618 cur.close() 

619 return res[0][0] 

620 

621 def len(self, table): 

622 """ 

623 Returns the number of lines of table ``table``. 

624 

625 @param table table 

626 @return int 

627 """ 

628 return self.get_table_nb_lines(table) 

629 

630 def get_table_nfirst_lines(self, table, n=1): 

631 """ 

632 Returns the *n* first lines. 

633 

634 @param table table name 

635 @param n number of asked lines 

636 @return integer 

637 """ 

638 sql = "SELECT * FROM %s ;" % table 

639 cur = self._connection.cursor() 

640 cur.execute(sql) 

641 if n <= 1: 

642 res = [cur.fetchone()] 

643 else: 

644 res = [] 

645 for line in cur: 

646 n -= 1 

647 if n <= -1: 

648 break 

649 res.append(line) 

650 cur.close() 

651 return res 

652 

653 def get_sql_columns(self, request): 

654 """ 

655 Returns the columns name for a SQL request. 

656 

657 @param request SQL request 

658 @return list of columns name 

659 """ 

660 cur = self.execute(request) 

661 col_name_list = [tuple[0] for tuple in cur.description] 

662 cur.close() 

663 return col_name_list 

664 

665 ########################################################################## 

666 # execution 

667 ########################################################################## 

668 

669 class _cross_product_iter: 

670 

671 """ 

672 Iterator for CROSS. 

673 """ 

674 

675 def __init__(self, db, request): 

676 com = re.compile("^(.*)(--.*)$") 

677 lines = request.split("\n") 

678 clean = [] 

679 for li in lines: 

680 r = com.match(li) 

681 if r is not None: 

682 li = li[:r.span(1)[1]] 

683 clean.append(li.strip()) 

684 req = " ".join(clean) 

685 cross = re.compile(" *CROSS +([ a-zA-Z_0-9,]+?) +" 

686 "PLACE +([,a-zA-Z_0-9()]+?) +" 

687 "FROM +([a-zA-Z_0-9]+?)( +AS +[_a-z0-9])? +ORDER +BY " 

688 "+([ a-zA-Z_0-9,]+?)( +WHERE(.*?))?( +LIMIT +([0-9]+)?)?$", re.IGNORECASE) 

689 db.LOG("cross product", req) 

690 find = cross.search(req) 

691 self.request = request 

692 self.find = find 

693 self.db = db 

694 

695 if self.find is None: 

696 return 

697 

698 gr = self.find.groups() 

699 key, value, table, count_as, order, where, _, __, limit = gr 

700 if limit is not None: 

701 limit = int(limit) 

702 db.LOG("parameters ", [key, value, table, order, where, limit]) 

703 fkey = key.split(",") 

704 fval = value.split(",") 

705 if where is None: 

706 where = "" 

707 

708 nkey = len(fkey) 

709 #nval = len (fval) 

710 sql = "SELECT %s,%s FROM %s %s ORDER BY %s" % ( 

711 key, value, table, where, order) 

712 cur = self.db.execute(sql) 

713 data = {} 

714 #tot = nkey + nval 

715 for sample in cur: 

716 key = sample[:nkey] 

717 val = sample[nkey:] 

718 if key not in data: 

719 data[key] = [] 

720 data[key].append(val) 

721 cur.close() 

722 

723 keys = sorted(data.keys()) 

724 

725 if nkey == 1: 

726 temp = [[str(k[0]) + ';' + s for s in fval] for k in keys] 

727 else: 

728 temp = [ 

729 [",".join([str(_s) for _s in k]) + ';' + s for s in fval] for k in keys] 

730 self.description = [] 

731 for t in temp: 

732 self.description.extend(t) 

733 self.description = [(k, None) for k in self.description] 

734 

735 matrix = [] 

736 pos = 0 

737 while True: 

738 stil = 0 

739 line = [] 

740 for k in keys: 

741 v = data[k] 

742 if pos < len(v): 

743 stil += 1 

744 line.extend(list(v[pos])) 

745 else: 

746 line.extend([None for f in fval]) 

747 if stil > 0: 

748 matrix.append(line) 

749 pos += 1 

750 if limit is not None and pos >= limit: 

751 break 

752 else: 

753 break 

754 self.matrix = matrix 

755 self.pos = 0 

756 

757 def is_working(self): 

758 return self.find is not None 

759 

760 def __iter__(self): 

761 """ 

762 iterator 

763 """ 

764 return self 

765 

766 def __next__(self): 

767 """ 

768 iterator 

769 """ 

770 if "matrix" not in self.__dict__: 

771 raise StopIteration # pragma: no cover 

772 if self.pos < len(self.matrix): 

773 n = self.pos 

774 self.pos += 1 

775 return self.matrix[n] 

776 else: 

777 raise StopIteration 

778 

779 def close(self): 

780 pass 

781 

782 def _analyse(self, request, header=False): 

783 """ 

784 Analyses the request does it contains cross product. 

785 

786 @param request request 

787 @param header add a header in the first line 

788 @return None or an iterator 

789 

790 Example: 

791 

792 :: 

793 

794 CROSS f1,f2,f3 

795 PLACE a,b,c 

796 FROM table 

797 ORDER BY f8 

798 WHERE f9 == ' ' -- optional 

799 """ 

800 if "CROSS" not in request.upper(): 

801 return None 

802 iter = DatabaseCore._cross_product_iter(self, request) 

803 if not iter.is_working(): 

804 return None 

805 else: 

806 return iter 

807 

808 def execute(self, request, nolog=False): 

809 """ 

810 Opens a cursor with a query and return it to the user. 

811 

812 @param request SQL request 

813 @param nolog if True, do not log anything 

814 @return cursor 

815 

816 .. exref:: 

817 :title: run a select command on a table 

818 :tag: SQL 

819 

820 :: 

821 

822 t = Database (file) 

823 cur = t.execute ("SELECT * FROM table1 ;") 

824 for f in cur : 

825 print(f) 

826 cur.close () 

827 

828 There is another case outside SQL syntax to build cross product. Syntax: 

829 

830 :: 

831 

832 CROSS f1,f2,f3 

833 FROM table 

834 PLACE a,b,c 

835 ORDER BY f8 

836 WHERE f9 == ' ' -- optional 

837 

838 The request must begin by CROSS 

839 """ 

840 res = self._analyse(request) 

841 if res is not None: 

842 return res 

843 else: 

844 # classic ways 

845 self._check_connection() 

846 cur = self._connection.cursor() 

847 dat = time.perf_counter() 

848 try: 

849 if not nolog: 

850 lines = request.split("\n") 

851 if len(lines) > 20: 

852 self.LOG("SQL ", "\n".join( 

853 [repr(x) for x in lines[:20]])) 

854 else: 

855 self.LOG("SQL ", "\n".join([repr(x) for x in lines])) 

856 cur.execute(request) 

857 dat2 = time.perf_counter() 

858 if dat2 - dat > 10: 

859 self.LOG("SQL end") # pragma: no cover 

860 except Exception as e: 

861 raise ExceptionSQL( 

862 "unable to execute a SQL request (1)(file %s)" % 

863 self.get_file(), 

864 e, 

865 request) from e 

866 return cur 

867 

868 def execute_view(self, request, add_column_name=False, nolog=True): 

869 """ 

870 Opens a cursor with a query and returns the result into a list. 

871 

872 @param request SQL request 

873 @param add_column_name add the column name before the first line 

874 @param nolog if True, do not log anything 

875 @return cursor 

876 

877 Example: 

878 

879 :: 

880 

881 t = Database (file) 

882 view = t.execute_view ("SELECT * FROM table1 ;") 

883 """ 

884 cur = self.execute(request, nolog=nolog) 

885 if add_column_name: 

886 col_name_list = [tuple[0] for tuple in cur.description] 

887 res = [col_name_list] + list(cur) 

888 else: 

889 res = list(cur) 

890 cur.close() 

891 if not nolog and (len(res) == 0 or len(res) > 1e4): 

892 self.LOG("execute_view ", len(res), "results") # pragma: no cover 

893 return res 

894 

895 def execute_script(self, script, nolog=True, close=True): 

896 """ 

897 Opens a cursor and run a script. 

898 

899 @param script SQL script 

900 @param nolog if True, do not log anything 

901 @param close close the cursor 

902 @return cursor 

903 """ 

904 self._check_connection() 

905 if not nolog: # pragma: no cover 

906 lines = script.split("\n") 

907 if len(lines) > 20: 

908 self.LOG("SQL start + ", 

909 "\n".join([repr(x) for x in lines[:20]])) 

910 else: 

911 self.LOG("SQL start + ", 

912 "\n".join([repr(x) for x in lines])) 

913 cur = self._connection.cursor() 

914 res = cur.executescript(script) 

915 if close: 

916 cur.close() 

917 if not nolog: 

918 self.LOG("SQL end") # pragma: no cover 

919 else: 

920 return res 

921 

922 ########################################################################## 

923 # extra functions 

924 ########################################################################## 

925 

926 def attach_database(self, db, alias): 

927 """ 

928 Attaches another database. 

929 

930 @param db database to attach 

931 @param alias database alias 

932 """ 

933 if isinstance(db, str): 

934 self.LOG("ATTACH DATABASE '%s' TO '%s' ALIAS %s" % (db, db, alias)) 

935 self.execute("ATTACH DATABASE '%s' AS %s;" % (db, alias)) 

936 else: # pragma: no cover 

937 self.LOG( 

938 "ATTACH DATABASE '%s' TO '%s' ALIAS %s" % 

939 (db._sql_file, self._sql_file, alias)) 

940 self.execute( 

941 "ATTACH DATABASE '%s' AS %s;" % 

942 (db.get_file(), alias)) 

943 

944 def add_function(self, name, nbparam, function): 

945 """ 

946 Adds a function which can be used as any other SQL function (strim, ...). 

947 

948 @param name function name (it does not allow _) 

949 @param nbparam number of parameters 

950 @param function function to add 

951 """ 

952 if "_" in name: 

953 raise RuntimeError( # pragma: no cover 

954 "SQLite does not allow function name with _") 

955 self._check_connection() 

956 if self._engine == "SQLite": 

957 self._connection.create_function(name, nbparam, function) 

958 

959 ########################################################################## 

960 # creation function 

961 ########################################################################## 

962 

963 def create_index(self, indexname, table, columns, unique=False): 

964 """ 

965 Creates an index on a table using some columns. 

966 

967 @param indexname index name 

968 @param table table name 

969 @param columns list of columns 

970 @param unique any value in the columns is unique? 

971 """ 

972 if not isinstance(columns, list) and not isinstance(columns, tuple): 

973 columns = [columns] 

974 

975 if "." in table: 

976 prefix = table.split(".")[0] + "." 

977 table = table.split(".")[1] 

978 else: 

979 prefix = "" 

980 # table = table 

981 

982 self.LOG("index create ", indexname, table, columns, unique) 

983 if unique: 

984 sql = "CREATE UNIQUE INDEX %s%s ON %s (%s);" % ( 

985 prefix, indexname, table, ",".join(columns)) 

986 else: 

987 sql = "CREATE INDEX %s%s ON %s (%s);" % ( 

988 prefix, indexname, table, ",".join(columns)) 

989 self.execute(sql) 

990 

991 def create_table(self, table, columns, temporary=False, nolog=False): 

992 """ 

993 Creates a table. 

994 

995 @param table table name 

996 @param columns columns definition, dictionary { key:(column_name,python_type) } 

997 if PRIMARYKEY is added, the key is considered as the primary key. 

998 @param temporary if True the table is temporary 

999 @param nolog @see me execute 

1000 @return cursor 

1001 

1002 Example for *columns*: 

1003 

1004 :: 

1005 

1006 columns = { -1:("key", int, "PRIMARYKEY", "AUTOINCREMENT"), 

1007 0:("name",str), 1:("number", float) } 

1008 

1009 """ 

1010 if self._engine == "SQLite" and table == "sqlite_sequence": 

1011 raise DBException( # pragma: no cover 

1012 "unable to create a table named sql_sequence") 

1013 

1014 tables = self.get_table_list() 

1015 if table in tables: 

1016 raise DBException( # pragma: no cover 

1017 "table %r is already present, it cannot be added" % table) 

1018 

1019 if temporary: 

1020 sql = "CREATE TEMPORARY TABLE " + table + "(" 

1021 else: 

1022 sql = "CREATE TABLE " + table + "(" 

1023 col = [] 

1024 for c, val in columns.items(): 

1025 if self.isMSSQL(): # pragma: no cover 

1026 if isinstance(val[1], tuple): 

1027 v, li = val[1] 

1028 else: 

1029 v, li = val[1], 2048 

1030 

1031 if li > 8000: 

1032 col.append(val[0] + " TEXT") 

1033 elif v is str: 

1034 col.append(val[0] + " VARCHAR(%d)" % li) 

1035 elif v is int: 

1036 col.append(val[0] + " INTEGER") 

1037 elif v is float: 

1038 col.append(val[0] + " FLOAT") 

1039 elif v is numpy.int64: 

1040 col.append(val[0] + " INTEGER") 

1041 elif v is numpy.float64: 

1042 col.append(val[0] + " FLOAT") 

1043 elif v is decimal.Decimal: 

1044 col.append(val[0] + " Decimal") 

1045 elif v is datetime.datetime: 

1046 col.append(val[0] + " DATETIME") 

1047 else: 

1048 raise DBException( # pragma: no cover 

1049 "unable to add column " + 

1050 str(c) + 

1051 " ... " + 

1052 str(val) + 

1053 " v= " + 

1054 str(v)) 

1055 else: 

1056 if isinstance(val[1], tuple): 

1057 v, li = val[1] 

1058 else: 

1059 v, li = val[1], 2048 

1060 

1061 if v is str: 

1062 col.append(val[0] + " TEXT") 

1063 elif v is int: 

1064 col.append(val[0] + " INTEGER") 

1065 elif v is float: 

1066 col.append(val[0] + " FLOAT") 

1067 elif v is numpy.int64: 

1068 col.append(val[0] + " INTEGER") 

1069 elif v is numpy.float64: 

1070 col.append(val[0] + " FLOAT") 

1071 elif v is decimal.Decimal: 

1072 col.append(val[0] + " Decimal") 

1073 elif v is datetime.datetime: 

1074 col.append(val[0] + " DATETIME") 

1075 else: 

1076 raise DBException( # pragma: no cover 

1077 "unable to add column " + 

1078 str(c) + 

1079 " ... " + 

1080 str(val) + 

1081 " v= " + 

1082 str(v)) 

1083 

1084 fval = val[2:] 

1085 for v in fval: 

1086 if v not in DatabaseCore._field_option: 

1087 raise DBException( # pragma: no cover 

1088 "an option is unexpected %s should be in %s" % 

1089 (v, str( 

1090 DatabaseCore._field_option))) 

1091 

1092 if "PRIMARYKEY" in val: 

1093 if val[1] != int: 

1094 raise DBException( 

1095 "unable to create a primary key on something differont from an integer (%s)" % 

1096 str(val)) 

1097 col[-1] += " PRIMARY KEY" 

1098 if "AUTOINCREMENT" in val: 

1099 if self.isMSSQL(): 

1100 col[-1] += " IDENTITY(0,1)" 

1101 else: 

1102 col[-1] += " AUTOINCREMENT" 

1103 

1104 sql += ",\n ".join(col) 

1105 sql += ");" 

1106 return self.execute(sql, nolog=nolog) 

1107 

1108 ########################################################################## 

1109 # deletion 

1110 ########################################################################## 

1111 

1112 def remove_table(self, table): 

1113 """ 

1114 Removes a table. 

1115 

1116 @param table table name 

1117 @return return a cursor 

1118 """ 

1119 self.execute("DROP TABLE %s" % table) 

1120 

1121 ########################################################################## 

1122 # modification 

1123 ########################################################################## 

1124 

1125 def _insert_sql(self, table, insert_values): 

1126 """ 

1127 Builds the sql for an insert request. 

1128 

1129 @param table table name 

1130 @param insert_values dictionary or a list 

1131 @return string 

1132 """ 

1133 if isinstance(insert_values, dict): 

1134 keys = [] 

1135 values = [] 

1136 for k, v in insert_values.items(): 

1137 keys.append(k) 

1138 if v is None: 

1139 values.append('') 

1140 elif isinstance(v, str): 

1141 v = "'" + str(v).replace("'", "''") + "'" 

1142 values.append(v) 

1143 elif isinstance(v, datetime.datetime): 

1144 v = "'" + str(v) + "'" 

1145 values.append(v) 

1146 else: 

1147 values.append(str(v)) 

1148 keys = ",".join(keys) 

1149 values = ",".join(values) 

1150 sql = "INSERT INTO %s (%s) VALUES (%s)" % (table, keys, values) 

1151 return sql 

1152 elif isinstance(insert_values, (tuple, list)): 

1153 values = [] 

1154 for v in insert_values: 

1155 if v is None: 

1156 values.append('') 

1157 elif isinstance(v, str): 

1158 v = "'" + str(v).replace("'", "''") + "'" 

1159 values.append(v) 

1160 elif isinstance(v, datetime.datetime): 

1161 v = "'" + str(v) + "'" 

1162 values.append(v) 

1163 else: 

1164 values.append(str(v)) 

1165 values = ",".join(values) 

1166 sql = "INSERT INTO %s VALUES (%s)" % (table, values) 

1167 return sql 

1168 else: 

1169 raise TypeError( # pragma: no cover 

1170 "unexpected type: " + str(type(insert_values))) 

1171 

1172 def insert(self, table, insert_values, cursor=None, nolog=True): 

1173 """ 

1174 Inserts into a table. 

1175 

1176 @param table table name 

1177 @param insert_values values to insert (a list of dictionary or a single dictionary) 

1178 @param cursor if *cursor is not None*, use it, otherwise creates a new one 

1179 @param nolog if True, do not log anything 

1180 @return sql request or None if several insertion were sent (result is too long) 

1181 

1182 @warning The commit is not done and must be done to stored these modifications. 

1183 """ 

1184 if isinstance(insert_values, list): 

1185 # we expect several insertion 

1186 if self._engine != "SQLite": 

1187 for d in insert_values: 

1188 self.insert(table, d, cursor) 

1189 else: 

1190 if isinstance(insert_values[0], dict): 

1191 ins = {} 

1192 for k in insert_values[0]: 

1193 ins[k] = ":" + k 

1194 sql = self._insert_sql(table, ins) 

1195 else: 

1196 q = tuple('?' for _ in insert_values[0]) 

1197 sql = self._insert_sql(table, q).replace("'", "") 

1198 

1199 sql = sql.replace("'", "") 

1200 try: 

1201 if not nolog: # pragma: no cover 

1202 if len(sql) > 1000: 

1203 self.LOG("SQLs", sql[:1000]) 

1204 else: 

1205 self.LOG("SQLs", sql) 

1206 self._connection.executemany(sql, insert_values) 

1207 return "" 

1208 except Exception as e: 

1209 raise ExceptionSQL( # pylint: disable=W0707 

1210 "Unable to execute a SQL request (3) (cursor %r) (file %r)" % 

1211 (str(cursor), self.get_file()), e, sql) 

1212 

1213 elif isinstance(insert_values, dict): 

1214 sql = self._insert_sql(table, insert_values) 

1215 

1216 try: 

1217 if not nolog: # pragma: no cover 

1218 if len(sql) > 1000: 

1219 self.LOG("SQLs", sql[:1000]) 

1220 else: 

1221 self.LOG("SQLs", sql) 

1222 if cursor is not None: 

1223 cursor.execute(sql) 

1224 else: 

1225 if self._buffer_insert_s > 0: 

1226 self._buffer_insert.append(sql) 

1227 

1228 if len(self._buffer_insert) >= self._buffer_insert_s: 

1229 for s in self._buffer_insert: 

1230 self._connection.execute(s) 

1231 del self._buffer_insert[:] 

1232 else: 

1233 self._connection.execute(sql) 

1234 

1235 return sql 

1236 except Exception as e: 

1237 raise ExceptionSQL( # pylint: disable=W0707 

1238 "unable to execute a SQL request (2) (cursor %r) (file %r)" % 

1239 (str(cursor), self.get_file()), e, sql) 

1240 

1241 else: 

1242 raise DBException( # pragma: no cover 

1243 "insert: expected type (list of dict or dict) instead of %s" % 

1244 (str( 

1245 type(insert_values)))) 

1246 

1247 def update(self, table, key, value, values): 

1248 """ 

1249 Updates some values ``WHERE key=value``. 

1250 

1251 @param table table to update 

1252 @param key key 

1253 @param value WHERE key = value 

1254 @param values values to be updated 

1255 

1256 @warning The commit is not done and must be done 

1257 to stored these modifications. 

1258 """ 

1259 

1260 self._check_values(values) 

1261 self._check_connection() 

1262 alls = [] 

1263 for k, v in values.items(): 

1264 if k != key: 

1265 if isinstance(v, (str, datetime.datetime)): 

1266 alls += ["%s='%s'" % (k, str(v))] 

1267 else: 

1268 alls += ["%s=%s" % (k, str(v))] 

1269 if isinstance(value, str): 

1270 sql = "UPDATE %s SET %s WHERE %s='%s'" % ( 

1271 table, ",".join(alls), key, value.replace("'", "''")) 

1272 elif isinstance(value, datetime.datetime): 

1273 sql = "UPDATE %s SET %s WHERE %s='%s'" % ( 

1274 table, ",".join(alls), key, str(value)) 

1275 else: 

1276 sql = "UPDATE %s SET %s WHERE %s=%s" % ( 

1277 table, ",".join(alls), key, value) 

1278 

1279 try: 

1280 self._connection.execute(sql) 

1281 return sql 

1282 except Exception as e: # pragma: no cover 

1283 raise ExceptionSQL( # pylint: disable=W0707 

1284 "Unable to execute a SQL request (4) (file %r)" % 

1285 self.get_file(), e, sql)