source: trunk/module/pg.py @ 441

Last change on this file since 441 was 441, checked in by cito, 7 years ago

Incorporate regular type names in the clear() method.

  • Property svn:keywords set to Id
File size: 26.7 KB
Line 
1#!/usr/bin/env python
2#
3# pg.py
4#
5# Written by D'Arcy J.M. Cain
6# Improved by Christoph Zwerschke
7#
8# $Id: pg.py 441 2012-05-12 22:07:58Z cito $
9#
10
11"""PyGreSQL classic interface.
12
13This pg module implements some basic database management stuff.
14It includes the _pg module and builds on it, providing the higher
15level wrapper class named DB with addtional functionality.
16This is known as the "classic" ("old style") PyGreSQL interface.
17For a DB-API 2 compliant interface use the newer pgdb module.
18
19"""
20
21from _pg import *
22try:
23    frozenset
24except NameError:  # Python < 2.4
25    from sets import ImmutableSet as frozenset
26try:
27    from decimal import Decimal
28    set_decimal(Decimal)
29except ImportError:
30    pass  # Python < 2.4
31
32
33# Auxiliary functions which are independent from a DB connection:
34
35def _is_quoted(s):
36    """Check whether this string is a quoted identifier."""
37    s = s.replace('_', 'a')
38    return not s.isalnum() or s[:1].isdigit() or s != s.lower()
39
40
41def _is_unquoted(s):
42    """Check whether this string is an unquoted identifier."""
43    s = s.replace('_', 'a')
44    return s.isalnum() and not s[:1].isdigit()
45
46
47def _split_first_part(s):
48    """Split the first part of a dot separated string."""
49    s = s.lstrip()
50    if s[:1] == '"':
51        p = []
52        s = s.split('"', 3)[1:]
53        p.append(s[0])
54        while len(s) == 3 and s[1] == '':
55            p.append('"')
56            s = s[2].split('"', 2)
57            p.append(s[0])
58        p = [''.join(p)]
59        s = '"'.join(s[1:]).lstrip()
60        if s:
61            if s[:0] == '.':
62                p.append(s[1:])
63            else:
64                s = _split_first_part(s)
65                p[0] += s[0]
66                if len(s) > 1:
67                    p.append(s[1])
68    else:
69        p = s.split('.', 1)
70        s = p[0].rstrip()
71        if _is_unquoted(s):
72            s = s.lower()
73        p[0] = s
74    return p
75
76
77def _split_parts(s):
78    """Split all parts of a dot separated string."""
79    q = []
80    while s:
81        s = _split_first_part(s)
82        q.append(s[0])
83        if len(s) < 2:
84            break
85        s = s[1]
86    return q
87
88
89def _join_parts(s):
90    """Join all parts of a dot separated string."""
91    return '.'.join([_is_quoted(p) and '"%s"' % p or p for p in s])
92
93
94def _oid_key(qcl):
95    """Build oid key from qualified class name."""
96    return 'oid(%s)' % qcl
97
98
99def _db_error(msg, cls=DatabaseError):
100    """Returns DatabaseError with empty sqlstate attribute."""
101    error = cls(msg)
102    error.sqlstate = None
103    return error
104
105
106def _int_error(msg):
107    """Returns InternalError."""
108    return _db_error(msg, InternalError)
109
110
111def _prg_error(msg):
112    """Returns ProgrammingError."""
113    return _db_error(msg, ProgrammingError)
114
115
116# The PostGreSQL database connection interface:
117
118class DB(object):
119    """Wrapper class for the _pg connection type."""
120
121    def __init__(self, *args, **kw):
122        """Create a new connection.
123
124        You can pass either the connection parameters or an existing
125        _pg or pgdb connection. This allows you to use the methods
126        of the classic pg interface with a DB-API 2 pgdb connection.
127
128        """
129        if not args and len(kw) == 1:
130            db = kw.get('db')
131        elif not kw and len(args) == 1:
132            db = args[0]
133        else:
134            db = None
135        if db:
136            if isinstance(db, DB):
137                db = db.db
138            else:
139                try:
140                    db = db._cnx
141                except AttributeError:
142                    pass
143        if not db or not hasattr(db, 'db') or not hasattr(db, 'query'):
144            db = connect(*args, **kw)
145            self._closeable = 1
146        else:
147            self._closeable = 0
148        self.db = db
149        self.dbname = db.db
150        self._attnames = {}
151        self._pkeys = {}
152        self._privileges = {}
153        self._args = args, kw
154        self.debug = None  # For debugging scripts, this can be set
155            # * to a string format specification (e.g. in CGI set to "%s<BR>"),
156            # * to a file object to write debug statements or
157            # * to a callable object which takes a string argument.
158
159    def __getattr__(self, name):
160        # All undefined members are same as in underlying pg connection:
161        if self.db:
162            return getattr(self.db, name)
163        else:
164            raise _int_error('Connection is not valid')
165
166    # Auxiliary methods
167
168    def _do_debug(self, s):
169        """Print a debug message."""
170        if self.debug:
171            if isinstance(self.debug, basestring):
172                print self.debug % s
173            elif isinstance(self.debug, file):
174                file.write(s + '\n')
175            elif callable(self.debug):
176                self.debug(s)
177
178    def _quote_text(self, d):
179        """Quote text value."""
180        if not isinstance(d, basestring):
181            d = str(d)
182        return "'%s'" % self.escape_string(d)
183
184    _bool_true = frozenset('t true 1 y yes on'.split())
185
186    def _quote_bool(self, d):
187        """Quote boolean value."""
188        if isinstance(d, basestring):
189            if not d:
190                return 'NULL'
191            d = d.lower() in self._bool_true
192        else:
193            d = bool(d)
194        return ("'f'", "'t'")[d]
195
196    _date_literals = frozenset('current_date current_time'
197        ' current_timestamp localtime localtimestamp'.split())
198
199    def _quote_date(self, d):
200        """Quote date value."""
201        if not d:
202            return 'NULL'
203        if isinstance(d, basestring) and d.lower() in self._date_literals:
204            return d
205        return self._quote_text(d)
206
207    def _quote_num(self, d):
208        """Quote numeric value."""
209        if not d and d != 0:
210            return 'NULL'
211        return str(d)
212
213    def _quote_money(self, d):
214        """Quote money value."""
215        if d is None or d == '':
216            return 'NULL'
217        return "'%.2f'" % float(d)
218
219    _quote_funcs = dict(  # quote methods for each type
220        text=_quote_text, bool=_quote_bool, date=_quote_date,
221        int=_quote_num, num=_quote_num, float=_quote_num,
222        money=_quote_money)
223
224    def _quote(self, d, t):
225        """Return quotes if needed."""
226        if d is None:
227            return 'NULL'
228        try:
229            quote_func = self._quote_funcs[t]
230        except KeyError:
231            quote_func = self._quote_funcs['text']
232        return quote_func(self, d)
233
234    def _split_schema(self, cl):
235        """Return schema and name of object separately.
236
237        This auxiliary function splits off the namespace (schema)
238        belonging to the class with the name cl. If the class name
239        is not qualified, the function is able to determine the schema
240        of the class, taking into account the current search path.
241
242        """
243        s = _split_parts(cl)
244        if len(s) > 1:  # name already qualfied?
245            # should be database.schema.table or schema.table
246            if len(s) > 3:
247                raise _prg_error('Too many dots in class name %s' % cl)
248            schema, cl = s[-2:]
249        else:
250            cl = s[0]
251            # determine search path
252            q = 'SELECT current_schemas(TRUE)'
253            schemas = self.db.query(q).getresult()[0][0][1:-1].split(',')
254            if schemas:  # non-empty path
255                # search schema for this object in the current search path
256                q = ' UNION '.join(
257                    ["SELECT %d::integer AS n, '%s'::name AS nspname"
258                        % s for s in enumerate(schemas)])
259                q = ("SELECT nspname FROM pg_class"
260                    " JOIN pg_namespace"
261                    " ON pg_class.relnamespace = pg_namespace.oid"
262                    " JOIN (%s) AS p USING (nspname)"
263                    " WHERE pg_class.relname = '%s'"
264                    " ORDER BY n LIMIT 1" % (q, cl))
265                schema = self.db.query(q).getresult()
266                if schema:  # schema found
267                    schema = schema[0][0]
268                else:  # object not found in current search path
269                    schema = 'public'
270            else:  # empty path
271                schema = 'public'
272        return schema, cl
273
274    def _add_schema(self, cl):
275        """Ensure that the class name is prefixed with a schema name."""
276        return _join_parts(self._split_schema(cl))
277
278    # Public methods
279
280    # escape_string and escape_bytea exist as methods,
281    # so we define unescape_bytea as a method as well
282    unescape_bytea = staticmethod(unescape_bytea)
283
284    def close(self):
285        """Close the database connection."""
286        # Wraps shared library function so we can track state.
287        if self._closeable:
288            if self.db:
289                self.db.close()
290                self.db = None
291            else:
292                raise _int_error('Connection already closed')
293
294    def reset(self):
295        """Reset connection with current parameters.
296
297        All derived queries and large objects derived from this connection
298        will not be usable after this call.
299
300        """
301        if self.db:
302            self.db.reset()
303        else:
304            raise _int_error('Connection already closed')
305
306    def reopen(self):
307        """Reopen connection to the database.
308
309        Used in case we need another connection to the same database.
310        Note that we can still reopen a database that we have closed.
311
312        """
313        # There is no such shared library function.
314        if self._closeable:
315            db = connect(*self._args[0], **self._args[1])
316            if self.db:
317                self.db.close()
318            self.db = db
319
320    def query(self, qstr):
321        """Executes a SQL command string.
322
323        This method simply sends a SQL query to the database. If the query is
324        an insert statement that inserted exactly one row into a table that
325        has OIDs, the return value is the OID of the newly inserted row.
326        If the query is an update or delete statement, or an insert statement
327        that did not insert exactly one row in a table with OIDs, then the
328        numer of rows affected is returned as a string. If it is a statement
329        that returns rows as a result (usually a select statement, but maybe
330        also an "insert/update ... returning" statement), this method returns
331        a pgqueryobject that can be accessed via getresult() or dictresult()
332        or simply printed. Otherwise, it returns `None`.
333
334        """
335        # Wraps shared library function for debugging.
336        if not self.db:
337            raise _int_error('Connection is not valid')
338        self._do_debug(qstr)
339        return self.db.query(qstr)
340
341    def pkey(self, cl, newpkey=None):
342        """This method gets or sets the primary key of a class.
343
344        Composite primary keys are represented as frozensets. Note that
345        this raises an exception if the table does not have a primary key.
346
347        If newpkey is set and is not a dictionary then set that
348        value as the primary key of the class.  If it is a dictionary
349        then replace the _pkeys dictionary with a copy of it.
350
351        """
352        # First see if the caller is supplying a dictionary
353        if isinstance(newpkey, dict):
354            # make sure that all classes have a namespace
355            self._pkeys = dict([
356                ('.' in cl and cl or 'public.' + cl, pkey)
357                for cl, pkey in newpkey.iteritems()])
358            return self._pkeys
359
360        qcl = self._add_schema(cl)  # build fully qualified class name
361        # Check if the caller is supplying a new primary key for the class
362        if newpkey:
363            self._pkeys[qcl] = newpkey
364            return newpkey
365
366        # Get all the primary keys at once
367        if qcl not in self._pkeys:
368            # if not found, check again in case it was added after we started
369            self._pkeys = {}
370            if self.server_version >= 80200:
371                # the ANY syntax works correctly only with PostgreSQL >= 8.2
372                any_indkey = "= ANY (pg_index.indkey)"
373            else:
374                any_indkey = "IN (%s)" % ', '.join(
375                    ['pg_index.indkey[%d]' % i for i in range(16)])
376            for r in self.db.query(
377                "SELECT pg_namespace.nspname, pg_class.relname,"
378                    " pg_attribute.attname FROM pg_class"
379                " JOIN pg_namespace"
380                    " ON pg_namespace.oid = pg_class.relnamespace"
381                    " AND pg_namespace.nspname NOT LIKE 'pg_%'"
382                " JOIN pg_attribute ON pg_attribute.attrelid = pg_class.oid"
383                    " AND pg_attribute.attisdropped = 'f'"
384                " JOIN pg_index ON pg_index.indrelid = pg_class.oid"
385                    " AND pg_index.indisprimary = 't'"
386                    " AND pg_attribute.attnum " + any_indkey).getresult():
387                cl, pkey = _join_parts(r[:2]), r[2]
388                self._pkeys.setdefault(cl, []).append(pkey)
389            # (only) for composite primary keys, the values will be frozensets
390            for cl, pkey in self._pkeys.iteritems():
391                self._pkeys[cl] = len(pkey) > 1 and frozenset(pkey) or pkey[0]
392            self._do_debug(self._pkeys)
393
394        # will raise an exception if primary key doesn't exist
395        return self._pkeys[qcl]
396
397    def get_databases(self):
398        """Get list of databases in the system."""
399        return [s[0] for s in
400            self.db.query('SELECT datname FROM pg_database').getresult()]
401
402    def get_relations(self, kinds=None):
403        """Get list of relations in connected database of specified kinds.
404
405            If kinds is None or empty, all kinds of relations are returned.
406            Otherwise kinds can be a string or sequence of type letters
407            specifying which kind of relations you want to list.
408
409        """
410        where = kinds and "pg_class.relkind IN (%s) AND" % ','.join(
411            ["'%s'" % x for x in kinds]) or ''
412        return map(_join_parts, self.db.query(
413            "SELECT pg_namespace.nspname, pg_class.relname "
414            "FROM pg_class "
415            "JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace "
416            "WHERE %s pg_class.relname !~ '^Inv' AND "
417                "pg_class.relname !~ '^pg_' "
418            "ORDER BY 1, 2" % where).getresult())
419
420    def get_tables(self):
421        """Return list of tables in connected database."""
422        return self.get_relations('r')
423
424    def get_attnames(self, cl, newattnames=None, regtypes=False):
425        """Given the name of a table, digs out the set of attribute names.
426
427        Returns a dictionary of attribute names (the names are the keys,
428        the values are the names of the attributes' types).
429        If the optional newattnames exists, it must be a dictionary and
430        will become the new attribute names dictionary.
431        If the optional regtypes flag is set, then the regular type names
432        will be returned instead of the simplified type names.
433
434        """
435        if isinstance(newattnames, dict):
436            self._attnames = newattnames
437            return
438        elif newattnames:
439            raise _prg_error('If supplied, newattnames must be a dictionary')
440        cl = self._split_schema(cl)  # split into schema and class
441        qcl = _join_parts(cl)  # build fully qualified name
442        # May as well cache them:
443        if qcl in self._attnames:
444            return self._attnames[qcl]
445        if qcl not in self.get_relations('rv'):
446            raise _prg_error('Class %s does not exist' % qcl)
447
448        q = "SELECT pg_attribute.attname, pg_type.typname"
449        if regtypes:
450            q += "::regtype"
451        q += (" FROM pg_class"
452            " JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid"
453            " JOIN pg_attribute ON pg_attribute.attrelid = pg_class.oid"
454            " JOIN pg_type ON pg_type.oid = pg_attribute.atttypid"
455            " WHERE pg_namespace.nspname = '%s' AND pg_class.relname = '%s'"
456            " AND (pg_attribute.attnum > 0 OR pg_attribute.attname = 'oid')"
457            " AND pg_attribute.attisdropped = 'f'") % cl
458        q = self.db.query(q).getresult()
459
460        if regtypes:
461            t = dict(q)
462        else:
463            t = {}
464            for att, typ in q:
465                if typ.startswith('bool'):
466                    typ = 'bool'
467                elif typ.startswith('abstime'):
468                    typ = 'date'
469                elif typ.startswith('date'):
470                    typ = 'date'
471                elif typ.startswith('interval'):
472                    typ = 'date'
473                elif typ.startswith('timestamp'):
474                    typ = 'date'
475                elif typ.startswith('oid'):
476                    typ = 'int'
477                elif typ.startswith('int'):
478                    typ = 'int'
479                elif typ.startswith('float'):
480                    typ = 'float'
481                elif typ.startswith('numeric'):
482                    typ = 'num'
483                elif typ.startswith('money'):
484                    typ = 'money'
485                else:
486                    typ = 'text'
487                t[att] = typ
488
489        self._attnames[qcl] = t  # cache it
490        return self._attnames[qcl]
491
492    def has_table_privilege(self, cl, privilege='select'):
493        """Check whether current user has specified table privilege."""
494        qcl = self._add_schema(cl)
495        privilege = privilege.lower()
496        try:
497            return self._privileges[(qcl, privilege)]
498        except KeyError:
499            q = "SELECT has_table_privilege('%s', '%s')" % (qcl, privilege)
500            ret = self.db.query(q).getresult()[0][0] == 't'
501            self._privileges[(qcl, privilege)] = ret
502            return ret
503
504    def get(self, cl, arg, keyname=None):
505        """Get a tuple from a database table or view.
506
507        This method is the basic mechanism to get a single row.  The keyname
508        that the key specifies a unique row.  If keyname is not specified
509        then the primary key for the table is used.  If arg is a dictionary
510        then the value for the key is taken from it and it is modified to
511        include the new values, replacing existing values where necessary.
512        For a composite key, keyname can also be a sequence of key names.
513        The OID is also put into the dictionary if the table has one, but
514        in order to allow the caller to work with multiple tables, it is
515        munged as oid(schema.table).
516
517        """
518        if cl.endswith('*'):  # scan descendant tables?
519            cl = cl[:-1].rstrip()  # need parent table name
520        # build qualified class name
521        qcl = self._add_schema(cl)
522        # To allow users to work with multiple tables,
523        # we munge the name of the "oid" the key
524        qoid = _oid_key(qcl)
525        if not keyname:
526            # use the primary key by default
527            try:
528                keyname = self.pkey(qcl)
529            except KeyError:
530                raise _prg_error('Class %s has no primary key' % qcl)
531        # We want the oid for later updates if that isn't the key
532        if keyname == 'oid':
533            if isinstance(arg, dict):
534                if qoid not in arg:
535                    raise _db_error('%s not in arg' % qoid)
536            else:
537                arg = {qoid: arg}
538            where = 'oid = %s' % arg[qoid]
539            attnames = '*'
540        else:
541            attnames = self.get_attnames(qcl)
542            if isinstance(keyname, basestring):
543                keyname = (keyname,)
544            if not isinstance(arg, dict):
545                if len(keyname) > 1:
546                    raise _prg_error('Composite key needs dict as arg')
547                arg = dict([(k, arg) for k in keyname])
548            where = ' AND '.join(['%s = %s'
549                % (k, self._quote(arg[k], attnames[k])) for k in keyname])
550            attnames = ', '.join(attnames)
551        q = 'SELECT %s FROM %s WHERE %s LIMIT 1' % (attnames, qcl, where)
552        self._do_debug(q)
553        res = self.db.query(q).dictresult()
554        if not res:
555            raise _db_error('No such record in %s where %s' % (qcl, where))
556        for att, value in res[0].iteritems():
557            arg[att == 'oid' and qoid or att] = value
558        return arg
559
560    def insert(self, cl, d=None, **kw):
561        """Insert a tuple into a database table.
562
563        This method inserts a row into a table.  If a dictionary is
564        supplied it starts with that.  Otherwise it uses a blank dictionary.
565        Either way the dictionary is updated from the keywords.
566
567        The dictionary is then, if possible, reloaded with the values actually
568        inserted in order to pick up values modified by rules, triggers, etc.
569
570        Note: The method currently doesn't support insert into views
571        although PostgreSQL does.
572
573        """
574        qcl = self._add_schema(cl)
575        qoid = _oid_key(qcl)
576        if d is None:
577            d = {}
578        d.update(kw)
579        attnames = self.get_attnames(qcl)
580        names, values = [], []
581        for n in attnames:
582            if n != 'oid' and n in d:
583                names.append('"%s"' % n)
584                values.append(self._quote(d[n], attnames[n]))
585        names, values = ', '.join(names), ', '.join(values)
586        selectable = self.has_table_privilege(qcl)
587        if selectable and self.server_version >= 80200:
588            ret = ' RETURNING %s*' % ('oid' in attnames and 'oid, ' or '')
589        else:
590            ret = ''
591        q = 'INSERT INTO %s (%s) VALUES (%s)%s' % (qcl, names, values, ret)
592        self._do_debug(q)
593        res = self.db.query(q)
594        if ret:
595            res = res.dictresult()
596            for att, value in res[0].iteritems():
597                d[att == 'oid' and qoid or att] = value
598        elif isinstance(res, int):
599            d[qoid] = res
600            if selectable:
601                self.get(qcl, d, 'oid')
602        elif selectable:
603            if qoid in d:
604                self.get(qcl, d, 'oid')
605            else:
606                try:
607                    self.get(qcl, d)
608                except ProgrammingError:
609                    pass  # table has no primary key
610        return d
611
612    def update(self, cl, d=None, **kw):
613        """Update an existing row in a database table.
614
615        Similar to insert but updates an existing row.  The update is based
616        on the OID value as munged by get or passed as keyword, or on the
617        primary key of the table.  The dictionary is modified, if possible,
618        to reflect any changes caused by the update due to triggers, rules,
619        default values, etc.
620
621        """
622        # Update always works on the oid which get returns if available,
623        # otherwise use the primary key.  Fail if neither.
624        # Note that we only accept oid key from named args for safety
625        qcl = self._add_schema(cl)
626        qoid = _oid_key(qcl)
627        if 'oid' in kw:
628            kw[qoid] = kw['oid']
629            del kw['oid']
630        if d is None:
631            d = {}
632        d.update(kw)
633        attnames = self.get_attnames(qcl)
634        if qoid in d:
635            where = 'oid = %s' % d[qoid]
636            keyname = ()
637        else:
638            try:
639                keyname = self.pkey(qcl)
640            except KeyError:
641                raise _prg_error('Class %s has no primary key' % qcl)
642            if isinstance(keyname, basestring):
643                keyname = (keyname,)
644            try:
645                where = ' AND '.join(['%s = %s'
646                    % (k, self._quote(d[k], attnames[k])) for k in keyname])
647            except KeyError:
648                raise _prg_error('Update needs primary key or oid.')
649        values = []
650        for n in attnames:
651            if n in d and n not in keyname:
652                values.append('%s = %s' % (n, self._quote(d[n], attnames[n])))
653        if not values:
654            return d
655        values = ', '.join(values)
656        selectable = self.has_table_privilege(qcl)
657        if selectable and self.server_version >= 880200:
658            ret = ' RETURNING %s*' % ('oid' in attnames and 'oid, ' or '')
659        else:
660            ret = ''
661        q = 'UPDATE %s SET %s WHERE %s%s' % (qcl, values, where, ret)
662        self._do_debug(q)
663        res = self.db.query(q)
664        if ret:
665            res = res.dictresult()[0]
666            for att, value in res.iteritems():
667                d[att == 'oid' and qoid or att] = value
668        else:
669            if selectable:
670                if qoid in d:
671                    self.get(qcl, d, 'oid')
672                else:
673                    self.get(qcl, d)
674        return d
675
676    def clear(self, cl, a=None):
677        """Clear all the attributes to values determined by the types.
678
679        Numeric types are set to 0, Booleans are set to 'f', and everything
680        else is set to the empty string.  If the array argument is present,
681        it is used as the array and any entries matching attribute names are
682        cleared with everything else left unchanged.
683
684        """
685        # At some point we will need a way to get defaults from a table.
686        qcl = self._add_schema(cl)
687        if a is None:
688            a = {}  # empty if argument is not present
689        attnames = self.get_attnames(qcl)
690        for n, t in attnames.iteritems():
691            if n == 'oid':
692                continue
693            if t in ('int', 'integer', 'smallint', 'bigint',
694                    'float', 'real', 'double precision',
695                    'num', 'numeric', 'money'):
696                a[n] = 0
697            elif t in ('bool', 'boolean'):
698                a[n] = 'f'
699            else:
700                a[n] = ''
701        return a
702
703    def delete(self, cl, d=None, **kw):
704        """Delete an existing row in a database table.
705
706        This method deletes the row from a table.  It deletes based on the
707        OID value as munged by get or passed as keyword, or on the primary
708        key of the table.  The return value is the number of deleted rows
709        (i.e. 0 if the row did not exist and 1 if the row was deleted).
710
711        """
712        # Like update, delete works on the oid.
713        # One day we will be testing that the record to be deleted
714        # isn't referenced somewhere (or else PostgreSQL will).
715        # Note that we only accept oid key from named args for safety
716        qcl = self._add_schema(cl)
717        qoid = _oid_key(qcl)
718        if 'oid' in kw:
719            kw[qoid] = kw['oid']
720            del kw['oid']
721        if d is None:
722            d = {}
723        d.update(kw)
724        if qoid in d:
725            where = 'oid = %s' % d[qoid]
726        else:
727            try:
728                keyname = self.pkey(qcl)
729            except KeyError:
730                raise _prg_error('Class %s has no primary key' % qcl)
731            if isinstance(keyname, basestring):
732                keyname = (keyname,)
733            attnames = self.get_attnames(qcl)
734            try:
735                where = ' AND '.join(['%s = %s'
736                    % (k, self._quote(d[k], attnames[k])) for k in keyname])
737            except KeyError:
738                raise _prg_error('Delete needs primary key or oid.')
739        q = 'DELETE FROM %s WHERE %s' % (qcl, where)
740        self._do_debug(q)
741        return int(self.db.query(q))
742
743
744# if run as script, print some information
745
746if __name__ == '__main__':
747    print('PyGreSQL version' + version)
748    print('')
749    print(__doc__)
Note: See TracBrowser for help on using the repository browser.