persist_data: use better items/values methods for SQLTable
[bitbake.git] / lib / bb / persist_data.py
1 """BitBake Persistent Data Store
2
3 Used to store data in a central location such that other threads/tasks can
4 access them at some future date.  Acts as a convenience wrapper around sqlite,
5 currently, 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
24 import collections
25 import logging
26 import os.path
27 import sys
28 import warnings
29 import bb.msg, bb.data, bb.utils
30
31 try:
32     import sqlite3
33 except ImportError:
34     from pysqlite2 import dbapi2 as sqlite3
35
36 sqlversion = sqlite3.sqlite_version_info
37 if sqlversion[0] < 3 or (sqlversion[0] == 3 and sqlversion[1] < 3):
38     raise Exception("sqlite3 version 3.3.0 or later is required.")
39
40
41 logger = logging.getLogger("BitBake.PersistData")
42
43
44 class SQLTable(collections.MutableMapping):
45     """Object representing a table/domain in the database"""
46     def __init__(self, cursor, table):
47         self.cursor = cursor
48         self.table = table
49
50         self._execute("CREATE TABLE IF NOT EXISTS %s(key TEXT, value TEXT);"
51                       % table)
52
53     def _execute(self, *query):
54         """Execute a query, waiting to acquire a lock if necessary"""
55         count = 0
56         while True:
57             try:
58                 return self.cursor.execute(*query)
59             except sqlite3.OperationalError as exc:
60                 if 'database is locked' in str(exc) and count < 500:
61                     count = count + 1
62                     continue
63                 raise
64
65     def __getitem__(self, key):
66         data = self._execute("SELECT * from %s where key=?;" %
67                              self.table, [key])
68         for row in data:
69             return row[1]
70
71     def __delitem__(self, key):
72         self._execute("DELETE from %s where key=?;" % self.table, [key])
73
74     def __setitem__(self, key, value):
75         data = self._execute("SELECT * from %s where key=?;" %
76                                    self.table, [key])
77         exists = len(list(data))
78         if exists:
79             self._execute("UPDATE %s SET value=? WHERE key=?;" % self.table,
80                           [value, key])
81         else:
82             self._execute("INSERT into %s(key, value) values (?, ?);" %
83                           self.table, [key, value])
84
85     def __contains__(self, key):
86         return key in set(self)
87
88     def __len__(self):
89         data = self._execute("SELECT COUNT(key) FROM %s;" % self.table)
90         for row in data:
91             return row[0]
92
93     def __iter__(self):
94         data = self._execute("SELECT key FROM %s;" % self.table)
95         return (row[0] for row in data)
96
97     def values(self):
98         return list(self.itervalues())
99
100     def itervalues(self):
101         data = self._execute("SELECT value FROM %s;" % self.table)
102         return (row[0] for row in data)
103
104     def items(self):
105         return list(self.iteritems())
106
107     def iteritems(self):
108         return self._execute("SELECT * FROM %s;" % self.table)
109
110
111 class SQLData(object):
112     """Object representing the persistent data"""
113     def __init__(self, filename):
114         bb.utils.mkdirhier(os.path.dirname(filename))
115
116         self.filename = filename
117         self.connection = sqlite3.connect(filename, timeout=5,
118                                           isolation_level=None)
119         self.cursor = self.connection.cursor()
120         self._tables = {}
121
122     def __getitem__(self, table):
123         if not isinstance(table, basestring):
124             raise TypeError("table argument must be a string, not '%s'" %
125                             type(table))
126
127         if table in self._tables:
128             return self._tables[table]
129         else:
130             tableobj = self._tables[table] = SQLTable(self.cursor, table)
131             return tableobj
132
133     def __delitem__(self, table):
134         if table in self._tables:
135             del self._tables[table]
136         self.cursor.execute("DROP TABLE IF EXISTS %s;" % table)
137
138
139 class PersistData(object):
140     """Deprecated representation of the bitbake persistent data store"""
141     def __init__(self, d):
142         warnings.warn("Use of PersistData will be deprecated in the future",
143                       category=PendingDeprecationWarning,
144                       stacklevel=2)
145
146         self.data = persist(d)
147         logger.debug(1, "Using '%s' as the persistent data cache",
148                      self.data.filename)
149
150     def addDomain(self, domain):
151         """
152         Add a domain (pending deprecation)
153         """
154         return self.data[domain]
155
156     def delDomain(self, domain):
157         """
158         Removes a domain and all the data it contains
159         """
160         del self.data[domain]
161
162     def getKeyValues(self, domain):
163         """
164         Return a list of key + value pairs for a domain
165         """
166         return self.data[domain].items()
167
168     def getValue(self, domain, key):
169         """
170         Return the value of a key for a domain
171         """
172         return self.data[domain][key]
173
174     def setValue(self, domain, key, value):
175         """
176         Sets the value of a key for a domain
177         """
178         self.data[domain][key] = value
179
180     def delValue(self, domain, key):
181         """
182         Deletes a key/value pair
183         """
184         del self.data[domain][key]
185
186
187 def persist(d):
188     """Convenience factory for construction of SQLData based upon metadata"""
189     cachedir = (bb.data.getVar("PERSISTENT_DIR", d, True) or
190                 bb.data.getVar("CACHE", d, True))
191     if not cachedir:
192         logger.critical("Please set the 'PERSISTENT_DIR' or 'CACHE' variable")
193         sys.exit(1)
194
195     cachefile = os.path.join(cachedir, "bb_persist_data.sqlite3")
196     return SQLData(cachefile)