3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU Library General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
16
# Copyright 2005 Duke University
19
# - Add support for multiple checksums per rpm (not required)
25
from sqlitesack import encodefiletypelist,encodefilenamelist
27
# This version refers to the internal structure of the sqlite cache files
28
# increasing this number forces all caches of a lower version number
32
class RepodataParserSqlite:
33
def __init__(self, storedir, repoid, callback=None):
34
self.storedir = storedir
35
self.callback = callback
44
def loadCache(self,filename):
45
"""Load cache from filename, check if it is valid and that dbversion
46
matches the required dbversion"""
47
db = sqlite.connect(filename)
49
cur.execute("select * from db_info")
51
# If info is not in there this is an incompelete cache file
52
# (this could happen when the user hits ctrl-c or kills yum
53
# when the cache is being generated or updated)
55
raise sqlite.DatabaseError, "Incomplete database cache file"
57
# Now check the database version
58
if (info['dbversion'] != dbversion):
59
self.log(2, "Warning: cache file is version %s, we need %s, will regenerate" % (
60
info['dbversion'], dbversion))
61
raise sqlite.DatabaseError, "Older version of yum sqlite"
63
# This appears to be a valid database, return checksum value and
65
return (info['checksum'],db)
67
def getFilename(self,location):
68
return location + '.sqlite'
70
def getDatabase(self, location, cachetype):
71
filename = self.getFilename(location)
73
# First try to open an existing database
76
(dbchecksum,db) = self.loadCache(filename)
77
except (IOError,sqlite.DatabaseError,KeyError):
78
# If it doesn't exist, create it
79
db = self.makeSqliteCacheFile(filename,cachetype)
80
return (db,dbchecksum)
82
def _getbase(self, location, checksum, metadatatype):
83
(db, dbchecksum) = self.getDatabase(location, metadatatype)
84
# db should now contain a valid database object, check if it is
86
if (checksum != dbchecksum):
87
self.log(3, "%s sqlite cache needs updating, reading in metadata" % (metadatatype))
88
parser = mdparser.MDParser(location)
89
self.updateSqliteCache(db, parser, checksum, metadatatype)
93
def getPrimary(self, location, checksum):
94
"""Load primary.xml.gz from an sqlite cache and update it
96
return self._getbase(location, checksum, 'primary')
98
def getFilelists(self, location, checksum):
99
"""Load filelist.xml.gz from an sqlite cache and update it if
101
return self._getbase(location, checksum, 'filelists')
103
def getOtherdata(self, location, checksum):
104
"""Load other.xml.gz from an sqlite cache and update it if required"""
105
return self._getbase(location, checksum, 'other')
107
def createTablesFilelists(self,db):
108
"""Create the required tables for filelists metadata in the sqlite
111
self.createDbInfo(cur)
112
# This table is needed to match pkgKeys to pkgIds
113
cur.execute("""CREATE TABLE packages(
114
pkgKey INTEGER PRIMARY KEY,
117
cur.execute("""CREATE TABLE filelist(
123
cur.execute("CREATE INDEX keyfile ON filelist (pkgKey)")
124
cur.execute("CREATE INDEX pkgId ON packages (pkgId)")
126
def createTablesOther(self,db):
127
"""Create the required tables for other.xml.gz metadata in the sqlite
130
self.createDbInfo(cur)
131
# This table is needed to match pkgKeys to pkgIds
132
cur.execute("""CREATE TABLE packages(
133
pkgKey INTEGER PRIMARY KEY,
136
cur.execute("""CREATE TABLE changelog(
142
cur.execute("CREATE INDEX keychange ON changelog (pkgKey)")
143
cur.execute("CREATE INDEX pkgId ON packages (pkgId)")
145
def createTablesPrimary(self,db):
146
"""Create the required tables for primary metadata in the sqlite
150
self.createDbInfo(cur)
151
# The packages table contains most of the information in primary.xml.gz
153
q = 'CREATE TABLE packages(\n' \
154
'pkgKey INTEGER PRIMARY KEY,\n'
157
for col in PackageToDBAdapter.COLUMNS:
158
cols.append('%s TEXT' % col)
159
q += ',\n'.join(cols) + ')'
163
# Create requires, provides, conflicts and obsoletes tables
165
for t in ('requires','provides','conflicts','obsoletes'):
166
cur.execute("""CREATE TABLE %s (
174
# Create the files table to hold all the file information
175
cur.execute("""CREATE TABLE files (
180
# Create indexes for faster searching
181
cur.execute("CREATE INDEX packagename ON packages (name)")
182
cur.execute("CREATE INDEX providesname ON provides (name)")
183
cur.execute("CREATE INDEX packageId ON packages (pkgId)")
186
def createDbInfo(self,cur):
187
# Create the db_info table, this contains sqlite cache metadata
188
cur.execute("""CREATE TABLE db_info (
193
def insertHash(self,table,hash,cursor):
194
"""Insert the key value pairs in hash into a database table"""
197
values = hash.values()
198
query = "INSERT INTO %s (" % (table)
199
query += ",".join(keys)
200
query += ") VALUES ("
201
# Quote all values by replacing None with NULL and ' by ''
207
query += "'%s'," % (x.replace("'","''"))
208
except AttributeError:
210
# Remove the last , from query
212
# And replace it with )
214
cursor.execute(query.encode('utf8'))
215
return cursor.lastrowid
217
def makeSqliteCacheFile(self, filename, cachetype):
218
"""Create an initial database in the given filename"""
220
# If it exists, remove it as we were asked to create a new one
221
if (os.path.exists(filename)):
222
self.log(3, "Warning: cache already exists, removing old version")
228
# Try to create the databse in filename, or use in memory when
231
f = open(filename,'w')
232
db = sqlite.connect(filename)
234
self.log(1, "Warning could not create sqlite cache file, using in memory cache instead")
235
db = sqlite.connect(":memory:")
237
# The file has been created, now create the tables and indexes
238
if (cachetype == 'primary'):
239
self.createTablesPrimary(db)
240
elif (cachetype == 'filelists'):
241
self.createTablesFilelists(db)
242
elif (cachetype == 'other'):
243
self.createTablesOther(db)
245
raise sqlite.DatabaseError, "Sorry don't know how to store %s" % (cachetype)
248
def addPrimary(self, pkgId, package, cur):
249
"""Add a package to the primary cache"""
250
# Store the package info into the packages table
251
pkgKey = self.insertHash('packages', PackageToDBAdapter(package), cur)
253
# Now store all prco data
254
for ptype in package.prco:
255
for entry in package.prco[ptype]:
258
'name': entry.get('name'),
259
'flags': entry.get('flags'),
260
'epoch': entry.get('epoch'),
261
'version': entry.get('ver'),
262
'release': entry.get('rel'),
264
self.insertHash(ptype,data,cur)
266
# Now store all file information
267
for f in package.files:
270
'type': package.files[f],
273
self.insertHash('files',data,cur)
275
def addFilelists(self, pkgId, package,cur):
276
"""Add a package to the filelists cache"""
277
pkginfo = {'pkgId': pkgId}
278
pkgKey = self.insertHash('packages',pkginfo, cur)
280
for (filename,filetype) in package.files.iteritems():
281
(dirname,filename) = (os.path.split(filename))
282
if (dirs.has_key(dirname)):
283
dirs[dirname]['files'].append(filename)
284
dirs[dirname]['types'].append(filetype)
287
dirs[dirname]['files'] = [filename]
288
dirs[dirname]['types'] = [filetype]
290
for (dirname,dir) in dirs.items():
294
'filenames': encodefilenamelist(dir['files']),
295
'filetypes': encodefiletypelist(dir['types'])
297
self.insertHash('filelist',data,cur)
299
def addOther(self, pkgId, package,cur):
300
pkginfo = {'pkgId': pkgId}
301
pkgKey = self.insertHash('packages', pkginfo, cur)
302
for entry in package['changelog']:
305
'author': entry.get('author'),
306
'date': entry.get('date'),
307
'changelog': entry.get('value'),
309
self.insertHash('changelog', data, cur)
311
def updateSqliteCache(self, db, parser, checksum, cachetype):
312
"""Update the sqlite cache by making it fit the packages described
313
in dobj (info that has been read from primary.xml metadata) afterwards
314
update the checksum of the database to checksum"""
320
# We start be removing the old db_info, as it is no longer valid
322
cur.execute("DELETE FROM db_info")
324
# First create a list of all pkgIds that are in the database now
325
cur.execute("SELECT pkgId, pkgKey from packages")
327
for pkg in cur.fetchall():
328
currentpkgs[pkg['pkgId']] = pkg['pkgKey']
330
if (cachetype == 'primary'):
331
deltables = ("packages","files","provides","requires",
332
"conflicts","obsoletes")
333
elif (cachetype == 'filelists'):
334
deltables = ("packages","filelist")
335
elif (cachetype == 'other'):
336
deltables = ("packages","changelog")
338
raise sqlite.DatabaseError,"Unknown type %s" % (cachetype)
340
# Add packages that are not in the database yet and build up a list of
341
# all pkgids in the current metadata
344
for package in parser:
346
if self.callback is not None:
347
self.callback.progressbar(parser.count, parser.total, self.repoid)
349
pkgId = package['pkgId']
350
all_pkgIds[pkgId] = 1
352
# This package is already in the database, skip it now
353
if (currentpkgs.has_key(pkgId)):
356
# This is a new package, lets insert it
358
if cachetype == 'primary':
359
self.addPrimary(pkgId, package, cur)
360
elif cachetype == 'filelists':
361
self.addFilelists(pkgId, package, cur)
362
elif cachetype == 'other':
363
self.addOther(pkgId, package, cur)
365
# Remove those which are not in dobj
367
for (pkgId, pkgKey) in currentpkgs.items():
368
if not all_pkgIds.has_key(pkgId):
370
delpkgs.append(str(pkgKey))
371
delpkgs = "("+",".join(delpkgs)+")"
372
for table in deltables:
373
cur.execute("DELETE FROM "+table+ " where pkgKey in %s" % delpkgs)
375
cur.execute("INSERT into db_info (dbversion,checksum) VALUES (%s,%s)",
376
(dbversion,checksum))
378
self.log(2, "Added %s new packages, deleted %s old in %.2f seconds" % (
379
newcount, delcount, time.time()-t))
382
def log(self, level, msg):
383
'''Log to callback (if set)
386
self.callback.log(level, msg)
388
class PackageToDBAdapter:
391
Adapt a PrimaryEntry instance to suit the sqlite database.
393
This hides certain attributes and converts some column names in order to
394
decouple the parser implementation from the sqlite database schema.
398
'rpm_package': 'package',
401
'rpm_license': 'license',
402
'rpm_vendor': 'vendor',
403
'rpm_group': 'group',
404
'rpm_buildhost': 'buildhost',
405
'rpm_sourcerpm': 'sourcerpm',
406
'rpm_packager': 'packager',
437
def __init__(self, package):
440
def __getitem__(self, k):
441
return self._pkg[self.NAME_MAPS.get(k, k)]
448
for k in self.keys():