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