blob: de8f87a8bfec99e0fa3d71e405f33940ec17a22d [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#
Brad Bishopc342db32019-05-15 21:57:59 -040011# SPDX-License-Identifier: GPL-2.0-only
Patrick Williamsc124f4f2015-09-15 14:41:29 -050012#
Patrick Williamsc124f4f2015-09-15 14:41:29 -050013
14import collections
15import logging
16import os.path
17import sys
18import warnings
19from bb.compat import total_ordering
20from collections import Mapping
Brad Bishop6e60e8b2018-02-01 10:27:11 -050021import sqlite3
Brad Bishop19323692019-04-05 15:28:33 -040022import contextlib
Patrick Williamsc124f4f2015-09-15 14:41:29 -050023
24sqlversion = sqlite3.sqlite_version_info
25if sqlversion[0] < 3 or (sqlversion[0] == 3 and sqlversion[1] < 3):
26 raise Exception("sqlite3 version 3.3.0 or later is required.")
27
28
29logger = logging.getLogger("BitBake.PersistData")
Patrick Williamsc124f4f2015-09-15 14:41:29 -050030
31@total_ordering
32class SQLTable(collections.MutableMapping):
Brad Bishop19323692019-04-05 15:28:33 -040033 class _Decorators(object):
34 @staticmethod
35 def retry(*, reconnect=True):
36 """
37 Decorator that restarts a function if a database locked sqlite
38 exception occurs. If reconnect is True, the database connection
39 will be closed and reopened each time a failure occurs
40 """
41 def retry_wrapper(f):
42 def wrap_func(self, *args, **kwargs):
43 # Reconnect if necessary
44 if self.connection is None and reconnect:
45 self.reconnect()
46
47 count = 0
48 while True:
49 try:
50 return f(self, *args, **kwargs)
51 except sqlite3.OperationalError as exc:
52 if count < 500 and ('is locked' in str(exc) or 'locking protocol' in str(exc)):
53 count = count + 1
54 if reconnect:
55 self.reconnect()
56 continue
57 raise
58 return wrap_func
59 return retry_wrapper
60
61 @staticmethod
62 def transaction(f):
63 """
64 Decorator that starts a database transaction and creates a database
65 cursor for performing queries. If no exception is thrown, the
66 database results are commited. If an exception occurs, the database
67 is rolled back. In all cases, the cursor is closed after the
68 function ends.
69
70 Note that the cursor is passed as an extra argument to the function
71 after `self` and before any of the normal arguments
72 """
73 def wrap_func(self, *args, **kwargs):
74 # Context manager will COMMIT the database on success,
75 # or ROLLBACK on an exception
76 with self.connection:
77 # Automatically close the cursor when done
78 with contextlib.closing(self.connection.cursor()) as cursor:
79 return f(self, cursor, *args, **kwargs)
80 return wrap_func
81
Patrick Williamsc124f4f2015-09-15 14:41:29 -050082 """Object representing a table/domain in the database"""
83 def __init__(self, cachefile, table):
84 self.cachefile = cachefile
85 self.table = table
Patrick Williamsc124f4f2015-09-15 14:41:29 -050086
Brad Bishop19323692019-04-05 15:28:33 -040087 self.connection = None
88 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 -050089
Brad Bishop19323692019-04-05 15:28:33 -040090 @_Decorators.retry(reconnect=False)
91 @_Decorators.transaction
92 def _setup_database(self, cursor):
93 cursor.execute("pragma synchronous = off;")
94 # Enable WAL and keep the autocheckpoint length small (the default is
95 # usually 1000). Persistent caches are usually read-mostly, so keeping
96 # this short will keep readers running quickly
97 cursor.execute("pragma journal_mode = WAL;")
98 cursor.execute("pragma wal_autocheckpoint = 100;")
99
100 def reconnect(self):
101 if self.connection is not None:
102 self.connection.close()
103 self.connection = sqlite3.connect(self.cachefile, timeout=5)
104 self.connection.text_factory = str
105 self._setup_database()
106
107 @_Decorators.retry()
108 @_Decorators.transaction
109 def _execute_single(self, cursor, *query):
110 """
111 Executes a single query and discards the results. This correctly closes
112 the database cursor when finished
113 """
114 cursor.execute(*query)
115
116 @_Decorators.retry()
117 def _row_iter(self, f, *query):
118 """
119 Helper function that returns a row iterator. Each time __next__ is
120 called on the iterator, the provided function is evaluated to determine
121 the return value
122 """
123 class CursorIter(object):
124 def __init__(self, cursor):
125 self.cursor = cursor
126
127 def __iter__(self):
128 return self
129
130 def __next__(self):
131 row = self.cursor.fetchone()
132 if row is None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500133 self.cursor.close()
Brad Bishop19323692019-04-05 15:28:33 -0400134 raise StopIteration
135 return f(row)
136
137 def __enter__(self):
138 return self
139
140 def __exit__(self, typ, value, traceback):
141 self.cursor.close()
142 return False
143
144 cursor = self.connection.cursor()
145 try:
146 cursor.execute(*query)
147 return CursorIter(cursor)
148 except:
149 cursor.close()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500150
151 def __enter__(self):
Brad Bishop19323692019-04-05 15:28:33 -0400152 self.connection.__enter__()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500153 return self
154
155 def __exit__(self, *excinfo):
Brad Bishop19323692019-04-05 15:28:33 -0400156 self.connection.__exit__(*excinfo)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500157
Brad Bishop19323692019-04-05 15:28:33 -0400158 @_Decorators.retry()
159 @_Decorators.transaction
160 def __getitem__(self, cursor, key):
161 cursor.execute("SELECT * from %s where key=?;" % self.table, [key])
162 row = cursor.fetchone()
163 if row is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500164 return row[1]
165 raise KeyError(key)
166
Brad Bishop19323692019-04-05 15:28:33 -0400167 @_Decorators.retry()
168 @_Decorators.transaction
169 def __delitem__(self, cursor, key):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500170 if key not in self:
171 raise KeyError(key)
Brad Bishop19323692019-04-05 15:28:33 -0400172 cursor.execute("DELETE from %s where key=?;" % self.table, [key])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500173
Brad Bishop19323692019-04-05 15:28:33 -0400174 @_Decorators.retry()
175 @_Decorators.transaction
176 def __setitem__(self, cursor, key, value):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600177 if not isinstance(key, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500178 raise TypeError('Only string keys are supported')
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600179 elif not isinstance(value, str):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500180 raise TypeError('Only string values are supported')
181
Brad Bishop19323692019-04-05 15:28:33 -0400182 cursor.execute("SELECT * from %s where key=?;" % self.table, [key])
183 row = cursor.fetchone()
184 if row is not None:
185 cursor.execute("UPDATE %s SET value=? WHERE key=?;" % self.table, [value, key])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500186 else:
Brad Bishop19323692019-04-05 15:28:33 -0400187 cursor.execute("INSERT into %s(key, value) values (?, ?);" % self.table, [key, value])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500188
Brad Bishop19323692019-04-05 15:28:33 -0400189 @_Decorators.retry()
190 @_Decorators.transaction
191 def __contains__(self, cursor, key):
192 cursor.execute('SELECT * from %s where key=?;' % self.table, [key])
193 return cursor.fetchone() is not None
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500194
Brad Bishop19323692019-04-05 15:28:33 -0400195 @_Decorators.retry()
196 @_Decorators.transaction
197 def __len__(self, cursor):
198 cursor.execute("SELECT COUNT(key) FROM %s;" % self.table)
199 row = cursor.fetchone()
200 if row is not None:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500201 return row[0]
202
203 def __iter__(self):
Brad Bishop19323692019-04-05 15:28:33 -0400204 return self._row_iter(lambda row: row[0], "SELECT key from %s;" % self.table)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500205
206 def __lt__(self, other):
207 if not isinstance(other, Mapping):
208 raise NotImplemented
209
210 return len(self) < len(other)
211
212 def get_by_pattern(self, pattern):
Brad Bishop19323692019-04-05 15:28:33 -0400213 return self._row_iter(lambda row: row[1], "SELECT * FROM %s WHERE key LIKE ?;" %
214 self.table, [pattern])
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500215
216 def values(self):
217 return list(self.itervalues())
218
219 def itervalues(self):
Brad Bishop19323692019-04-05 15:28:33 -0400220 return self._row_iter(lambda row: row[0], "SELECT value FROM %s;" %
221 self.table)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500222
223 def items(self):
224 return list(self.iteritems())
225
226 def iteritems(self):
Brad Bishop19323692019-04-05 15:28:33 -0400227 return self._row_iter(lambda row: (row[0], row[1]), "SELECT * FROM %s;" %
228 self.table)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500229
Brad Bishop19323692019-04-05 15:28:33 -0400230 @_Decorators.retry()
231 @_Decorators.transaction
232 def clear(self, cursor):
233 cursor.execute("DELETE FROM %s;" % self.table)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500234
235 def has_key(self, key):
236 return key in self
237
238
239class PersistData(object):
240 """Deprecated representation of the bitbake persistent data store"""
241 def __init__(self, d):
242 warnings.warn("Use of PersistData is deprecated. Please use "
243 "persist(domain, d) instead.",
244 category=DeprecationWarning,
245 stacklevel=2)
246
247 self.data = persist(d)
248 logger.debug(1, "Using '%s' as the persistent data cache",
249 self.data.filename)
250
251 def addDomain(self, domain):
252 """
253 Add a domain (pending deprecation)
254 """
255 return self.data[domain]
256
257 def delDomain(self, domain):
258 """
259 Removes a domain and all the data it contains
260 """
261 del self.data[domain]
262
263 def getKeyValues(self, domain):
264 """
265 Return a list of key + value pairs for a domain
266 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600267 return list(self.data[domain].items())
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500268
269 def getValue(self, domain, key):
270 """
271 Return the value of a key for a domain
272 """
273 return self.data[domain][key]
274
275 def setValue(self, domain, key, value):
276 """
277 Sets the value of a key for a domain
278 """
279 self.data[domain][key] = value
280
281 def delValue(self, domain, key):
282 """
283 Deletes a key/value pair
284 """
285 del self.data[domain][key]
286
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500287def persist(domain, d):
288 """Convenience factory for SQLTable objects based upon metadata"""
289 import bb.utils
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500290 cachedir = (d.getVar("PERSISTENT_DIR") or
291 d.getVar("CACHE"))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500292 if not cachedir:
293 logger.critical("Please set the 'PERSISTENT_DIR' or 'CACHE' variable")
294 sys.exit(1)
295
296 bb.utils.mkdirhier(cachedir)
297 cachefile = os.path.join(cachedir, "bb_persist_data.sqlite3")
298 return SQLTable(cachefile, domain)