0001"""
0002Contributed by Edigram SAS,Paris France
0003Tel:01 44 77 94 00
0004Ahmed MOHAMED ALI <ahmedmoali@yahoo.com> 27 April 2004
0005This program is distributed in the hope that it will be useful,
0006but WITHOUT ANY WARRANTY; without even the implied warranty of
0007MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0008GNU General Public License for more details.
0009
0010connection creation sample
0011__connection__ = DBConnection.maxdbConnection(
0012 host=hostname, database=dbname,
0013 user=user_name, password=user_password,autoCommit=1,debug=1)
0014"""
0015from sqlobject.dbconnection import DBAPI
0016from sqlobject import col
0017dbapi = None
0018
0019
0020
0021class maxdbException(Exception):
0022
0023 def __init__(self, value):
0024 self.value = value
0025
0026 def __str__(self):
0027 return repr(self.value)
0028
0029class LowerBoundOfSliceIsNotSupported(maxdbException):
0030 def __init__(self, value):
0031 maxdbException.__init__(self, '')
0032
0033class IncorrectIDStyleError(maxdbException) :
0034 def __init__(self,value):
0035 maxdbException.__init__(
0036 self,
0037 'This primary key name is not in the expected style, '
0038 'please rename the column to %r or switch to another style'
0039 % value)
0040
0041class StyleMismatchError(maxdbException):
0042 def __init__(self, value):
0043 maxdbException.__init__(
0044 self,
0045 'The name %r is only permitted for primary key, change the '
0046 'column name or switch to another style' % value)
0047
0048class PrimaryKeyNotFounded(maxdbException):
0049 def __init__(self, value):
0050 maxdbException.__init__(
0051 self,
0052 "No primary key was defined on table %r" % value)
0053
0054SAPDBMAX_ID_LENGTH=32
0055
0056class MaxdbConnection(DBAPI):
0057
0058 supportTransactions = True
0059 dbName = 'maxdb'
0060 schemes = [dbName]
0061
0062 def __init__ (self, user, password, database,
0063 host='', autoCommit=1, sqlmode='internal',
0064 isolation=None, timeout=None, **kw):
0065 global dbapi
0066 if dbapi is None:
0067 from sapdb import dbapi
0068 self.module = dbapi
0069 self.autoCommit = autoCommit
0070 self.user = user
0071 self.password = password
0072 self.database = database
0073 self.host = host
0074 self.sqlmode = sqlmode
0075 self.isolation = isolation
0076 self.timeout = timeout
0077
0078 DBAPI.__init__(self, **kw)
0079
0080 def connectionFromURI(cls, uri):
0081 auth, password, host, port, path, args = cls._parseURI(uri)
0082 path = path.replace('/', os.path.sep)
0083 return cls(host, db=path, user=auth, passwd=password, **args)
0084 connectionFromURI = classmethod(connectionFromURI)
0085
0086 def _getConfigParams(self,sqlmode,auto):
0087 autocommit='off'
0088 if auto:
0089 autocommit='on'
0090 opt = {}
0091 opt["autocommit"] = autocommit
0092 opt["sqlmode"] = sqlmode
0093 if self.isolation:
0094 opt["isolation"]=self.isolation
0095 if self.timeout :
0096 opt["timeout"]=self.timeout
0097 return opt
0098
0099 def _setAutoCommit(self, conn, auto):
0100 conn.close()
0101 conn.__init__(self.user, self.password, self.database,
0102 self.host,
0103 **self._getConfigParams(self.sqlmode,auto))
0104
0105 def createSequenceName(self,table):
0106 """
0107 sequence name are builded with the concatenation of the table
0108 name with '_SEQ' word we truncate the name of the
0109 sequence_name because sapdb identifier cannot exceed 32
0110 characters so that the name of the sequence does not exceed 32
0111 characters
0112 """
0113 return '%s_SEQ'%(table[:SAPDBMAX_ID_LENGTH -4])
0114
0115 def makeConnection(self):
0116 conn = dbapi.Connection(
0117 self.user, self.password, self.database, self.host,
0118 **self._getConfigParams(self.sqlmode,self.autoCommit))
0119 return conn
0120
0121 def _queryInsertID(self, conn, soInstance, id, names, values):
0122 table = soInstance.sqlmeta.table
0123 idName = soInstance.sqlmeta.idName
0124 c = conn.cursor()
0125 if id is None:
0126 c.execute('SELECT %s.NEXTVAL FROM DUAL' % (self.createSequenceName(table)))
0127 id = c.fetchone()[0]
0128 names = [idName] + names
0129 values = [id] + values
0130 q = self._insertSQL(table, names, values)
0131 if self.debug:
0132 self.printDebug(conn, q, 'QueryIns')
0133 c.execute(q)
0134 if self.debugOutput:
0135 self.printDebug(conn, id, 'QueryIns', 'result')
0136 return id
0137
0138 def sqlAddLimit(self,query,limit):
0139 sql = query
0140 sql = sql.replace("SELECT","SELECT ROWNO, ")
0141 if sql.find('WHERE') != -1:
0142 sql = sql + ' AND ' + limit
0143 else:
0144 sql = sql + 'WHERE ' + limit
0145 return sql
0146
0147 def _queryAddLimitOffset(self, query, start, end):
0148 if start:
0149 raise LowerBoundOfSliceIsNotSupported
0150 limit = ' ROWNO <= %d ' % (end)
0151 return self.sqlAddLimit(query,limit)
0152
0153
0154 def createTable(self, soClass):
0155
0156
0157
0158
0159
0160
0161
0162
0163
0164
0165
0166 self.query('CREATE TABLE %s (\n%s\n)' % (soClass.sqlmeta.table, self.createColumns(soClass)))
0168 self.query("CREATE SEQUENCE %s"
0169 % self.createSequenceName(soClass.sqlmeta.table))
0170
0171 def createColumn(self, soClass, col):
0172 return col.maxdbCreateSQL()
0173
0174 def createIDColumn(self, soClass):
0175 return '%s INT PRIMARY KEY' % soClass.sqlmeta.idName
0176
0177 def createIndexSQL(self, soClass, index):
0178 return index.maxdbCreateIndexSQL(soClass)
0179
0180 def dropTable(self, tableName,cascade=False):
0181
0182
0183
0184
0185
0186
0187
0188
0189
0190
0191
0192 self.query("DROP TABLE %s" % tableName)
0193 self.query("DROP SEQUENCE %s" % self.createSequenceName(tableName))
0194
0195 def joinSQLType(self, join):
0196 return 'INT NOT NULL'
0197
0198 def tableExists(self, tableName):
0199 for (table,) in self.queryAll("SELECT OBJECT_NAME FROM ALL_OBJECTS WHERE OBJECT_TYPE='TABLE'"):
0200 if table.lower() == tableName.lower():
0201 return True
0202 return False
0203
0204 def addColumn(self, tableName, column):
0205 self.query('ALTER TABLE %s ADD %s' %
0206 (tableName,
0207 column.maxdbCreateSQL()))
0208
0209 def delColumn(self, tableName, column):
0210 self.query('ALTER TABLE %s DROP COLUMN %s' %
0211 (tableName,
0212 column.dbName))
0213
0214 GET_COLUMNS = """
0215 SELECT COLUMN_NAME, NULLABLE, DATA_DEFAULT, DATA_TYPE,
0216 DATA_LENGTH, DATA_SCALE
0217 FROM USER_TAB_COLUMNS WHERE TABLE_NAME=UPPER('%s')"""
0218
0219 GET_PK_AND_FK = """
0220 SELECT constraint_cols.column_name, constraints.constraint_type,
0221 refname,reftablename
0222 FROM user_cons_columns constraint_cols
0223 INNER JOIN user_constraints constraints
0224 ON constraint_cols.constraint_name = constraints.constraint_name
0225 LEFT OUTER JOIN show_foreign_key fk
0226 ON constraint_cols.column_name = fk.columnname
0227 WHERE constraints.table_name =UPPER('%s')"""
0228
0229 def columnsFromSchema(self, tableName, soClass):
0230 colData = self.queryAll(self.GET_COLUMNS
0231 % tableName)
0232
0233 results = []
0234 keymap = {}
0235 pkmap={}
0236 fkData = self.queryAll(self.GET_PK_AND_FK% tableName)
0237 for col, cons_type, refcol, reftable in fkData:
0238 col_name= col.lower()
0239 pkmap[col_name]=False
0240 if cons_type == 'R':
0241 keymap[col_name]=reftable.lower()
0242
0243 elif cons_type == 'P':
0244 pkmap[col_name]=True
0245
0246 if len(pkmap) == 0:
0247 raise PrimaryKeyNotFounded, tableName
0248
0249 for (field, nullAllowed, default, data_type, data_len,
0250 data_scale) in colData:
0251
0252
0253 field_name = field.lower()
0254 if field_name == 'id' and pkmap[field_name]:
0255 continue
0256
0257 colClass, kw = self.guessClass(data_type,data_len,data_scale)
0258 kw['name'] = field_name
0259
0260 if nullAllowed == 'Y' :
0261 nullAllowed=False
0262 else:
0263 nullAllowed=True
0264
0265 kw['notNone'] = nullAllowed
0266 if default is not None:
0267 kw['default'] = default
0268
0269 if keymap.has_key(field_name):
0270 kw['foreignKey'] = keymap[field_name]
0271
0272 results.append(colClass(**kw))
0273
0274 return results
0275
0276 _numericTypes=['INTEGER', 'INT','SMALLINT']
0277 _dateTypes=['DATE','TIME','TIMESTAMP']
0278
0279 def guessClass(self, t, flength, fscale=None):
0280 """
0281 An internal method that tries to figure out what Col subclass
0282 is appropriate given whatever introspective information is
0283 available -- both very database-specific.
0284 """
0285 if t in self._numericTypes:
0286 return col.IntCol, {}
0287
0288
0289
0290
0291 elif t.find('LONG') != -1:
0292 return col.StringCol, {'length': flength,
0293 'varchar': False}
0294 elif t in self._dateTypes:
0295 return col.DateTimeCol, {}
0296 elif t == 'FIXED':
0297 return CurrencyCol,{'size':flength,
0298 'precision':fscale}
0299 else:
0300 return col.Col, {}