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