blob: eb41508198e94d6addb7b47a4403b4c8330a7fc8 [file] [log] [blame]
Brad Bishopc342db32019-05-15 21:57:59 -04001#
Patrick Williams92b42cb2022-09-03 06:53:57 -05002# Copyright BitBake Contributors
3#
Brad Bishopc342db32019-05-15 21:57:59 -04004# SPDX-License-Identifier: GPL-2.0-only
5#
6
Patrick Williamsc124f4f2015-09-15 14:41:29 -05007import logging
8import os.path
9import errno
10import prserv
11import time
12
13try:
14 import sqlite3
15except ImportError:
16 from pysqlite2 import dbapi2 as sqlite3
17
18logger = logging.getLogger("BitBake.PRserv")
19
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#
25# "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
27# (version, pkgarch). This means the PR value returned can NOT be decremented.
28#
29# "History" mode - Return a new higher value for previously unseen query
30# tuple (version, pkgarch, checksum), otherwise return historical value.
31# Value can decrement if returning to a previous build.
32#
33
34class PRTable(object):
Andrew Geisslerd159c7f2021-09-02 21:05:58 -050035 def __init__(self, conn, table, nohist, read_only):
Patrick Williamsc124f4f2015-09-15 14:41:29 -050036 self.conn = conn
37 self.nohist = nohist
Andrew Geisslerd159c7f2021-09-02 21:05:58 -050038 self.read_only = read_only
Patrick Williamsc124f4f2015-09-15 14:41:29 -050039 self.dirty = False
40 if nohist:
Patrick Williams44b3caf2024-04-12 16:51:14 -050041 self.table = "%s_nohist" % table
Patrick Williamsc124f4f2015-09-15 14:41:29 -050042 else:
Patrick Williams44b3caf2024-04-12 16:51:14 -050043 self.table = "%s_hist" % table
Patrick Williamsc124f4f2015-09-15 14:41:29 -050044
Andrew Geisslerd159c7f2021-09-02 21:05:58 -050045 if self.read_only:
46 table_exists = self._execute(
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, \
54 pkgarch TEXT NOT NULL, \
55 checksum TEXT NOT NULL, \
56 value INTEGER, \
57 PRIMARY KEY (version, pkgarch, checksum));" % self.table)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050058
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:
Patrick Williams44b3caf2024-04-12 16:51:14 -050067 if "is locked" in str(exc) and end > time.time():
Patrick Williamsc124f4f2015-09-15 14:41:29 -050068 continue
69 raise exc
70
71 def sync(self):
Andrew Geisslerd159c7f2021-09-02 21:05:58 -050072 if not self.read_only:
73 self.conn.commit()
74 self._execute("BEGIN EXCLUSIVE TRANSACTION")
Patrick Williamsc124f4f2015-09-15 14:41:29 -050075
76 def sync_if_dirty(self):
77 if self.dirty:
78 self.sync()
79 self.dirty = False
80
Patrick Williams44b3caf2024-04-12 16:51:14 -050081 def test_package(self, version, pkgarch):
82 """Returns whether the specified package version is found in the database for the specified architecture"""
83
84 # Just returns the value if found or None otherwise
85 data=self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=?;" % self.table,
86 (version, pkgarch))
87 row=data.fetchone()
88 if row is not None:
89 return True
90 else:
91 return False
92
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"""
95
96 # Just returns the value if found or None otherwise
97 data=self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? and value=?;" % self.table,
98 (version, pkgarch, value))
99 row=data.fetchone()
100 if row is not None:
101 return True
102 else:
103 return False
104
105 def find_value(self, version, pkgarch, checksum):
106 """Returns the value for the specified checksum if found or None otherwise."""
107
108 data=self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table,
109 (version, pkgarch, checksum))
110 row=data.fetchone()
111 if row is not None:
112 return row[0]
113 else:
114 return None
115
116 def find_max_value(self, version, pkgarch):
117 """Returns the greatest value for (version, pkgarch), or None if not found. Doesn't create a new value"""
118
119 data = self._execute("SELECT max(value) FROM %s where version=? AND pkgarch=?;" % (self.table),
120 (version, pkgarch))
121 row = data.fetchone()
122 if row is not None:
123 return row[0]
124 else:
125 return None
126
127 def _get_value_hist(self, version, pkgarch, checksum):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500128 data=self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table,
129 (version, pkgarch, checksum))
130 row=data.fetchone()
Andrew Geissler82c905d2020-04-13 13:39:40 -0500131 if row is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500132 return row[0]
133 else:
134 #no value found, try to insert
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500135 if self.read_only:
Patrick Williams44b3caf2024-04-12 16:51:14 -0500136 data = self._execute("SELECT ifnull(max(value)+1, 0) FROM %s where version=? AND pkgarch=?;" % (self.table),
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500137 (version, pkgarch))
138 row = data.fetchone()
139 if row is not None:
140 return row[0]
141 else:
142 return 0
143
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500144 try:
Patrick Williams44b3caf2024-04-12 16:51:14 -0500145 self._execute("INSERT INTO %s VALUES (?, ?, ?, (select ifnull(max(value)+1, 0) from %s where version=? AND pkgarch=?));"
146 % (self.table, self.table),
147 (version, pkgarch, checksum, version, pkgarch))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500148 except sqlite3.IntegrityError as exc:
149 logger.error(str(exc))
150
151 self.dirty = True
152
153 data=self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table,
154 (version, pkgarch, checksum))
155 row=data.fetchone()
Andrew Geissler82c905d2020-04-13 13:39:40 -0500156 if row is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500157 return row[0]
158 else:
159 raise prserv.NotFoundError
160
Patrick Williams44b3caf2024-04-12 16:51:14 -0500161 def _get_value_no_hist(self, version, pkgarch, checksum):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500162 data=self._execute("SELECT value FROM %s \
163 WHERE version=? AND pkgarch=? AND checksum=? AND \
Patrick Williams44b3caf2024-04-12 16:51:14 -0500164 value >= (select max(value) from %s where version=? AND pkgarch=?);"
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500165 % (self.table, self.table),
166 (version, pkgarch, checksum, version, pkgarch))
167 row=data.fetchone()
Andrew Geissler82c905d2020-04-13 13:39:40 -0500168 if row is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500169 return row[0]
170 else:
171 #no value found, try to insert
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500172 if self.read_only:
Patrick Williams44b3caf2024-04-12 16:51:14 -0500173 data = self._execute("SELECT ifnull(max(value)+1, 0) FROM %s where version=? AND pkgarch=?;" % (self.table),
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500174 (version, pkgarch))
Patrick Williams44b3caf2024-04-12 16:51:14 -0500175 return data.fetchone()[0]
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500176
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500177 try:
Patrick Williams44b3caf2024-04-12 16:51:14 -0500178 self._execute("INSERT OR REPLACE INTO %s VALUES (?, ?, ?, (select ifnull(max(value)+1, 0) from %s where version=? AND pkgarch=?));"
179 % (self.table, self.table),
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500180 (version, pkgarch, checksum, version, pkgarch))
181 except sqlite3.IntegrityError as exc:
182 logger.error(str(exc))
183 self.conn.rollback()
184
185 self.dirty = True
186
187 data=self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table,
188 (version, pkgarch, checksum))
189 row=data.fetchone()
Andrew Geissler82c905d2020-04-13 13:39:40 -0500190 if row is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500191 return row[0]
192 else:
193 raise prserv.NotFoundError
194
Patrick Williams44b3caf2024-04-12 16:51:14 -0500195 def get_value(self, version, pkgarch, checksum):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500196 if self.nohist:
Patrick Williams44b3caf2024-04-12 16:51:14 -0500197 return self._get_value_no_hist(version, pkgarch, checksum)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500198 else:
Patrick Williams44b3caf2024-04-12 16:51:14 -0500199 return self._get_value_hist(version, pkgarch, checksum)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500200
Patrick Williams44b3caf2024-04-12 16:51:14 -0500201 def _import_hist(self, version, pkgarch, checksum, value):
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500202 if self.read_only:
203 return None
204
Patrick Williams44b3caf2024-04-12 16:51:14 -0500205 val = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500206 data = self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table,
207 (version, pkgarch, checksum))
208 row = data.fetchone()
Andrew Geissler82c905d2020-04-13 13:39:40 -0500209 if row is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500210 val=row[0]
211 else:
212 #no value found, try to insert
213 try:
214 self._execute("INSERT INTO %s VALUES (?, ?, ?, ?);" % (self.table),
215 (version, pkgarch, checksum, value))
216 except sqlite3.IntegrityError as exc:
217 logger.error(str(exc))
218
219 self.dirty = True
220
221 data = self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table,
222 (version, pkgarch, checksum))
223 row = data.fetchone()
Andrew Geissler82c905d2020-04-13 13:39:40 -0500224 if row is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500225 val = row[0]
226 return val
227
Patrick Williams44b3caf2024-04-12 16:51:14 -0500228 def _import_no_hist(self, version, pkgarch, checksum, value):
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500229 if self.read_only:
230 return None
231
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500232 try:
233 #try to insert
234 self._execute("INSERT INTO %s VALUES (?, ?, ?, ?);" % (self.table),
Patrick Williams44b3caf2024-04-12 16:51:14 -0500235 (version, pkgarch, checksum, value))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500236 except sqlite3.IntegrityError as exc:
237 #already have the record, try to update
238 try:
Patrick Williams44b3caf2024-04-12 16:51:14 -0500239 self._execute("UPDATE %s SET value=? WHERE version=? AND pkgarch=? AND checksum=? AND value<?"
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500240 % (self.table),
Patrick Williams44b3caf2024-04-12 16:51:14 -0500241 (value, version, pkgarch, checksum, value))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500242 except sqlite3.IntegrityError as exc:
243 logger.error(str(exc))
244
245 self.dirty = True
246
247 data = self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=? AND value>=?;" % self.table,
Patrick Williams44b3caf2024-04-12 16:51:14 -0500248 (version, pkgarch, checksum, value))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500249 row=data.fetchone()
Andrew Geissler82c905d2020-04-13 13:39:40 -0500250 if row is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500251 return row[0]
252 else:
253 return None
254
255 def importone(self, version, pkgarch, checksum, value):
256 if self.nohist:
Patrick Williams44b3caf2024-04-12 16:51:14 -0500257 return self._import_no_hist(version, pkgarch, checksum, value)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500258 else:
Patrick Williams44b3caf2024-04-12 16:51:14 -0500259 return self._import_hist(version, pkgarch, checksum, value)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500260
261 def export(self, version, pkgarch, checksum, colinfo):
262 metainfo = {}
Patrick Williams44b3caf2024-04-12 16:51:14 -0500263 #column info
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500264 if colinfo:
Patrick Williams44b3caf2024-04-12 16:51:14 -0500265 metainfo["tbl_name"] = self.table
266 metainfo["core_ver"] = prserv.__version__
267 metainfo["col_info"] = []
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500268 data = self._execute("PRAGMA table_info(%s);" % self.table)
269 for row in data:
270 col = {}
Patrick Williams44b3caf2024-04-12 16:51:14 -0500271 col["name"] = row["name"]
272 col["type"] = row["type"]
273 col["notnull"] = row["notnull"]
274 col["dflt_value"] = row["dflt_value"]
275 col["pk"] = row["pk"]
276 metainfo["col_info"].append(col)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500277
278 #data info
279 datainfo = []
280
281 if self.nohist:
282 sqlstmt = "SELECT T1.version, T1.pkgarch, T1.checksum, T1.value FROM %s as T1, \
Patrick Williams44b3caf2024-04-12 16:51:14 -0500283 (SELECT version, pkgarch, max(value) as maxvalue FROM %s GROUP BY version, pkgarch) as T2 \
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500284 WHERE T1.version=T2.version AND T1.pkgarch=T2.pkgarch AND T1.value=T2.maxvalue " % (self.table, self.table)
285 else:
286 sqlstmt = "SELECT * FROM %s as T1 WHERE 1=1 " % self.table
287 sqlarg = []
288 where = ""
289 if version:
290 where += "AND T1.version=? "
291 sqlarg.append(str(version))
292 if pkgarch:
293 where += "AND T1.pkgarch=? "
294 sqlarg.append(str(pkgarch))
295 if checksum:
296 where += "AND T1.checksum=? "
297 sqlarg.append(str(checksum))
298
299 sqlstmt += where + ";"
300
301 if len(sqlarg):
302 data = self._execute(sqlstmt, tuple(sqlarg))
303 else:
304 data = self._execute(sqlstmt)
305 for row in data:
Patrick Williams44b3caf2024-04-12 16:51:14 -0500306 if row["version"]:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500307 col = {}
Patrick Williams44b3caf2024-04-12 16:51:14 -0500308 col["version"] = row["version"]
309 col["pkgarch"] = row["pkgarch"]
310 col["checksum"] = row["checksum"]
311 col["value"] = row["value"]
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500312 datainfo.append(col)
313 return (metainfo, datainfo)
314
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500315 def dump_db(self, fd):
316 writeCount = 0
317 for line in self.conn.iterdump():
318 writeCount = writeCount + len(line) + 1
319 fd.write(line)
Patrick Williams44b3caf2024-04-12 16:51:14 -0500320 fd.write("\n")
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500321 return writeCount
322
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500323class PRData(object):
324 """Object representing the PR database"""
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500325 def __init__(self, filename, nohist=True, read_only=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500326 self.filename=os.path.abspath(filename)
327 self.nohist=nohist
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500328 self.read_only = read_only
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500329 #build directory hierarchy
330 try:
331 os.makedirs(os.path.dirname(self.filename))
332 except OSError as e:
333 if e.errno != errno.EEXIST:
334 raise e
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500335 uri = "file:%s%s" % (self.filename, "?mode=ro" if self.read_only else "")
336 logger.debug("Opening PRServ database '%s'" % (uri))
337 self.connection=sqlite3.connect(uri, uri=True, isolation_level="EXCLUSIVE", check_same_thread = False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500338 self.connection.row_factory=sqlite3.Row
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500339 if not self.read_only:
340 self.connection.execute("pragma synchronous = off;")
341 self.connection.execute("PRAGMA journal_mode = MEMORY;")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500342 self._tables={}
343
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500344 def disconnect(self):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500345 self.connection.close()
346
Patrick Williams44b3caf2024-04-12 16:51:14 -0500347 def __getitem__(self, tblname):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600348 if not isinstance(tblname, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500349 raise TypeError("tblname argument must be a string, not '%s'" %
350 type(tblname))
351 if tblname in self._tables:
352 return self._tables[tblname]
353 else:
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500354 tableobj = self._tables[tblname] = PRTable(self.connection, tblname, self.nohist, self.read_only)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500355 return tableobj
356
357 def __delitem__(self, tblname):
358 if tblname in self._tables:
359 del self._tables[tblname]
360 logger.info("drop table %s" % (tblname))
Patrick Williams44b3caf2024-04-12 16:51:14 -0500361 self.connection.execute("DROP TABLE IF EXISTS %s;" % tblname)