Code

mysql backend passes all tests (at last!)
authorkedder <kedder@57a73879-2fb5-44c3-a270-3262357dd7e2>
Sat, 8 Feb 2003 15:31:28 +0000 (15:31 +0000)
committerkedder <kedder@57a73879-2fb5-44c3-a270-3262357dd7e2>
Sat, 8 Feb 2003 15:31:28 +0000 (15:31 +0000)
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@1493 57a73879-2fb5-44c3-a270-3262357dd7e2

roundup/backends/back_mysql.py
roundup/backends/rdbms_common.py
test/test_db.py

index fc7162b0ee7ccf8f42dea6712852570b0afade86..4c9c8af34ea8a3b797f0276e17c5397b9041d90f 100644 (file)
@@ -1,4 +1,15 @@
+#
+# Copyright (c) 2003 Martynas Sklyzmantas, Andrey Lebedev
+#
+# This module is free software, and you may redistribute it and/or modify
+# under the same terms as Python, so long as this copyright message and
+# disclaimer are retained in their original form.
+#
+# Mysql backend for roundup
+#
+
 from roundup.backends.rdbms_common import *
+from roundup.backends import rdbms_common
 import MySQLdb
 from MySQLdb.constants import ER
 
@@ -13,14 +24,17 @@ class Database(Database):
             raise DatabaseError, message
 
         self.cursor = self.conn.cursor()
+        # start transaction
+        self.sql("SET AUTOCOMMIT=0")
+        self.sql("BEGIN")
         try:
             self.database_schema = self.load_dbschema()
         except MySQLdb.ProgrammingError, message:
             if message[0] != ER.NO_SUCH_TABLE:
                 raise DatabaseError, message
             self.database_schema = {}
-            self.cursor.execute("CREATE TABLE schema (schema TEXT)")
-            self.cursor.execute("CREATE TABLE ids (name varchar(255), num INT)")
+            self.sql("CREATE TABLE schema (schema TEXT) TYPE=BDB")
+            self.sql("CREATE TABLE ids (name varchar(255), num INT) TYPE=BDB")
     
     def close(self):
         try:
@@ -43,7 +57,10 @@ class Database(Database):
     
     def load_dbschema(self):
         self.cursor.execute('SELECT schema FROM schema')
-        return eval(self.cursor.fetchone()[0])
+        schema = self.cursor.fetchone()
+        if schema:
+            return eval(schema[0])
+        return None
 
     def save_journal(self, classname, cols, nodeid, journaldate,
                 journaltag, action, params):
@@ -74,7 +91,7 @@ class Database(Database):
         cols.append('id')
         cols.append('__retired__')
         scols = ',' . join(['`%s` VARCHAR(255)'%x for x in cols])
-        sql = 'CREATE TABLE `_%s` (%s)'%(spec.classname, scols)
+        sql = 'CREATE TABLE `_%s` (%s) TYPE=BDB'%(spec.classname, scols)
         if __debug__:
           print >>hyperdb.DEBUG, 'create_class', (self, sql)
         self.cursor.execute(sql)
@@ -83,16 +100,91 @@ class Database(Database):
     def create_journal_table(self, spec):
         cols = ',' . join(['`%s` VARCHAR(255)'%x
           for x in 'nodeid date tag action params' . split()])
-        sql  = 'CREATE TABLE `%s__journal` (%s)'%(spec.classname, cols)
+        sql  = 'CREATE TABLE `%s__journal` (%s) TYPE=BDB'%(spec.classname, cols)
         if __debug__:
             print >>hyperdb.DEBUG, 'create_class', (self, sql)
         self.cursor.execute(sql)
 
     def create_multilink_table(self, spec, ml):
         sql = '''CREATE TABLE `%s_%s` (linkid VARCHAR(255),
-            nodeid VARCHAR(255))'''%(spec.classname, ml)
+            nodeid VARCHAR(255)) TYPE=BDB'''%(spec.classname, ml)
         if __debug__:
           print >>hyperdb.DEBUG, 'create_class', (self, sql)
         self.cursor.execute(sql)
 
+class MysqlClass:
+    def find(self, **propspec):
+        '''Get the ids of nodes in this class which link to the given nodes.
+
+        Since MySQL < 4.0.0 does not support unions, so we overrideg this
+        method without using this keyword
+
+        '''
+        if __debug__:
+            print >>hyperdb.DEBUG, 'find', (self, propspec)
+
+        # shortcut
+        if not propspec:
+            return []
+
+        # validate the args
+        props = self.getprops()
+        propspec = propspec.items()
+        for propname, nodeids in propspec:
+            # check the prop is OK
+            prop = props[propname]
+            if not isinstance(prop, Link) and not isinstance(prop, Multilink):
+                raise TypeError, "'%s' not a Link/Multilink property"%propname
+
+        # first, links
+        l = []
+        where = []
+        allvalues = ()
+        a = self.db.arg
+        for prop, values in propspec:
+            if not isinstance(props[prop], hyperdb.Link):
+                continue
+            if type(values) is type(''):
+                allvalues += (values,)
+                where.append('_%s = %s'%(prop, a))
+            else:
+                allvalues += tuple(values.keys())
+                where.append('_%s in (%s)'%(prop, ','.join([a]*len(values))))
+        tables = []
+        if where:
+            self.db.sql('select id as nodeid from _%s where %s' % (self.classname, ' and '.join(where)), allvalues)
+            l += [x[0] for x in self.db.sql_fetchall()]
+
+        # now multilinks
+        for prop, values in propspec:
+            vals = ()
+            if not isinstance(props[prop], hyperdb.Multilink):
+                continue
+            if type(values) is type(''):
+                vals = (values,)
+                s = a
+            else:
+                vals = tuple(values.keys())
+                s = ','.join([a]*len(values))
+            query = 'select nodeid from %s_%s where linkid in (%s)'%(
+                self.classname, prop, s)
+            self.db.sql(query, vals)
+            l += [x[0] for x in self.db.sql_fetchall()]
+        if __debug__:
+            print >>hyperdb.DEBUG, 'find ... ', l
+
+        # Remove duplicated ids
+        d = {}
+        for k in l:
+            d[k] = 1
+        return d.keys()
+
+        return l
+
+class Class(MysqlClass, rdbms_common.Class):
+    pass
+class IssueClass(MysqlClass, rdbms_common.IssueClass):
+    pass
+class FileClass(MysqlClass, rdbms_common.FileClass):
+    pass
 
index 002f5a721fceaac5f8901343d8bca066fa6873e6..3d41cafeafb3e85ea2aa12f4c0f713c92fd96fc0 100644 (file)
@@ -1,4 +1,4 @@
-# $Id: rdbms_common.py,v 1.30 2003-02-06 05:43:47 richard Exp $
+# $Id: rdbms_common.py,v 1.31 2003-02-08 15:31:28 kedder Exp $
 ''' Relational database (SQL) backend common code.
 
 Basics:
@@ -738,6 +738,8 @@ class Database(FileStorage, hyperdb.Database, roundupdb.Database):
                 p = password.Password()
                 p.unpack(v)
                 d[k] = p
+            elif (isinstance(prop, Boolean) or isinstance(prop, Number)) and v is not None:
+                d[k]=float(v)
             else:
                 d[k] = v
         return d
index 7f2a209f1786b7204659ebb7121b3fd3730a3cbd..5c078bbeb21985d0fdadd0632fbcad3421cd1628 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: test_db.py,v 1.68 2003-01-20 23:03:41 richard Exp $ 
+# $Id: test_db.py,v 1.69 2003-02-08 15:31:28 kedder Exp $ 
 
 import unittest, os, shutil, time
 
@@ -732,7 +732,14 @@ class mysqlDBTestCase(anydbmDBTestCase):
         config.MYSQL_DATABASE = ('localhost', 'rounduptest', 'rounduptest',
             'rounduptest')
         os.makedirs(config.DATABASE + '/files')
+        # open database for cleaning
         self.db = mysql.Database(config, 'admin')
+        self.db.sql("DROP DATABASE %s" % config.MYSQL_DATABASE[1])
+        self.db.sql("CREATE DATABASE %s" % config.MYSQL_DATABASE[1])
+        self.db.close()
+        # open database for testing
+        self.db = mysql.Database(config, 'admin')
+        
         setupSchema(self.db, 1, mysql)
 
 class mysqlReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
@@ -744,13 +751,15 @@ class mysqlReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
         config.MYSQL_DATABASE = ('localhost', 'rounduptest', 'rounduptest',
             'rounduptest')
         os.makedirs(config.DATABASE + '/files')
-        db = mysql.Database(config, 'admin')
-        setupSchema(db, 1, mysql)
-        db.close()
-        self.db = sqlite.Database(config)
+        # open database for cleaning
+        self.db = mysql.Database(config, 'admin')
+        self.db.sql("DROP DATABASE %s" % config.MYSQL_DATABASE[1])
+        self.db.sql("CREATE DATABASE %s" % config.MYSQL_DATABASE[1])
+        self.db.close()
+        # open database for testing
+        self.db = mysql.Database(config)
         setupSchema(self.db, 0, mysql)
 
-
 class sqliteDBTestCase(anydbmDBTestCase):
     def setUp(self):
         from roundup.backends import sqlite
@@ -846,19 +855,20 @@ class metakitReadOnlyDBTestCase(anydbmReadOnlyDBTestCase):
         setupSchema(self.db, 0, metakit)
 
 def suite():
-    l = [
-         unittest.makeSuite(anydbmDBTestCase, 'test'),
-         unittest.makeSuite(anydbmReadOnlyDBTestCase, 'test')
-    ]
+    l = []
+#    l = [
+#         unittest.makeSuite(anydbmDBTestCase, 'test'),
+#         unittest.makeSuite(anydbmReadOnlyDBTestCase, 'test')
+#    ]
 #    return unittest.TestSuite(l)
 
     from roundup import backends
     p = []
-#    if hasattr(backends, 'mysql'):
-#        p.append('mysql')
-#        l.append(unittest.makeSuite(mysqlDBTestCase, 'test'))
-#        l.append(unittest.makeSuite(mysqlReadOnlyDBTestCase, 'test'))
-#    return unittest.TestSuite(l)
+    if hasattr(backends, 'mysql'):
+        p.append('mysql')
+        l.append(unittest.makeSuite(mysqlDBTestCase, 'test'))
+        l.append(unittest.makeSuite(mysqlReadOnlyDBTestCase, 'test'))
+    #return unittest.TestSuite(l)
 
     if hasattr(backends, 'gadfly'):
         p.append('gadfly')