[DB-SIG] qmark to %s converter

Magnus Lyckå magnus at thinkware.se
Fri May 16 14:57:45 EDT 2003

I think this code could be used to support qmark format on
platforms that support format today. If we want to support
*both* %s and ?, we either need to "autodetect" this, or to
be able to tell the driver what to use. I haven't done that.
Anyway, this is a start, and it works with the SQL statements
I tried. Please try to break it. If it doesn't break, please
use it! :)

It would be great if we could make one paramstyle work with all

#This code converts stuff like "SELECT * FROM ? WHERE X > ?" to
#"SELECT * FROM %s WHERE X > %s". It should be fairly simple to
#make the same thing for :this, :that.

# Magnus Lyckå, 2003-05-16

import re

#Cache parsed statements using
#"statements.setdefault(sql, parseQmark(sql))"
#to save time for repeated calls.
statements = {}

def parseQmark(sql):
     '''parseQmark(sql_with_?) -> sql_with_%s

     Parse SQL written with qmark parameter style (i.e. Direct SQL
     or SQL CLI style) so that the python %-operator can be used to
     pass in parameters. This means that % needs to be changed to %%,
     and ? needs to be changed to %s if it's not inside quotes.

     sql = sql.replace('%', '%%')

     #The tricky part is to separate stuff inside quotes from stuff
     #outside quotes. This is done with a regular expression. We get a
     #list of tuples with [(not quoted, quoted), (not quoted, quoted)...].
     #The present re only works if the string ends with ' though, so
     #we'll append '' and remove it before we return the final string.

     quote_separation = re.compile(r"(.*?)('.*?')", re.DOTALL)
     notQuoted_quoted = quote_separation.findall(sql+"''")
     replaced = [nq.replace('?', '%s')+q for (nq, q) in notQuoted_quoted]

     return "".join(replaced)[:-2] # remove extra ''

# Borrowed from sqlite and patched to allow %f etc (which isn't really
# needed if we use ?, since it'll be %s then anyway... :)
def _quote(value):
     """_quote(value) -> string
     This function transforms the Python value into a string suitable to 
send to
     the SQLite database in a SQL statement.  This function is automatically
     applied to all parameters sent with an execute() call.  Because of this a
     SQL statement string in an execute() call should only use '%s' [or
     '%(name)s'] for variable substitution without any quoting."""
     if value is None:
         return 'NULL'
     elif isinstance(value, str):
         return "'%s'" % value.replace("'", "''")
     elif type(value) in [long, int, float]:
         return value
     elif hasattr(value, '_quote'):
         return value._quote()
# Never mind dates right now...
##    elif have_datetime and type(value) in \
##            (DateTime.DateTimeType, DateTime.DateTimeDeltaType):
##        return "'%s'" % value
         return repr(value)

def execute(sql, params):
     sql = statements.setdefault(sql, parseQmark(sql))
     print sql % tuple(map(_quote, params))

testdata = [

     ("select * from x where i = ? and s = ?",
     (5, "Aren't you done?")),

     ("""insert into x
     (i, f, s) values (?, ?, ?)""",
     (42L, 3.14, 'Hello')),

     ("""SELECT 'It''s'||col2 FROM ?
     WHERE ? LIKE '%aren''t they cute?%'
     AND x = ?""",
     ('table1', 'col1', 5)),

     ("""SELECT 'It''s'||col2 FROM ?
     WHERE ? = 'aren''t they cute?'
     AND x = '?'""",
     ('table1', 'col1'))


for sql, params in testdata:
     execute(sql, params)

Magnus Lycka (It's really Lyckå), magnus at thinkware.se
Thinkware AB, Sweden, www.thinkware.se
I code Python ~ The shortest path from thought to working program 

More information about the DB-SIG mailing list