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