mirror of
https://github.com/djohnlewis/stackdump
synced 2025-01-22 22:51:36 +00:00
304 lines
11 KiB
Python
304 lines
11 KiB
Python
"""
|
|
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)
|
|
"""
|
|
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)
|
|
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 CurrencyCol,{'size':flength,
|
|
'precision':fscale}
|
|
else:
|
|
return col.Col, {}
|