xref: /OK3568_Linux_fs/yocto/poky/bitbake/lib/bb/persist_data.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
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