xref: /OK3568_Linux_fs/yocto/poky/bitbake/lib/prserv/db.py (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun#
2*4882a593Smuzhiyun# Copyright BitBake Contributors
3*4882a593Smuzhiyun#
4*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only
5*4882a593Smuzhiyun#
6*4882a593Smuzhiyun
7*4882a593Smuzhiyunimport logging
8*4882a593Smuzhiyunimport os.path
9*4882a593Smuzhiyunimport errno
10*4882a593Smuzhiyunimport prserv
11*4882a593Smuzhiyunimport time
12*4882a593Smuzhiyun
13*4882a593Smuzhiyuntry:
14*4882a593Smuzhiyun    import sqlite3
15*4882a593Smuzhiyunexcept ImportError:
16*4882a593Smuzhiyun    from pysqlite2 import dbapi2 as sqlite3
17*4882a593Smuzhiyun
18*4882a593Smuzhiyunlogger = logging.getLogger("BitBake.PRserv")
19*4882a593Smuzhiyun
20*4882a593Smuzhiyunsqlversion = sqlite3.sqlite_version_info
21*4882a593Smuzhiyunif sqlversion[0] < 3 or (sqlversion[0] == 3 and sqlversion[1] < 3):
22*4882a593Smuzhiyun    raise Exception("sqlite3 version 3.3.0 or later is required.")
23*4882a593Smuzhiyun
24*4882a593Smuzhiyun#
25*4882a593Smuzhiyun# "No History" mode - for a given query tuple (version, pkgarch, checksum),
26*4882a593Smuzhiyun# the returned value will be the largest among all the values of the same
27*4882a593Smuzhiyun# (version, pkgarch). This means the PR value returned can NOT be decremented.
28*4882a593Smuzhiyun#
29*4882a593Smuzhiyun# "History" mode - Return a new higher value for previously unseen query
30*4882a593Smuzhiyun# tuple (version, pkgarch, checksum), otherwise return historical value.
31*4882a593Smuzhiyun# Value can decrement if returning to a previous build.
32*4882a593Smuzhiyun#
33*4882a593Smuzhiyun
34*4882a593Smuzhiyunclass PRTable(object):
35*4882a593Smuzhiyun    def __init__(self, conn, table, nohist, read_only):
36*4882a593Smuzhiyun        self.conn = conn
37*4882a593Smuzhiyun        self.nohist = nohist
38*4882a593Smuzhiyun        self.read_only = read_only
39*4882a593Smuzhiyun        self.dirty = False
40*4882a593Smuzhiyun        if nohist:
41*4882a593Smuzhiyun            self.table = "%s_nohist" % table
42*4882a593Smuzhiyun        else:
43*4882a593Smuzhiyun            self.table = "%s_hist" % table
44*4882a593Smuzhiyun
45*4882a593Smuzhiyun        if self.read_only:
46*4882a593Smuzhiyun            table_exists = self._execute(
47*4882a593Smuzhiyun                        "SELECT count(*) FROM sqlite_master \
48*4882a593Smuzhiyun                        WHERE type='table' AND name='%s'" % (self.table))
49*4882a593Smuzhiyun            if not table_exists:
50*4882a593Smuzhiyun                raise prserv.NotFoundError
51*4882a593Smuzhiyun        else:
52*4882a593Smuzhiyun            self._execute("CREATE TABLE IF NOT EXISTS %s \
53*4882a593Smuzhiyun                        (version TEXT NOT NULL, \
54*4882a593Smuzhiyun                        pkgarch TEXT NOT NULL,  \
55*4882a593Smuzhiyun                        checksum TEXT NOT NULL, \
56*4882a593Smuzhiyun                        value INTEGER, \
57*4882a593Smuzhiyun                        PRIMARY KEY (version, pkgarch, checksum));" % self.table)
58*4882a593Smuzhiyun
59*4882a593Smuzhiyun    def _execute(self, *query):
60*4882a593Smuzhiyun        """Execute a query, waiting to acquire a lock if necessary"""
61*4882a593Smuzhiyun        start = time.time()
62*4882a593Smuzhiyun        end = start + 20
63*4882a593Smuzhiyun        while True:
64*4882a593Smuzhiyun            try:
65*4882a593Smuzhiyun                return self.conn.execute(*query)
66*4882a593Smuzhiyun            except sqlite3.OperationalError as exc:
67*4882a593Smuzhiyun                if 'is locked' in str(exc) and end > time.time():
68*4882a593Smuzhiyun                    continue
69*4882a593Smuzhiyun                raise exc
70*4882a593Smuzhiyun
71*4882a593Smuzhiyun    def sync(self):
72*4882a593Smuzhiyun        if not self.read_only:
73*4882a593Smuzhiyun            self.conn.commit()
74*4882a593Smuzhiyun            self._execute("BEGIN EXCLUSIVE TRANSACTION")
75*4882a593Smuzhiyun
76*4882a593Smuzhiyun    def sync_if_dirty(self):
77*4882a593Smuzhiyun        if self.dirty:
78*4882a593Smuzhiyun            self.sync()
79*4882a593Smuzhiyun            self.dirty = False
80*4882a593Smuzhiyun
81*4882a593Smuzhiyun    def _getValueHist(self, version, pkgarch, checksum):
82*4882a593Smuzhiyun        data=self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table,
83*4882a593Smuzhiyun                           (version, pkgarch, checksum))
84*4882a593Smuzhiyun        row=data.fetchone()
85*4882a593Smuzhiyun        if row is not None:
86*4882a593Smuzhiyun            return row[0]
87*4882a593Smuzhiyun        else:
88*4882a593Smuzhiyun            #no value found, try to insert
89*4882a593Smuzhiyun            if self.read_only:
90*4882a593Smuzhiyun                data = self._execute("SELECT ifnull(max(value)+1,0) FROM %s where version=? AND pkgarch=?;" % (self.table),
91*4882a593Smuzhiyun                                   (version, pkgarch))
92*4882a593Smuzhiyun                row = data.fetchone()
93*4882a593Smuzhiyun                if row is not None:
94*4882a593Smuzhiyun                    return row[0]
95*4882a593Smuzhiyun                else:
96*4882a593Smuzhiyun                    return 0
97*4882a593Smuzhiyun
98*4882a593Smuzhiyun            try:
99*4882a593Smuzhiyun                self._execute("INSERT INTO %s VALUES (?, ?, ?, (select ifnull(max(value)+1,0) from %s where version=? AND pkgarch=?));"
100*4882a593Smuzhiyun                           % (self.table,self.table),
101*4882a593Smuzhiyun                           (version,pkgarch, checksum,version, pkgarch))
102*4882a593Smuzhiyun            except sqlite3.IntegrityError as exc:
103*4882a593Smuzhiyun                logger.error(str(exc))
104*4882a593Smuzhiyun
105*4882a593Smuzhiyun            self.dirty = True
106*4882a593Smuzhiyun
107*4882a593Smuzhiyun            data=self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table,
108*4882a593Smuzhiyun                               (version, pkgarch, checksum))
109*4882a593Smuzhiyun            row=data.fetchone()
110*4882a593Smuzhiyun            if row is not None:
111*4882a593Smuzhiyun                return row[0]
112*4882a593Smuzhiyun            else:
113*4882a593Smuzhiyun                raise prserv.NotFoundError
114*4882a593Smuzhiyun
115*4882a593Smuzhiyun    def _getValueNohist(self, version, pkgarch, checksum):
116*4882a593Smuzhiyun        data=self._execute("SELECT value FROM %s \
117*4882a593Smuzhiyun                            WHERE version=? AND pkgarch=? AND checksum=? AND \
118*4882a593Smuzhiyun                            value >= (select max(value) from %s where version=? AND pkgarch=?);"
119*4882a593Smuzhiyun                            % (self.table, self.table),
120*4882a593Smuzhiyun                            (version, pkgarch, checksum, version, pkgarch))
121*4882a593Smuzhiyun        row=data.fetchone()
122*4882a593Smuzhiyun        if row is not None:
123*4882a593Smuzhiyun            return row[0]
124*4882a593Smuzhiyun        else:
125*4882a593Smuzhiyun            #no value found, try to insert
126*4882a593Smuzhiyun            if self.read_only:
127*4882a593Smuzhiyun                data = self._execute("SELECT ifnull(max(value)+1,0) FROM %s where version=? AND pkgarch=?;" % (self.table),
128*4882a593Smuzhiyun                                   (version, pkgarch))
129*4882a593Smuzhiyun                row = data.fetchone()
130*4882a593Smuzhiyun                if row is not None:
131*4882a593Smuzhiyun                    return row[0]
132*4882a593Smuzhiyun                else:
133*4882a593Smuzhiyun                    return 0
134*4882a593Smuzhiyun
135*4882a593Smuzhiyun            try:
136*4882a593Smuzhiyun                self._execute("INSERT OR REPLACE INTO %s VALUES (?, ?, ?, (select ifnull(max(value)+1,0) from %s where version=? AND pkgarch=?));"
137*4882a593Smuzhiyun                               % (self.table,self.table),
138*4882a593Smuzhiyun                               (version, pkgarch, checksum, version, pkgarch))
139*4882a593Smuzhiyun            except sqlite3.IntegrityError as exc:
140*4882a593Smuzhiyun                logger.error(str(exc))
141*4882a593Smuzhiyun                self.conn.rollback()
142*4882a593Smuzhiyun
143*4882a593Smuzhiyun            self.dirty = True
144*4882a593Smuzhiyun
145*4882a593Smuzhiyun            data=self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table,
146*4882a593Smuzhiyun                               (version, pkgarch, checksum))
147*4882a593Smuzhiyun            row=data.fetchone()
148*4882a593Smuzhiyun            if row is not None:
149*4882a593Smuzhiyun                return row[0]
150*4882a593Smuzhiyun            else:
151*4882a593Smuzhiyun                raise prserv.NotFoundError
152*4882a593Smuzhiyun
153*4882a593Smuzhiyun    def getValue(self, version, pkgarch, checksum):
154*4882a593Smuzhiyun        if self.nohist:
155*4882a593Smuzhiyun            return self._getValueNohist(version, pkgarch, checksum)
156*4882a593Smuzhiyun        else:
157*4882a593Smuzhiyun            return self._getValueHist(version, pkgarch, checksum)
158*4882a593Smuzhiyun
159*4882a593Smuzhiyun    def _importHist(self, version, pkgarch, checksum, value):
160*4882a593Smuzhiyun        if self.read_only:
161*4882a593Smuzhiyun            return None
162*4882a593Smuzhiyun
163*4882a593Smuzhiyun        val = None
164*4882a593Smuzhiyun        data = self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table,
165*4882a593Smuzhiyun                           (version, pkgarch, checksum))
166*4882a593Smuzhiyun        row = data.fetchone()
167*4882a593Smuzhiyun        if row is not None:
168*4882a593Smuzhiyun            val=row[0]
169*4882a593Smuzhiyun        else:
170*4882a593Smuzhiyun            #no value found, try to insert
171*4882a593Smuzhiyun            try:
172*4882a593Smuzhiyun                self._execute("INSERT INTO %s VALUES (?, ?, ?, ?);"  % (self.table),
173*4882a593Smuzhiyun                           (version, pkgarch, checksum, value))
174*4882a593Smuzhiyun            except sqlite3.IntegrityError as exc:
175*4882a593Smuzhiyun                logger.error(str(exc))
176*4882a593Smuzhiyun
177*4882a593Smuzhiyun            self.dirty = True
178*4882a593Smuzhiyun
179*4882a593Smuzhiyun            data = self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table,
180*4882a593Smuzhiyun                           (version, pkgarch, checksum))
181*4882a593Smuzhiyun            row = data.fetchone()
182*4882a593Smuzhiyun            if row is not None:
183*4882a593Smuzhiyun                val = row[0]
184*4882a593Smuzhiyun        return val
185*4882a593Smuzhiyun
186*4882a593Smuzhiyun    def _importNohist(self, version, pkgarch, checksum, value):
187*4882a593Smuzhiyun        if self.read_only:
188*4882a593Smuzhiyun            return None
189*4882a593Smuzhiyun
190*4882a593Smuzhiyun        try:
191*4882a593Smuzhiyun            #try to insert
192*4882a593Smuzhiyun            self._execute("INSERT INTO %s VALUES (?, ?, ?, ?);"  % (self.table),
193*4882a593Smuzhiyun                           (version, pkgarch, checksum,value))
194*4882a593Smuzhiyun        except sqlite3.IntegrityError as exc:
195*4882a593Smuzhiyun            #already have the record, try to update
196*4882a593Smuzhiyun            try:
197*4882a593Smuzhiyun                self._execute("UPDATE %s SET value=? WHERE version=? AND pkgarch=? AND checksum=? AND value<?"
198*4882a593Smuzhiyun                              % (self.table),
199*4882a593Smuzhiyun                               (value,version,pkgarch,checksum,value))
200*4882a593Smuzhiyun            except sqlite3.IntegrityError as exc:
201*4882a593Smuzhiyun                logger.error(str(exc))
202*4882a593Smuzhiyun
203*4882a593Smuzhiyun        self.dirty = True
204*4882a593Smuzhiyun
205*4882a593Smuzhiyun        data = self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=? AND value>=?;" % self.table,
206*4882a593Smuzhiyun                            (version,pkgarch,checksum,value))
207*4882a593Smuzhiyun        row=data.fetchone()
208*4882a593Smuzhiyun        if row is not None:
209*4882a593Smuzhiyun            return row[0]
210*4882a593Smuzhiyun        else:
211*4882a593Smuzhiyun            return None
212*4882a593Smuzhiyun
213*4882a593Smuzhiyun    def importone(self, version, pkgarch, checksum, value):
214*4882a593Smuzhiyun        if self.nohist:
215*4882a593Smuzhiyun            return self._importNohist(version, pkgarch, checksum, value)
216*4882a593Smuzhiyun        else:
217*4882a593Smuzhiyun            return self._importHist(version, pkgarch, checksum, value)
218*4882a593Smuzhiyun
219*4882a593Smuzhiyun    def export(self, version, pkgarch, checksum, colinfo):
220*4882a593Smuzhiyun        metainfo = {}
221*4882a593Smuzhiyun        #column info
222*4882a593Smuzhiyun        if colinfo:
223*4882a593Smuzhiyun            metainfo['tbl_name'] = self.table
224*4882a593Smuzhiyun            metainfo['core_ver'] = prserv.__version__
225*4882a593Smuzhiyun            metainfo['col_info'] = []
226*4882a593Smuzhiyun            data = self._execute("PRAGMA table_info(%s);" % self.table)
227*4882a593Smuzhiyun            for row in data:
228*4882a593Smuzhiyun                col = {}
229*4882a593Smuzhiyun                col['name'] = row['name']
230*4882a593Smuzhiyun                col['type'] = row['type']
231*4882a593Smuzhiyun                col['notnull'] = row['notnull']
232*4882a593Smuzhiyun                col['dflt_value'] = row['dflt_value']
233*4882a593Smuzhiyun                col['pk'] = row['pk']
234*4882a593Smuzhiyun                metainfo['col_info'].append(col)
235*4882a593Smuzhiyun
236*4882a593Smuzhiyun        #data info
237*4882a593Smuzhiyun        datainfo = []
238*4882a593Smuzhiyun
239*4882a593Smuzhiyun        if self.nohist:
240*4882a593Smuzhiyun            sqlstmt = "SELECT T1.version, T1.pkgarch, T1.checksum, T1.value FROM %s as T1, \
241*4882a593Smuzhiyun                    (SELECT version,pkgarch,max(value) as maxvalue FROM %s GROUP BY version,pkgarch) as T2 \
242*4882a593Smuzhiyun                    WHERE T1.version=T2.version AND T1.pkgarch=T2.pkgarch AND T1.value=T2.maxvalue " % (self.table, self.table)
243*4882a593Smuzhiyun        else:
244*4882a593Smuzhiyun            sqlstmt = "SELECT * FROM %s as T1 WHERE 1=1 " % self.table
245*4882a593Smuzhiyun        sqlarg = []
246*4882a593Smuzhiyun        where = ""
247*4882a593Smuzhiyun        if version:
248*4882a593Smuzhiyun            where += "AND T1.version=? "
249*4882a593Smuzhiyun            sqlarg.append(str(version))
250*4882a593Smuzhiyun        if pkgarch:
251*4882a593Smuzhiyun            where += "AND T1.pkgarch=? "
252*4882a593Smuzhiyun            sqlarg.append(str(pkgarch))
253*4882a593Smuzhiyun        if checksum:
254*4882a593Smuzhiyun            where += "AND T1.checksum=? "
255*4882a593Smuzhiyun            sqlarg.append(str(checksum))
256*4882a593Smuzhiyun
257*4882a593Smuzhiyun        sqlstmt += where + ";"
258*4882a593Smuzhiyun
259*4882a593Smuzhiyun        if len(sqlarg):
260*4882a593Smuzhiyun            data = self._execute(sqlstmt, tuple(sqlarg))
261*4882a593Smuzhiyun        else:
262*4882a593Smuzhiyun            data = self._execute(sqlstmt)
263*4882a593Smuzhiyun        for row in data:
264*4882a593Smuzhiyun            if row['version']:
265*4882a593Smuzhiyun                col = {}
266*4882a593Smuzhiyun                col['version'] = row['version']
267*4882a593Smuzhiyun                col['pkgarch'] = row['pkgarch']
268*4882a593Smuzhiyun                col['checksum'] = row['checksum']
269*4882a593Smuzhiyun                col['value'] = row['value']
270*4882a593Smuzhiyun                datainfo.append(col)
271*4882a593Smuzhiyun        return (metainfo, datainfo)
272*4882a593Smuzhiyun
273*4882a593Smuzhiyun    def dump_db(self, fd):
274*4882a593Smuzhiyun        writeCount = 0
275*4882a593Smuzhiyun        for line in self.conn.iterdump():
276*4882a593Smuzhiyun            writeCount = writeCount + len(line) + 1
277*4882a593Smuzhiyun            fd.write(line)
278*4882a593Smuzhiyun            fd.write('\n')
279*4882a593Smuzhiyun        return writeCount
280*4882a593Smuzhiyun
281*4882a593Smuzhiyunclass PRData(object):
282*4882a593Smuzhiyun    """Object representing the PR database"""
283*4882a593Smuzhiyun    def __init__(self, filename, nohist=True, read_only=False):
284*4882a593Smuzhiyun        self.filename=os.path.abspath(filename)
285*4882a593Smuzhiyun        self.nohist=nohist
286*4882a593Smuzhiyun        self.read_only = read_only
287*4882a593Smuzhiyun        #build directory hierarchy
288*4882a593Smuzhiyun        try:
289*4882a593Smuzhiyun            os.makedirs(os.path.dirname(self.filename))
290*4882a593Smuzhiyun        except OSError as e:
291*4882a593Smuzhiyun            if e.errno != errno.EEXIST:
292*4882a593Smuzhiyun                raise e
293*4882a593Smuzhiyun        uri = "file:%s%s" % (self.filename, "?mode=ro" if self.read_only else "")
294*4882a593Smuzhiyun        logger.debug("Opening PRServ database '%s'" % (uri))
295*4882a593Smuzhiyun        self.connection=sqlite3.connect(uri, uri=True, isolation_level="EXCLUSIVE", check_same_thread = False)
296*4882a593Smuzhiyun        self.connection.row_factory=sqlite3.Row
297*4882a593Smuzhiyun        if not self.read_only:
298*4882a593Smuzhiyun            self.connection.execute("pragma synchronous = off;")
299*4882a593Smuzhiyun            self.connection.execute("PRAGMA journal_mode = MEMORY;")
300*4882a593Smuzhiyun        self._tables={}
301*4882a593Smuzhiyun
302*4882a593Smuzhiyun    def disconnect(self):
303*4882a593Smuzhiyun        self.connection.close()
304*4882a593Smuzhiyun
305*4882a593Smuzhiyun    def __getitem__(self,tblname):
306*4882a593Smuzhiyun        if not isinstance(tblname, str):
307*4882a593Smuzhiyun            raise TypeError("tblname argument must be a string, not '%s'" %
308*4882a593Smuzhiyun                            type(tblname))
309*4882a593Smuzhiyun        if tblname in self._tables:
310*4882a593Smuzhiyun            return self._tables[tblname]
311*4882a593Smuzhiyun        else:
312*4882a593Smuzhiyun            tableobj = self._tables[tblname] = PRTable(self.connection, tblname, self.nohist, self.read_only)
313*4882a593Smuzhiyun            return tableobj
314*4882a593Smuzhiyun
315*4882a593Smuzhiyun    def __delitem__(self, tblname):
316*4882a593Smuzhiyun        if tblname in self._tables:
317*4882a593Smuzhiyun            del self._tables[tblname]
318*4882a593Smuzhiyun        logger.info("drop table %s" % (tblname))
319*4882a593Smuzhiyun        self.connection.execute("DROP TABLE IF EXISTS %s;" % tblname)
320