[Zope-CVS] CVS: Products/Ape/lib/apelib/sql - dbapi.py:1.1 keygen.py:1.3 querygen.py:1.2 pg.py:NONE

Shane Hathaway shane@zope.com
Sat, 12 Apr 2003 16:56:57 -0400


Update of /cvs-repository/Products/Ape/lib/apelib/sql
In directory cvs.zope.org:/tmp/cvs-serv28924/lib/apelib/sql

Modified Files:
	keygen.py querygen.py 
Added Files:
	dbapi.py 
Removed Files:
	pg.py 
Log Message:
Implemented support for MySQL 4.0, this time for real.  The change required
further abstraction of the query generator.  Supporting more databases
should now be straightforward.


=== Added File Products/Ape/lib/apelib/sql/dbapi.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""DB-API to Ape gateway connectivity

$Id: dbapi.py,v 1.1 2003/04/12 20:56:26 shane Exp $
"""

import os
from time import time
from types import StringType

from apelib.core.interfaces import ITPCConnection
from querygen import makeQueryGenerator

DEBUG = os.environ.get('APE_DEBUG_SQL')
PROFILE = os.environ.get('APE_PROFILE_SQL')


class DBAPIConnector:

    __implements__ = ITPCConnection

    _final = 0
    db = None
    cursor = None

    def __init__(self, module_name, params=(), kwparams='', prefix='zodb'):
        self.module_name = module_name
        self.module = __import__(module_name, {}, {}, ('__doc__',))
        self.error = self.module.DatabaseError
        self.prefix = prefix
        self.connect_callbacks = []
        if isinstance(kwparams, StringType):
            # Convert the keyword string to a dictionary.
            kw = {}
            for p in kwparams.split():
                k, v = p.split(':')
                kw[k.strip()] = v.strip()
            kwparams = kw
        self.kwparams = kwparams
        if isinstance(params, StringType):
            params = (params,)
        self.params = params

    def makeQueryGenerator(self, table_name, column_defs):
        return makeQueryGenerator(self.module_name, table_name, column_defs)

    def isConnected(self):
        return (self.db is not None)

    def addConnectCallback(self, f):
        self.connect_callbacks.append(f)

    def connect(self):
        if self.kwparams:
            self.db = self.module.connect(*self.params, **self.kwparams)
        else:
            self.db = self.module.connect(*self.params)
        self.cursor = self.db.cursor()
        for f in self.connect_callbacks:
            f()
        self.connect_callbacks = []

    def sortKey(self):
        return repr(self)

    def getName(self):
        return repr(self)

    def execute(self, query, fetch=0, cursor=None, **kw):
        if cursor is None:
            cursor = self.cursor
            if cursor is None:
                raise RuntimeError('Not connected')
        if DEBUG or PROFILE:
            if kw:
                print 'Query: %s, %s' % (repr(query), kw)
            else:
                print 'Query: %s' % repr(query)
        if PROFILE:
            start = time()
            cursor.execute(query, kw)
            end = time()
            print 'Query time: %0.6fs' % (end - start)
        else:
            cursor.execute(query, kw)
        if fetch:
            res = list(cursor.fetchall())
            if DEBUG:
                print 'Query result: %s' % repr(res)
            return res
        return None

    def asBinary(self, data):
        return self.module.Binary(data)

    def begin(self):
        pass

    def vote(self):
        self._final = 1

    def reset(self):
        self._final = 0

    def abort(self):
        try:
            self.db.rollback()
        finally:
            self.reset()

    def finish(self):
        if self._final:
            try:
                self.db.commit()
            finally:
                self.reset()

    def close(self):
        if self.isConnected():
            self.cursor.close()
            self.cursor = None
            self.db.close()
            self.db = None



=== Products/Ape/lib/apelib/sql/keygen.py 1.2 => 1.3 ===
--- Products/Ape/lib/apelib/sql/keygen.py:1.2	Fri Apr 11 02:17:49 2003
+++ Products/Ape/lib/apelib/sql/keygen.py	Sat Apr 12 16:56:26 2003
@@ -31,6 +31,21 @@
         gen = self.conn.makeQueryGenerator(self.table, ())
         self.queries = gen.generateAllForSequence()
 
+    def setUpTables(self):
+        conn = self.conn
+        insert = 0
+        try:
+            rows = self.execute('check', 1)
+            if len(rows) != 1:
+                insert = 1
+        except conn.error:
+            conn.db.rollback()
+            self.execute('create')
+            insert = 1
+        if insert and self.queries.get('insert'):
+            self.execute('insert')
+        conn.db.commit()
+
     def makeKeychain(self, event, name, stored):
         if not stored:
             raise RuntimeError(
@@ -39,6 +54,8 @@
             # Request that the other side do the work (for ZEO)
             n = event.getKeyedObjectSystem().newKey()
         else:
+            if self.queries.get('update'):
+                self.execute('update')
             n = self.execute('read', 1)[0][0]
         return event.getKeychain()[:-1] + (long(n),)
 


=== Products/Ape/lib/apelib/sql/querygen.py 1.1 => 1.2 ===
--- Products/Ape/lib/apelib/sql/querygen.py:1.1	Fri Apr 11 02:17:49 2003
+++ Products/Ape/lib/apelib/sql/querygen.py	Sat Apr 12 16:56:26 2003
@@ -16,16 +16,24 @@
 $Id$
 """
 
-class QueryGenerator:
+query_generator_factories = {}
 
-    db_column_types = {
-        'int':    'int',
-        'long':   'bigint',
-        'string': 'character varying(255)',
-        'blob':   'bytea',
-        }
+def addFactory(name, f):
+    if query_generator_factories.has_key(name):
+        raise KeyError("Factory name %s conflicts" % repr(name))
+    query_generator_factories[name] = f
 
-    db_column_names = {}
+def makeQueryGenerator(name, table_name, column_defs):
+    f = query_generator_factories.get(name)
+    if f is None:
+        raise KeyError("No query generator registered for %s" % repr(name))
+    return f(table_name, column_defs)
+
+
+class AbstractQueryGenerator:
+
+    column_type_translations = None  # { local type name -> db type name }
+    column_name_translations = None  # { local col name -> db col name }
 
     key_column = ('key', 'int', 1)
 
@@ -39,14 +47,14 @@
 
         Defaults to no translation.
         """
-        return self.db_column_names.get(column_name, column_name)
+        return self.column_name_translations.get(column_name, column_name)
 
     def translateType(self, column_type):
         """Returns a database type for a variable type.
 
         If the type is unknown, raises KeyError.
         """
-        return self.db_column_types[column_type]
+        return self.column_type_translations[column_type]
 
 
     def generateAll(self):
@@ -131,7 +139,7 @@
 
     def generateClear(self):
         return 'DELETE FROM %s' % self.table_name
-        
+
 
     def generateAllForSequence(self):
         table_name = self.table_name
@@ -142,3 +150,47 @@
             'clear':  "SELECT setval('%s', 1)" % table_name,
             }
         return res
+
+
+class PostgreSQLQueryGenerator (AbstractQueryGenerator):
+
+    column_type_translations = {
+        'int':    'int',
+        'long':   'bigint',
+        'string': 'character varying(255)',
+        'blob':   'bytea',
+        }
+
+    column_name_translations = {}
+
+addFactory('psycopg', PostgreSQLQueryGenerator)
+
+
+class MySQLQueryGenerator (AbstractQueryGenerator):
+
+    column_type_translations = {
+        'int':    'int',
+        'long':   'bigint',
+        'string': 'character varying(255)',
+        'blob':   'longblob',
+        }
+
+    column_name_translations = {
+        'key': 'objkey',
+        }
+
+    def generateAllForSequence(self):
+        table_name = self.table_name
+        res = {
+            'check':  "SELECT last_value FROM %s" % table_name,
+            'create': "CREATE TABLE %s (last_value int)" % table_name,
+            'insert': "INSERT INTO %s VALUES (0)" % table_name,
+            'update': ("UPDATE %s SET last_value=LAST_INSERT_ID(last_value+1)"
+                       % table_name),
+            'read':   "SELECT LAST_INSERT_ID()",
+            'clear':  "UPDATE %s SET last_value=0" % table_name,
+            }
+        return res
+
+addFactory('MySQLdb', MySQLQueryGenerator)
+

=== Removed File Products/Ape/lib/apelib/sql/pg.py ===