blob: 2710d4a225e3a8dbd4a6f1f94bcc4ded92fad238 [file] [log] [blame]
Brad Bishopc342db32019-05-15 21:57:59 -04001#
2# SPDX-License-Identifier: GPL-2.0-only
3#
4
Patrick Williamsc124f4f2015-09-15 14:41:29 -05005import logging
6import os.path
7import errno
8import prserv
9import time
10
11try:
12 import sqlite3
13except ImportError:
14 from pysqlite2 import dbapi2 as sqlite3
15
16logger = logging.getLogger("BitBake.PRserv")
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#
23# "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
25# (version, pkgarch). This means the PR value returned can NOT be decremented.
26#
27# "History" mode - Return a new higher value for previously unseen query
28# tuple (version, pkgarch, checksum), otherwise return historical value.
29# Value can decrement if returning to a previous build.
30#
31
32class PRTable(object):
Andrew Geisslerd159c7f2021-09-02 21:05:58 -050033 def __init__(self, conn, table, nohist, read_only):
Patrick Williamsc124f4f2015-09-15 14:41:29 -050034 self.conn = conn
35 self.nohist = nohist
Andrew Geisslerd159c7f2021-09-02 21:05:58 -050036 self.read_only = read_only
Patrick Williamsc124f4f2015-09-15 14:41:29 -050037 self.dirty = False
38 if nohist:
39 self.table = "%s_nohist" % table
40 else:
41 self.table = "%s_hist" % table
42
Andrew Geisslerd159c7f2021-09-02 21:05:58 -050043 if self.read_only:
44 table_exists = self._execute(
45 "SELECT count(*) FROM sqlite_master \
46 WHERE type='table' AND name='%s'" % (self.table))
47 if not table_exists:
48 raise prserv.NotFoundError
49 else:
50 self._execute("CREATE TABLE IF NOT EXISTS %s \
51 (version TEXT NOT NULL, \
52 pkgarch TEXT NOT NULL, \
53 checksum TEXT NOT NULL, \
54 value INTEGER, \
55 PRIMARY KEY (version, pkgarch, checksum));" % self.table)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050056
57 def _execute(self, *query):
58 """Execute a query, waiting to acquire a lock if necessary"""
59 start = time.time()
60 end = start + 20
61 while True:
62 try:
63 return self.conn.execute(*query)
64 except sqlite3.OperationalError as exc:
65 if 'is locked' in str(exc) and end > time.time():
66 continue
67 raise exc
68
69 def sync(self):
Andrew Geisslerd159c7f2021-09-02 21:05:58 -050070 if not self.read_only:
71 self.conn.commit()
72 self._execute("BEGIN EXCLUSIVE TRANSACTION")
Patrick Williamsc124f4f2015-09-15 14:41:29 -050073
74 def sync_if_dirty(self):
75 if self.dirty:
76 self.sync()
77 self.dirty = False
78
79 def _getValueHist(self, version, pkgarch, checksum):
80 data=self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table,
81 (version, pkgarch, checksum))
82 row=data.fetchone()
Andrew Geissler82c905d2020-04-13 13:39:40 -050083 if row is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050084 return row[0]
85 else:
86 #no value found, try to insert
Andrew Geisslerd159c7f2021-09-02 21:05:58 -050087 if self.read_only:
88 data = self._execute("SELECT ifnull(max(value)+1,0) FROM %s where version=? AND pkgarch=?;" % (self.table),
89 (version, pkgarch))
90 row = data.fetchone()
91 if row is not None:
92 return row[0]
93 else:
94 return 0
95
Patrick Williamsc124f4f2015-09-15 14:41:29 -050096 try:
97 self._execute("INSERT INTO %s VALUES (?, ?, ?, (select ifnull(max(value)+1,0) from %s where version=? AND pkgarch=?));"
98 % (self.table,self.table),
99 (version,pkgarch, checksum,version, pkgarch))
100 except sqlite3.IntegrityError as exc:
101 logger.error(str(exc))
102
103 self.dirty = True
104
105 data=self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table,
106 (version, pkgarch, checksum))
107 row=data.fetchone()
Andrew Geissler82c905d2020-04-13 13:39:40 -0500108 if row is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500109 return row[0]
110 else:
111 raise prserv.NotFoundError
112
113 def _getValueNohist(self, version, pkgarch, checksum):
114 data=self._execute("SELECT value FROM %s \
115 WHERE version=? AND pkgarch=? AND checksum=? AND \
116 value >= (select max(value) from %s where version=? AND pkgarch=?);"
117 % (self.table, self.table),
118 (version, pkgarch, checksum, version, pkgarch))
119 row=data.fetchone()
Andrew Geissler82c905d2020-04-13 13:39:40 -0500120 if row is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500121 return row[0]
122 else:
123 #no value found, try to insert
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500124 if self.read_only:
125 data = self._execute("SELECT ifnull(max(value)+1,0) FROM %s where version=? AND pkgarch=?;" % (self.table),
126 (version, pkgarch))
127 row = data.fetchone()
128 if row is not None:
129 return row[0]
130 else:
131 return 0
132
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500133 try:
134 self._execute("INSERT OR REPLACE INTO %s VALUES (?, ?, ?, (select ifnull(max(value)+1,0) from %s where version=? AND pkgarch=?));"
135 % (self.table,self.table),
136 (version, pkgarch, checksum, version, pkgarch))
137 except sqlite3.IntegrityError as exc:
138 logger.error(str(exc))
139 self.conn.rollback()
140
141 self.dirty = True
142
143 data=self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table,
144 (version, pkgarch, checksum))
145 row=data.fetchone()
Andrew Geissler82c905d2020-04-13 13:39:40 -0500146 if row is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500147 return row[0]
148 else:
149 raise prserv.NotFoundError
150
151 def getValue(self, version, pkgarch, checksum):
152 if self.nohist:
153 return self._getValueNohist(version, pkgarch, checksum)
154 else:
155 return self._getValueHist(version, pkgarch, checksum)
156
157 def _importHist(self, version, pkgarch, checksum, value):
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500158 if self.read_only:
159 return None
160
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500161 val = None
162 data = self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table,
163 (version, pkgarch, checksum))
164 row = data.fetchone()
Andrew Geissler82c905d2020-04-13 13:39:40 -0500165 if row is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500166 val=row[0]
167 else:
168 #no value found, try to insert
169 try:
170 self._execute("INSERT INTO %s VALUES (?, ?, ?, ?);" % (self.table),
171 (version, pkgarch, checksum, value))
172 except sqlite3.IntegrityError as exc:
173 logger.error(str(exc))
174
175 self.dirty = True
176
177 data = self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table,
178 (version, pkgarch, checksum))
179 row = data.fetchone()
Andrew Geissler82c905d2020-04-13 13:39:40 -0500180 if row is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500181 val = row[0]
182 return val
183
184 def _importNohist(self, version, pkgarch, checksum, value):
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500185 if self.read_only:
186 return None
187
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500188 try:
189 #try to insert
190 self._execute("INSERT INTO %s VALUES (?, ?, ?, ?);" % (self.table),
191 (version, pkgarch, checksum,value))
192 except sqlite3.IntegrityError as exc:
193 #already have the record, try to update
194 try:
195 self._execute("UPDATE %s SET value=? WHERE version=? AND pkgarch=? AND checksum=? AND value<?"
196 % (self.table),
197 (value,version,pkgarch,checksum,value))
198 except sqlite3.IntegrityError as exc:
199 logger.error(str(exc))
200
201 self.dirty = True
202
203 data = self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=? AND value>=?;" % self.table,
204 (version,pkgarch,checksum,value))
205 row=data.fetchone()
Andrew Geissler82c905d2020-04-13 13:39:40 -0500206 if row is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500207 return row[0]
208 else:
209 return None
210
211 def importone(self, version, pkgarch, checksum, value):
212 if self.nohist:
213 return self._importNohist(version, pkgarch, checksum, value)
214 else:
215 return self._importHist(version, pkgarch, checksum, value)
216
217 def export(self, version, pkgarch, checksum, colinfo):
218 metainfo = {}
219 #column info
220 if colinfo:
221 metainfo['tbl_name'] = self.table
222 metainfo['core_ver'] = prserv.__version__
223 metainfo['col_info'] = []
224 data = self._execute("PRAGMA table_info(%s);" % self.table)
225 for row in data:
226 col = {}
227 col['name'] = row['name']
228 col['type'] = row['type']
229 col['notnull'] = row['notnull']
230 col['dflt_value'] = row['dflt_value']
231 col['pk'] = row['pk']
232 metainfo['col_info'].append(col)
233
234 #data info
235 datainfo = []
236
237 if self.nohist:
238 sqlstmt = "SELECT T1.version, T1.pkgarch, T1.checksum, T1.value FROM %s as T1, \
239 (SELECT version,pkgarch,max(value) as maxvalue FROM %s GROUP BY version,pkgarch) as T2 \
240 WHERE T1.version=T2.version AND T1.pkgarch=T2.pkgarch AND T1.value=T2.maxvalue " % (self.table, self.table)
241 else:
242 sqlstmt = "SELECT * FROM %s as T1 WHERE 1=1 " % self.table
243 sqlarg = []
244 where = ""
245 if version:
246 where += "AND T1.version=? "
247 sqlarg.append(str(version))
248 if pkgarch:
249 where += "AND T1.pkgarch=? "
250 sqlarg.append(str(pkgarch))
251 if checksum:
252 where += "AND T1.checksum=? "
253 sqlarg.append(str(checksum))
254
255 sqlstmt += where + ";"
256
257 if len(sqlarg):
258 data = self._execute(sqlstmt, tuple(sqlarg))
259 else:
260 data = self._execute(sqlstmt)
261 for row in data:
262 if row['version']:
263 col = {}
264 col['version'] = row['version']
265 col['pkgarch'] = row['pkgarch']
266 col['checksum'] = row['checksum']
267 col['value'] = row['value']
268 datainfo.append(col)
269 return (metainfo, datainfo)
270
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500271 def dump_db(self, fd):
272 writeCount = 0
273 for line in self.conn.iterdump():
274 writeCount = writeCount + len(line) + 1
275 fd.write(line)
276 fd.write('\n')
277 return writeCount
278
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500279class PRData(object):
280 """Object representing the PR database"""
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500281 def __init__(self, filename, nohist=True, read_only=False):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500282 self.filename=os.path.abspath(filename)
283 self.nohist=nohist
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500284 self.read_only = read_only
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500285 #build directory hierarchy
286 try:
287 os.makedirs(os.path.dirname(self.filename))
288 except OSError as e:
289 if e.errno != errno.EEXIST:
290 raise e
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500291 uri = "file:%s%s" % (self.filename, "?mode=ro" if self.read_only else "")
292 logger.debug("Opening PRServ database '%s'" % (uri))
293 self.connection=sqlite3.connect(uri, uri=True, isolation_level="EXCLUSIVE", check_same_thread = False)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500294 self.connection.row_factory=sqlite3.Row
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500295 if not self.read_only:
296 self.connection.execute("pragma synchronous = off;")
297 self.connection.execute("PRAGMA journal_mode = MEMORY;")
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500298 self._tables={}
299
Patrick Williamsf1e5d692016-03-30 15:21:19 -0500300 def disconnect(self):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500301 self.connection.close()
302
303 def __getitem__(self,tblname):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600304 if not isinstance(tblname, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500305 raise TypeError("tblname argument must be a string, not '%s'" %
306 type(tblname))
307 if tblname in self._tables:
308 return self._tables[tblname]
309 else:
Andrew Geisslerd159c7f2021-09-02 21:05:58 -0500310 tableobj = self._tables[tblname] = PRTable(self.connection, tblname, self.nohist, self.read_only)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500311 return tableobj
312
313 def __delitem__(self, tblname):
314 if tblname in self._tables:
315 del self._tables[tblname]
316 logger.info("drop table %s" % (tblname))
317 self.connection.execute("DROP TABLE IF EXISTS %s;" % tblname)