123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301 |
- #
- # Copyright BitBake Contributors
- #
- # SPDX-License-Identifier: GPL-2.0-only
- #
- import logging
- import os.path
- import errno
- import prserv
- import sqlite3
- from contextlib import closing
- from . import increase_revision, revision_greater, revision_smaller
- logger = logging.getLogger("BitBake.PRserv")
- #
- # "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, read_only):
- self.conn = conn
- self.read_only = read_only
- self.table = table
- # Creating the table even if the server is read-only.
- # This avoids a race condition if a shared database
- # is accessed by a read-only server first.
- with closing(self.conn.cursor()) as cursor:
- cursor.execute("CREATE TABLE IF NOT EXISTS %s \
- (version TEXT NOT NULL, \
- pkgarch TEXT NOT NULL, \
- checksum TEXT NOT NULL, \
- value TEXT, \
- PRIMARY KEY (version, pkgarch, checksum, value));" % self.table)
- self.conn.commit()
- def _extremum_value(self, rows, is_max):
- value = None
- for row in rows:
- current_value = row[0]
- if value is None:
- value = current_value
- else:
- if is_max:
- is_new_extremum = revision_greater(current_value, value)
- else:
- is_new_extremum = revision_smaller(current_value, value)
- if is_new_extremum:
- value = current_value
- return value
- def _max_value(self, rows):
- return self._extremum_value(rows, True)
- def _min_value(self, rows):
- return self._extremum_value(rows, False)
- def test_package(self, version, pkgarch):
- """Returns whether the specified package version is found in the database for the specified architecture"""
- # Just returns the value if found or None otherwise
- with closing(self.conn.cursor()) as cursor:
- data=cursor.execute("SELECT value FROM %s WHERE version=? AND pkgarch=?;" % self.table,
- (version, pkgarch))
- row=data.fetchone()
- if row is not None:
- return True
- else:
- return False
- def test_checksum_value(self, version, pkgarch, checksum, value):
- """Returns whether the specified value is found in the database for the specified package, architecture and checksum"""
- with closing(self.conn.cursor()) as cursor:
- data=cursor.execute("SELECT value FROM %s WHERE version=? AND pkgarch=? and checksum=? and value=?;" % self.table,
- (version, pkgarch, checksum, value))
- row=data.fetchone()
- if row is not None:
- return True
- else:
- return False
- def test_value(self, version, pkgarch, value):
- """Returns whether the specified value is found in the database for the specified package and architecture"""
- # Just returns the value if found or None otherwise
- with closing(self.conn.cursor()) as cursor:
- data=cursor.execute("SELECT value FROM %s WHERE version=? AND pkgarch=? and value=?;" % self.table,
- (version, pkgarch, value))
- row=data.fetchone()
- if row is not None:
- return True
- else:
- return False
- def find_package_max_value(self, version, pkgarch):
- """Returns the greatest value for (version, pkgarch), or None if not found. Doesn't create a new value"""
- with closing(self.conn.cursor()) as cursor:
- data = cursor.execute("SELECT value FROM %s where version=? AND pkgarch=?;" % (self.table),
- (version, pkgarch))
- rows = data.fetchall()
- value = self._max_value(rows)
- return value
- def find_value(self, version, pkgarch, checksum, history=False):
- """Returns the value for the specified checksum if found or None otherwise."""
- if history:
- return self.find_min_value(version, pkgarch, checksum)
- else:
- return self.find_max_value(version, pkgarch, checksum)
- def _find_extremum_value(self, version, pkgarch, checksum, is_max):
- """Returns the maximum (if is_max is True) or minimum (if is_max is False) value
- for (version, pkgarch, checksum), or None if not found. Doesn't create a new value"""
- with closing(self.conn.cursor()) as cursor:
- data = cursor.execute("SELECT value FROM %s where version=? AND pkgarch=? AND checksum=?;" % (self.table),
- (version, pkgarch, checksum))
- rows = data.fetchall()
- return self._extremum_value(rows, is_max)
- def find_max_value(self, version, pkgarch, checksum):
- return self._find_extremum_value(version, pkgarch, checksum, True)
- def find_min_value(self, version, pkgarch, checksum):
- return self._find_extremum_value(version, pkgarch, checksum, False)
- def find_new_subvalue(self, version, pkgarch, base):
- """Take and increase the greatest "<base>.y" value for (version, pkgarch), or return "<base>.0" if not found.
- This doesn't store a new value."""
- with closing(self.conn.cursor()) as cursor:
- data = cursor.execute("SELECT value FROM %s where version=? AND pkgarch=? AND value LIKE '%s.%%';" % (self.table, base),
- (version, pkgarch))
- rows = data.fetchall()
- value = self._max_value(rows)
- if value is not None:
- return increase_revision(value)
- else:
- return base + ".0"
- def store_value(self, version, pkgarch, checksum, value):
- """Store value in the database"""
- if not self.read_only and not self.test_checksum_value(version, pkgarch, checksum, value):
- with closing(self.conn.cursor()) as cursor:
- cursor.execute("INSERT INTO %s VALUES (?, ?, ?, ?);" % (self.table),
- (version, pkgarch, checksum, value))
- self.conn.commit()
- def _get_value(self, version, pkgarch, checksum, history):
- max_value = self.find_package_max_value(version, pkgarch)
- if max_value is None:
- # version, pkgarch completely unknown. Return initial value.
- return "0"
- value = self.find_value(version, pkgarch, checksum, history)
- if value is None:
- # version, pkgarch found but not checksum. Create a new value from the maximum one
- return increase_revision(max_value)
- if history:
- return value
- # "no history" mode - If the value is not the maximum value for the package, need to increase it.
- if max_value > value:
- return increase_revision(max_value)
- else:
- return value
- def get_value(self, version, pkgarch, checksum, history):
- value = self._get_value(version, pkgarch, checksum, history)
- if not self.read_only:
- self.store_value(version, pkgarch, checksum, value)
- return value
- def importone(self, version, pkgarch, checksum, value):
- self.store_value(version, pkgarch, checksum, value)
- return value
- def export(self, version, pkgarch, checksum, colinfo, history=False):
- metainfo = {}
- with closing(self.conn.cursor()) as cursor:
- #column info
- if colinfo:
- metainfo["tbl_name"] = self.table
- metainfo["core_ver"] = prserv.__version__
- metainfo["col_info"] = []
- data = cursor.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 history:
- sqlstmt = "SELECT * FROM %s as T1 WHERE 1=1 " % self.table
- else:
- 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)
- 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 = cursor.execute(sqlstmt, tuple(sqlarg))
- else:
- data = cursor.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, read_only=False):
- self.filename=os.path.abspath(filename)
- self.read_only = read_only
- #build directory hierarchy
- try:
- os.makedirs(os.path.dirname(self.filename))
- except OSError as e:
- if e.errno != errno.EEXIST:
- raise e
- uri = "file:%s%s" % (self.filename, "?mode=ro" if self.read_only else "")
- logger.debug("Opening PRServ database '%s'" % (uri))
- self.connection=sqlite3.connect(uri, uri=True)
- self.connection.row_factory=sqlite3.Row
- self.connection.execute("PRAGMA synchronous = OFF;")
- self.connection.execute("PRAGMA journal_mode = WAL;")
- self.connection.commit()
- self._tables={}
- def disconnect(self):
- self.connection.commit()
- 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.read_only)
- 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)
- self.connection.commit()
|