blob: 0d44100f10382db600ec818ac368e672000ed2ae [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001"""BitBake Persistent Data Store
2
3Used to store data in a central location such that other threads/tasks can
4access them at some future date. Acts as a convenience wrapper around sqlite,
5currently, providing a key/value store accessed by 'domain'.
6"""
7
8# Copyright (C) 2007 Richard Purdie
9# Copyright (C) 2010 Chris Larson <chris_larson@mentor.com>
10#
11# This program is free software; you can redistribute it and/or modify
12# it under the terms of the GNU General Public License version 2 as
13# published by the Free Software Foundation.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License along
21# with this program; if not, write to the Free Software Foundation, Inc.,
22# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23
24import collections
25import logging
26import os.path
27import sys
28import warnings
29from bb.compat import total_ordering
30from collections import Mapping
Brad Bishop6e60e8b2018-02-01 10:27:11 -050031import sqlite3
Brad Bishop19323692019-04-05 15:28:33 -040032import contextlib
Patrick Williamsc124f4f2015-09-15 14:41:29 -050033
34sqlversion = sqlite3.sqlite_version_info
35if sqlversion[0] < 3 or (sqlversion[0] == 3 and sqlversion[1] < 3):
36 raise Exception("sqlite3 version 3.3.0 or later is required.")
37
38
39logger = logging.getLogger("BitBake.PersistData")
Patrick Williamsc124f4f2015-09-15 14:41:29 -050040
41@total_ordering
42class SQLTable(collections.MutableMapping):
Brad Bishop19323692019-04-05 15:28:33 -040043 class _Decorators(object):
44 @staticmethod
45 def retry(*, reconnect=True):
46 """
47 Decorator that restarts a function if a database locked sqlite
48 exception occurs. If reconnect is True, the database connection
49 will be closed and reopened each time a failure occurs
50 """
51 def retry_wrapper(f):
52 def wrap_func(self, *args, **kwargs):
53 # Reconnect if necessary
54 if self.connection is None and reconnect:
55 self.reconnect()
56
57 count = 0
58 while True:
59 try:
60 return f(self, *args, **kwargs)
61 except sqlite3.OperationalError as exc:
62 if count < 500 and ('is locked' in str(exc) or 'locking protocol' in str(exc)):
63 count = count + 1
64 if reconnect:
65 self.reconnect()
66 continue
67 raise
68 return wrap_func
69 return retry_wrapper
70
71 @staticmethod
72 def transaction(f):
73 """
74 Decorator that starts a database transaction and creates a database
75 cursor for performing queries. If no exception is thrown, the
76 database results are commited. If an exception occurs, the database
77 is rolled back. In all cases, the cursor is closed after the
78 function ends.
79
80 Note that the cursor is passed as an extra argument to the function
81 after `self` and before any of the normal arguments
82 """
83 def wrap_func(self, *args, **kwargs):
84 # Context manager will COMMIT the database on success,
85 # or ROLLBACK on an exception
86 with self.connection:
87 # Automatically close the cursor when done
88 with contextlib.closing(self.connection.cursor()) as cursor:
89 return f(self, cursor, *args, **kwargs)
90 return wrap_func
91
Patrick Williamsc124f4f2015-09-15 14:41:29 -050092 """Object representing a table/domain in the database"""
93 def __init__(self, cachefile, table):
94 self.cachefile = cachefile
95 self.table = table
Patrick Williamsc124f4f2015-09-15 14:41:29 -050096
Brad Bishop19323692019-04-05 15:28:33 -040097 self.connection = None
98 self._execute_single("CREATE TABLE IF NOT EXISTS %s(key TEXT PRIMARY KEY NOT NULL, value TEXT);" % table)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050099
Brad Bishop19323692019-04-05 15:28:33 -0400100 @_Decorators.retry(reconnect=False)
101 @_Decorators.transaction
102 def _setup_database(self, cursor):
103 cursor.execute("pragma synchronous = off;")
104 # Enable WAL and keep the autocheckpoint length small (the default is
105 # usually 1000). Persistent caches are usually read-mostly, so keeping
106 # this short will keep readers running quickly
107 cursor.execute("pragma journal_mode = WAL;")
108 cursor.execute("pragma wal_autocheckpoint = 100;")
109
110 def reconnect(self):
111 if self.connection is not None:
112 self.connection.close()
113 self.connection = sqlite3.connect(self.cachefile, timeout=5)
114 self.connection.text_factory = str
115 self._setup_database()
116
117 @_Decorators.retry()
118 @_Decorators.transaction
119 def _execute_single(self, cursor, *query):
120 """
121 Executes a single query and discards the results. This correctly closes
122 the database cursor when finished
123 """
124 cursor.execute(*query)
125
126 @_Decorators.retry()
127 def _row_iter(self, f, *query):
128 """
129 Helper function that returns a row iterator. Each time __next__ is
130 called on the iterator, the provided function is evaluated to determine
131 the return value
132 """
133 class CursorIter(object):
134 def __init__(self, cursor):
135 self.cursor = cursor
136
137 def __iter__(self):
138 return self
139
140 def __next__(self):
141 row = self.cursor.fetchone()
142 if row is None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500143 self.cursor.close()
Brad Bishop19323692019-04-05 15:28:33 -0400144 raise StopIteration
145 return f(row)
146
147 def __enter__(self):
148 return self
149
150 def __exit__(self, typ, value, traceback):
151 self.cursor.close()
152 return False
153
154 cursor = self.connection.cursor()
155 try:
156 cursor.execute(*query)
157 return CursorIter(cursor)
158 except:
159 cursor.close()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500160
161 def __enter__(self):
Brad Bishop19323692019-04-05 15:28:33 -0400162 self.connection.__enter__()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500163 return self
164
165 def __exit__(self, *excinfo):
Brad Bishop19323692019-04-05 15:28:33 -0400166 self.connection.__exit__(*excinfo)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500167
Brad Bishop19323692019-04-05 15:28:33 -0400168 @_Decorators.retry()
169 @_Decorators.transaction
170 def __getitem__(self, cursor, key):
171 cursor.execute("SELECT * from %s where key=?;" % self.table, [key])
172 row = cursor.fetchone()
173 if row is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500174 return row[1]
175 raise KeyError(key)
176
Brad Bishop19323692019-04-05 15:28:33 -0400177 @_Decorators.retry()
178 @_Decorators.transaction
179 def __delitem__(self, cursor, key):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500180 if key not in self:
181 raise KeyError(key)
Brad Bishop19323692019-04-05 15:28:33 -0400182 cursor.execute("DELETE from %s where key=?;" % self.table, [key])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500183
Brad Bishop19323692019-04-05 15:28:33 -0400184 @_Decorators.retry()
185 @_Decorators.transaction
186 def __setitem__(self, cursor, key, value):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600187 if not isinstance(key, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500188 raise TypeError('Only string keys are supported')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600189 elif not isinstance(value, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500190 raise TypeError('Only string values are supported')
191
Brad Bishop19323692019-04-05 15:28:33 -0400192 cursor.execute("SELECT * from %s where key=?;" % self.table, [key])
193 row = cursor.fetchone()
194 if row is not None:
195 cursor.execute("UPDATE %s SET value=? WHERE key=?;" % self.table, [value, key])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500196 else:
Brad Bishop19323692019-04-05 15:28:33 -0400197 cursor.execute("INSERT into %s(key, value) values (?, ?);" % self.table, [key, value])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500198
Brad Bishop19323692019-04-05 15:28:33 -0400199 @_Decorators.retry()
200 @_Decorators.transaction
201 def __contains__(self, cursor, key):
202 cursor.execute('SELECT * from %s where key=?;' % self.table, [key])
203 return cursor.fetchone() is not None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500204
Brad Bishop19323692019-04-05 15:28:33 -0400205 @_Decorators.retry()
206 @_Decorators.transaction
207 def __len__(self, cursor):
208 cursor.execute("SELECT COUNT(key) FROM %s;" % self.table)
209 row = cursor.fetchone()
210 if row is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500211 return row[0]
212
213 def __iter__(self):
Brad Bishop19323692019-04-05 15:28:33 -0400214 return self._row_iter(lambda row: row[0], "SELECT key from %s;" % self.table)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500215
216 def __lt__(self, other):
217 if not isinstance(other, Mapping):
218 raise NotImplemented
219
220 return len(self) < len(other)
221
222 def get_by_pattern(self, pattern):
Brad Bishop19323692019-04-05 15:28:33 -0400223 return self._row_iter(lambda row: row[1], "SELECT * FROM %s WHERE key LIKE ?;" %
224 self.table, [pattern])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500225
226 def values(self):
227 return list(self.itervalues())
228
229 def itervalues(self):
Brad Bishop19323692019-04-05 15:28:33 -0400230 return self._row_iter(lambda row: row[0], "SELECT value FROM %s;" %
231 self.table)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500232
233 def items(self):
234 return list(self.iteritems())
235
236 def iteritems(self):
Brad Bishop19323692019-04-05 15:28:33 -0400237 return self._row_iter(lambda row: (row[0], row[1]), "SELECT * FROM %s;" %
238 self.table)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500239
Brad Bishop19323692019-04-05 15:28:33 -0400240 @_Decorators.retry()
241 @_Decorators.transaction
242 def clear(self, cursor):
243 cursor.execute("DELETE FROM %s;" % self.table)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500244
245 def has_key(self, key):
246 return key in self
247
248
249class PersistData(object):
250 """Deprecated representation of the bitbake persistent data store"""
251 def __init__(self, d):
252 warnings.warn("Use of PersistData is deprecated. Please use "
253 "persist(domain, d) instead.",
254 category=DeprecationWarning,
255 stacklevel=2)
256
257 self.data = persist(d)
258 logger.debug(1, "Using '%s' as the persistent data cache",
259 self.data.filename)
260
261 def addDomain(self, domain):
262 """
263 Add a domain (pending deprecation)
264 """
265 return self.data[domain]
266
267 def delDomain(self, domain):
268 """
269 Removes a domain and all the data it contains
270 """
271 del self.data[domain]
272
273 def getKeyValues(self, domain):
274 """
275 Return a list of key + value pairs for a domain
276 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600277 return list(self.data[domain].items())
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500278
279 def getValue(self, domain, key):
280 """
281 Return the value of a key for a domain
282 """
283 return self.data[domain][key]
284
285 def setValue(self, domain, key, value):
286 """
287 Sets the value of a key for a domain
288 """
289 self.data[domain][key] = value
290
291 def delValue(self, domain, key):
292 """
293 Deletes a key/value pair
294 """
295 del self.data[domain][key]
296
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500297def persist(domain, d):
298 """Convenience factory for SQLTable objects based upon metadata"""
299 import bb.utils
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500300 cachedir = (d.getVar("PERSISTENT_DIR") or
301 d.getVar("CACHE"))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500302 if not cachedir:
303 logger.critical("Please set the 'PERSISTENT_DIR' or 'CACHE' variable")
304 sys.exit(1)
305
306 bb.utils.mkdirhier(cachedir)
307 cachefile = os.path.join(cachedir, "bb_persist_data.sqlite3")
308 return SQLTable(cachefile, domain)