[POS-commit] r6302 - in stoqlib/trunk: data/sql external/sqlobject/postgres stoqlib stoqlib/database stoqlib/domain

Johan Dahlin jdahlin at async.com.br
Tue Apr 3 19:20:06 BRT 2007


Author: jdahlin
Date: Tue Apr  3 19:20:05 2007
New Revision: 6302

Added:
   stoqlib/trunk/data/sql/patch-1.sql
Removed:
   stoqlib/trunk/data/sql/postgres-schema-migration-6.sql
   stoqlib/trunk/data/sql/postgres-schema-migration-7.sql
Modified:
   stoqlib/trunk/external/sqlobject/postgres/pgconnection.py
   stoqlib/trunk/stoqlib/__init__.py
   stoqlib/trunk/stoqlib/database/admin.py
   stoqlib/trunk/stoqlib/database/migration.py
   stoqlib/trunk/stoqlib/domain/system.py

Log:
#2752: Rework database migration


Added: stoqlib/trunk/data/sql/patch-1.sql
==============================================================================
--- (empty file)
+++ stoqlib/trunk/data/sql/patch-1.sql	Tue Apr  3 19:20:05 2007
@@ -0,0 +1,5 @@
+-- #2752: Rework database migration
+DELETE FROM system_table;
+ALTER TABLE system_table ADD COLUMN generation integer;
+ALTER TABLE system_table RENAME COLUMN version TO patchlevel;
+ALTER TABLE system_table RENAME COLUMN update_date TO updated;

Modified: stoqlib/trunk/external/sqlobject/postgres/pgconnection.py
==============================================================================
--- stoqlib/trunk/external/sqlobject/postgres/pgconnection.py	(original)
+++ stoqlib/trunk/external/sqlobject/postgres/pgconnection.py	Tue Apr  3 19:20:05 2007
@@ -216,6 +216,15 @@
             name)
         return res[0] == 1
 
+    def tableHasColumn(self, table_name, column):
+        res = self.queryOne(
+            """SELECT 1 FROM pg_class, pg_attribute
+             WHERE pg_attribute.attrelid = pg_class.oid AND
+                   pg_class.relname=%s AND
+                   attname=%s""" % (self.sqlrepr(table_name),
+                                    self.sqlrepr(column)))
+        return bool(res)
+
     def dbVersion(self):
         version_string = self.queryOne('SELECT VERSION();')[0]
         version = version_string.split(' ', 2)[1]

Modified: stoqlib/trunk/stoqlib/__init__.py
==============================================================================
--- stoqlib/trunk/stoqlib/__init__.py	(original)
+++ stoqlib/trunk/stoqlib/__init__.py	Tue Apr  3 19:20:05 2007
@@ -29,8 +29,6 @@
 
 from kiwi.environ import Library
 
-FIRST_DB_VERSION = 5
-
 program_name    = "Stoqlib"
 website         = 'http://www.stoq.com.br/'
 version         = "0.8.10"

Modified: stoqlib/trunk/stoqlib/database/admin.py
==============================================================================
--- stoqlib/trunk/stoqlib/database/admin.py	(original)
+++ stoqlib/trunk/stoqlib/database/admin.py	Tue Apr  3 19:20:05 2007
@@ -2,7 +2,7 @@
 # vi:si:et:sw=4:sts=4:ts=4
 
 ##
-## Copyright (C) 2005 Async Open Source <http://www.async.com.br>
+## Copyright (C) 2005-2007 Async Open Source <http://www.async.com.br>
 ## All rights reserved
 ##
 ## This program is free software; you can redistribute it and/or modify
@@ -38,6 +38,7 @@
 
 from stoqlib.database.database import execute_sql, clean_database
 from stoqlib.database.interfaces import ICurrentUser, IDatabaseSettings
+from stoqlib.database.migration import schema_migration
 from stoqlib.database.runtime import new_transaction
 from stoqlib.domain.interfaces import (IIndividual, IEmployee, IUser,
                                        ISalesPerson)
@@ -45,7 +46,6 @@
 from stoqlib.domain.person import EmployeeRoleHistory
 from stoqlib.domain.profile import UserProfile
 from stoqlib.domain.sellable import SellableTaxConstant, SellableUnit
-from stoqlib.domain.system import SystemTable
 from stoqlib.exceptions import StoqlibError
 from stoqlib.lib.parameters import sysparam, ensure_system_parameters
 from stoqlib.lib.translation import stoqlib_gettext
@@ -205,6 +205,8 @@
     schema = environ.find_resource('sql', 'views.sql')
     execute_sql(schema)
 
+    schema_migration.update_schema()
+
 def create_default_profiles():
     trans = new_transaction()
 
@@ -228,7 +230,3 @@
     ensure_sellable_constants()
     ensure_system_parameters()
     create_default_profiles()
-
-    trans = new_transaction()
-    SystemTable.update(trans, check_new_db=True)
-    trans.commit(close=True)

Modified: stoqlib/trunk/stoqlib/database/migration.py
==============================================================================
--- stoqlib/trunk/stoqlib/database/migration.py	(original)
+++ stoqlib/trunk/stoqlib/database/migration.py	Tue Apr  3 19:20:05 2007
@@ -2,7 +2,7 @@
 # vi:si:et:sw=4:sts=4:ts=4
 
 ##
-## Copyright (C) 2006 Async Open Source <http://www.async.com.br>
+## Copyright (C) 2007 Async Open Source <http://www.async.com.br>
 ## All rights reserved
 ##
 ## This program is free software; you can redistribute it and/or modify
@@ -20,102 +20,126 @@
 ## Foundation, Inc., or visit: http://www.gnu.org/.
 ##
 ## Author(s):       Evandro Vale Miquelito      <evandro at async.com.br>
+##                  Johan Dahlin                <jdahlin at async.com.br>
 ##
 ##
 """ Routines for database schema migration"""
 
-from kiwi.component import get_utility
+import glob
+import operator
+import os
+import shutil
+import tempfile
+
 from kiwi.environ import environ
 
-import stoqlib
-from stoqlib.database.admin import create_base_schema
 from stoqlib.database.database import execute_sql
-from stoqlib.database.interfaces import IDatabaseSettings
-from stoqlib.database.runtime import new_transaction
+from stoqlib.database.runtime import new_transaction, get_connection
 from stoqlib.domain.profile import update_profile_applications
 from stoqlib.domain.system import SystemTable
 from stoqlib.exceptions import DatabaseInconsistency
+from stoqlib.lib.defaults import stoqlib_gettext
 from stoqlib.lib.parameters import (check_parameter_presence,
                                     ensure_system_parameters)
 
+_ = stoqlib_gettext
+
+def _extract_version(patch_filename):
+    return int(patch_filename[:-4].split('-', 1)[1])
+
 class SchemaMigration:
     """Schema migration management"""
 
-    def __init__(self):
-        self.current_db_version = None
-        self.db_version = stoqlib.db_version
-
-    def _get_migration_files(self, current_db_version, db_version):
-        """Returns a list of all the migration sql files for a certain
-        db schema version
-        """
-        rdbms_name = get_utility(IDatabaseSettings).rdbms
-        migration_files = []
-        for version in range(current_db_version + 1, self.db_version +1):
-            filename = '%s-schema-migration-%s.sql' % (rdbms_name, version)
-            sql_file = environ.find_resource('sql', filename)
-            migration_files.append(sql_file)
-        return migration_files
+    def _get_patches(self):
+        patches = []
+        for directory in environ.get_resource_paths('sql'):
+            for patch in glob.glob(os.path.join(directory, 'patch-*.sql')):
+                patches.append((patch, _extract_version(os.path.basename(patch))))
+        return sorted(patches, key=operator.itemgetter(1))
+
+    def _get_generation(self, conn, current_version):
+        if current_version > 0:
+            generation = SystemTable.select(connection=conn).max('generation')
+        else:
+            generation = 0
+
+        return generation
 
     def _check_up_to_date(self, conn):
-        """Checks if the current database schema is up to date"""
-        if not conn.tableExists(SystemTable.sqlmeta.table):
-            SystemTable.createTable(connection=conn)
-            self.current_db_version = stoqlib.FIRST_DB_VERSION
-            SystemTable.update(conn, check_new_db=True,
-                               version=self.current_db_version)
+        # Fetch the latest, eg the last in the list
+        patches = self._get_patches()
+        unused, latest_available = patches[-1]
+
+        current_version = self.get_current_version(conn)
+        if current_version == latest_available:
             return True
-        results = SystemTable.select(connection=conn)
-        self.current_db_version = results.max('version')
-        if self.current_db_version == self.db_version:
-            return False
-        if self.current_db_version > self.db_version:
-            raise DatabaseInconsistency('The current version of database '
-                                        '(%s) is greater than the system '
-                                        'version (%s)'
-                                        % (self.current_db_version,
-                                           self.db_version))
-        return True
+        elif current_version > latest_available:
+            raise DatabaseInconsistency(
+                'The current version of database (%d) is greater than the '
+                'latest available version (%d)'
+                % (current_version, latest_available))
+
+        return False
 
     def check_updated(self, conn):
         if not self._check_up_to_date(conn):
-            return True
+            return False
 
-        if check_parameter_presence(conn):
-            return True
+        if not check_parameter_presence(conn):
+            return False
 
-        return False
+        return True
 
     def update_schema(self):
         """Check the current version of database and update the schema if
         it's needed
         """
-        trans = new_transaction()
+        conn = get_connection()
 
-        if not self._check_up_to_date(trans):
-            trans.commit(close=True)
+        if self._check_up_to_date(conn):
             return
 
-        # Updating the schema for all the versions from the current database
-        # version to the last schema version.
-        sql_files = self._get_migration_files(self.current_db_version,
-                                              self.db_version)
-        for sql_file in sql_files:
-            execute_sql(sql_file, trans)
-            parts = sql_file.replace('.sql', '').split('-')
-            version = parts[-1]
-            try:
-                version = int(version)
-            except ValueError:
-                raise ValueError("Bad sql file name, got %s" % sql_file)
-            SystemTable.update(trans, version=version)
-        # checks if there is new applications and update all the user
-        # profiles on the system
-        update_profile_applications(trans)
-        # Updating the parameter list
-        ensure_system_parameters(update=True)
-        # Update the base schema
-        create_base_schema()
-        trans.commit(close=True)
+        patches = self._get_patches()
+        current_version = self.get_current_version(conn)
+        generation = self._get_generation(conn, current_version)
+
+        initializing = current_version == 0
+
+        for patch, patchlevel in patches:
+            if patchlevel <= current_version:
+                continue
+            temporary = tempfile.mktemp(prefix="patch-%d" % patchlevel)
+            shutil.copy(patch, temporary)
+            open(temporary, 'a').write(
+                """INSERT INTO system_table (updated, patchlevel, generation) VALUES
+                (NOW(), %s, %s)""" % (conn.sqlrepr(patchlevel),
+                                      conn.sqlrepr(generation)))
+            execute_sql(temporary)
+            os.unlink(temporary)
+
+        if not initializing:
+            # checks if there is new applications and update all the user
+            # profiles on the system
+            trans = new_transaction()
+            update_profile_applications(trans)
+            trans.commit(close=True)
+
+            # Updating the parameter list
+            ensure_system_parameters(update=True)
+
+        return current_version, patchlevel
+
+    def get_current_version(self, conn):
+        assert conn.tableExists('system_table')
+
+        if conn.tableHasColumn('system_table', 'generation'):
+            results = SystemTable.select(connection=conn)
+            current_version = results.max('patchlevel')
+        elif conn.tableHasColumn('asellable', 'code'):
+            raise SystemExit(_("Unsupported database version, you need to reinstall"))
+        else:
+            current_version = 0
+
+        return current_version
 
 schema_migration = SchemaMigration()

Modified: stoqlib/trunk/stoqlib/domain/system.py
==============================================================================
--- stoqlib/trunk/stoqlib/domain/system.py	(original)
+++ stoqlib/trunk/stoqlib/domain/system.py	Tue Apr  3 19:20:05 2007
@@ -2,7 +2,7 @@
 # vi:si:et:sw=4:sts=4:ts=4
 
 ##
-## Copyright (C) 2006 Async Open Source <http://www.async.com.br>
+## Copyright (C) 2006-2007 Async Open Source <http://www.async.com.br>
 ## All rights reserved
 ##
 ## This program is free software; you can redistribute it and/or modify
@@ -25,40 +25,21 @@
 ##
 """ Routines for system data management"""
 
-import datetime
-
 from sqlobject import SQLObject
 from sqlobject import DateTimeCol, IntCol
 
-from stoqlib import db_version
 from stoqlib.domain.base import AbstractModel
 
 class SystemTable(SQLObject, AbstractModel):
     """Stores information about database schema migration
 
-    I{update_date}: the date when the database schema was updated
-    I{version}: the version of the schema installed
+    I{update}: the date when the database schema was updated
+    I{patchlevel}: the version of the schema installed
     """
 
-    update_date = DateTimeCol()
-    version = IntCol()
-
-    @classmethod
-    def update(cls, trans, check_new_db=False, version=None):
-        """Add a new entry on SystemTable with the current schema version"""
-        result = cls.select(connection=trans)
-        if result and check_new_db:
-            raise ValueError(
-                'SystemTable should be empty at this point got %d results' %
-                result.count())
-        elif not result and not check_new_db:
-            raise ValueError(
-                'SystemTable should have at least one item at this point, '
-                'got nothing')
-
-        return cls(version=version or db_version,
-                   update_date=datetime.datetime.now(),
-                   connection=trans)
+    updated = DateTimeCol()
+    patchlevel = IntCol()
+    generation = IntCol()
 
     @classmethod
     def is_available(cls, conn):


More information about the POS-commit mailing list