| # |
| # SPDX-License-Identifier: GPL-2.0-only |
| # |
| |
| import logging |
| import os.path |
| import errno |
| import prserv |
| import time |
| |
| try: |
| import sqlite3 |
| except ImportError: |
| from pysqlite2 import dbapi2 as sqlite3 |
| |
| logger = logging.getLogger("BitBake.PRserv") |
| |
| sqlversion = sqlite3.sqlite_version_info |
| if sqlversion[0] < 3 or (sqlversion[0] == 3 and sqlversion[1] < 3): |
| raise Exception("sqlite3 version 3.3.0 or later is required.") |
| |
| # |
| # "No History" mode - for a given query tuple (version, pkgarch, checksum), |
| # the returned value will be the largest among all the values of the same |
| # (version, pkgarch). This means the PR value returned can NOT be decremented. |
| # |
| # "History" mode - Return a new higher value for previously unseen query |
| # tuple (version, pkgarch, checksum), otherwise return historical value. |
| # Value can decrement if returning to a previous build. |
| # |
| |
| class PRTable(object): |
| def __init__(self, conn, table, nohist): |
| self.conn = conn |
| self.nohist = nohist |
| self.dirty = False |
| if nohist: |
| self.table = "%s_nohist" % table |
| else: |
| self.table = "%s_hist" % table |
| |
| self._execute("CREATE TABLE IF NOT EXISTS %s \ |
| (version TEXT NOT NULL, \ |
| pkgarch TEXT NOT NULL, \ |
| checksum TEXT NOT NULL, \ |
| value INTEGER, \ |
| PRIMARY KEY (version, pkgarch, checksum));" % self.table) |
| |
| def _execute(self, *query): |
| """Execute a query, waiting to acquire a lock if necessary""" |
| start = time.time() |
| end = start + 20 |
| while True: |
| try: |
| return self.conn.execute(*query) |
| except sqlite3.OperationalError as exc: |
| if 'is locked' in str(exc) and end > time.time(): |
| continue |
| raise exc |
| |
| def sync(self): |
| self.conn.commit() |
| self._execute("BEGIN EXCLUSIVE TRANSACTION") |
| |
| def sync_if_dirty(self): |
| if self.dirty: |
| self.sync() |
| self.dirty = False |
| |
| def _getValueHist(self, version, pkgarch, checksum): |
| data=self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table, |
| (version, pkgarch, checksum)) |
| row=data.fetchone() |
| if row != None: |
| return row[0] |
| else: |
| #no value found, try to insert |
| try: |
| self._execute("INSERT INTO %s VALUES (?, ?, ?, (select ifnull(max(value)+1,0) from %s where version=? AND pkgarch=?));" |
| % (self.table,self.table), |
| (version,pkgarch, checksum,version, pkgarch)) |
| except sqlite3.IntegrityError as exc: |
| logger.error(str(exc)) |
| |
| self.dirty = True |
| |
| data=self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table, |
| (version, pkgarch, checksum)) |
| row=data.fetchone() |
| if row != None: |
| return row[0] |
| else: |
| raise prserv.NotFoundError |
| |
| def _getValueNohist(self, version, pkgarch, checksum): |
| data=self._execute("SELECT value FROM %s \ |
| WHERE version=? AND pkgarch=? AND checksum=? AND \ |
| value >= (select max(value) from %s where version=? AND pkgarch=?);" |
| % (self.table, self.table), |
| (version, pkgarch, checksum, version, pkgarch)) |
| row=data.fetchone() |
| if row != None: |
| return row[0] |
| else: |
| #no value found, try to insert |
| try: |
| self._execute("INSERT OR REPLACE INTO %s VALUES (?, ?, ?, (select ifnull(max(value)+1,0) from %s where version=? AND pkgarch=?));" |
| % (self.table,self.table), |
| (version, pkgarch, checksum, version, pkgarch)) |
| except sqlite3.IntegrityError as exc: |
| logger.error(str(exc)) |
| self.conn.rollback() |
| |
| self.dirty = True |
| |
| data=self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table, |
| (version, pkgarch, checksum)) |
| row=data.fetchone() |
| if row != None: |
| return row[0] |
| else: |
| raise prserv.NotFoundError |
| |
| def getValue(self, version, pkgarch, checksum): |
| if self.nohist: |
| return self._getValueNohist(version, pkgarch, checksum) |
| else: |
| return self._getValueHist(version, pkgarch, checksum) |
| |
| def _importHist(self, version, pkgarch, checksum, value): |
| val = None |
| data = self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table, |
| (version, pkgarch, checksum)) |
| row = data.fetchone() |
| if row != None: |
| val=row[0] |
| else: |
| #no value found, try to insert |
| try: |
| self._execute("INSERT INTO %s VALUES (?, ?, ?, ?);" % (self.table), |
| (version, pkgarch, checksum, value)) |
| except sqlite3.IntegrityError as exc: |
| logger.error(str(exc)) |
| |
| self.dirty = True |
| |
| data = self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=?;" % self.table, |
| (version, pkgarch, checksum)) |
| row = data.fetchone() |
| if row != None: |
| val = row[0] |
| return val |
| |
| def _importNohist(self, version, pkgarch, checksum, value): |
| try: |
| #try to insert |
| self._execute("INSERT INTO %s VALUES (?, ?, ?, ?);" % (self.table), |
| (version, pkgarch, checksum,value)) |
| except sqlite3.IntegrityError as exc: |
| #already have the record, try to update |
| try: |
| self._execute("UPDATE %s SET value=? WHERE version=? AND pkgarch=? AND checksum=? AND value<?" |
| % (self.table), |
| (value,version,pkgarch,checksum,value)) |
| except sqlite3.IntegrityError as exc: |
| logger.error(str(exc)) |
| |
| self.dirty = True |
| |
| data = self._execute("SELECT value FROM %s WHERE version=? AND pkgarch=? AND checksum=? AND value>=?;" % self.table, |
| (version,pkgarch,checksum,value)) |
| row=data.fetchone() |
| if row != None: |
| return row[0] |
| else: |
| return None |
| |
| def importone(self, version, pkgarch, checksum, value): |
| if self.nohist: |
| return self._importNohist(version, pkgarch, checksum, value) |
| else: |
| return self._importHist(version, pkgarch, checksum, value) |
| |
| def export(self, version, pkgarch, checksum, colinfo): |
| metainfo = {} |
| #column info |
| if colinfo: |
| metainfo['tbl_name'] = self.table |
| metainfo['core_ver'] = prserv.__version__ |
| metainfo['col_info'] = [] |
| data = self._execute("PRAGMA table_info(%s);" % self.table) |
| for row in data: |
| col = {} |
| col['name'] = row['name'] |
| col['type'] = row['type'] |
| col['notnull'] = row['notnull'] |
| col['dflt_value'] = row['dflt_value'] |
| col['pk'] = row['pk'] |
| metainfo['col_info'].append(col) |
| |
| #data info |
| datainfo = [] |
| |
| if self.nohist: |
| sqlstmt = "SELECT T1.version, T1.pkgarch, T1.checksum, T1.value FROM %s as T1, \ |
| (SELECT version,pkgarch,max(value) as maxvalue FROM %s GROUP BY version,pkgarch) as T2 \ |
| WHERE T1.version=T2.version AND T1.pkgarch=T2.pkgarch AND T1.value=T2.maxvalue " % (self.table, self.table) |
| else: |
| sqlstmt = "SELECT * FROM %s as T1 WHERE 1=1 " % self.table |
| sqlarg = [] |
| where = "" |
| if version: |
| where += "AND T1.version=? " |
| sqlarg.append(str(version)) |
| if pkgarch: |
| where += "AND T1.pkgarch=? " |
| sqlarg.append(str(pkgarch)) |
| if checksum: |
| where += "AND T1.checksum=? " |
| sqlarg.append(str(checksum)) |
| |
| sqlstmt += where + ";" |
| |
| if len(sqlarg): |
| data = self._execute(sqlstmt, tuple(sqlarg)) |
| else: |
| data = self._execute(sqlstmt) |
| for row in data: |
| if row['version']: |
| col = {} |
| col['version'] = row['version'] |
| col['pkgarch'] = row['pkgarch'] |
| col['checksum'] = row['checksum'] |
| col['value'] = row['value'] |
| datainfo.append(col) |
| return (metainfo, datainfo) |
| |
| def dump_db(self, fd): |
| writeCount = 0 |
| for line in self.conn.iterdump(): |
| writeCount = writeCount + len(line) + 1 |
| fd.write(line) |
| fd.write('\n') |
| return writeCount |
| |
| class PRData(object): |
| """Object representing the PR database""" |
| def __init__(self, filename, nohist=True): |
| self.filename=os.path.abspath(filename) |
| self.nohist=nohist |
| #build directory hierarchy |
| try: |
| os.makedirs(os.path.dirname(self.filename)) |
| except OSError as e: |
| if e.errno != errno.EEXIST: |
| raise e |
| self.connection=sqlite3.connect(self.filename, isolation_level="EXCLUSIVE", check_same_thread = False) |
| self.connection.row_factory=sqlite3.Row |
| self.connection.execute("pragma synchronous = off;") |
| self.connection.execute("PRAGMA journal_mode = WAL;") |
| self._tables={} |
| |
| def disconnect(self): |
| self.connection.close() |
| |
| def __getitem__(self,tblname): |
| if not isinstance(tblname, str): |
| raise TypeError("tblname argument must be a string, not '%s'" % |
| type(tblname)) |
| if tblname in self._tables: |
| return self._tables[tblname] |
| else: |
| tableobj = self._tables[tblname] = PRTable(self.connection, tblname, self.nohist) |
| return tableobj |
| |
| def __delitem__(self, tblname): |
| if tblname in self._tables: |
| del self._tables[tblname] |
| logger.info("drop table %s" % (tblname)) |
| self.connection.execute("DROP TABLE IF EXISTS %s;" % tblname) |