Changeset 730 for trunk/pg.py


Ignore:
Timestamp:
Jan 12, 2016, 8:58:54 PM (4 years ago)
Author:
cito
Message:

Use query parameters instead of inline values

The single row methods of the DB wrapper class created queries with inline values
instead of passing them separately as parameters, even though our query method
does have this capability. Using query parameters also spares us a lot of quoting
and escaping that is necessary when passing values inline.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/pg.py

    r729 r730  
    3838from decimal import Decimal
    3939from collections import namedtuple
    40 from itertools import groupby
     40from functools import partial
    4141
    4242try:
     
    316316    # Auxiliary methods
    317317
    318     def _do_debug(self, s):
     318    def _do_debug(self, *args):
    319319        """Print a debug message."""
    320320        if self.debug:
     321            s = '\n'.join(args)
    321322            if isinstance(self.debug, basestring):
    322323                print(self.debug % s)
     
    333334        return bool(d) if get_bool() else ('t' if d else 'f')
    334335
    335     def _quote_text(self, d):
    336         """Quote text value."""
    337         if not isinstance(d, basestring):
    338             d = str(d)
    339         return "'%s'" % self.escape_string(d)
    340 
    341     _bool_true = frozenset('t true 1 y yes on'.split())
    342 
    343     def _quote_bool(self, d):
    344         """Quote boolean value."""
     336    _bool_true_values = frozenset('t true 1 y yes on'.split())
     337
     338    def _prepare_bool(self, d):
     339        """Prepare a boolean parameter."""
    345340        if isinstance(d, basestring):
    346341            if not d:
    347                 return 'NULL'
    348             d = d.lower() in self._bool_true
    349         return "'t'" if d else "'f'"
     342                return None
     343            d = d.lower() in self._bool_true_values
     344        return 't' if d else 'f'
    350345
    351346    _date_literals = frozenset('current_date current_time'
    352347        ' current_timestamp localtime localtimestamp'.split())
    353348
    354     def _quote_date(self, d):
    355         """Quote date value."""
     349    def _prepare_date(self, d):
     350        """Prepare a date parameter."""
    356351        if not d:
    357             return 'NULL'
     352            return None
    358353        if isinstance(d, basestring) and d.lower() in self._date_literals:
    359             return d
    360         return self._quote_text(d)
    361 
    362     def _quote_num(self, d):
    363         """Quote numeric value."""
     354            raise ValueError
     355        return d
     356
     357    def _prepare_num(self, d):
     358        """Prepare a numeric parameter."""
    364359        if not d and d != 0:
    365             return 'NULL'
    366         return str(d)
    367 
    368     def _quote_money(self, d):
    369         """Quote money value."""
    370         if d is None or d == '':
    371             return 'NULL'
    372         if not isinstance(d, basestring):
    373             d = str(d)
     360            return None
    374361        return d
    375362
    376     if bytes is str:  # Python < 3.0
    377         """Quote bytes value."""
    378 
    379         def _quote_bytea(self, d):
    380             return "'%s'" % self.escape_bytea(d)
    381 
    382     else:
    383 
    384         def _quote_bytea(self, d):
    385             return "'%s'" % self.escape_bytea(d).decode('ascii')
    386 
    387     _quote_funcs = dict(  # quote methods for each type
    388         text=_quote_text, bool=_quote_bool, date=_quote_date,
    389         int=_quote_num, num=_quote_num, float=_quote_num,
    390         money=_quote_money, bytea=_quote_bytea)
    391 
    392     def _quote(self, d, t):
    393         """Return quotes if needed."""
    394         if d is None:
    395             return 'NULL'
    396         try:
    397             quote_func = self._quote_funcs[t]
    398         except KeyError:
    399             quote_func = self._quote_funcs['text']
    400         return quote_func(self, d)
     363    def _prepare_bytea(self, d):
     364        return self.escape_bytea(d)
     365
     366    _prepare_funcs = dict(  # quote methods for each type
     367        bool=_prepare_bool, date=_prepare_date,
     368        int=_prepare_num, num=_prepare_num, float=_prepare_num,
     369        money=_prepare_num, bytea=_prepare_bytea)
     370
     371    def _prepare_param(self, value, typ, params):
     372        """Prepare and add a parameter to the list."""
     373        if value is not None and typ != 'text':
     374            try:
     375                prepare = self._prepare_funcs[typ]
     376            except KeyError:
     377                pass
     378            else:
     379                try:
     380                    value = prepare(self, value)
     381                except ValueError:
     382                    return value
     383        params.append(value)
     384        return '$%d' % len(params)
    401385
    402386    # Public methods
     
    579563            attnames.clear()
    580564            self._do_debug('pkey cache has been flushed')
    581 
    582565        try:  # cache lookup
    583566            names = attnames[cl]
     
    652635                raise _prg_error('Class %s has no primary key' % cl)
    653636        attnames = self.get_attnames(cl)
     637        params = []
     638        param = partial(self._prepare_param, params=params)
    654639        # We want the oid for later updates if that isn't the key
    655640        if keyname == 'oid':
     
    660645                arg = {qoid: arg}
    661646            what = '*'
    662             where = 'oid = %s' % arg[qoid]
     647            where = 'oid = %s' % param(arg[qoid], 'int')
    663648        else:
    664649            if isinstance(keyname, basestring):
     
    670655            what = ', '.join(attnames)
    671656            where = ' AND '.join(['%s = %s'
    672                 % (k, self._quote(arg[k], attnames[k])) for k in keyname])
     657                % (k, param(arg[k], attnames[k])) for k in keyname])
    673658        q = 'SELECT %s FROM %s WHERE %s LIMIT 1' % (
    674659            what, _quote_class_name(cl), where)
    675         self._do_debug(q)
    676         res = self.db.query(q).dictresult()
     660        self._do_debug(q, params)
     661        res = self.db.query(q, params).dictresult()
    677662        if not res:
    678663            raise _db_error('No such record in %s where %s' % (cl, where))
     
    707692        d.update(kw)
    708693        attnames = self.get_attnames(cl)
     694        params = []
     695        param = partial(self._prepare_param, params=params)
    709696        names, values = [], []
    710697        for n in attnames:
    711698            if n != 'oid' and n in d:
    712699                names.append('"%s"' % n)
    713                 values.append(self._quote(d[n], attnames[n]))
     700                values.append(param(d[n], attnames[n]))
    714701        names, values = ', '.join(names), ', '.join(values)
    715702        selectable = self.has_table_privilege(cl)
     
    720707        q = 'INSERT INTO %s (%s) VALUES (%s)%s' % (
    721708            _quote_class_name(cl), names, values, ret)
    722         self._do_debug(q)
    723         res = self.db.query(q)
     709        self._do_debug(q, params)
     710        res = self.db.query(q, params)
    724711        if ret:
    725712            res = res.dictresult()[0]
     
    727714                if n == 'oid':
    728715                    n = qoid
    729                 elif attnames.get(n) == 'bytea':
     716                elif attnames.get(n) == 'bytea' and value is not None:
    730717                    value = self.unescape_bytea(value)
    731718                d[n] = value
     
    765752        d.update(kw)
    766753        attnames = self.get_attnames(cl)
     754        params = []
     755        param = partial(self._prepare_param, params=params)
    767756        if qoid in d:
    768             where = 'oid = %s' % d[qoid]
     757            where = 'oid = %s' % param(d[qoid], 'int')
    769758            keyname = ()
    770759        else:
     
    777766            try:
    778767                where = ' AND '.join(['%s = %s'
    779                     % (k, self._quote(d[k], attnames[k])) for k in keyname])
     768                    % (k, param(d[k], attnames[k])) for k in keyname])
    780769            except KeyError:
    781770                raise _prg_error('Update needs primary key or oid.')
     
    783772        for n in attnames:
    784773            if n in d and n not in keyname:
    785                 values.append('%s = %s' % (n, self._quote(d[n], attnames[n])))
     774                values.append('%s = %s' % (n, param(d[n], attnames[n])))
    786775        if not values:
    787776            return d
     
    794783        q = 'UPDATE %s SET %s WHERE %s%s' % (
    795784            _quote_class_name(cl), values, where, ret)
    796         self._do_debug(q)
    797         res = self.db.query(q)
     785        self._do_debug(q, params)
     786        res = self.db.query(q, params)
    798787        if ret:
    799788            res = res.dictresult()[0]
     
    801790                if n == 'oid':
    802791                    n = qoid
    803                 elif attnames.get(n) == 'bytea':
     792                elif attnames.get(n) == 'bytea' and value is not None:
    804793                    value = self.unescape_bytea(value)
    805794                d[n] = value
     
    858847            d = {}
    859848        d.update(kw)
     849        params = []
     850        param = partial(self._prepare_param, params=params)
    860851        if qoid in d:
    861             where = 'oid = %s' % d[qoid]
     852            where = 'oid = %s' % param(d[qoid], 'int')
    862853        else:
    863854            try:
     
    870861            try:
    871862                where = ' AND '.join(['%s = %s'
    872                     % (k, self._quote(d[k], attnames[k])) for k in keyname])
     863                    % (k, param(d[k], attnames[k])) for k in keyname])
    873864            except KeyError:
    874865                raise _prg_error('Delete needs primary key or oid.')
    875866        q = 'DELETE FROM %s WHERE %s' % (_quote_class_name(cl), where)
    876         self._do_debug(q)
    877         return int(self.db.query(q))
     867        self._do_debug(q, params)
     868        return int(self.db.query(q, params))
    878869
    879870    def notification_handler(self, event, callback, arg_dict={}, timeout=None):
Note: See TracChangeset for help on using the changeset viewer.