summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/prserv/db.py
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib/prserv/db.py')
-rw-r--r--bitbake/lib/prserv/db.py430
1 files changed, 206 insertions, 224 deletions
diff --git a/bitbake/lib/prserv/db.py b/bitbake/lib/prserv/db.py
index b4bda7078c..2da493ddf5 100644
--- a/bitbake/lib/prserv/db.py
+++ b/bitbake/lib/prserv/db.py
@@ -8,19 +8,13 @@ import logging
8import os.path 8import os.path
9import errno 9import errno
10import prserv 10import prserv
11import time 11import sqlite3
12 12
13try: 13from contextlib import closing
14 import sqlite3 14from . import increase_revision, revision_greater, revision_smaller
15except ImportError:
16 from pysqlite2 import dbapi2 as sqlite3
17 15
18logger = logging.getLogger("BitBake.PRserv") 16logger = logging.getLogger("BitBake.PRserv")
19 17
20sqlversion = sqlite3.sqlite_version_info
21if sqlversion[0] < 3 or (sqlversion[0] == 3 and sqlversion[1] < 3):
22 raise Exception("sqlite3 version 3.3.0 or later is required.")
23
24# 18#
25# "No History" mode - for a given query tuple (version, pkgarch, checksum), 19# "No History" mode - for a given query tuple (version, pkgarch, checksum),
26# the returned value will be the largest among all the values of the same 20# the returned value will be the largest among all the values of the same
@@ -29,245 +23,232 @@ if sqlversion[0] < 3 or (sqlversion[0] == 3 and sqlversion[1] < 3):
29# "History" mode - Return a new higher value for previously unseen query 23# "History" mode - Return a new higher value for previously unseen query
30# tuple (version, pkgarch, checksum), otherwise return historical value. 24# tuple (version, pkgarch, checksum), otherwise return historical value.
31# Value can decrement if returning to a previous build. 25# Value can decrement if returning to a previous build.
32#
33 26
34class PRTable(object): 27class PRTable(object):
35 def __init__(self, conn, table, nohist, read_only): 28 def __init__(self, conn, table, read_only):
36 self.conn = conn 29 self.conn = conn
37 self.nohist = nohist
38 self.read_only = read_only 30 self.read_only = read_only
39 self.dirty = False 31 self.table = table
40 if nohist: 32
41 self.table = "%s_nohist" % table 33 # Creating the table even if the server is read-only.
42 else: 34 # This avoids a race condition if a shared database
43 self.table = "%s_hist" % table 35 # is accessed by a read-only server first.
44 36
45 if self.read_only: 37 with closing(self.conn.cursor()) as cursor:
46 table_exists = self._execute( 38 cursor.execute("CREATE TABLE IF NOT EXISTS %s \
47 "SELECT count(*) FROM sqlite_master \
48 WHERE type='table' AND name='%s'" % (self.table))
49 if not table_exists:
50 raise prserv.NotFoundError
51 else:
52 self._execute("CREATE TABLE IF NOT EXISTS %s \
53 (version TEXT NOT NULL, \ 39 (version TEXT NOT NULL, \
54 pkgarch TEXT NOT NULL, \ 40 pkgarch TEXT NOT NULL, \
55 checksum TEXT NOT NULL, \ 41 checksum TEXT NOT NULL, \
56 value INTEGER, \ 42 value TEXT, \
57 PRIMARY KEY (version, pkgarch, checksum));" % self.table) 43 PRIMARY KEY (version, pkgarch, checksum, value));" % self.table)
58
59 def _execute(self, *query):
60 """Execute a query, waiting to acquire a lock if necessary"""
61 start = time.time()
62 end = start + 20
63 while True:
64 try:
65 return self.conn.execute(*query)
66 except sqlite3.OperationalError as exc:
67 if 'is locked' in str(exc) and end > time.time():
68 continue
69 raise exc
70
71 def sync(self):
72 if not self.read_only:
73 self.conn.commit() 44 self.conn.commit()
74 self._execute("BEGIN EXCLUSIVE TRANSACTION") 45
75 46 def _extremum_value(self, rows, is_max):
76 def sync_if_dirty(self): 47 value = None
77 if self.dirty: 48
78 self.sync() 49 for row in rows:
79 self.dirty = False 50 current_value = row[0]
80 51 if value is None:
81 def _getValueHist(self, version, pkgarch, checksum): 52 value = current_value
82 data=self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table, 53 else:
83 (version, pkgarch, checksum)) 54 if is_max:
84 row=data.fetchone() 55 is_new_extremum = revision_greater(current_value, value)
85 if row is not None:
86 return row[0]
87 else:
88 #no value found, try to insert
89 if self.read_only:
90 data = self._execute("SELECT ifnull(max(value)+1,0) FROM %s where version=? AND pkgarch=?;" % (self.table),
91 (version, pkgarch))
92 row = data.fetchone()
93 if row is not None:
94 return row[0]
95 else: 56 else:
96 return 0 57 is_new_extremum = revision_smaller(current_value, value)
58 if is_new_extremum:
59 value = current_value
60 return value
61
62 def _max_value(self, rows):
63 return self._extremum_value(rows, True)
97 64
98 try: 65 def _min_value(self, rows):
99 self._execute("INSERT INTO %s VALUES (?, ?, ?, (select ifnull(max(value)+1,0) from %s where version=? AND pkgarch=?));" 66 return self._extremum_value(rows, False)
100 % (self.table,self.table),
101 (version,pkgarch, checksum,version, pkgarch))
102 except sqlite3.IntegrityError as exc:
103 logger.error(str(exc))
104 67
105 self.dirty = True 68 def test_package(self, version, pkgarch):
69 """Returns whether the specified package version is found in the database for the specified architecture"""
106 70
107 data=self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table, 71 # Just returns the value if found or None otherwise
108 (version, pkgarch, checksum)) 72 with closing(self.conn.cursor()) as cursor:
73 data=cursor.execute("SELECT value FROM %s WHERE version=? AND pkgarch=?;" % self.table,
74 (version, pkgarch))
109 row=data.fetchone() 75 row=data.fetchone()
110 if row is not None: 76 if row is not None:
111 return row[0] 77 return True
112 else: 78 else:
113 raise prserv.NotFoundError 79 return False
114 80
115 def _getValueNohist(self, version, pkgarch, checksum): 81 def test_checksum_value(self, version, pkgarch, checksum, value):
116 data=self._execute("SELECT value FROM %s \ 82 """Returns whether the specified value is found in the database for the specified package, architecture and checksum"""
117 WHERE version=? AND pkgarch=? AND checksum=? AND \
118 value >= (select max(value) from %s where version=? AND pkgarch=?);"
119 % (self.table, self.table),
120 (version, pkgarch, checksum, version, pkgarch))
121 row=data.fetchone()
122 if row is not None:
123 return row[0]
124 else:
125 #no value found, try to insert
126 if self.read_only:
127 data = self._execute("SELECT ifnull(max(value)+1,0) FROM %s where version=? AND pkgarch=?;" % (self.table),
128 (version, pkgarch))
129 row = data.fetchone()
130 if row is not None:
131 return row[0]
132 else:
133 return 0
134 83
135 try: 84 with closing(self.conn.cursor()) as cursor:
136 self._execute("INSERT OR REPLACE INTO %s VALUES (?, ?, ?, (select ifnull(max(value)+1,0) from %s where version=? AND pkgarch=?));" 85 data=cursor.execute("SELECT value FROM %s WHERE version=? AND pkgarch=? and checksum=? and value=?;" % self.table,
137 % (self.table,self.table), 86 (version, pkgarch, checksum, value))
138 (version, pkgarch, checksum, version, pkgarch)) 87 row=data.fetchone()
139 except sqlite3.IntegrityError as exc: 88 if row is not None:
140 logger.error(str(exc)) 89 return True
141 self.conn.rollback() 90 else:
91 return False
142 92
143 self.dirty = True 93 def test_value(self, version, pkgarch, value):
94 """Returns whether the specified value is found in the database for the specified package and architecture"""
144 95
145 data=self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table, 96 # Just returns the value if found or None otherwise
146 (version, pkgarch, checksum)) 97 with closing(self.conn.cursor()) as cursor:
98 data=cursor.execute("SELECT value FROM %s WHERE version=? AND pkgarch=? and value=?;" % self.table,
99 (version, pkgarch, value))
147 row=data.fetchone() 100 row=data.fetchone()
148 if row is not None: 101 if row is not None:
149 return row[0] 102 return True
150 else: 103 else:
151 raise prserv.NotFoundError 104 return False
152 105
153 def getValue(self, version, pkgarch, checksum): 106
154 if self.nohist: 107 def find_package_max_value(self, version, pkgarch):
155 return self._getValueNohist(version, pkgarch, checksum) 108 """Returns the greatest value for (version, pkgarch), or None if not found. Doesn't create a new value"""
156 else: 109
157 return self._getValueHist(version, pkgarch, checksum) 110 with closing(self.conn.cursor()) as cursor:
158 111 data = cursor.execute("SELECT value FROM %s where version=? AND pkgarch=?;" % (self.table),
159 def _importHist(self, version, pkgarch, checksum, value): 112 (version, pkgarch))
160 if self.read_only: 113 rows = data.fetchall()
161 return None 114 value = self._max_value(rows)
162 115 return value
163 val = None 116
164 data = self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table, 117 def find_value(self, version, pkgarch, checksum, history=False):
165 (version, pkgarch, checksum)) 118 """Returns the value for the specified checksum if found or None otherwise."""
166 row = data.fetchone() 119
167 if row is not None: 120 if history:
168 val=row[0] 121 return self.find_min_value(version, pkgarch, checksum)
169 else: 122 else:
170 #no value found, try to insert 123 return self.find_max_value(version, pkgarch, checksum)
171 try: 124
172 self._execute("INSERT INTO %s VALUES (?, ?, ?, ?);" % (self.table), 125
126 def _find_extremum_value(self, version, pkgarch, checksum, is_max):
127 """Returns the maximum (if is_max is True) or minimum (if is_max is False) value
128 for (version, pkgarch, checksum), or None if not found. Doesn't create a new value"""
129
130 with closing(self.conn.cursor()) as cursor:
131 data = cursor.execute("SELECT value FROM %s where version=? AND pkgarch=? AND checksum=?;" % (self.table),
132 (version, pkgarch, checksum))
133 rows = data.fetchall()
134 return self._extremum_value(rows, is_max)
135
136 def find_max_value(self, version, pkgarch, checksum):
137 return self._find_extremum_value(version, pkgarch, checksum, True)
138
139 def find_min_value(self, version, pkgarch, checksum):
140 return self._find_extremum_value(version, pkgarch, checksum, False)
141
142 def find_new_subvalue(self, version, pkgarch, base):
143 """Take and increase the greatest "<base>.y" value for (version, pkgarch), or return "<base>.0" if not found.
144 This doesn't store a new value."""
145
146 with closing(self.conn.cursor()) as cursor:
147 data = cursor.execute("SELECT value FROM %s where version=? AND pkgarch=? AND value LIKE '%s.%%';" % (self.table, base),
148 (version, pkgarch))
149 rows = data.fetchall()
150 value = self._max_value(rows)
151
152 if value is not None:
153 return increase_revision(value)
154 else:
155 return base + ".0"
156
157 def store_value(self, version, pkgarch, checksum, value):
158 """Store value in the database"""
159
160 if not self.read_only and not self.test_checksum_value(version, pkgarch, checksum, value):
161 with closing(self.conn.cursor()) as cursor:
162 cursor.execute("INSERT INTO %s VALUES (?, ?, ?, ?);" % (self.table),
173 (version, pkgarch, checksum, value)) 163 (version, pkgarch, checksum, value))
174 except sqlite3.IntegrityError as exc: 164 self.conn.commit()
175 logger.error(str(exc))
176 165
177 self.dirty = True 166 def _get_value(self, version, pkgarch, checksum, history):
178 167
179 data = self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table, 168 max_value = self.find_package_max_value(version, pkgarch)
180 (version, pkgarch, checksum))
181 row = data.fetchone()
182 if row is not None:
183 val = row[0]
184 return val
185 169
186 def _importNohist(self, version, pkgarch, checksum, value): 170 if max_value is None:
187 if self.read_only: 171 # version, pkgarch completely unknown. Return initial value.
188 return None 172 return "0"
189 173
190 try: 174 value = self.find_value(version, pkgarch, checksum, history)
191 #try to insert 175
192 self._execute("INSERT INTO %s VALUES (?, ?, ?, ?);" % (self.table), 176 if value is None:
193 (version, pkgarch, checksum,value)) 177 # version, pkgarch found but not checksum. Create a new value from the maximum one
194 except sqlite3.IntegrityError as exc: 178 return increase_revision(max_value)
195 #already have the record, try to update 179
196 try: 180 if history:
197 self._execute("UPDATE %s SET value=? WHERE version=? AND pkgarch=? AND checksum=? AND value<?" 181 return value
198 % (self.table), 182
199 (value,version,pkgarch,checksum,value)) 183 # "no history" mode - If the value is not the maximum value for the package, need to increase it.
200 except sqlite3.IntegrityError as exc: 184 if max_value > value:
201 logger.error(str(exc)) 185 return increase_revision(max_value)
202
203 self.dirty = True
204
205 data = self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=? AND value>=?;" % self.table,
206 (version,pkgarch,checksum,value))
207 row=data.fetchone()
208 if row is not None:
209 return row[0]
210 else: 186 else:
211 return None 187 return value
188
189 def get_value(self, version, pkgarch, checksum, history):
190 value = self._get_value(version, pkgarch, checksum, history)
191 if not self.read_only:
192 self.store_value(version, pkgarch, checksum, value)
193 return value
212 194
213 def importone(self, version, pkgarch, checksum, value): 195 def importone(self, version, pkgarch, checksum, value):
214 if self.nohist: 196 self.store_value(version, pkgarch, checksum, value)
215 return self._importNohist(version, pkgarch, checksum, value) 197 return value
216 else:
217 return self._importHist(version, pkgarch, checksum, value)
218 198
219 def export(self, version, pkgarch, checksum, colinfo): 199 def export(self, version, pkgarch, checksum, colinfo, history=False):
220 metainfo = {} 200 metainfo = {}
221 #column info 201 with closing(self.conn.cursor()) as cursor:
222 if colinfo: 202 #column info
223 metainfo['tbl_name'] = self.table 203 if colinfo:
224 metainfo['core_ver'] = prserv.__version__ 204 metainfo["tbl_name"] = self.table
225 metainfo['col_info'] = [] 205 metainfo["core_ver"] = prserv.__version__
226 data = self._execute("PRAGMA table_info(%s);" % self.table) 206 metainfo["col_info"] = []
207 data = cursor.execute("PRAGMA table_info(%s);" % self.table)
208 for row in data:
209 col = {}
210 col["name"] = row["name"]
211 col["type"] = row["type"]
212 col["notnull"] = row["notnull"]
213 col["dflt_value"] = row["dflt_value"]
214 col["pk"] = row["pk"]
215 metainfo["col_info"].append(col)
216
217 #data info
218 datainfo = []
219
220 if history:
221 sqlstmt = "SELECT * FROM %s as T1 WHERE 1=1 " % self.table
222 else:
223 sqlstmt = "SELECT T1.version, T1.pkgarch, T1.checksum, T1.value FROM %s as T1, \
224 (SELECT version, pkgarch, max(value) as maxvalue FROM %s GROUP BY version, pkgarch) as T2 \
225 WHERE T1.version=T2.version AND T1.pkgarch=T2.pkgarch AND T1.value=T2.maxvalue " % (self.table, self.table)
226 sqlarg = []
227 where = ""
228 if version:
229 where += "AND T1.version=? "
230 sqlarg.append(str(version))
231 if pkgarch:
232 where += "AND T1.pkgarch=? "
233 sqlarg.append(str(pkgarch))
234 if checksum:
235 where += "AND T1.checksum=? "
236 sqlarg.append(str(checksum))
237
238 sqlstmt += where + ";"
239
240 if len(sqlarg):
241 data = cursor.execute(sqlstmt, tuple(sqlarg))
242 else:
243 data = cursor.execute(sqlstmt)
227 for row in data: 244 for row in data:
228 col = {} 245 if row["version"]:
229 col['name'] = row['name'] 246 col = {}
230 col['type'] = row['type'] 247 col["version"] = row["version"]
231 col['notnull'] = row['notnull'] 248 col["pkgarch"] = row["pkgarch"]
232 col['dflt_value'] = row['dflt_value'] 249 col["checksum"] = row["checksum"]
233 col['pk'] = row['pk'] 250 col["value"] = row["value"]
234 metainfo['col_info'].append(col) 251 datainfo.append(col)
235
236 #data info
237 datainfo = []
238
239 if self.nohist:
240 sqlstmt = "SELECT T1.version, T1.pkgarch, T1.checksum, T1.value FROM %s as T1, \
241 (SELECT version,pkgarch,max(value) as maxvalue FROM %s GROUP BY version,pkgarch) as T2 \
242 WHERE T1.version=T2.version AND T1.pkgarch=T2.pkgarch AND T1.value=T2.maxvalue " % (self.table, self.table)
243 else:
244 sqlstmt = "SELECT * FROM %s as T1 WHERE 1=1 " % self.table
245 sqlarg = []
246 where = ""
247 if version:
248 where += "AND T1.version=? "
249 sqlarg.append(str(version))
250 if pkgarch:
251 where += "AND T1.pkgarch=? "
252 sqlarg.append(str(pkgarch))
253 if checksum:
254 where += "AND T1.checksum=? "
255 sqlarg.append(str(checksum))
256
257 sqlstmt += where + ";"
258
259 if len(sqlarg):
260 data = self._execute(sqlstmt, tuple(sqlarg))
261 else:
262 data = self._execute(sqlstmt)
263 for row in data:
264 if row['version']:
265 col = {}
266 col['version'] = row['version']
267 col['pkgarch'] = row['pkgarch']
268 col['checksum'] = row['checksum']
269 col['value'] = row['value']
270 datainfo.append(col)
271 return (metainfo, datainfo) 252 return (metainfo, datainfo)
272 253
273 def dump_db(self, fd): 254 def dump_db(self, fd):
@@ -275,14 +256,13 @@ class PRTable(object):
275 for line in self.conn.iterdump(): 256 for line in self.conn.iterdump():
276 writeCount = writeCount + len(line) + 1 257 writeCount = writeCount + len(line) + 1
277 fd.write(line) 258 fd.write(line)
278 fd.write('\n') 259 fd.write("\n")
279 return writeCount 260 return writeCount
280 261
281class PRData(object): 262class PRData(object):
282 """Object representing the PR database""" 263 """Object representing the PR database"""
283 def __init__(self, filename, nohist=True, read_only=False): 264 def __init__(self, filename, read_only=False):
284 self.filename=os.path.abspath(filename) 265 self.filename=os.path.abspath(filename)
285 self.nohist=nohist
286 self.read_only = read_only 266 self.read_only = read_only
287 #build directory hierarchy 267 #build directory hierarchy
288 try: 268 try:
@@ -292,28 +272,30 @@ class PRData(object):
292 raise e 272 raise e
293 uri = "file:%s%s" % (self.filename, "?mode=ro" if self.read_only else "") 273 uri = "file:%s%s" % (self.filename, "?mode=ro" if self.read_only else "")
294 logger.debug("Opening PRServ database '%s'" % (uri)) 274 logger.debug("Opening PRServ database '%s'" % (uri))
295 self.connection=sqlite3.connect(uri, uri=True, isolation_level="EXCLUSIVE", check_same_thread = False) 275 self.connection=sqlite3.connect(uri, uri=True)
296 self.connection.row_factory=sqlite3.Row 276 self.connection.row_factory=sqlite3.Row
297 if not self.read_only: 277 self.connection.execute("PRAGMA synchronous = OFF;")
298 self.connection.execute("pragma synchronous = off;") 278 self.connection.execute("PRAGMA journal_mode = WAL;")
299 self.connection.execute("PRAGMA journal_mode = MEMORY;") 279 self.connection.commit()
300 self._tables={} 280 self._tables={}
301 281
302 def disconnect(self): 282 def disconnect(self):
283 self.connection.commit()
303 self.connection.close() 284 self.connection.close()
304 285
305 def __getitem__(self,tblname): 286 def __getitem__(self, tblname):
306 if not isinstance(tblname, str): 287 if not isinstance(tblname, str):
307 raise TypeError("tblname argument must be a string, not '%s'" % 288 raise TypeError("tblname argument must be a string, not '%s'" %
308 type(tblname)) 289 type(tblname))
309 if tblname in self._tables: 290 if tblname in self._tables:
310 return self._tables[tblname] 291 return self._tables[tblname]
311 else: 292 else:
312 tableobj = self._tables[tblname] = PRTable(self.connection, tblname, self.nohist, self.read_only) 293 tableobj = self._tables[tblname] = PRTable(self.connection, tblname, self.read_only)
313 return tableobj 294 return tableobj
314 295
315 def __delitem__(self, tblname): 296 def __delitem__(self, tblname):
316 if tblname in self._tables: 297 if tblname in self._tables:
317 del self._tables[tblname] 298 del self._tables[tblname]
318 logger.info("drop table %s" % (tblname)) 299 logger.info("drop table %s" % (tblname))
319 self.connection.execute("DROP TABLE IF EXISTS %s;" % tblname) 300 self.connection.execute("DROP TABLE IF EXISTS %s;" % tblname)
301 self.connection.commit()