Package yum :: Module history
[hide private]
[frames] | no frames]

Source Code for Module yum.history

  1  #!/usr/bin/python -t 
  2  # This program is free software; you can redistribute it and/or modify 
  3  # it under the terms of the GNU General Public License as published by 
  4  # the Free Software Foundation; either version 2 of the License, or 
  5  # (at your option) any later version. 
  6  # 
  7  # This program is distributed in the hope that it will be useful, 
  8  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
  9  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 10  # GNU Library General Public License for more details. 
 11  # 
 12  # You should have received a copy of the GNU General Public License 
 13  # along with this program; if not, write to the Free Software 
 14  # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 
 15  # 
 16  # Copyright 2009 Red Hat 
 17  # 
 18  # James Antill <james@fedoraproject.org> 
 19   
 20  import time 
 21  import os, os.path 
 22  import glob 
 23  from weakref import proxy as weakref 
 24   
 25  from sqlutils import sqlite, executeSQL, sql_esc, sql_esc_glob 
 26  import yum.misc as misc 
 27  import yum.constants 
 28  from yum.constants import * 
 29  from yum.packages import YumInstalledPackage, YumAvailablePackage, PackageObject 
 30  from yum.i18n import to_unicode 
 31   
 32   
 33  _history_dir = '/var/lib/yum/history' 
 34   
 35  # NOTE: That we don't list TS_FAILED, because pkgs shouldn't go into the 
 36  #       transaction with that. And if they come out with that we don't want to 
 37  #       match them to anything anyway. 
 38  _stcode2sttxt = {TS_UPDATE : 'Update', 
 39                   TS_UPDATED : 'Updated',  
 40                   TS_ERASE: 'Erase', 
 41                   TS_INSTALL: 'Install',  
 42                   TS_TRUEINSTALL : 'True-Install', 
 43                   TS_OBSOLETED: 'Obsoleted', 
 44                   TS_OBSOLETING: 'Obsoleting'} 
 45   
 46  _sttxt2stcode = {'Update' : TS_UPDATE, 
 47                   'Updated' : TS_UPDATED,  
 48                   'Erase' : TS_ERASE, 
 49                   'Install' : TS_INSTALL,  
 50                   'True-Install' : TS_TRUEINSTALL, 
 51                   'Dep-Install' : TS_INSTALL, 
 52                   'Reinstall' : TS_INSTALL, # Broken 
 53                   'Downgrade' : TS_INSTALL, # Broken 
 54                   'Downgraded' : TS_INSTALL, # Broken 
 55                   'Obsoleted' : TS_OBSOLETED, 
 56                   'Obsoleting' : TS_OBSOLETING} 
57 58 # ---- horrible Copy and paste from sqlitesack ---- 59 60 -def _setupHistorySearchSQL(patterns=None, ignore_case=False):
61 """Setup need_full and patterns for _yieldSQLDataList, also see if 62 we can get away with just using searchNames(). """ 63 64 if patterns is None: 65 patterns = [] 66 67 fields = ['name', 'sql_nameArch', 'sql_nameVerRelArch', 68 'sql_nameVer', 'sql_nameVerRel', 69 'sql_envra', 'sql_nevra'] 70 need_full = False 71 for pat in patterns: 72 if yum.misc.re_full_search_needed(pat): 73 need_full = True 74 break 75 76 pat_max = PATTERNS_MAX 77 if not need_full: 78 fields = ['name'] 79 pat_max = PATTERNS_INDEXED_MAX 80 if len(patterns) > pat_max: 81 patterns = [] 82 if ignore_case: 83 patterns = sql_esc_glob(patterns) 84 else: 85 tmp = [] 86 need_glob = False 87 for pat in patterns: 88 if misc.re_glob(pat): 89 tmp.append((pat, 'glob')) 90 need_glob = True 91 else: 92 tmp.append((pat, '=')) 93 if not need_full and not need_glob and patterns: 94 return (need_full, patterns, fields, True) 95 patterns = tmp 96 return (need_full, patterns, fields, False)
97 # ---- horrible Copy and paste from sqlitesack ----
98 99 -class YumHistoryPackage(PackageObject):
100
101 - def __init__(self, name, arch, epoch, version, release, checksum):
102 self.name = name 103 self.version = version 104 self.release = release 105 self.epoch = epoch 106 self.arch = arch 107 self.pkgtup = (self.name, self.arch, 108 self.epoch, self.version, self.release) 109 if checksum is None: 110 self._checksums = [] # (type, checksum, id(0,1) 111 else: 112 chk = checksum.split(':') 113 self._checksums = [(chk[0], chk[1], 0)] # (type, checksum, id(0,1))
114
115 -class YumHistoryTransaction:
116 """ Holder for a history transaction. """ 117
118 - def __init__(self, history, row):
119 self._history = weakref(history) 120 121 self.tid = row[0] 122 self.beg_timestamp = row[1] 123 self.beg_rpmdbversion = row[2] 124 self.end_timestamp = row[3] 125 self.end_rpmdbversion = row[4] 126 self.loginuid = row[5] 127 self.return_code = row[6] 128 129 self._loaded_TW = None 130 self._loaded_TD = None 131 132 self._loaded_ER = None 133 self._loaded_OT = None 134 135 self.altered_lt_rpmdb = None 136 self.altered_gt_rpmdb = None
137
138 - def __cmp__(self, other):
139 if other is None: 140 return 1 141 ret = cmp(self.beg_timestamp, other.beg_timestamp) 142 if ret: return -ret 143 ret = cmp(self.end_timestamp, other.end_timestamp) 144 if ret: return ret 145 ret = cmp(self.tid, other.tid) 146 return -ret
147
148 - def _getTransWith(self):
149 if self._loaded_TW is None: 150 self._loaded_TW = sorted(self._history._old_with_pkgs(self.tid)) 151 return self._loaded_TW
152 - def _getTransData(self):
153 if self._loaded_TD is None: 154 self._loaded_TD = sorted(self._history._old_data_pkgs(self.tid)) 155 return self._loaded_TD
156 157 trans_with = property(fget=lambda self: self._getTransWith()) 158 trans_data = property(fget=lambda self: self._getTransData()) 159
160 - def _getErrors(self):
161 if self._loaded_ER is None: 162 self._loaded_ER = self._history._load_errors(self.tid) 163 return self._loaded_ER
164 - def _getOutput(self):
165 if self._loaded_OT is None: 166 self._loaded_OT = self._history._load_output(self.tid) 167 return self._loaded_OT
168 169 errors = property(fget=lambda self: self._getErrors()) 170 output = property(fget=lambda self: self._getOutput())
171
172 -class YumHistory:
173 """ API for accessing the history sqlite data. """ 174
175 - def __init__(self, root='/', db_path=_history_dir):
176 self._conn = None 177 178 self.conf = yum.misc.GenericHolder() 179 self.conf.db_path = os.path.normpath(root + '/' + db_path) 180 self.conf.writable = False 181 182 if not os.path.exists(self.conf.db_path): 183 try: 184 os.makedirs(self.conf.db_path) 185 except (IOError, OSError), e: 186 # some sort of useful thing here? A warning? 187 return 188 self.conf.writable = True 189 else: 190 if os.access(self.conf.db_path, os.W_OK): 191 self.conf.writable = True 192 193 DBs = glob.glob('%s/history-*-*-*.sqlite' % self.conf.db_path) 194 self._db_file = None 195 for d in reversed(sorted(DBs)): 196 fname = os.path.basename(d) 197 fname = fname[len("history-"):-len(".sqlite")] 198 pieces = fname.split('-', 4) 199 if len(pieces) != 3: 200 continue 201 try: 202 map(int, pieces) 203 except ValueError: 204 continue 205 206 self._db_file = d 207 break 208 209 if self._db_file is None: 210 self._create_db_file()
211
212 - def __del__(self):
213 self.close()
214
215 - def _get_cursor(self):
216 if self._conn is None: 217 self._conn = sqlite.connect(self._db_file) 218 return self._conn.cursor()
219 - def _commit(self):
220 return self._conn.commit()
221
222 - def close(self):
223 if self._conn is not None: 224 self._conn.close() 225 self._conn = None
226
227 - def _pkgtup2pid(self, pkgtup, checksum=None):
228 cur = self._get_cursor() 229 executeSQL(cur, """SELECT pkgtupid, checksum FROM pkgtups 230 WHERE name=? AND arch=? AND 231 epoch=? AND version=? AND release=?""", pkgtup) 232 for sql_pkgtupid, sql_checksum in cur: 233 if checksum is None and sql_checksum is None: 234 return sql_pkgtupid 235 if checksum is None: 236 continue 237 if sql_checksum is None: 238 continue 239 if checksum == sql_checksum: 240 return sql_pkgtupid 241 242 (n,a,e,v,r) = pkgtup 243 (n,a,e,v,r) = (to_unicode(n),to_unicode(a), 244 to_unicode(e),to_unicode(v),to_unicode(r)) 245 if checksum is not None: 246 res = executeSQL(cur, 247 """INSERT INTO pkgtups 248 (name, arch, epoch, version, release, checksum) 249 VALUES (?, ?, ?, ?, ?, ?)""", (n,a,e,v,r, 250 checksum)) 251 else: 252 res = executeSQL(cur, 253 """INSERT INTO pkgtups 254 (name, arch, epoch, version, release) 255 VALUES (?, ?, ?, ?, ?)""", (n,a,e,v,r)) 256 return cur.lastrowid
257 - def _apkg2pid(self, po):
258 csum = po.returnIdSum() 259 if csum is not None: 260 csum = "%s:%s" % (str(csum[0]), str(csum[1])) 261 return self._pkgtup2pid(po.pkgtup, csum)
262 - def _ipkg2pid(self, po):
263 csum = None 264 yumdb = po.yumdb_info 265 if 'checksum_type' in yumdb and 'checksum_data' in yumdb: 266 csum = "%s:%s" % (yumdb.checksum_type, yumdb.checksum_data) 267 return self._pkgtup2pid(po.pkgtup, csum)
268 - def pkg2pid(self, po):
269 if isinstance(po, YumInstalledPackage): 270 return self._ipkg2pid(po) 271 if isinstance(po, YumAvailablePackage): 272 return self._apkg2pid(po) 273 return self._pkgtup2pid(po.pkgtup, None)
274 275 @staticmethod
276 - def txmbr2state(txmbr):
277 state = None 278 if txmbr.output_state in (TS_INSTALL, TS_TRUEINSTALL): 279 if hasattr(txmbr, 'reinstall'): 280 state = 'Reinstall' 281 elif txmbr.downgrades: 282 state = 'Downgrade' 283 if txmbr.output_state == TS_ERASE: 284 if txmbr.downgraded_by: 285 state = 'Downgraded' 286 if state is None: 287 state = _stcode2sttxt.get(txmbr.output_state) 288 if state == 'Install' and txmbr.isDep: 289 state = 'Dep-Install' 290 return state
291
292 - def trans_with_pid(self, pid):
293 cur = self._get_cursor() 294 res = executeSQL(cur, 295 """INSERT INTO trans_with_pkgs 296 (tid, pkgtupid) 297 VALUES (?, ?)""", (self._tid, pid)) 298 return cur.lastrowid
299
300 - def trans_data_pid_beg(self, pid, state):
301 assert state is not None 302 if not hasattr(self, '_tid') or state is None: 303 return # Not configured to run 304 cur = self._get_cursor() 305 res = executeSQL(cur, 306 """INSERT INTO trans_data_pkgs 307 (tid, pkgtupid, state) 308 VALUES (?, ?, ?)""", (self._tid, pid, state)) 309 return cur.lastrowid
310 - def trans_data_pid_end(self, pid, state):
311 # State can be none here, Eg. TS_FAILED from rpmtrans 312 if not hasattr(self, '_tid') or state is None: 313 return # Not configured to run 314 315 cur = self._get_cursor() 316 res = executeSQL(cur, 317 """UPDATE trans_data_pkgs SET done = ? 318 WHERE tid = ? AND pkgtupid = ? AND state = ? 319 """, ('TRUE', self._tid, pid, state)) 320 self._commit() 321 return cur.lastrowid
322
323 - def beg(self, rpmdb_version, using_pkgs, txmbrs):
324 cur = self._get_cursor() 325 res = executeSQL(cur, 326 """INSERT INTO trans_beg 327 (timestamp, rpmdb_version, loginuid) 328 VALUES (?, ?, ?)""", (int(time.time()), 329 str(rpmdb_version), 330 yum.misc.getloginuid())) 331 self._tid = cur.lastrowid 332 333 for pkg in using_pkgs: 334 pid = self._ipkg2pid(pkg) 335 self.trans_with_pid(pid) 336 337 for txmbr in txmbrs: 338 pid = self.pkg2pid(txmbr.po) 339 state = self.txmbr2state(txmbr) 340 self.trans_data_pid_beg(pid, state) 341 342 self._commit()
343
344 - def _log_errors(self, errors):
345 cur = self._get_cursor() 346 for error in errors: 347 error = to_unicode(error) 348 executeSQL(cur, 349 """INSERT INTO trans_error 350 (tid, msg) VALUES (?, ?)""", (self._tid, error)) 351 self._commit()
352
353 - def log_scriptlet_output(self, data, msg):
354 """ Note that data can be either a real pkg. ... or not. """ 355 if msg is None or not hasattr(self, '_tid'): 356 return # Not configured to run 357 358 cur = self._get_cursor() 359 for error in msg.split('\n'): 360 error = to_unicode(error) 361 executeSQL(cur, 362 """INSERT INTO trans_script_stdout 363 (tid, line) VALUES (?, ?)""", (self._tid, error)) 364 self._commit()
365
366 - def _load_errors(self, tid):
367 cur = self._get_cursor() 368 executeSQL(cur, 369 """SELECT msg FROM trans_error 370 WHERE tid = ? 371 ORDER BY mid ASC""", (tid,)) 372 ret = [] 373 for row in cur: 374 ret.append(row[0]) 375 return ret
376
377 - def _load_output(self, tid):
378 cur = self._get_cursor() 379 executeSQL(cur, 380 """SELECT line FROM trans_script_stdout 381 WHERE tid = ? 382 ORDER BY lid ASC""", (tid,)) 383 ret = [] 384 for row in cur: 385 ret.append(row[0]) 386 return ret
387
388 - def end(self, rpmdb_version, return_code, errors=None):
389 assert return_code or not errors 390 cur = self._get_cursor() 391 res = executeSQL(cur, 392 """INSERT INTO trans_end 393 (tid, timestamp, rpmdb_version, return_code) 394 VALUES (?, ?, ?, ?)""", (self._tid,int(time.time()), 395 str(rpmdb_version), 396 return_code)) 397 self._commit() 398 if not return_code: 399 # Simple hack, if the transaction finished. Note that this 400 # catches the erase cases (as we still don't get pkgtups for them), 401 # Eg. Updated elements. 402 executeSQL(cur, 403 """UPDATE trans_data_pkgs SET done = ? 404 WHERE tid = ?""", ('TRUE', self._tid,)) 405 self._commit() 406 if errors is not None: 407 self._log_errors(errors) 408 del self._tid
409
410 - def _old_with_pkgs(self, tid):
411 cur = self._get_cursor() 412 executeSQL(cur, 413 """SELECT name, arch, epoch, version, release, checksum 414 FROM trans_with_pkgs JOIN pkgtups USING(pkgtupid) 415 WHERE tid = ? 416 ORDER BY name ASC, epoch ASC""", (tid,)) 417 ret = [] 418 for row in cur: 419 obj = YumHistoryPackage(row[0],row[1],row[2],row[3],row[4], row[5]) 420 ret.append(obj) 421 return ret
422 - def _old_data_pkgs(self, tid):
423 cur = self._get_cursor() 424 executeSQL(cur, 425 """SELECT name, arch, epoch, version, release, 426 checksum, done, state 427 FROM trans_data_pkgs JOIN pkgtups USING(pkgtupid) 428 WHERE tid = ? 429 ORDER BY name ASC, epoch ASC, state DESC""", (tid,)) 430 ret = [] 431 for row in cur: 432 obj = YumHistoryPackage(row[0],row[1],row[2],row[3],row[4], row[5]) 433 obj.done = row[6] == 'TRUE' 434 obj.state = row[7] 435 obj.state_installed = None 436 if _sttxt2stcode[obj.state] in TS_INSTALL_STATES: 437 obj.state_installed = True 438 if _sttxt2stcode[obj.state] in TS_REMOVE_STATES: 439 obj.state_installed = False 440 ret.append(obj) 441 return ret
442
443 - def old(self, tids=[], limit=None, complete_transactions_only=False):
444 """ Return a list of the last transactions, note that this includes 445 partial transactions (ones without an end transaction). """ 446 cur = self._get_cursor() 447 sql = """SELECT tid, 448 trans_beg.timestamp AS beg_ts, 449 trans_beg.rpmdb_version AS beg_rv, 450 trans_end.timestamp AS end_ts, 451 trans_end.rpmdb_version AS end_rv, 452 loginuid, return_code 453 FROM trans_beg JOIN trans_end USING(tid)""" 454 # NOTE: sqlite doesn't do OUTER JOINs ... *sigh*. So we have to do it 455 # ourself. 456 if not complete_transactions_only: 457 sql = """SELECT tid, 458 trans_beg.timestamp AS beg_ts, 459 trans_beg.rpmdb_version AS beg_rv, 460 NULL, NULL, 461 loginuid, NULL 462 FROM trans_beg""" 463 params = None 464 if tids and len(tids) <= yum.constants.PATTERNS_INDEXED_MAX: 465 params = tids = list(set(tids)) 466 sql += " WHERE tid IN (%s)" % ", ".join(['?'] * len(tids)) 467 sql += " ORDER BY beg_ts DESC, tid ASC" 468 if limit is not None: 469 sql += " LIMIT " + str(limit) 470 executeSQL(cur, sql, params) 471 ret = [] 472 tid2obj = {} 473 for row in cur: 474 if tids and len(tids) > yum.constants.PATTERNS_INDEXED_MAX: 475 if row[0] not in tids: 476 continue 477 obj = YumHistoryTransaction(self, row) 478 tid2obj[row[0]] = obj 479 ret.append(obj) 480 481 sql = """SELECT tid, 482 trans_end.timestamp AS end_ts, 483 trans_end.rpmdb_version AS end_rv, 484 return_code 485 FROM trans_end""" 486 params = tid2obj.keys() 487 if len(params) > yum.constants.PATTERNS_INDEXED_MAX: 488 executeSQL(cur, sql) 489 else: 490 sql += " WHERE tid IN (%s)" % ", ".join(['?'] * len(params)) 491 executeSQL(cur, sql, params) 492 for row in cur: 493 if row[0] not in tid2obj: 494 continue 495 tid2obj[row[0]].end_timestamp = row[1] 496 tid2obj[row[0]].end_rpmdbversion = row[2] 497 tid2obj[row[0]].return_code = row[3] 498 499 # Go through backwards, and see if the rpmdb versions match 500 las = None 501 for obj in reversed(ret): 502 cur_rv = obj.beg_rpmdbversion 503 las_rv = None 504 if las is not None: 505 las_rv = las.end_rpmdbversion 506 if las_rv is None or cur_rv is None or (las.tid + 1) != obj.tid: 507 pass 508 elif las_rv != cur_rv: 509 obj.altered_lt_rpmdb = True 510 las.altered_gt_rpmdb = True 511 else: 512 obj.altered_lt_rpmdb = False 513 las.altered_gt_rpmdb = False 514 las = obj 515 516 return ret
517
518 - def last(self, complete_transactions_only=True):
519 """ This is the last full transaction. So any incomplete transactions 520 do not count, by default. """ 521 ret = self.old([], 1, complete_transactions_only) 522 if not ret: 523 return None 524 assert len(ret) == 1 525 return ret[0]
526
527 - def _yieldSQLDataList(self, patterns, fields, ignore_case):
528 """Yields all the package data for the given params. """ 529 530 cur = self._get_cursor() 531 qsql = _FULL_PARSE_QUERY_BEG 532 533 pat_sqls = [] 534 pat_data = [] 535 for (pattern, rest) in patterns: 536 for field in fields: 537 if ignore_case: 538 pat_sqls.append("%s LIKE ?%s" % (field, rest)) 539 else: 540 pat_sqls.append("%s %s ?" % (field, rest)) 541 pat_data.append(pattern) 542 assert pat_sqls 543 544 qsql += " OR ".join(pat_sqls) 545 executeSQL(cur, qsql, pat_data) 546 for x in cur: 547 yield x
548
549 - def search(self, patterns, ignore_case=True):
550 """ Search for history transactions which contain specified 551 packages al. la. "yum list". Returns transaction ids. """ 552 # Search packages ... kind of sucks that it's search not list, pkglist? 553 554 data = _setupHistorySearchSQL(patterns, ignore_case) 555 (need_full, patterns, fields, names) = data 556 557 ret = [] 558 pkgtupids = set() 559 for row in self._yieldSQLDataList(patterns, fields, ignore_case): 560 pkgtupids.add(row[0]) 561 562 cur = self._get_cursor() 563 sql = """SELECT tid FROM trans_data_pkgs WHERE pkgtupid IN """ 564 sql += "(%s)" % ",".join(['?'] * len(pkgtupids)) 565 params = list(pkgtupids) 566 tids = set() 567 if len(params) > yum.constants.PATTERNS_INDEXED_MAX: 568 executeSQL(cur, """SELECT tid FROM trans_data_pkgs""") 569 for row in cur: 570 if row[0] in params: 571 tids.add(row[0]) 572 return tids 573 if not params: 574 return tids 575 executeSQL(cur, sql, params) 576 for row in cur: 577 tids.add(row[0]) 578 return tids
579
580 - def _create_db_file(self):
581 """ Create a new history DB file, populating tables etc. """ 582 583 _db_file = '%s/%s-%s.%s' % (self.conf.db_path, 584 'history', 585 time.strftime('%Y-%m-%d'), 586 'sqlite') 587 if self._db_file == _db_file: 588 os.rename(_db_file, _db_file + '.old') 589 self._db_file = _db_file 590 591 if self.conf.writable and not os.path.exists(self._db_file): 592 # make them default to 0600 - sysadmin can change it later 593 # if they want 594 fo = os.open(self._db_file, os.O_CREAT, 0600) 595 os.close(fo) 596 597 cur = self._get_cursor() 598 ops = ['''\ 599 CREATE TABLE trans_beg ( 600 tid INTEGER PRIMARY KEY, 601 timestamp INTEGER NOT NULL, rpmdb_version TEXT NOT NULL, 602 loginuid INTEGER); 603 ''', '''\ 604 CREATE TABLE trans_end ( 605 tid INTEGER PRIMARY KEY REFERENCES trans_beg, 606 timestamp INTEGER NOT NULL, rpmdb_version TEXT NOT NULL, 607 return_code INTEGER NOT NULL); 608 ''', '''\ 609 \ 610 CREATE TABLE trans_with_pkgs ( 611 tid INTEGER NOT NULL REFERENCES trans_beg, 612 pkgtupid INTEGER NOT NULL REFERENCES pkgtups); 613 ''', '''\ 614 \ 615 CREATE TABLE trans_error ( 616 mid INTEGER PRIMARY KEY, 617 tid INTEGER NOT NULL REFERENCES trans_beg, 618 msg TEXT NOT NULL); 619 ''', '''\ 620 CREATE TABLE trans_script_stdout ( 621 lid INTEGER PRIMARY KEY, 622 tid INTEGER NOT NULL REFERENCES trans_beg, 623 line TEXT NOT NULL); 624 ''', '''\ 625 \ 626 CREATE TABLE trans_data_pkgs ( 627 tid INTEGER NOT NULL REFERENCES trans_beg, 628 pkgtupid INTEGER NOT NULL REFERENCES pkgtups, 629 done BOOL NOT NULL DEFAULT FALSE, state TEXT NOT NULL); 630 ''', '''\ 631 \ 632 CREATE TABLE pkgtups ( 633 pkgtupid INTEGER PRIMARY KEY, name TEXT NOT NULL, arch TEXT NOT NULL, 634 epoch TEXT NOT NULL, version TEXT NOT NULL, release TEXT NOT NULL, 635 checksum TEXT); 636 ''', '''\ 637 CREATE INDEX i_pkgtup_naevr ON pkgtups (name, arch, epoch, version, release); 638 '''] 639 for op in ops: 640 cur.execute(op) 641 self._commit()
642 643 # Pasted from sqlitesack 644 _FULL_PARSE_QUERY_BEG = """ 645 SELECT pkgtupid,name,epoch,version,release,arch, 646 name || "." || arch AS sql_nameArch, 647 name || "-" || version || "-" || release || "." || arch AS sql_nameVerRelArch, 648 name || "-" || version AS sql_nameVer, 649 name || "-" || version || "-" || release AS sql_nameVerRel, 650 epoch || ":" || name || "-" || version || "-" || release || "." || arch AS sql_envra, 651 name || "-" || epoch || ":" || version || "-" || release || "." || arch AS sql_nevra 652 FROM pkgtups 653 WHERE 654 """ 655