]> git.decadent.org.uk Git - dak.git/blob - daklib/database.py
Merge branch 'master' into psycopg2
[dak.git] / daklib / database.py
1 #!/usr/bin/env python
2
3 """ DB access functions
4 @group readonly: get_suite_id, get_section_id, get_priority_id, get_override_type_id,
5                  get_architecture_id, get_archive_id, get_component_id, get_location_id,
6                  get_source_id, get_suite_version, get_files_id, get_maintainer, get_suites
7 @group read/write: get_or_set*, set_files_id
8
9 @contact: Debian FTP Master <ftpmaster@debian.org>
10 @copyright: 2000, 2001, 2002, 2003, 2004, 2006  James Troup <james@nocrew.org>
11 @copyright: 2009  Joerg Jaspert <joerg@debian.org>
12 @license: GNU General Public License version 2 or later
13 """
14
15 # This program is free software; you can redistribute it and/or modify
16 # it under the terms of the GNU General Public License as published by
17 # the Free Software Foundation; either version 2 of the License, or
18 # (at your option) any later version.
19
20 # This program is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23 # GNU General Public License for more details.
24
25 # You should have received a copy of the GNU General Public License
26 # along with this program; if not, write to the Free Software
27 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
28
29 ################################################################################
30
31 import sys
32 import time
33 import types
34
35 ################################################################################
36
37 Cnf = None                    #: Configuration, apt_pkg.Configuration
38 projectB = None               #: database connection, pgobject
39 suite_id_cache = {}           #: cache for suites
40 section_id_cache = {}         #: cache for sections
41 priority_id_cache = {}        #: cache for priorities
42 override_type_id_cache = {}   #: cache for overrides
43 architecture_id_cache = {}    #: cache for architectures
44 archive_id_cache = {}         #: cache for archives
45 component_id_cache = {}       #: cache for components
46 location_id_cache = {}        #: cache for locations
47 maintainer_id_cache = {}      #: cache for maintainers
48 keyring_id_cache = {}         #: cache for keyrings
49 source_id_cache = {}          #: cache for sources
50 files_id_cache = {}           #: cache for files
51 maintainer_cache = {}         #: cache for maintainer names
52 fingerprint_id_cache = {}     #: cache for fingerprints
53 queue_id_cache = {}           #: cache for queues
54 uid_id_cache = {}             #: cache for uids
55 suite_version_cache = {}      #: cache for suite_versions (packages)
56
57 ################################################################################
58
59 def init (config, sql):
60     """
61     database module init.
62
63     @type config: apt_pkg.Configuration
64     @param config: apt config, see U{http://apt.alioth.debian.org/python-apt-doc/apt_pkg/cache.html#Configuration}
65
66     @type sql: pgobject
67     @param sql: database connection
68
69     """
70     global Cnf, projectB
71
72     Cnf = config
73     projectB = sql
74
75
76 def do_query(query):
77     """
78     Executes a database query. Writes statistics / timing to stderr.
79
80     @type query: string
81     @param query: database query string, passed unmodified
82
83     @return: db result
84
85     @warning: The query is passed B{unmodified}, so be careful what you use this for.
86     """
87     sys.stderr.write("query: \"%s\" ... " % (query))
88     before = time.time()
89     r = projectB.query(query)
90     time_diff = time.time()-before
91     sys.stderr.write("took %.3f seconds.\n" % (time_diff))
92     if type(r) is int:
93         sys.stderr.write("int result: %s\n" % (r))
94     elif type(r) is types.NoneType:
95         sys.stderr.write("result: None\n")
96     else:
97         sys.stderr.write("pgresult: %s\n" % (r.getresult()))
98     return r
99
100 ################################################################################
101
102 def get_suite_id (suite):
103     """
104     Returns database id for given C{suite}.
105     Results are kept in a cache during runtime to minimize database queries.
106
107     @type suite: string
108     @param suite: The name of the suite
109
110     @rtype: int
111     @return: the database id for the given suite
112
113     """
114     global suite_id_cache
115
116     if suite_id_cache.has_key(suite):
117         return suite_id_cache[suite]
118
119     q = projectB.query("SELECT id FROM suite WHERE suite_name = '%s'" % (suite))
120     ql = q.getresult()
121     if not ql:
122         return -1
123
124     suite_id = ql[0][0]
125     suite_id_cache[suite] = suite_id
126
127     return suite_id
128
129 def get_section_id (section):
130     """
131     Returns database id for given C{section}.
132     Results are kept in a cache during runtime to minimize database queries.
133
134     @type section: string
135     @param section: The name of the section
136
137     @rtype: int
138     @return: the database id for the given section
139
140     """
141     global section_id_cache
142
143     if section_id_cache.has_key(section):
144         return section_id_cache[section]
145
146     q = projectB.query("SELECT id FROM section WHERE section = '%s'" % (section))
147     ql = q.getresult()
148     if not ql:
149         return -1
150
151     section_id = ql[0][0]
152     section_id_cache[section] = section_id
153
154     return section_id
155
156 def get_priority_id (priority):
157     """
158     Returns database id for given C{priority}.
159     Results are kept in a cache during runtime to minimize database queries.
160
161     @type priority: string
162     @param priority: The name of the priority
163
164     @rtype: int
165     @return: the database id for the given priority
166
167     """
168     global priority_id_cache
169
170     if priority_id_cache.has_key(priority):
171         return priority_id_cache[priority]
172
173     q = projectB.query("SELECT id FROM priority WHERE priority = '%s'" % (priority))
174     ql = q.getresult()
175     if not ql:
176         return -1
177
178     priority_id = ql[0][0]
179     priority_id_cache[priority] = priority_id
180
181     return priority_id
182
183 def get_override_type_id (type):
184     """
185     Returns database id for given override C{type}.
186     Results are kept in a cache during runtime to minimize database queries.
187
188     @type type: string
189     @param type: The name of the override type
190
191     @rtype: int
192     @return: the database id for the given override type
193
194     """
195     global override_type_id_cache
196
197     if override_type_id_cache.has_key(type):
198         return override_type_id_cache[type]
199
200     q = projectB.query("SELECT id FROM override_type WHERE type = '%s'" % (type))
201     ql = q.getresult()
202     if not ql:
203         return -1
204
205     override_type_id = ql[0][0]
206     override_type_id_cache[type] = override_type_id
207
208     return override_type_id
209
210 def get_architecture_id (architecture):
211     """
212     Returns database id for given C{architecture}.
213     Results are kept in a cache during runtime to minimize database queries.
214
215     @type architecture: string
216     @param architecture: The name of the override type
217
218     @rtype: int
219     @return: the database id for the given architecture
220
221     """
222     global architecture_id_cache
223
224     if architecture_id_cache.has_key(architecture):
225         return architecture_id_cache[architecture]
226
227     q = projectB.query("SELECT id FROM architecture WHERE arch_string = '%s'" % (architecture))
228     ql = q.getresult()
229     if not ql:
230         return -1
231
232     architecture_id = ql[0][0]
233     architecture_id_cache[architecture] = architecture_id
234
235     return architecture_id
236
237 def get_archive_id (archive):
238     """
239     Returns database id for given C{archive}.
240     Results are kept in a cache during runtime to minimize database queries.
241
242     @type archive: string
243     @param archive: The name of the override type
244
245     @rtype: int
246     @return: the database id for the given archive
247
248     """
249     global archive_id_cache
250
251     archive = archive.lower()
252
253     if archive_id_cache.has_key(archive):
254         return archive_id_cache[archive]
255
256     q = projectB.query("SELECT id FROM archive WHERE lower(name) = '%s'" % (archive))
257     ql = q.getresult()
258     if not ql:
259         return -1
260
261     archive_id = ql[0][0]
262     archive_id_cache[archive] = archive_id
263
264     return archive_id
265
266 def get_component_id (component):
267     """
268     Returns database id for given C{component}.
269     Results are kept in a cache during runtime to minimize database queries.
270
271     @type component: string
272     @param component: The name of the component
273
274     @rtype: int
275     @return: the database id for the given component
276
277     """
278     global component_id_cache
279
280     component = component.lower()
281
282     if component_id_cache.has_key(component):
283         return component_id_cache[component]
284
285     q = projectB.query("SELECT id FROM component WHERE lower(name) = '%s'" % (component))
286     ql = q.getresult()
287     if not ql:
288         return -1
289
290     component_id = ql[0][0]
291     component_id_cache[component] = component_id
292
293     return component_id
294
295 def get_location_id (location, component, archive):
296     """
297     Returns database id for the location behind the given combination of
298       - B{location} - the path of the location, eg. I{/srv/ftp.debian.org/ftp/pool/}
299       - B{component} - the id of the component as returned by L{get_component_id}
300       - B{archive} - the id of the archive as returned by L{get_archive_id}
301     Results are kept in a cache during runtime to minimize database queries.
302
303     @type location: string
304     @param location: the path of the location
305
306     @type component: int
307     @param component: the id of the component
308
309     @type archive: int
310     @param archive: the id of the archive
311
312     @rtype: int
313     @return: the database id for the location
314
315     """
316     global location_id_cache
317
318     cache_key = location + '_' + component + '_' + location
319     if location_id_cache.has_key(cache_key):
320         return location_id_cache[cache_key]
321
322     archive_id = get_archive_id (archive)
323     if component != "":
324         component_id = get_component_id (component)
325         if component_id != -1:
326             q = projectB.query("SELECT id FROM location WHERE path = '%s' AND component = %d AND archive = %d" % (location, component_id, archive_id))
327     else:
328         q = projectB.query("SELECT id FROM location WHERE path = '%s' AND archive = %d" % (location, archive_id))
329     ql = q.getresult()
330     if not ql:
331         return -1
332
333     location_id = ql[0][0]
334     location_id_cache[cache_key] = location_id
335
336     return location_id
337
338 def get_source_id (source, version):
339     """
340     Returns database id for the combination of C{source} and C{version}
341       - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
342       - B{version}
343     Results are kept in a cache during runtime to minimize database queries.
344
345     @type source: string
346     @param source: source package name
347
348     @type version: string
349     @param version: the source version
350
351     @rtype: int
352     @return: the database id for the source
353
354     """
355     global source_id_cache
356
357     cache_key = source + '_' + version + '_'
358     if source_id_cache.has_key(cache_key):
359         return source_id_cache[cache_key]
360
361     q = projectB.query("SELECT id FROM source s WHERE s.source = '%s' AND s.version = '%s'" % (source, version))
362
363     if not q.getresult():
364         return None
365
366     source_id = q.getresult()[0][0]
367     source_id_cache[cache_key] = source_id
368
369     return source_id
370
371 def get_suite_version(source, suite):
372     """
373     Returns database id for a combination of C{source} and C{suite}.
374
375       - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
376       - B{suite} - a suite name, eg. I{unstable}
377
378     Results are kept in a cache during runtime to minimize database queries.
379
380     @type source: string
381     @param source: source package name
382
383     @type suite: string
384     @param suite: the suite name
385
386     @rtype: string
387     @return: the version for I{source} in I{suite}
388
389     """
390     global suite_version_cache
391     cache_key = "%s_%s" % (source, suite)
392
393     if suite_version_cache.has_key(cache_key):
394         return suite_version_cache[cache_key]
395
396     q = projectB.query("""
397     SELECT s.version FROM source s, suite su, src_associations sa
398     WHERE sa.source=s.id
399       AND sa.suite=su.id
400       AND su.suite_name='%s'
401       AND s.source='%s'"""
402                               % (suite, source))
403
404     if not q.getresult():
405         return None
406
407     version = q.getresult()[0][0]
408     suite_version_cache[cache_key] = version
409
410     return version
411
412 ################################################################################
413
414 def get_or_set_maintainer_id (maintainer):
415     """
416     If C{maintainer} does not have an entry in the maintainer table yet, create one
417     and return the new id.
418     If C{maintainer} already has an entry, simply return the existing id.
419
420     Results are kept in a cache during runtime to minimize database queries.
421
422     @type maintainer: string
423     @param maintainer: the maintainer name
424
425     @rtype: int
426     @return: the database id for the maintainer
427
428     """
429     global maintainer_id_cache
430
431     if maintainer_id_cache.has_key(maintainer):
432         return maintainer_id_cache[maintainer]
433
434     q = projectB.query("SELECT id FROM maintainer WHERE name = '%s'" % (maintainer))
435     if not q.getresult():
436         projectB.query("INSERT INTO maintainer (name) VALUES ('%s')" % (maintainer))
437         q = projectB.query("SELECT id FROM maintainer WHERE name = '%s'" % (maintainer))
438     maintainer_id = q.getresult()[0][0]
439     maintainer_id_cache[maintainer] = maintainer_id
440
441     return maintainer_id
442
443 ################################################################################
444
445 def get_or_set_keyring_id (keyring):
446     """
447     If C{keyring} does not have an entry in the C{keyrings} table yet, create one
448     and return the new id.
449     If C{keyring} already has an entry, simply return the existing id.
450
451     Results are kept in a cache during runtime to minimize database queries.
452
453     @type keyring: string
454     @param keyring: the keyring name
455
456     @rtype: int
457     @return: the database id for the keyring
458
459     """
460     global keyring_id_cache
461
462     if keyring_id_cache.has_key(keyring):
463         return keyring_id_cache[keyring]
464
465     q = projectB.query("SELECT id FROM keyrings WHERE name = '%s'" % (keyring))
466     if not q.getresult():
467         projectB.query("INSERT INTO keyrings (name) VALUES ('%s')" % (keyring))
468         q = projectB.query("SELECT id FROM keyrings WHERE name = '%s'" % (keyring))
469     keyring_id = q.getresult()[0][0]
470     keyring_id_cache[keyring] = keyring_id
471
472     return keyring_id
473
474 ################################################################################
475
476 def get_or_set_uid_id (uid):
477     """
478     If C{uid} does not have an entry in the uid table yet, create one
479     and return the new id.
480     If C{uid} already has an entry, simply return the existing id.
481
482     Results are kept in a cache during runtime to minimize database queries.
483
484     @type uid: string
485     @param uid: the uid.
486
487     @rtype: int
488     @return: the database id for the uid
489
490     """
491
492     global uid_id_cache
493
494     if uid_id_cache.has_key(uid):
495         return uid_id_cache[uid]
496
497     q = projectB.query("SELECT id FROM uid WHERE uid = '%s'" % (uid))
498     if not q.getresult():
499         projectB.query("INSERT INTO uid (uid) VALUES ('%s')" % (uid))
500         q = projectB.query("SELECT id FROM uid WHERE uid = '%s'" % (uid))
501     uid_id = q.getresult()[0][0]
502     uid_id_cache[uid] = uid_id
503
504     return uid_id
505
506 ################################################################################
507
508 def get_or_set_fingerprint_id (fingerprint):
509     """
510     If C{fingerprint} does not have an entry in the fingerprint table yet, create one
511     and return the new id.
512     If C{fingerprint} already has an entry, simply return the existing id.
513
514     Results are kept in a cache during runtime to minimize database queries.
515
516     @type fingerprint: string
517     @param fingerprint: the fingerprint
518
519     @rtype: int
520     @return: the database id for the fingerprint
521
522     """
523     global fingerprint_id_cache
524
525     if fingerprint_id_cache.has_key(fingerprint):
526         return fingerprint_id_cache[fingerprint]
527
528     q = projectB.query("SELECT id FROM fingerprint WHERE fingerprint = '%s'" % (fingerprint))
529     if not q.getresult():
530         projectB.query("INSERT INTO fingerprint (fingerprint) VALUES ('%s')" % (fingerprint))
531         q = projectB.query("SELECT id FROM fingerprint WHERE fingerprint = '%s'" % (fingerprint))
532     fingerprint_id = q.getresult()[0][0]
533     fingerprint_id_cache[fingerprint] = fingerprint_id
534
535     return fingerprint_id
536
537 ################################################################################
538
539 def get_files_id (filename, size, md5sum, location_id):
540     """
541     Returns -1, -2 or the file_id for filename, if its C{size} and C{md5sum} match an
542     existing copy.
543
544     The database is queried using the C{filename} and C{location_id}. If a file does exist
545     at that location, the existing size and md5sum are checked against the provided
546     parameters. A size or checksum mismatch returns -2. If more than one entry is
547     found within the database, a -1 is returned, no result returns None, otherwise
548     the file id.
549
550     Results are kept in a cache during runtime to minimize database queries.
551
552     @type filename: string
553     @param filename: the filename of the file to check against the DB
554
555     @type size: int
556     @param size: the size of the file to check against the DB
557
558     @type md5sum: string
559     @param md5sum: the md5sum of the file to check against the DB
560
561     @type location_id: int
562     @param location_id: the id of the location as returned by L{get_location_id}
563
564     @rtype: int / None
565     @return: Various return values are possible:
566                - -2: size/checksum error
567                - -1: more than one file found in database
568                - None: no file found in database
569                - int: file id
570
571     """
572     global files_id_cache
573
574     cache_key = "%s_%d" % (filename, location_id)
575
576     if files_id_cache.has_key(cache_key):
577         return files_id_cache[cache_key]
578
579     size = int(size)
580     q = projectB.query("SELECT id, size, md5sum FROM files WHERE filename = '%s' AND location = %d" % (filename, location_id))
581     ql = q.getresult()
582     if ql:
583         if len(ql) != 1:
584             return -1
585         ql = ql[0]
586         orig_size = int(ql[1])
587         orig_md5sum = ql[2]
588         if orig_size != size or orig_md5sum != md5sum:
589             return -2
590         files_id_cache[cache_key] = ql[0]
591         return files_id_cache[cache_key]
592     else:
593         return None
594
595 ################################################################################
596
597 def get_or_set_queue_id (queue):
598     """
599     If C{queue} does not have an entry in the queue table yet, create one
600     and return the new id.
601     If C{queue} already has an entry, simply return the existing id.
602
603     Results are kept in a cache during runtime to minimize database queries.
604
605     @type queue: string
606     @param queue: the queue name (no full path)
607
608     @rtype: int
609     @return: the database id for the queue
610
611     """
612     global queue_id_cache
613
614     if queue_id_cache.has_key(queue):
615         return queue_id_cache[queue]
616
617     q = projectB.query("SELECT id FROM queue WHERE queue_name = '%s'" % (queue))
618     if not q.getresult():
619         projectB.query("INSERT INTO queue (queue_name) VALUES ('%s')" % (queue))
620         q = projectB.query("SELECT id FROM queue WHERE queue_name = '%s'" % (queue))
621     queue_id = q.getresult()[0][0]
622     queue_id_cache[queue] = queue_id
623
624     return queue_id
625
626 ################################################################################
627
628 def set_files_id (filename, size, md5sum, sha1sum, sha256sum, location_id):
629     """
630     Insert a new entry into the files table and return its id.
631
632     @type filename: string
633     @param filename: the filename
634
635     @type size: int
636     @param size: the size in bytes
637
638     @type md5sum: string
639     @param md5sum: md5sum of the file
640
641     @type sha1sum: string
642     @param sha1sum: sha1sum of the file
643
644     @type sha256sum: string
645     @param sha256sum: sha256sum of the file
646
647     @type location_id: int
648     @param location_id: the id of the location as returned by L{get_location_id}
649
650     @rtype: int
651     @return: the database id for the new file
652
653     """
654     global files_id_cache
655
656     projectB.query("INSERT INTO files (filename, size, md5sum, sha1sum, sha256sum, location) VALUES ('%s', %d, '%s', '%s', '%s', %d)" % (filename, long(size), md5sum, sha1sum, sha256sum, location_id))
657
658     return get_files_id (filename, size, md5sum, location_id)
659
660     ### currval has issues with postgresql 7.1.3 when the table is big
661     ### it was taking ~3 seconds to return on auric which is very Not
662     ### Cool(tm).
663     ##
664     ##q = projectB.query("SELECT id FROM files WHERE id = currval('files_id_seq')")
665     ##ql = q.getresult()[0]
666     ##cache_key = "%s_%d" % (filename, location_id)
667     ##files_id_cache[cache_key] = ql[0]
668     ##return files_id_cache[cache_key]
669
670 ################################################################################
671
672 def get_maintainer (maintainer_id):
673     """
674     Return the name of the maintainer behind C{maintainer_id}.
675
676     Results are kept in a cache during runtime to minimize database queries.
677
678     @type maintainer_id: int
679     @param maintainer_id: the id of the maintainer, eg. from L{get_or_set_maintainer_id}
680
681     @rtype: string
682     @return: the name of the maintainer
683
684     """
685     global maintainer_cache
686
687     if not maintainer_cache.has_key(maintainer_id):
688         q = projectB.query("SELECT name FROM maintainer WHERE id = %s" % (maintainer_id))
689         maintainer_cache[maintainer_id] = q.getresult()[0][0]
690
691     return maintainer_cache[maintainer_id]
692
693 ################################################################################
694
695 def get_suites(pkgname, src=False):
696     """
697     Return the suites in which C{pkgname} can be found. If C{src} is True query for source
698     package, else binary package.
699
700     @type pkgname: string
701     @param pkgname: name of the package
702
703     @type src: bool
704     @param src: if True look for source packages, false (default) looks for binary.
705
706     @rtype: list
707     @return: list of suites, or empty list if no match
708
709     """
710     if src:
711         sql = """
712         SELECT suite_name
713         FROM source,
714              src_associations,
715              suite
716         WHERE source.id = src_associations.source
717         AND   source.source = '%s'
718         AND   src_associations.suite = suite.id
719         """ % (pkgname)
720     else:
721         sql = """
722         SELECT suite_name
723         FROM binaries,
724              bin_associations,
725              suite
726         WHERE binaries.id = bin_associations.bin
727         AND   package = '%s'
728         AND   bin_associations.suite = suite.id
729         """ % (pkgname)
730
731     q = projectB.query(sql)
732     return map(lambda x: x[0], q.getresult())