1*4882a593Smuzhiyun"""BitBake Persistent Data Store 2*4882a593Smuzhiyun 3*4882a593SmuzhiyunUsed to store data in a central location such that other threads/tasks can 4*4882a593Smuzhiyunaccess them at some future date. Acts as a convenience wrapper around sqlite, 5*4882a593Smuzhiyuncurrently, providing a key/value store accessed by 'domain'. 6*4882a593Smuzhiyun""" 7*4882a593Smuzhiyun 8*4882a593Smuzhiyun# Copyright (C) 2007 Richard Purdie 9*4882a593Smuzhiyun# Copyright (C) 2010 Chris Larson <chris_larson@mentor.com> 10*4882a593Smuzhiyun# 11*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only 12*4882a593Smuzhiyun# 13*4882a593Smuzhiyun 14*4882a593Smuzhiyunimport collections 15*4882a593Smuzhiyunimport collections.abc 16*4882a593Smuzhiyunimport contextlib 17*4882a593Smuzhiyunimport functools 18*4882a593Smuzhiyunimport logging 19*4882a593Smuzhiyunimport os.path 20*4882a593Smuzhiyunimport sqlite3 21*4882a593Smuzhiyunimport sys 22*4882a593Smuzhiyunfrom collections.abc import Mapping 23*4882a593Smuzhiyun 24*4882a593Smuzhiyunsqlversion = sqlite3.sqlite_version_info 25*4882a593Smuzhiyunif sqlversion[0] < 3 or (sqlversion[0] == 3 and sqlversion[1] < 3): 26*4882a593Smuzhiyun raise Exception("sqlite3 version 3.3.0 or later is required.") 27*4882a593Smuzhiyun 28*4882a593Smuzhiyun 29*4882a593Smuzhiyunlogger = logging.getLogger("BitBake.PersistData") 30*4882a593Smuzhiyun 31*4882a593Smuzhiyun@functools.total_ordering 32*4882a593Smuzhiyunclass SQLTable(collections.abc.MutableMapping): 33*4882a593Smuzhiyun class _Decorators(object): 34*4882a593Smuzhiyun @staticmethod 35*4882a593Smuzhiyun def retry(*, reconnect=True): 36*4882a593Smuzhiyun """ 37*4882a593Smuzhiyun Decorator that restarts a function if a database locked sqlite 38*4882a593Smuzhiyun exception occurs. If reconnect is True, the database connection 39*4882a593Smuzhiyun will be closed and reopened each time a failure occurs 40*4882a593Smuzhiyun """ 41*4882a593Smuzhiyun def retry_wrapper(f): 42*4882a593Smuzhiyun def wrap_func(self, *args, **kwargs): 43*4882a593Smuzhiyun # Reconnect if necessary 44*4882a593Smuzhiyun if self.connection is None and reconnect: 45*4882a593Smuzhiyun self.reconnect() 46*4882a593Smuzhiyun 47*4882a593Smuzhiyun count = 0 48*4882a593Smuzhiyun while True: 49*4882a593Smuzhiyun try: 50*4882a593Smuzhiyun return f(self, *args, **kwargs) 51*4882a593Smuzhiyun except sqlite3.OperationalError as exc: 52*4882a593Smuzhiyun if count < 500 and ('is locked' in str(exc) or 'locking protocol' in str(exc)): 53*4882a593Smuzhiyun count = count + 1 54*4882a593Smuzhiyun if reconnect: 55*4882a593Smuzhiyun self.reconnect() 56*4882a593Smuzhiyun continue 57*4882a593Smuzhiyun raise 58*4882a593Smuzhiyun return wrap_func 59*4882a593Smuzhiyun return retry_wrapper 60*4882a593Smuzhiyun 61*4882a593Smuzhiyun @staticmethod 62*4882a593Smuzhiyun def transaction(f): 63*4882a593Smuzhiyun """ 64*4882a593Smuzhiyun Decorator that starts a database transaction and creates a database 65*4882a593Smuzhiyun cursor for performing queries. If no exception is thrown, the 66*4882a593Smuzhiyun database results are committed. If an exception occurs, the database 67*4882a593Smuzhiyun is rolled back. In all cases, the cursor is closed after the 68*4882a593Smuzhiyun function ends. 69*4882a593Smuzhiyun 70*4882a593Smuzhiyun Note that the cursor is passed as an extra argument to the function 71*4882a593Smuzhiyun after `self` and before any of the normal arguments 72*4882a593Smuzhiyun """ 73*4882a593Smuzhiyun def wrap_func(self, *args, **kwargs): 74*4882a593Smuzhiyun # Context manager will COMMIT the database on success, 75*4882a593Smuzhiyun # or ROLLBACK on an exception 76*4882a593Smuzhiyun with self.connection: 77*4882a593Smuzhiyun # Automatically close the cursor when done 78*4882a593Smuzhiyun with contextlib.closing(self.connection.cursor()) as cursor: 79*4882a593Smuzhiyun return f(self, cursor, *args, **kwargs) 80*4882a593Smuzhiyun return wrap_func 81*4882a593Smuzhiyun 82*4882a593Smuzhiyun """Object representing a table/domain in the database""" 83*4882a593Smuzhiyun def __init__(self, cachefile, table): 84*4882a593Smuzhiyun self.cachefile = cachefile 85*4882a593Smuzhiyun self.table = table 86*4882a593Smuzhiyun 87*4882a593Smuzhiyun self.connection = None 88*4882a593Smuzhiyun self._execute_single("CREATE TABLE IF NOT EXISTS %s(key TEXT PRIMARY KEY NOT NULL, value TEXT);" % table) 89*4882a593Smuzhiyun 90*4882a593Smuzhiyun @_Decorators.retry(reconnect=False) 91*4882a593Smuzhiyun @_Decorators.transaction 92*4882a593Smuzhiyun def _setup_database(self, cursor): 93*4882a593Smuzhiyun cursor.execute("pragma synchronous = off;") 94*4882a593Smuzhiyun # Enable WAL and keep the autocheckpoint length small (the default is 95*4882a593Smuzhiyun # usually 1000). Persistent caches are usually read-mostly, so keeping 96*4882a593Smuzhiyun # this short will keep readers running quickly 97*4882a593Smuzhiyun cursor.execute("pragma journal_mode = WAL;") 98*4882a593Smuzhiyun cursor.execute("pragma wal_autocheckpoint = 100;") 99*4882a593Smuzhiyun 100*4882a593Smuzhiyun def reconnect(self): 101*4882a593Smuzhiyun if self.connection is not None: 102*4882a593Smuzhiyun self.connection.close() 103*4882a593Smuzhiyun self.connection = sqlite3.connect(self.cachefile, timeout=5) 104*4882a593Smuzhiyun self.connection.text_factory = str 105*4882a593Smuzhiyun self._setup_database() 106*4882a593Smuzhiyun 107*4882a593Smuzhiyun @_Decorators.retry() 108*4882a593Smuzhiyun @_Decorators.transaction 109*4882a593Smuzhiyun def _execute_single(self, cursor, *query): 110*4882a593Smuzhiyun """ 111*4882a593Smuzhiyun Executes a single query and discards the results. This correctly closes 112*4882a593Smuzhiyun the database cursor when finished 113*4882a593Smuzhiyun """ 114*4882a593Smuzhiyun cursor.execute(*query) 115*4882a593Smuzhiyun 116*4882a593Smuzhiyun @_Decorators.retry() 117*4882a593Smuzhiyun def _row_iter(self, f, *query): 118*4882a593Smuzhiyun """ 119*4882a593Smuzhiyun Helper function that returns a row iterator. Each time __next__ is 120*4882a593Smuzhiyun called on the iterator, the provided function is evaluated to determine 121*4882a593Smuzhiyun the return value 122*4882a593Smuzhiyun """ 123*4882a593Smuzhiyun class CursorIter(object): 124*4882a593Smuzhiyun def __init__(self, cursor): 125*4882a593Smuzhiyun self.cursor = cursor 126*4882a593Smuzhiyun 127*4882a593Smuzhiyun def __iter__(self): 128*4882a593Smuzhiyun return self 129*4882a593Smuzhiyun 130*4882a593Smuzhiyun def __next__(self): 131*4882a593Smuzhiyun row = self.cursor.fetchone() 132*4882a593Smuzhiyun if row is None: 133*4882a593Smuzhiyun self.cursor.close() 134*4882a593Smuzhiyun raise StopIteration 135*4882a593Smuzhiyun return f(row) 136*4882a593Smuzhiyun 137*4882a593Smuzhiyun def __enter__(self): 138*4882a593Smuzhiyun return self 139*4882a593Smuzhiyun 140*4882a593Smuzhiyun def __exit__(self, typ, value, traceback): 141*4882a593Smuzhiyun self.cursor.close() 142*4882a593Smuzhiyun return False 143*4882a593Smuzhiyun 144*4882a593Smuzhiyun cursor = self.connection.cursor() 145*4882a593Smuzhiyun try: 146*4882a593Smuzhiyun cursor.execute(*query) 147*4882a593Smuzhiyun return CursorIter(cursor) 148*4882a593Smuzhiyun except: 149*4882a593Smuzhiyun cursor.close() 150*4882a593Smuzhiyun 151*4882a593Smuzhiyun def __enter__(self): 152*4882a593Smuzhiyun self.connection.__enter__() 153*4882a593Smuzhiyun return self 154*4882a593Smuzhiyun 155*4882a593Smuzhiyun def __exit__(self, *excinfo): 156*4882a593Smuzhiyun self.connection.__exit__(*excinfo) 157*4882a593Smuzhiyun 158*4882a593Smuzhiyun @_Decorators.retry() 159*4882a593Smuzhiyun @_Decorators.transaction 160*4882a593Smuzhiyun def __getitem__(self, cursor, key): 161*4882a593Smuzhiyun cursor.execute("SELECT * from %s where key=?;" % self.table, [key]) 162*4882a593Smuzhiyun row = cursor.fetchone() 163*4882a593Smuzhiyun if row is not None: 164*4882a593Smuzhiyun return row[1] 165*4882a593Smuzhiyun raise KeyError(key) 166*4882a593Smuzhiyun 167*4882a593Smuzhiyun @_Decorators.retry() 168*4882a593Smuzhiyun @_Decorators.transaction 169*4882a593Smuzhiyun def __delitem__(self, cursor, key): 170*4882a593Smuzhiyun if key not in self: 171*4882a593Smuzhiyun raise KeyError(key) 172*4882a593Smuzhiyun cursor.execute("DELETE from %s where key=?;" % self.table, [key]) 173*4882a593Smuzhiyun 174*4882a593Smuzhiyun @_Decorators.retry() 175*4882a593Smuzhiyun @_Decorators.transaction 176*4882a593Smuzhiyun def __setitem__(self, cursor, key, value): 177*4882a593Smuzhiyun if not isinstance(key, str): 178*4882a593Smuzhiyun raise TypeError('Only string keys are supported') 179*4882a593Smuzhiyun elif not isinstance(value, str): 180*4882a593Smuzhiyun raise TypeError('Only string values are supported') 181*4882a593Smuzhiyun 182*4882a593Smuzhiyun # Ensure the entire transaction (including SELECT) executes under write lock 183*4882a593Smuzhiyun cursor.execute("BEGIN EXCLUSIVE") 184*4882a593Smuzhiyun 185*4882a593Smuzhiyun cursor.execute("SELECT * from %s where key=?;" % self.table, [key]) 186*4882a593Smuzhiyun row = cursor.fetchone() 187*4882a593Smuzhiyun if row is not None: 188*4882a593Smuzhiyun cursor.execute("UPDATE %s SET value=? WHERE key=?;" % self.table, [value, key]) 189*4882a593Smuzhiyun else: 190*4882a593Smuzhiyun cursor.execute("INSERT into %s(key, value) values (?, ?);" % self.table, [key, value]) 191*4882a593Smuzhiyun 192*4882a593Smuzhiyun @_Decorators.retry() 193*4882a593Smuzhiyun @_Decorators.transaction 194*4882a593Smuzhiyun def __contains__(self, cursor, key): 195*4882a593Smuzhiyun cursor.execute('SELECT * from %s where key=?;' % self.table, [key]) 196*4882a593Smuzhiyun return cursor.fetchone() is not None 197*4882a593Smuzhiyun 198*4882a593Smuzhiyun @_Decorators.retry() 199*4882a593Smuzhiyun @_Decorators.transaction 200*4882a593Smuzhiyun def __len__(self, cursor): 201*4882a593Smuzhiyun cursor.execute("SELECT COUNT(key) FROM %s;" % self.table) 202*4882a593Smuzhiyun row = cursor.fetchone() 203*4882a593Smuzhiyun if row is not None: 204*4882a593Smuzhiyun return row[0] 205*4882a593Smuzhiyun 206*4882a593Smuzhiyun def __iter__(self): 207*4882a593Smuzhiyun return self._row_iter(lambda row: row[0], "SELECT key from %s;" % self.table) 208*4882a593Smuzhiyun 209*4882a593Smuzhiyun def __lt__(self, other): 210*4882a593Smuzhiyun if not isinstance(other, Mapping): 211*4882a593Smuzhiyun raise NotImplementedError() 212*4882a593Smuzhiyun 213*4882a593Smuzhiyun return len(self) < len(other) 214*4882a593Smuzhiyun 215*4882a593Smuzhiyun def get_by_pattern(self, pattern): 216*4882a593Smuzhiyun return self._row_iter(lambda row: row[1], "SELECT * FROM %s WHERE key LIKE ?;" % 217*4882a593Smuzhiyun self.table, [pattern]) 218*4882a593Smuzhiyun 219*4882a593Smuzhiyun def values(self): 220*4882a593Smuzhiyun return list(self.itervalues()) 221*4882a593Smuzhiyun 222*4882a593Smuzhiyun def itervalues(self): 223*4882a593Smuzhiyun return self._row_iter(lambda row: row[0], "SELECT value FROM %s;" % 224*4882a593Smuzhiyun self.table) 225*4882a593Smuzhiyun 226*4882a593Smuzhiyun def items(self): 227*4882a593Smuzhiyun return list(self.iteritems()) 228*4882a593Smuzhiyun 229*4882a593Smuzhiyun def iteritems(self): 230*4882a593Smuzhiyun return self._row_iter(lambda row: (row[0], row[1]), "SELECT * FROM %s;" % 231*4882a593Smuzhiyun self.table) 232*4882a593Smuzhiyun 233*4882a593Smuzhiyun @_Decorators.retry() 234*4882a593Smuzhiyun @_Decorators.transaction 235*4882a593Smuzhiyun def clear(self, cursor): 236*4882a593Smuzhiyun cursor.execute("DELETE FROM %s;" % self.table) 237*4882a593Smuzhiyun 238*4882a593Smuzhiyun def has_key(self, key): 239*4882a593Smuzhiyun return key in self 240*4882a593Smuzhiyun 241*4882a593Smuzhiyundef persist(domain, d): 242*4882a593Smuzhiyun """Convenience factory for SQLTable objects based upon metadata""" 243*4882a593Smuzhiyun import bb.utils 244*4882a593Smuzhiyun cachedir = (d.getVar("PERSISTENT_DIR") or 245*4882a593Smuzhiyun d.getVar("CACHE")) 246*4882a593Smuzhiyun if not cachedir: 247*4882a593Smuzhiyun logger.critical("Please set the 'PERSISTENT_DIR' or 'CACHE' variable") 248*4882a593Smuzhiyun sys.exit(1) 249*4882a593Smuzhiyun 250*4882a593Smuzhiyun bb.utils.mkdirhier(cachedir) 251*4882a593Smuzhiyun cachefile = os.path.join(cachedir, "bb_persist_data.sqlite3") 252*4882a593Smuzhiyun return SQLTable(cachefile, domain) 253