summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/hashserv/sqlite.py
diff options
context:
space:
mode:
authorJoshua Watt <JPEWhacker@gmail.com>2023-11-03 08:26:25 -0600
committerRichard Purdie <richard.purdie@linuxfoundation.org>2023-11-09 17:33:02 +0000
commitbaa3e5391daf41b6dd6e914a112abb00d3517da1 (patch)
tree8280b21caf78db2783f586250776c4813f24cb87 /bitbake/lib/hashserv/sqlite.py
parente90fccfefd7693d8cdfa731fa7e170c8bd4b1a1b (diff)
downloadpoky-baa3e5391daf41b6dd6e914a112abb00d3517da1.tar.gz
bitbake: hashserv: Abstract database
Abstracts the way the database backend is accessed by the hash equivalence server to make it possible to use other backends (Bitbake rev: 04b53deacf857488408bc82b9890b1e19874b5f1) Signed-off-by: Joshua Watt <JPEWhacker@gmail.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'bitbake/lib/hashserv/sqlite.py')
-rw-r--r--bitbake/lib/hashserv/sqlite.py259
1 files changed, 259 insertions, 0 deletions
diff --git a/bitbake/lib/hashserv/sqlite.py b/bitbake/lib/hashserv/sqlite.py
new file mode 100644
index 0000000000..6809c53706
--- /dev/null
+++ b/bitbake/lib/hashserv/sqlite.py
@@ -0,0 +1,259 @@
1#! /usr/bin/env python3
2#
3# Copyright (C) 2023 Garmin Ltd.
4#
5# SPDX-License-Identifier: GPL-2.0-only
6#
7import sqlite3
8import logging
9from contextlib import closing
10
11logger = logging.getLogger("hashserv.sqlite")
12
13UNIHASH_TABLE_DEFINITION = (
14 ("method", "TEXT NOT NULL", "UNIQUE"),
15 ("taskhash", "TEXT NOT NULL", "UNIQUE"),
16 ("unihash", "TEXT NOT NULL", ""),
17)
18
19UNIHASH_TABLE_COLUMNS = tuple(name for name, _, _ in UNIHASH_TABLE_DEFINITION)
20
21OUTHASH_TABLE_DEFINITION = (
22 ("method", "TEXT NOT NULL", "UNIQUE"),
23 ("taskhash", "TEXT NOT NULL", "UNIQUE"),
24 ("outhash", "TEXT NOT NULL", "UNIQUE"),
25 ("created", "DATETIME", ""),
26 # Optional fields
27 ("owner", "TEXT", ""),
28 ("PN", "TEXT", ""),
29 ("PV", "TEXT", ""),
30 ("PR", "TEXT", ""),
31 ("task", "TEXT", ""),
32 ("outhash_siginfo", "TEXT", ""),
33)
34
35OUTHASH_TABLE_COLUMNS = tuple(name for name, _, _ in OUTHASH_TABLE_DEFINITION)
36
37
38def _make_table(cursor, name, definition):
39 cursor.execute(
40 """
41 CREATE TABLE IF NOT EXISTS {name} (
42 id INTEGER PRIMARY KEY AUTOINCREMENT,
43 {fields}
44 UNIQUE({unique})
45 )
46 """.format(
47 name=name,
48 fields=" ".join("%s %s," % (name, typ) for name, typ, _ in definition),
49 unique=", ".join(
50 name for name, _, flags in definition if "UNIQUE" in flags
51 ),
52 )
53 )
54
55
56class DatabaseEngine(object):
57 def __init__(self, dbname, sync):
58 self.dbname = dbname
59 self.logger = logger
60 self.sync = sync
61
62 async def create(self):
63 db = sqlite3.connect(self.dbname)
64 db.row_factory = sqlite3.Row
65
66 with closing(db.cursor()) as cursor:
67 _make_table(cursor, "unihashes_v2", UNIHASH_TABLE_DEFINITION)
68 _make_table(cursor, "outhashes_v2", OUTHASH_TABLE_DEFINITION)
69
70 cursor.execute("PRAGMA journal_mode = WAL")
71 cursor.execute(
72 "PRAGMA synchronous = %s" % ("NORMAL" if self.sync else "OFF")
73 )
74
75 # Drop old indexes
76 cursor.execute("DROP INDEX IF EXISTS taskhash_lookup")
77 cursor.execute("DROP INDEX IF EXISTS outhash_lookup")
78 cursor.execute("DROP INDEX IF EXISTS taskhash_lookup_v2")
79 cursor.execute("DROP INDEX IF EXISTS outhash_lookup_v2")
80
81 # TODO: Upgrade from tasks_v2?
82 cursor.execute("DROP TABLE IF EXISTS tasks_v2")
83
84 # Create new indexes
85 cursor.execute(
86 "CREATE INDEX IF NOT EXISTS taskhash_lookup_v3 ON unihashes_v2 (method, taskhash)"
87 )
88 cursor.execute(
89 "CREATE INDEX IF NOT EXISTS outhash_lookup_v3 ON outhashes_v2 (method, outhash)"
90 )
91
92 def connect(self, logger):
93 return Database(logger, self.dbname)
94
95
96class Database(object):
97 def __init__(self, logger, dbname, sync=True):
98 self.dbname = dbname
99 self.logger = logger
100
101 self.db = sqlite3.connect(self.dbname)
102 self.db.row_factory = sqlite3.Row
103
104 async def __aenter__(self):
105 return self
106
107 async def __aexit__(self, exc_type, exc_value, traceback):
108 await self.close()
109
110 async def close(self):
111 self.db.close()
112
113 async def get_unihash_by_taskhash_full(self, method, taskhash):
114 with closing(self.db.cursor()) as cursor:
115 cursor.execute(
116 """
117 SELECT *, unihashes_v2.unihash AS unihash FROM outhashes_v2
118 INNER JOIN unihashes_v2 ON unihashes_v2.method=outhashes_v2.method AND unihashes_v2.taskhash=outhashes_v2.taskhash
119 WHERE outhashes_v2.method=:method AND outhashes_v2.taskhash=:taskhash
120 ORDER BY outhashes_v2.created ASC
121 LIMIT 1
122 """,
123 {
124 "method": method,
125 "taskhash": taskhash,
126 },
127 )
128 return cursor.fetchone()
129
130 async def get_unihash_by_outhash(self, method, outhash):
131 with closing(self.db.cursor()) as cursor:
132 cursor.execute(
133 """
134 SELECT *, unihashes_v2.unihash AS unihash FROM outhashes_v2
135 INNER JOIN unihashes_v2 ON unihashes_v2.method=outhashes_v2.method AND unihashes_v2.taskhash=outhashes_v2.taskhash
136 WHERE outhashes_v2.method=:method AND outhashes_v2.outhash=:outhash
137 ORDER BY outhashes_v2.created ASC
138 LIMIT 1
139 """,
140 {
141 "method": method,
142 "outhash": outhash,
143 },
144 )
145 return cursor.fetchone()
146
147 async def get_outhash(self, method, outhash):
148 with closing(self.db.cursor()) as cursor:
149 cursor.execute(
150 """
151 SELECT * FROM outhashes_v2
152 WHERE outhashes_v2.method=:method AND outhashes_v2.outhash=:outhash
153 ORDER BY outhashes_v2.created ASC
154 LIMIT 1
155 """,
156 {
157 "method": method,
158 "outhash": outhash,
159 },
160 )
161 return cursor.fetchone()
162
163 async def get_equivalent_for_outhash(self, method, outhash, taskhash):
164 with closing(self.db.cursor()) as cursor:
165 cursor.execute(
166 """
167 SELECT outhashes_v2.taskhash AS taskhash, unihashes_v2.unihash AS unihash FROM outhashes_v2
168 INNER JOIN unihashes_v2 ON unihashes_v2.method=outhashes_v2.method AND unihashes_v2.taskhash=outhashes_v2.taskhash
169 -- Select any matching output hash except the one we just inserted
170 WHERE outhashes_v2.method=:method AND outhashes_v2.outhash=:outhash AND outhashes_v2.taskhash!=:taskhash
171 -- Pick the oldest hash
172 ORDER BY outhashes_v2.created ASC
173 LIMIT 1
174 """,
175 {
176 "method": method,
177 "outhash": outhash,
178 "taskhash": taskhash,
179 },
180 )
181 return cursor.fetchone()
182
183 async def get_equivalent(self, method, taskhash):
184 with closing(self.db.cursor()) as cursor:
185 cursor.execute(
186 "SELECT taskhash, method, unihash FROM unihashes_v2 WHERE method=:method AND taskhash=:taskhash",
187 {
188 "method": method,
189 "taskhash": taskhash,
190 },
191 )
192 return cursor.fetchone()
193
194 async def remove(self, condition):
195 def do_remove(columns, table_name, cursor):
196 where = {}
197 for c in columns:
198 if c in condition and condition[c] is not None:
199 where[c] = condition[c]
200
201 if where:
202 query = ("DELETE FROM %s WHERE " % table_name) + " AND ".join(
203 "%s=:%s" % (k, k) for k in where.keys()
204 )
205 cursor.execute(query, where)
206 return cursor.rowcount
207
208 return 0
209
210 count = 0
211 with closing(self.db.cursor()) as cursor:
212 count += do_remove(OUTHASH_TABLE_COLUMNS, "outhashes_v2", cursor)
213 count += do_remove(UNIHASH_TABLE_COLUMNS, "unihashes_v2", cursor)
214 self.db.commit()
215
216 return count
217
218 async def clean_unused(self, oldest):
219 with closing(self.db.cursor()) as cursor:
220 cursor.execute(
221 """
222 DELETE FROM outhashes_v2 WHERE created<:oldest AND NOT EXISTS (
223 SELECT unihashes_v2.id FROM unihashes_v2 WHERE unihashes_v2.method=outhashes_v2.method AND unihashes_v2.taskhash=outhashes_v2.taskhash LIMIT 1
224 )
225 """,
226 {
227 "oldest": oldest,
228 },
229 )
230 return cursor.rowcount
231
232 async def insert_unihash(self, method, taskhash, unihash):
233 with closing(self.db.cursor()) as cursor:
234 prevrowid = cursor.lastrowid
235 cursor.execute(
236 """
237 INSERT OR IGNORE INTO unihashes_v2 (method, taskhash, unihash) VALUES(:method, :taskhash, :unihash)
238 """,
239 {
240 "method": method,
241 "taskhash": taskhash,
242 "unihash": unihash,
243 },
244 )
245 self.db.commit()
246 return cursor.lastrowid != prevrowid
247
248 async def insert_outhash(self, data):
249 data = {k: v for k, v in data.items() if k in OUTHASH_TABLE_COLUMNS}
250 keys = sorted(data.keys())
251 query = "INSERT OR IGNORE INTO outhashes_v2 ({fields}) VALUES({values})".format(
252 fields=", ".join(keys),
253 values=", ".join(":" + k for k in keys),
254 )
255 with closing(self.db.cursor()) as cursor:
256 prevrowid = cursor.lastrowid
257 cursor.execute(query, data)
258 self.db.commit()
259 return cursor.lastrowid != prevrowid