blob: b4bda7078cdc677124e3f7d060eb9a243bc0824b [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:
41 self.table = "%s_nohist" % table
42 else:
43 self.table = "%s_hist" % table
44
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:
67 if 'is locked' in str(exc) and end > time.time():
68 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
81 def _getValueHist(self, version, pkgarch, checksum):
82 data=self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table,
83 (version, pkgarch, checksum))
84 row=data.fetchone()
Andrew Geissler82c905d2020-04-13 13:39:40 -050085 if row is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050086 return row[0]
87 else:
88 #no value found, try to insert
Andrew Geisslerd159c7f2021-09-02 21:05:58 -050089 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:
96 return 0
97
Patrick Williamsc124f4f2015-09-15 14:41:29 -050098 try:
99 self._execute("INSERT INTO %s VALUES (?, ?, ?, (select ifnull(max(value)+1,0) from %s where version=? AND pkgarch=?));"
100 % (self.table,self.table),
101 (version,pkgarch, checksum,version, pkgarch))
102 except sqlite3.IntegrityError as exc:
103 logger.error(str(exc))
104
105 self.dirty = True
106
107 data=self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table,
108 (version, pkgarch, checksum))
109 row=data.fetchone()
Andrew Geissler82c905d2020-04-13 13:39:40 -0500110 if row is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500111 return row[0]
112 else:
113 raise prserv.NotFoundError
114
115 def _getValueNohist(self, version, pkgarch, checksum):
116 data=self._execute("SELECT value FROM %s \
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()
Andrew Geissler82c905d2020-04-13 13:39:40 -0500122 if row is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500123 return row[0]
124 else:
125 #no value found, try to insert
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500126 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
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500135 try:
136 self._execute("INSERT OR REPLACE INTO %s VALUES (?, ?, ?, (select ifnull(max(value)+1,0) from %s where version=? AND pkgarch=?));"
137 % (self.table,self.table),
138 (version, pkgarch, checksum, version, pkgarch))
139 except sqlite3.IntegrityError as exc:
140 logger.error(str(exc))
141 self.conn.rollback()
142
143 self.dirty = True
144
145 data=self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table,
146 (version, pkgarch, checksum))
147 row=data.fetchone()
Andrew Geissler82c905d2020-04-13 13:39:40 -0500148 if row is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500149 return row[0]
150 else:
151 raise prserv.NotFoundError
152
153 def getValue(self, version, pkgarch, checksum):
154 if self.nohist:
155 return self._getValueNohist(version, pkgarch, checksum)
156 else:
157 return self._getValueHist(version, pkgarch, checksum)
158
159 def _importHist(self, version, pkgarch, checksum, value):
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500160 if self.read_only:
161 return None
162
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500163 val = None
164 data = self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table,
165 (version, pkgarch, checksum))
166 row = data.fetchone()
Andrew Geissler82c905d2020-04-13 13:39:40 -0500167 if row is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500168 val=row[0]
169 else:
170 #no value found, try to insert
171 try:
172 self._execute("INSERT INTO %s VALUES (?, ?, ?, ?);" % (self.table),
173 (version, pkgarch, checksum, value))
174 except sqlite3.IntegrityError as exc:
175 logger.error(str(exc))
176
177 self.dirty = True
178
179 data = self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table,
180 (version, pkgarch, checksum))
181 row = data.fetchone()
Andrew Geissler82c905d2020-04-13 13:39:40 -0500182 if row is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500183 val = row[0]
184 return val
185
186 def _importNohist(self, version, pkgarch, checksum, value):
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500187 if self.read_only:
188 return None
189
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500190 try:
191 #try to insert
192 self._execute("INSERT INTO %s VALUES (?, ?, ?, ?);" % (self.table),
193 (version, pkgarch, checksum,value))
194 except sqlite3.IntegrityError as exc:
195 #already have the record, try to update
196 try:
197 self._execute("UPDATE %s SET value=? WHERE version=? AND pkgarch=? AND checksum=? AND value<?"
198 % (self.table),
199 (value,version,pkgarch,checksum,value))
200 except sqlite3.IntegrityError as exc:
201 logger.error(str(exc))
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()
Andrew Geissler82c905d2020-04-13 13:39:40 -0500208 if row is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500209 return row[0]
210 else:
211 return None
212
213 def importone(self, version, pkgarch, checksum, value):
214 if self.nohist:
215 return self._importNohist(version, pkgarch, checksum, value)
216 else:
217 return self._importHist(version, pkgarch, checksum, value)
218
219 def export(self, version, pkgarch, checksum, colinfo):
220 metainfo = {}
221 #column info
222 if colinfo:
223 metainfo['tbl_name'] = self.table
224 metainfo['core_ver'] = prserv.__version__
225 metainfo['col_info'] = []
226 data = self._execute("PRAGMA table_info(%s);" % self.table)
227 for row in data:
228 col = {}
229 col['name'] = row['name']
230 col['type'] = row['type']
231 col['notnull'] = row['notnull']
232 col['dflt_value'] = row['dflt_value']
233 col['pk'] = row['pk']
234 metainfo['col_info'].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)
272
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500273 def dump_db(self, fd):
274 writeCount = 0
275 for line in self.conn.iterdump():
276 writeCount = writeCount + len(line) + 1
277 fd.write(line)
278 fd.write('\n')
279 return writeCount
280
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500281class PRData(object):
282 """Object representing the PR database"""
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500283 def __init__(self, filename, nohist=True, read_only=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500284 self.filename=os.path.abspath(filename)
285 self.nohist=nohist
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500286 self.read_only = read_only
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500287 #build directory hierarchy
288 try:
289 os.makedirs(os.path.dirname(self.filename))
290 except OSError as e:
291 if e.errno != errno.EEXIST:
292 raise e
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500293 uri = "file:%s%s" % (self.filename, "?mode=ro" if self.read_only else "")
294 logger.debug("Opening PRServ database '%s'" % (uri))
295 self.connection=sqlite3.connect(uri, uri=True, isolation_level="EXCLUSIVE", check_same_thread = False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500296 self.connection.row_factory=sqlite3.Row
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500297 if not self.read_only:
298 self.connection.execute("pragma synchronous = off;")
299 self.connection.execute("PRAGMA journal_mode = MEMORY;")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500300 self._tables={}
301
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500302 def disconnect(self):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500303 self.connection.close()
304
305 def __getitem__(self,tblname):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600306 if not isinstance(tblname, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500307 raise TypeError("tblname argument must be a string, not '%s'" %
308 type(tblname))
309 if tblname in self._tables:
310 return self._tables[tblname]
311 else:
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500312 tableobj = self._tables[tblname] = PRTable(self.connection, tblname, self.nohist, self.read_only)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500313 return tableobj
314
315 def __delitem__(self, tblname):
316 if tblname in self._tables:
317 del self._tables[tblname]
318 logger.info("drop table %s" % (tblname))
319 self.connection.execute("DROP TABLE IF EXISTS %s;" % tblname)