dbwrap.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714
  1. #
  2. # Metrix++, Copyright 2009-2013, Metrix++ Project
  3. # Link: http://metrixplusplus.sourceforge.net
  4. #
  5. # This file is a part of Metrix++ Tool.
  6. #
  7. # Metrix++ is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, version 3 of the License.
  10. #
  11. # Metrix++ is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with Metrix++. If not, see <http://www.gnu.org/licenses/>.
  18. #
  19. import sqlite3
  20. import re
  21. import os.path
  22. import logging
  23. import itertools
  24. import shutil
  25. import traceback
  26. class Database(object):
  27. last_used_id = 0
  28. version = "1.0"
  29. class TableData(object):
  30. def __init__(self, table_id, name, support_regions):
  31. self.id = table_id
  32. self.name = name
  33. self.support_regions = support_regions
  34. class ColumnData(object):
  35. def __init__(self, column_id, name, sql_type, non_zero):
  36. self.id = column_id
  37. self.name = name
  38. self.sql_type = sql_type
  39. self.non_zero = non_zero
  40. class TagData(object):
  41. def __init__(self, tag_id, name):
  42. self.id = tag_id
  43. self.name = name
  44. class PropertyData(object):
  45. def __init__(self, property_id, name, value):
  46. self.id = property_id
  47. self.name = name
  48. self.value = value
  49. class FileData(object):
  50. def __init__(self, file_id, path, checksum):
  51. self.id = file_id
  52. self.path = path
  53. self.checksum = checksum
  54. class RegionData(object):
  55. def __init__(self, file_id, region_id, name, begin, end, line_begin, line_end, cursor, group, checksum):
  56. self.file_id = file_id
  57. self.region_id = region_id
  58. self.name = name
  59. self.begin = begin
  60. self.end = end
  61. self.line_begin = line_begin
  62. self.line_end = line_end
  63. self.cursor = cursor
  64. self.group = group
  65. self.checksum = checksum
  66. class MarkerData(object):
  67. def __init__(self, file_id, begin, end, group):
  68. self.file_id = file_id
  69. self.begin = begin
  70. self.end = end
  71. self.group = group
  72. def __init__(self):
  73. self.read_only = False
  74. self.conn = None
  75. self.dirs = None
  76. self.is_cloned = False
  77. self.last_used_id += 1
  78. self.id = self.last_used_id
  79. self._tables_cache = None
  80. def __del__(self):
  81. if self.conn != None:
  82. if self.is_cloned == True:
  83. logging.debug("Cleaning up database file")
  84. self.InternalCleanUpUtils().clean_up_not_confirmed(self)
  85. logging.debug("Committing database file")
  86. self.conn.commit()
  87. class InternalCleanUpUtils(object):
  88. def clean_up_not_confirmed(self, db_loader):
  89. sql = "DELETE FROM __info__ WHERE (confirmed = 0)"
  90. db_loader.log(sql)
  91. db_loader.conn.execute(sql)
  92. sql = "DELETE FROM __tags__ WHERE (confirmed = 0)"
  93. db_loader.log(sql)
  94. db_loader.conn.execute(sql)
  95. sql = "SELECT * FROM __tables__ WHERE (confirmed = 0)"
  96. db_loader.log(sql)
  97. for table in db_loader.conn.execute(sql).fetchall():
  98. sql = "DELETE FROM __columns__ WHERE table_id = '" + str(table['id']) + "'"
  99. db_loader.log(sql)
  100. db_loader.conn.execute(sql)
  101. sql = "DELETE FROM __tables__ WHERE id = '" + str(table['id']) + "'"
  102. db_loader.log(sql)
  103. db_loader.conn.execute(sql)
  104. self._tables_cache = None # reset cache when a list of tables is modified
  105. sql = "DROP TABLE '" + table['name'] + "'"
  106. db_loader.log(sql)
  107. db_loader.conn.execute(sql)
  108. sql = "SELECT __columns__.name AS column_name, __tables__.name AS table_name, __columns__.id AS column_id FROM __columns__, __tables__ WHERE (__columns__.confirmed = 0 AND __columns__.table_id = __tables__.id)"
  109. db_loader.log(sql)
  110. for column in db_loader.conn.execute(sql).fetchall():
  111. logging.info("New database file inherits useless column: '" + column['table_name'] + "'.'" + column['column_name'] + "'")
  112. sql = "DELETE FROM __columns__ WHERE id = '" + str(column['column_id']) + "'"
  113. db_loader.log(sql)
  114. db_loader.conn.execute(sql)
  115. sql = "UPDATE '" + column['table_name'] + "' SET '" + column['column_name'] + "' = NULL"
  116. # TODO bug here: collect with column, recollect without, recollect with again
  117. # it will be impossible to create the column in the table
  118. db_loader.log(sql)
  119. db_loader.conn.execute(sql)
  120. self.clean_up_file(db_loader)
  121. def clean_up_file(self, db_loader, file_id = None):
  122. # this function is called on every updated file, so cache table names
  123. if db_loader._tables_cache == None:
  124. db_loader._tables_cache = []
  125. sql = "SELECT * FROM __tables__"
  126. db_loader.log(sql)
  127. for table in db_loader.conn.execute(sql).fetchall():
  128. db_loader._tables_cache.append(table['name'])
  129. for table_name in itertools.chain(db_loader._tables_cache, ['__regions__', '__markers__']):
  130. sql = ""
  131. if file_id == None:
  132. sql = "DELETE FROM '" + table_name + "' WHERE file_id IN (SELECT __files__.id FROM __files__ WHERE __files__.confirmed = 0)"
  133. else:
  134. sql = "DELETE FROM '" + table_name + "' WHERE (file_id = " + str(file_id) + ")"
  135. db_loader.log(sql)
  136. db_loader.conn.execute(sql)
  137. class InternalPathUtils(object):
  138. def iterate_heads(self, path):
  139. dirs = []
  140. head = os.path.dirname(path)
  141. last_head = None # to process Windows drives
  142. while (head != "" and last_head != head):
  143. dirs.append(os.path.basename(head))
  144. last_head = head
  145. head = os.path.dirname(head)
  146. dirs.reverse()
  147. for each in dirs:
  148. yield each
  149. def normalize_path(self, path):
  150. if path == None:
  151. return None
  152. path =re.sub(r'''[\\]''', "/", path)
  153. if len(path) > 0 and path[len(path) - 1] == '/':
  154. return path[:-1]
  155. return path
  156. def update_dirs(self, db_loader, path = None):
  157. if db_loader.dirs == None:
  158. if path == None:
  159. db_loader.dirs = {} # initial construction
  160. else:
  161. return # avoid useless cache updates
  162. elif path == None:
  163. return # avoid multiple initial constructions
  164. path = self.normalize_path(path)
  165. rows = None
  166. if path == None:
  167. sql = "SELECT * FROM __files__"
  168. db_loader.log(sql)
  169. rows = db_loader.conn.execute(sql).fetchall()
  170. else:
  171. rows = [{"path": path}]
  172. for row in rows:
  173. cur_head = db_loader.dirs
  174. for dir_name in self.iterate_heads(row["path"]):
  175. if dir_name not in cur_head.keys():
  176. cur_head[dir_name] = {}
  177. cur_head = cur_head[dir_name]
  178. cur_head[os.path.basename(row["path"])] = None
  179. def create(self, file_name, clone_from = None):
  180. if clone_from != None:
  181. self.is_cloned = True
  182. logging.debug("Cloning database file: " + clone_from)
  183. shutil.copy2(clone_from, file_name)
  184. logging.debug("Connecting database file: " + file_name)
  185. self.conn = sqlite3.connect(file_name)
  186. self.conn.row_factory = sqlite3.Row
  187. self.read_only = False
  188. sql = "UPDATE __tables__ SET confirmed = 0"
  189. self.log(sql)
  190. self.conn.execute(sql)
  191. self._tables_cache = None # reset cache when a list of tables is modified
  192. sql = "UPDATE __columns__ SET confirmed = 0"
  193. self.log(sql)
  194. self.conn.execute(sql)
  195. sql = "UPDATE __tags__ SET confirmed = 0"
  196. self.log(sql)
  197. self.conn.execute(sql)
  198. sql = "UPDATE __files__ SET confirmed = 0"
  199. self.log(sql)
  200. self.conn.execute(sql)
  201. else:
  202. self.connect(file_name)
  203. def connect(self, file_name, read_only = False):
  204. logging.debug("Connecting database file: " + file_name)
  205. self.conn = sqlite3.connect(file_name)
  206. self.conn.row_factory = sqlite3.Row
  207. self.read_only = read_only
  208. if self.read_only == False:
  209. try:
  210. sql = "CREATE TABLE __info__ (id integer NOT NULL PRIMARY KEY AUTOINCREMENT, property text NOT NULL, value text, confirmed integer NOT NULL, UNIQUE (property) ON CONFLICT REPLACE)"
  211. self.log(sql)
  212. self.conn.execute(sql)
  213. sql = "INSERT INTO __info__ (property, value, confirmed) VALUES ('version', '" + self.version + "', 1)"
  214. self.log(sql)
  215. self.conn.execute(sql)
  216. sql = "CREATE TABLE __tables__ (id integer NOT NULL PRIMARY KEY, name text NOT NULL, version text NOT NULL, support_regions integer NOT NULL, confirmed integer NOT NULL, UNIQUE (name))"
  217. self.log(sql)
  218. self.conn.execute(sql)
  219. self._tables_cache = None # reset cache when a list of tables is modified
  220. sql = "CREATE TABLE __columns__ (id integer NOT NULL PRIMARY KEY, name text NOT NULL, type text NOT NULL, table_id integer NOT_NULL, non_zero integer NOT NULL, confirmed integer NOT NULL, UNIQUE (name, table_id))"
  221. self.log(sql)
  222. self.conn.execute(sql)
  223. sql = "CREATE TABLE __tags__ (id integer NOT NULL PRIMARY KEY, name text NOT NULL UNIQUE, confirmed integer NOT NULL)"
  224. self.log(sql)
  225. self.conn.execute(sql)
  226. sql = "CREATE TABLE __files__ (id integer NOT NULL PRIMARY KEY AUTOINCREMENT, path text NOT NULL, checksum integer NOT NULL, tag1 integer, tag2 integer, tag3 integer, confirmed integer NOT NULL, UNIQUE(path))"
  227. self.log(sql)
  228. self.conn.execute(sql)
  229. sql = "CREATE TABLE __regions__ (file_id integer NOT NULL, region_id integer NOT NULL, name text NOT NULL, begin integer NOT NULL, end integer NOT NULL, line_begin integer NOT NULL, line_end integer NOT NULL, cursor integer NOT NULL, group_id integer NOT NULL, checksum integer NOT NULL, PRIMARY KEY (file_id, region_id))"
  230. self.log(sql)
  231. self.conn.execute(sql)
  232. sql = "CREATE TABLE __markers__ (id integer NOT NULL PRIMARY KEY, file_id integer NOT NULL, begin integer NOT NULL, end integer NOT NULL, group_id integer NOT NULL)"
  233. self.log(sql)
  234. self.conn.execute(sql)
  235. except sqlite3.OperationalError as e:
  236. logging.debug("sqlite3.OperationalError: " + str(e))
  237. def set_property(self, property_name, value):
  238. ret_val = None
  239. sql = "SELECT * FROM __info__ WHERE (property = '" + property_name + "')"
  240. self.log(sql)
  241. result = self.conn.execute(sql).fetchall()
  242. if len(result) != 0:
  243. ret_val = result[0]['value']
  244. sql = "INSERT INTO __info__ (property, value, confirmed) VALUES ('" + property_name + "', '" + value + "', 1)"
  245. self.log(sql)
  246. self.conn.execute(sql)
  247. return ret_val
  248. def get_property(self, property_name):
  249. ret_val = None
  250. sql = "SELECT * FROM __info__ WHERE (property = '" + property_name + "' AND confirmed = 1)"
  251. self.log(sql)
  252. result = self.conn.execute(sql).fetchall()
  253. if len(result) != 0:
  254. ret_val = result[0]['value']
  255. return ret_val
  256. def iterate_properties(self):
  257. sql = "SELECT * FROM __info__ WHERE (confirmed = 1)"
  258. self.log(sql)
  259. for each in self.conn.execute(sql).fetchall():
  260. yield self.PropertyData(each['id'], each['property'], each['value'])
  261. def create_table(self, table_name, support_regions = False, version='1.0'):
  262. assert(self.read_only == False)
  263. self._tables_cache = None # reset cache when a list of tables is modified
  264. sql = "SELECT * FROM __tables__ WHERE (name = '" + table_name + "'AND confirmed == 0)"
  265. self.log(sql)
  266. result = self.conn.execute(sql).fetchall()
  267. if len(result) != 0:
  268. if result[0]['version'] != version:
  269. # in case of changes in version, drop existing table data
  270. sql = "DELETE FROM __columns__ WHERE table_id = '" + str(result[0]['id']) + "'"
  271. self.log(sql)
  272. self.conn.execute(sql)
  273. sql = "DELETE FROM __tables__ WHERE id = '" + str(result[0]['id']) + "'"
  274. self.log(sql)
  275. self.conn.execute(sql)
  276. sql = "DROP TABLE '" + result[0]['name'] + "'"
  277. self.log(sql)
  278. self.conn.execute(sql)
  279. else:
  280. sql = "UPDATE __tables__ SET confirmed = 1 WHERE (name = '" + table_name + "')"
  281. self.log(sql)
  282. self.conn.execute(sql)
  283. return False
  284. sql = "CREATE TABLE '" + table_name + "' (file_id integer NOT NULL PRIMARY KEY)"
  285. if support_regions == True:
  286. sql = str("CREATE TABLE '" + table_name + "' (file_id integer NOT NULL, region_id integer NOT NULL, "
  287. + "PRIMARY KEY (file_id, region_id))")
  288. self.log(sql)
  289. self.conn.execute(sql)
  290. sql = "INSERT INTO __tables__ (name, version, support_regions, confirmed) VALUES ('" + table_name + "', '" + version + "', '" + str(int(support_regions)) + "', 1)"
  291. self.log(sql)
  292. self.conn.execute(sql)
  293. return True
  294. def iterate_tables(self):
  295. sql = "SELECT * FROM __tables__ WHERE (confirmed = 1)"
  296. self.log(sql)
  297. result = self.conn.execute(sql).fetchall()
  298. for row in result:
  299. yield self.TableData(int(row["id"]), str(row["name"]), bool(row["support_regions"]))
  300. def check_table(self, table_name):
  301. sql = "SELECT * FROM __tables__ WHERE (name = '" + table_name + "' AND confirmed = 1)"
  302. self.log(sql)
  303. result = self.conn.execute(sql).fetchall()
  304. if len(result) == 0:
  305. return False
  306. return True
  307. def create_column(self, table_name, column_name, column_type, non_zero=False):
  308. assert(self.read_only == False)
  309. if column_type == None:
  310. logging.debug("Skipping column '" + column_name + "' creation for table '" + table_name + "'")
  311. return
  312. sql = "SELECT id FROM __tables__ WHERE (name = '" + table_name + "')"
  313. self.log(sql)
  314. table_id = self.conn.execute(sql).next()['id']
  315. sql = "SELECT * FROM __columns__ WHERE (table_id = '" + str(table_id) + "' AND name = '" + column_name + "' AND confirmed == 0)"
  316. self.log(sql)
  317. result = self.conn.execute(sql).fetchall()
  318. if len(result) != 0:
  319. # Major changes in columns should result in step up of table version,
  320. # which causes drop the table in case of database reuse
  321. assert(result[0]['type'] == column_type)
  322. assert(result[0]['non_zero'] == non_zero)
  323. sql = "UPDATE __columns__ SET confirmed = 1 WHERE (table_id = '" + str(table_id) + "' AND name = '" + column_name + "')"
  324. self.log(sql)
  325. self.conn.execute(sql)
  326. return False
  327. sql = "ALTER TABLE '" + table_name + "' ADD COLUMN '" + column_name + "' " + column_type
  328. self.log(sql)
  329. self.conn.execute(sql)
  330. sql = "SELECT id FROM __tables__ WHERE (name = '" + table_name + "')"
  331. self.log(sql)
  332. table_id = self.conn.execute(sql).next()['id']
  333. sql = "INSERT INTO __columns__ (name, type, table_id, non_zero, confirmed) VALUES ('" + column_name + "', '" + column_type + "', '" + str(table_id) + "', '" + str(int(non_zero)) + "', 1)"
  334. self.log(sql)
  335. self.conn.execute(sql)
  336. return True
  337. def iterate_columns(self, table_name):
  338. sql = "SELECT id FROM __tables__ WHERE (name = '" + table_name + "')"
  339. self.log(sql)
  340. table_id = self.conn.execute(sql).next()['id']
  341. sql = "SELECT * FROM __columns__ WHERE (table_id = '" + str(table_id) + "' AND confirmed = 1)"
  342. self.log(sql)
  343. result = self.conn.execute(sql).fetchall()
  344. for row in result:
  345. yield self.ColumnData(int(row["id"]), str(row["name"]), str(row["type"]), bool(row["non_zero"]))
  346. def check_column(self, table_name, column_name):
  347. sql = "SELECT id FROM __tables__ WHERE (name = '" + table_name + "')"
  348. self.log(sql)
  349. table_id = self.conn.execute(sql).next()['id']
  350. sql = "SELECT * FROM __columns__ WHERE (table_id = '" + str(table_id) + "' AND name = '" + column_name + "' AND confirmed = 1)"
  351. self.log(sql)
  352. result = self.conn.execute(sql).fetchall()
  353. if len(result) == 0:
  354. return False
  355. return True
  356. def create_tag(self, tag_name):
  357. assert(self.read_only == False)
  358. sql = "SELECT * FROM __tags__ WHERE (name = '" + tag_name + "' AND confirmed == 0)"
  359. self.log(sql)
  360. result = self.conn.execute(sql).fetchall()
  361. if len(result) != 0:
  362. sql = "UPDATE __tags__ SET confirmed = 1 WHERE (name = '" + tag_name + "')"
  363. self.log(sql)
  364. self.conn.execute(sql)
  365. return
  366. sql = "INSERT INTO __tags__ (name, confirmed) VALUES ('" + tag_name + "', 1)"
  367. self.log(sql)
  368. self.conn.execute(sql)
  369. def iterate_tags(self):
  370. sql = "SELECT * FROM __tags__ WHERE (confirmed = 1)"
  371. self.log(sql)
  372. result = self.conn.execute(sql).fetchall()
  373. for row in result:
  374. yield self.TagData(int(row["id"]), str(row["name"]))
  375. def check_tag(self, tag_name):
  376. sql = "SELECT * FROM __tags__ WHERE (name = '" + tag_name + "' AND confirmed = 1)"
  377. self.log(sql)
  378. result = self.conn.execute(sql).fetchall()
  379. if len(result) == 0:
  380. return False
  381. return True
  382. # TODO activate usage of tags
  383. def create_file(self, path, checksum):
  384. assert(self.read_only == False)
  385. path = self.InternalPathUtils().normalize_path(path)
  386. if self.is_cloned == True:
  387. sql = "SELECT * FROM __files__ WHERE (path = '" + path + "')"
  388. self.log(sql)
  389. result = self.conn.execute(sql).fetchall()
  390. if len(result) != 0:
  391. if result[0]['checksum'] == checksum:
  392. old_id = result[0]['id']
  393. sql = "UPDATE __files__ SET confirmed = 1 WHERE (id = " + str(old_id) +")"
  394. self.log(sql)
  395. self.conn.execute(sql)
  396. return (old_id, False)
  397. else:
  398. self.InternalCleanUpUtils().clean_up_file(self, result[0]['id'])
  399. sql = "INSERT OR REPLACE INTO __files__ (path, checksum, confirmed) VALUES (?, ?, 1)"
  400. column_data = [path, checksum]
  401. self.log(sql + " /with arguments: " + str(column_data))
  402. cur = self.conn.cursor()
  403. cur.execute(sql, column_data)
  404. self.InternalPathUtils().update_dirs(self, path=path)
  405. return (cur.lastrowid, True)
  406. def iterate_dircontent(self, path, include_subdirs = True, include_subfiles = True):
  407. self.InternalPathUtils().update_dirs(self)
  408. path = self.InternalPathUtils().normalize_path(path)
  409. cur_head = self.dirs
  410. valid = True
  411. if path != "":
  412. for head in self.InternalPathUtils().iterate_heads(path):
  413. if head not in cur_head.keys():
  414. # non existing directory
  415. valid = False
  416. else:
  417. cur_head = cur_head[head]
  418. basename = os.path.basename(path)
  419. if basename not in cur_head.keys() or cur_head[basename] == None:
  420. # do not exist or points to the file
  421. valid = False
  422. else:
  423. cur_head = cur_head[basename]
  424. if valid == True:
  425. for elem in cur_head.keys():
  426. if include_subdirs == True and cur_head[elem] != None:
  427. yield elem
  428. if include_subfiles == True and cur_head[elem] == None:
  429. yield elem
  430. def check_file(self, path):
  431. return self.get_file(path) != None
  432. def check_dir(self, path):
  433. for each in self.iterate_dircontent(path):
  434. each = each # used
  435. return True # there is at least one item
  436. return False
  437. def get_file(self, path):
  438. path = self.InternalPathUtils().normalize_path(path)
  439. result = self.select_rows("__files__", filters = [("path", "=", path), ("confirmed", "=", 1)])
  440. if len(result) == 0:
  441. return None
  442. assert(len(result) == 1)
  443. return self.FileData(result[0]['id'], result[0]['path'], result[0]['checksum'])
  444. def iterate_files(self, path_like = None):
  445. for row in self.select_rows('__files__', path_like=path_like, filters=[('confirmed','=','1')]):
  446. yield self.FileData(row['id'], row['path'], row['checksum'])
  447. def create_region(self, file_id, region_id, name, begin, end, line_begin, line_end, cursor, group, checksum):
  448. assert(self.read_only == False)
  449. sql = "INSERT OR REPLACE INTO __regions__ (file_id, region_id, name, begin, end, line_begin, line_end, cursor, group_id, checksum) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
  450. column_data = [file_id, region_id, name, begin, end, line_begin, line_end, cursor, group, checksum]
  451. self.log(sql + " /with arguments: " + str(column_data))
  452. cur = self.conn.cursor()
  453. cur.execute(sql, column_data)
  454. return cur.lastrowid
  455. def get_region(self, file_id, region_id):
  456. result = self.select_rows("__regions__", filters = [("file_id", "=", file_id), ("region_id", "=", region_id)])
  457. if len(result) == 0:
  458. return None
  459. return self.RegionData(result[0]['file_id'],
  460. result[0]['region_id'],
  461. result[0]['name'],
  462. result[0]['begin'],
  463. result[0]['end'],
  464. result[0]['line_begin'],
  465. result[0]['line_end'],
  466. result[0]['cursor'],
  467. result[0]['group_id'],
  468. result[0]['checksum'])
  469. def iterate_regions(self, file_id):
  470. for each in self.select_rows("__regions__", filters = [("file_id", "=", file_id)]):
  471. yield self.RegionData(each['file_id'],
  472. each['region_id'],
  473. each['name'],
  474. each['begin'],
  475. each['end'],
  476. each['line_begin'],
  477. each['line_end'],
  478. each['cursor'],
  479. each['group_id'],
  480. each['checksum'])
  481. def create_marker(self, file_id, begin, end, group):
  482. assert(self.read_only == False)
  483. sql = "INSERT OR REPLACE INTO __markers__ (file_id, begin, end, group_id) VALUES (?, ?, ?, ?)"
  484. column_data = [file_id, begin, end, group]
  485. self.log(sql + " /with arguments: " + str(column_data))
  486. cur = self.conn.cursor()
  487. cur.execute(sql, column_data)
  488. return cur.lastrowid
  489. def iterate_markers(self, file_id):
  490. for each in self.select_rows("__markers__", filters = [("file_id", "=", file_id)]):
  491. yield self.MarkerData(each['file_id'],
  492. each['begin'],
  493. each['end'],
  494. each['group_id'])
  495. def add_row(self, table_name, file_id, region_id, array_data):
  496. assert(self.read_only == False)
  497. column_names = "'file_id'"
  498. column_values = "?"
  499. column_data = [file_id]
  500. if region_id != None:
  501. column_names += ", 'region_id'"
  502. column_values += ", ?"
  503. column_data.append(region_id)
  504. useful_data = 0
  505. for each in array_data:
  506. column_names += ", '" + each[0] + "'"
  507. column_values += ", ?"
  508. column_data.append(each[1])
  509. useful_data += 1
  510. if useful_data == 0:
  511. return
  512. sql = "INSERT OR REPLACE INTO '" + table_name + "' (" + column_names + ") VALUES (" + column_values + ")"
  513. self.log(sql + " /with arguments: " + str(column_data))
  514. cur = self.conn.cursor()
  515. cur.execute(sql, column_data)
  516. return cur.lastrowid
  517. def select_rows(self, table_name, path_like = None, column_names = [], filters = [], order_by = None, limit_by = None):
  518. safe_column_names = []
  519. for each in column_names:
  520. safe_column_names.append("'" + each + "'")
  521. return self.select_rows_unsafe(table_name, path_like = path_like,
  522. column_names = safe_column_names, filters = filters,
  523. order_by = order_by, limit_by = limit_by)
  524. def select_rows_unsafe(self, table_name, path_like = None, column_names = [], filters = [],
  525. group_by = None, order_by = None, limit_by = None):
  526. path_like = self.InternalPathUtils().normalize_path(path_like)
  527. if self.conn == None:
  528. return []
  529. table_stmt = "'" + table_name + "'"
  530. what_stmt = ", ".join(column_names)
  531. if len(what_stmt) == 0:
  532. what_stmt = "*"
  533. elif path_like != None and table_name != '__files__' and group_by == None:
  534. what_stmt += ", '__files__'.'path', '__files__'.'id'"
  535. inner_stmt = ""
  536. if path_like != None and table_name != '__files__':
  537. inner_stmt = " INNER JOIN '__files__' ON '__files__'.'id' = '" + table_name + "'.'file_id' "
  538. where_stmt = " "
  539. values = ()
  540. if len(filters) != 0:
  541. if filters[0][1] == 'IN':
  542. where_stmt = " WHERE (`" + filters[0][0] + "` " + filters[0][1] + " " + filters[0][2]
  543. else:
  544. where_stmt = " WHERE (`" + filters[0][0] + "` " + filters[0][1] + " ?"
  545. values = (filters[0][2],)
  546. for each in filters[1:]:
  547. if each[1] == 'IN':
  548. where_stmt += " AND `" + each[0] + "` " + each[1] + " " + each[2]
  549. else:
  550. where_stmt += " AND `" + each[0] + "` " + each[1] + " ?"
  551. values += (each[2], )
  552. if path_like != None:
  553. where_stmt += " AND '__files__'.'path' LIKE ?"
  554. values += (path_like, )
  555. where_stmt += ")"
  556. elif path_like != None:
  557. where_stmt = " WHERE '__files__'.'path' LIKE ?"
  558. values += (path_like, )
  559. group_stmt = ""
  560. if group_by != None:
  561. group_stmt = " GROUP BY (`" + group_by + "`)"
  562. order_stmt = ""
  563. if order_by != None:
  564. if order_by.startswith("-"):
  565. order_stmt = " ORDER BY (`" + order_by[1:] + "`) DESC "
  566. else:
  567. order_stmt = " ORDER BY (`" + order_by + "`) "
  568. limit_stmt = ""
  569. if limit_by != None:
  570. limit_stmt = " LIMIT " + str(limit_by)
  571. sql = "SELECT " + what_stmt + " FROM " + table_stmt + inner_stmt + where_stmt + group_stmt + order_stmt + limit_stmt
  572. self.log(sql + " /with arguments: " + str(values))
  573. return self.conn.execute(sql, values).fetchall()
  574. def get_row(self, table_name, file_id, region_id):
  575. selected = self.get_rows(table_name, file_id, region_id)
  576. # assures that only one row in database
  577. # if assertion happens, caller's intention is not right, use get_rows instead
  578. assert(len(selected) == 0 or len(selected) == 1)
  579. if len(selected) == 0:
  580. return None
  581. return selected[0]
  582. def get_rows(self, table_name, file_id, region_id):
  583. filters = [("file_id", '=', file_id)]
  584. if region_id != None:
  585. filters.append(("region_id", '=', region_id))
  586. return self.select_rows(table_name, filters=filters)
  587. def aggregate_rows(self, table_name, path_like = None, column_names = None, filters = []):
  588. if column_names == None:
  589. column_names = []
  590. for column in self.iterate_columns(table_name):
  591. column_names.append(column.name)
  592. if len(column_names) == 0:
  593. # it is possible that a table does not have meanfull columns
  594. return {}
  595. total_column_names = []
  596. for column_name in column_names:
  597. for func in ['max', 'min', 'avg', 'total', 'count']:
  598. total_column_names.append(func + "('" + table_name + "'.'" + column_name + "') AS " + "'" + column_name + "_" + func + "'")
  599. data = self.select_rows_unsafe(table_name, path_like = path_like, column_names = total_column_names, filters = filters)
  600. assert(len(data) == 1)
  601. result = {}
  602. for column_name in column_names:
  603. result[column_name] = {}
  604. for func in ['max', 'min', 'avg', 'total', 'count']:
  605. result[column_name][func] = data[0][column_name + "_" + func]
  606. return result
  607. def count_rows(self, table_name, path_like = None, group_by_column = None, filters = []):
  608. count_column_names = None
  609. if group_by_column != None:
  610. for column in self.iterate_columns(table_name):
  611. if group_by_column == column.name:
  612. count_column_names = ["`" + group_by_column + "`", "COUNT(`" + group_by_column + "`)"]
  613. break
  614. else:
  615. count_column_names = ["COUNT(*)"]
  616. if count_column_names == None:
  617. return []
  618. data = self.select_rows_unsafe(table_name, path_like = path_like, column_names = count_column_names,
  619. filters = filters, group_by = group_by_column)
  620. return data
  621. def log(self, sql):
  622. if logging.getLogger().getEffectiveLevel() <= logging.DEBUG:
  623. logging.debug("[" + str(self.id) + "] Executing query: " + sql)
  624. traceback.print_stack()