# Source code for sqlobject.maxdb.maxdbconnection

    """
    Contributed by Edigram SAS, Paris France Tel:01 44 77 94 00
    Ahmed MOHAMED ALI <ahmedmoali@yahoo.com> 27 April 2004

    This program is distributed in the hope that it will be useful, but
    WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    General Public License for more details.

    connection creation sample::

        __connection__ = DBConnection.maxdbConnection(
            host=hostname, database=dbname,
            user=user_name, password=user_password, autoCommit=1, debug=1)
    """

    import os
    from sqlobject.dbconnection import DBAPI
    from sqlobject import col

    class maxdbException(Exception):

        def __init__(self, value):
            self.value = value

        def __str__(self):
            return repr(self.value)

    class LowerBoundOfSliceIsNotSupported(maxdbException):
        def __init__(self, value):
            maxdbException.__init__(self, '')

    class IncorrectIDStyleError(maxdbException):
        def __init__(self, value):
            maxdbException.__init__(
                self,
                'This primary key name is not in the expected style, '
                'please rename the column to %r or switch to another style'
                % value)

    class StyleMismatchError(maxdbException):
        def __init__(self, value):
            maxdbException.__init__(
                self,
                'The name %r is only permitted for primary key, change the '
                'column name or switch to another style' % value)

    class PrimaryKeyNotFounded(maxdbException):
        def __init__(self, value):
            maxdbException.__init__(
                self,
                "No primary key was defined on table %r" % value)

    SAPDBMAX_ID_LENGTH = 32

    class MaxdbConnection(DBAPI):

        supportTransactions = True
        dbName = 'maxdb'
        schemes = [dbName]

        def __init__(self, host='', port=None, user=None, password=None,
                     database=None, autoCommit=1, sqlmode='internal',
                     isolation=None, timeout=None, **kw):
            from sapdb import dbapi
            self.module = dbapi
            self.host = host
            self.port = port
            self.user = user
            self.password = password
            self.db = database
            self.autoCommit = autoCommit
            self.sqlmode = sqlmode
            self.isolation = isolation
            self.timeout = timeout

            DBAPI.__init__(self, **kw)

        @classmethod
        def _connectionFromParams(cls, auth, password, host, port, path, args):
            path = path.replace('/', os.path.sep)
            return cls(host, port, user=auth, password=password, database=path,
                       **args)

        def _getConfigParams(self, sqlmode, auto):
            autocommit = 'off'
            if auto:
                autocommit = 'on'
            opt = {}
            opt["autocommit"] = autocommit
            opt["sqlmode"] = sqlmode
            if self.isolation:
                opt["isolation"] = self.isolation
            if self.timeout:
                opt["timeout"] = self.timeout
            return opt

        def _setAutoCommit(self, conn, auto):
            conn.close()
            conn.__init__(self.user, self.password, self.db, self.host,
                          **self._getConfigParams(self.sqlmode, auto))

        def createSequenceName(self, table):
            """
            sequence name are builded with the concatenation of the table
            name with '_SEQ' word we truncate the name of the
            sequence_name because sapdb identifier cannot exceed 32
            characters so that the name of the sequence does not exceed 32
            characters
            """
            return '%s_SEQ' % (table[:SAPDBMAX_ID_LENGTH - 4])

        def makeConnection(self):
            conn = self.module.Connection(
                self.user, self.password, self.db, self.host,
                **self._getConfigParams(self.sqlmode, self.autoCommit))
            return conn

        def _queryInsertID(self, conn, soInstance, id, names, values):
            table = soInstance.sqlmeta.table
            idName = soInstance.sqlmeta.idName
            c = conn.cursor()
            if id is None:
                c.execute(
                    'SELECT %s.NEXTVAL FROM DUAL' % (
                        self.createSequenceName(table)))
                id = c.fetchone()[0]
            names = [idName] + names
            values = [id] + values
            q = self._insertSQL(table, names, values)
            if self.debug:
                self.printDebug(conn, q, 'QueryIns')
            c.execute(q)
            c.close()
            if self.debugOutput:
                self.printDebug(conn, id, 'QueryIns', 'result')
            return id

        @classmethod
        def sqlAddLimit(cls, query, limit):
            sql = query
            sql = sql.replace("SELECT", "SELECT ROWNO, ")
            if sql.find('WHERE') != -1:
                sql = sql + ' AND ' + limit
            else:
                sql = sql + 'WHERE ' + limit
            return sql

        @classmethod
        def _queryAddLimitOffset(cls, query, start, end):
            if start:
                raise LowerBoundOfSliceIsNotSupported
            limit = ' ROWNO   <= %d ' % (end)
            return cls.sqlAddLimit(query, limit)

        def createTable(self, soClass):
            # We create the table in a transaction because the addition of the
            # table and the sequence must be atomic.

            # I tried to use the transaction class
            # but I get a recursion limit error.
            # t=self.transaction()
            # t.query('CREATE TABLE %s (\n%s\n)' %
            #            (soClass.sqlmeta.table, self.createColumns(soClass)))
            #
            # t.query("CREATE SEQUENCE %s" %
            #         self.createSequenceName(soClass.sqlmeta.table))
            # t.commit()
            # so use transaction when the problem will be solved
            self.query('CREATE TABLE %s (\n%s\n)' %
                       (soClass.sqlmeta.table, self.createColumns(soClass)))
            self.query("CREATE SEQUENCE %s"
                       % self.createSequenceName(soClass.sqlmeta.table))
            return []

        def createReferenceConstraint(self, soClass, col):
            return col.maxdbCreateReferenceConstraint()

        def createColumn(self, soClass, col):
            return col.maxdbCreateSQL()

        def createIDColumn(self, soClass):
            key_type = {int: "INT", str: "TEXT"}[soClass.sqlmeta.idType]
            return '%s %s PRIMARY KEY' % (soClass.sqlmeta.idName, key_type)

        def createIndexSQL(self, soClass, index):
            return index.maxdbCreateIndexSQL(soClass)

        def dropTable(self, tableName, cascade=False):
            # We drop the table in a transaction because the removal of the
            # table and the sequence must be atomic.
            # I tried to use the transaction class
            # but I get a recursion limit error.
            # try:
            #     t=self.transaction()
            #     t.query("DROP TABLE %s" % tableName)
            #     t.query("DROP SEQUENCE %s" % self.createSequenceName(tableName))
            #     t.commit()
            # except:
            #     t.rollback()
            # so use transaction when the problem will be solved
            self.query("DROP TABLE %s" % tableName)
            self.query("DROP SEQUENCE %s" % self.createSequenceName(tableName))

        def joinSQLType(self, join):
            return 'INT NOT NULL'

        def tableExists(self, tableName):
            for (table,) in self.queryAll(
                    "SELECT OBJECT_NAME FROM ALL_OBJECTS "
                    "WHERE OBJECT_TYPE='TABLE'"):
                if table.lower() == tableName.lower():
                    return True
            return False

        def addColumn(self, tableName, column):
            self.query('ALTER TABLE %s ADD %s' %
                       (tableName,
                        column.maxdbCreateSQL()))

        def delColumn(self, sqlmeta, column):
            self.query('ALTER TABLE %s DROP COLUMN %s' % (sqlmeta.table,
                                                          column.dbName))

        GET_COLUMNS = """
        SELECT COLUMN_NAME, NULLABLE, DATA_DEFAULT, DATA_TYPE,
               DATA_LENGTH, DATA_SCALE
        FROM USER_TAB_COLUMNS WHERE TABLE_NAME=UPPER('%s')"""

        GET_PK_AND_FK = """
        SELECT constraint_cols.column_name, constraints.constraint_type,
               refname,reftablename
        FROM user_cons_columns constraint_cols
        INNER JOIN user_constraints constraints
        ON constraint_cols.constraint_name = constraints.constraint_name
        LEFT OUTER JOIN show_foreign_key fk
        ON constraint_cols.column_name = fk.columnname
        WHERE constraints.table_name =UPPER('%s')"""

        def columnsFromSchema(self, tableName, soClass):
            colData = self.queryAll(self.GET_COLUMNS
                                    % tableName)

            results = []
            keymap = {}
            pkmap = {}
            fkData = self.queryAll(self.GET_PK_AND_FK % tableName)
            for _col, cons_type, refcol, reftable in fkData:
                col_name = _col.lower()
                pkmap[col_name] = False
                if cons_type == 'R':
                    keymap[col_name] = reftable.lower()

                elif cons_type == 'P':
                    pkmap[col_name] = True

            if len(pkmap) == 0:
                raise PrimaryKeyNotFounded(tableName)

            for (field, nullAllowed, default, data_type, data_len,
                 data_scale) in colData:
                # id is defined as primary key --> ok
                # We let sqlobject raise error if the 'id' is used
                # for another column.
                field_name = field.lower()
                if (field_name == soClass.sqlmeta.idName) and pkmap[field_name]:
                    continue

                colClass, kw = self.guessClass(data_type, data_len, data_scale)
                kw['name'] = field_name
                kw['dbName'] = field

                if nullAllowed == 'Y':
                    nullAllowed = False
                else:
                    nullAllowed = True

                kw['notNone'] = nullAllowed
                if default is not None:
                    kw['default'] = default

                if field_name in keymap:
                    kw['foreignKey'] = keymap[field_name]

                results.append(colClass(**kw))

            return results

        _numericTypes = ['INTEGER', 'INT', 'SMALLINT']
        _dateTypes = ['DATE', 'TIME', 'TIMESTAMP']

        def guessClass(self, t, flength, fscale=None):
            """
            An internal method that tries to figure out what Col subclass
            is appropriate given whatever introspective information is
            available -- both very database-specific.
            """
            if t in self._numericTypes:
                return col.IntCol, {}
            # The type returned by the sapdb library for LONG is
            # SapDB_LongReader To get the data call the read member with
            # desired size (default =-1 means get all)

            elif t.find('LONG') != -1:
                return col.StringCol, {'length': flength,
                                       'varchar': False}
            elif t in self._dateTypes:
                return col.DateTimeCol, {}
            elif t == 'FIXED':
                return col.CurrencyCol, {'size': flength,
                                         'precision': fscale}
            else:
                return col.Col, {}