metter Teradata support, thanks Andrew Willmott
This commit is contained in:
1 parent
08ca99a993
commit
7815a87555
3 files changed
+177
-10
No files matched your search
@@ -24,6 +24,7 @@
|
|||||||
- new DAL syntax: db(...).select().group_by_value(db.table.field) (thanks Yair)
|
- new DAL syntax: db(...).select().group_by_value(db.table.field) (thanks Yair)
|
||||||
- auth.is_impersonating()
|
- auth.is_impersonating()
|
||||||
- reponse.delimiters = ('\\[','\\]') (thanks Denes)
|
- reponse.delimiters = ('\\[','\\]') (thanks Denes)
|
||||||
|
- Teradata support (experimental)
|
||||||
- 100's of small bug fixes and small improvements
|
- 100's of small bug fixes and small improvements
|
||||||
|
|
||||||
## 1.99.4
|
## 1.99.4
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
Version 1.99.4 (2012-02-28 13:48:35) stable
|
Version 1.99.4 (2012-02-28 13:57:08) stable
|
||||||
+175
-9
@@ -31,6 +31,7 @@ including:
|
|||||||
- MongoDB (in progress)
|
- MongoDB (in progress)
|
||||||
- Google:nosql
|
- Google:nosql
|
||||||
- Google:sql
|
- Google:sql
|
||||||
|
- Teradata
|
||||||
- IMAP (experimental)
|
- IMAP (experimental)
|
||||||
|
|
||||||
Example of usage:
|
Example of usage:
|
||||||
@@ -110,7 +111,7 @@ Supported DAL URI strings:
|
|||||||
'informixu://user:password@server:3050/database' # unicode informix
|
'informixu://user:password@server:3050/database' # unicode informix
|
||||||
'google:datastore' # for google app engine datastore
|
'google:datastore' # for google app engine datastore
|
||||||
'google:sql' # for google app engine with sql (mysql compatible)
|
'google:sql' # for google app engine with sql (mysql compatible)
|
||||||
'teradata://DSN=dsn;UID=user;PWD=pass' # experimental
|
'teradata://DSN=dsn;UID=user;PWD=pass; DATABASE=database' # experimental
|
||||||
'imap://user:password@server:port' # experimental
|
'imap://user:password@server:port' # experimental
|
||||||
|
|
||||||
For more info:
|
For more info:
|
||||||
@@ -265,9 +266,9 @@ if not 'google' in drivers:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
import pyodbc
|
import pyodbc
|
||||||
drivers.append('MSSQL/DB2')
|
drivers.append('MSSQL/DB2/Teradata')
|
||||||
except ImportError:
|
except ImportError:
|
||||||
logger.debug('no MSSQL/DB2 driver')
|
logger.debug('no MSSQL/DB2/Teradata driver')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import kinterbasdb
|
import kinterbasdb
|
||||||
@@ -2752,7 +2753,7 @@ class DB2Adapter(BaseAdapter):
|
|||||||
return rows[minimum:maximum]
|
return rows[minimum:maximum]
|
||||||
|
|
||||||
|
|
||||||
class TeradataAdapter(DB2Adapter):
|
class TeradataAdapter(BaseAdapter):
|
||||||
|
|
||||||
driver = globals().get('pyodbc',None)
|
driver = globals().get('pyodbc',None)
|
||||||
|
|
||||||
@@ -2769,15 +2770,174 @@ class TeradataAdapter(DB2Adapter):
|
|||||||
'date': 'DATE',
|
'date': 'DATE',
|
||||||
'time': 'TIME',
|
'time': 'TIME',
|
||||||
'datetime': 'TIMESTAMP',
|
'datetime': 'TIMESTAMP',
|
||||||
'id': 'INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY NOT NULL',
|
'id': 'INTEGER GENERATED ALWAYS AS IDENTITY', # Teradata Specific
|
||||||
'reference': 'INT, FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
# Modified Constraint syntax for Teradata.
|
||||||
'reference FK': ', CONSTRAINT FK_%(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
|
'reference TFK': ' CONSTRAINT FK_%(foreign_table)s_PK FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s)',
|
||||||
'reference TFK': ' CONSTRAINT FK_%(foreign_table)s_PK FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s',
|
|
||||||
'list:integer': 'CLOB',
|
'list:integer': 'CLOB',
|
||||||
'list:string': 'CLOB',
|
'list:string': 'CLOB',
|
||||||
'list:reference': 'CLOB',
|
'list:reference': 'CLOB',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def LEFT_JOIN(self):
|
||||||
|
return 'LEFT OUTER JOIN'
|
||||||
|
|
||||||
|
def create_table(self, table,
|
||||||
|
migrate=True,
|
||||||
|
fake_migrate=False,
|
||||||
|
polymodel=None):
|
||||||
|
fields = []
|
||||||
|
sql_fields = {}
|
||||||
|
sql_fields_aux = {}
|
||||||
|
TFK = {}
|
||||||
|
tablename = table._tablename
|
||||||
|
sortable = 0
|
||||||
|
for field in table:
|
||||||
|
sortable += 1
|
||||||
|
k = field.name
|
||||||
|
if isinstance(field.type,SQLCustomType):
|
||||||
|
ftype = field.type.native or field.type.type
|
||||||
|
elif field.type.startswith('reference'):
|
||||||
|
referenced = field.type[10:].strip()
|
||||||
|
constraint_name = self.constraint_name(tablename, field.name)
|
||||||
|
if hasattr(table,'_primarykey'):
|
||||||
|
rtablename,rfieldname = referenced.split('.')
|
||||||
|
rtable = table._db[rtablename]
|
||||||
|
rfield = rtable[rfieldname]
|
||||||
|
# must be PK reference or unique
|
||||||
|
if rfieldname in rtable._primarykey or rfield.unique:
|
||||||
|
ftype = self.types[rfield.type[:9]] % dict(length=rfield.length)
|
||||||
|
# multicolumn primary key reference?
|
||||||
|
if not rfield.unique and len(rtable._primarykey)>1 :
|
||||||
|
# then it has to be a table level FK
|
||||||
|
if rtablename not in TFK:
|
||||||
|
TFK[rtablename] = {}
|
||||||
|
TFK[rtablename][rfieldname] = field.name
|
||||||
|
else:
|
||||||
|
ftype = ftype + \
|
||||||
|
self.types['reference FK'] %dict(\
|
||||||
|
constraint_name=constraint_name,
|
||||||
|
table_name=tablename,
|
||||||
|
field_name=field.name,
|
||||||
|
foreign_key='%s (%s)'%(rtablename, rfieldname),
|
||||||
|
on_delete_action=field.ondelete)
|
||||||
|
else:
|
||||||
|
# make a guess here for circular references
|
||||||
|
id_fieldname = referenced in table._db and table._db[referenced]._id.name or 'id'
|
||||||
|
ftype = self.types[field.type[:9]]\
|
||||||
|
% dict(table_name=tablename,
|
||||||
|
field_name=field.name,
|
||||||
|
constraint_name=constraint_name,
|
||||||
|
foreign_key=referenced + ('(%s)' % id_fieldname),
|
||||||
|
on_delete_action=field.ondelete)
|
||||||
|
elif field.type.startswith('list:reference'):
|
||||||
|
ftype = self.types[field.type[:14]]
|
||||||
|
elif field.type.startswith('decimal'):
|
||||||
|
precision, scale = map(int,field.type[8:-1].split(','))
|
||||||
|
ftype = self.types[field.type[:7]] % \
|
||||||
|
dict(precision=precision,scale=scale)
|
||||||
|
elif not field.type in self.types:
|
||||||
|
raise SyntaxError, 'Field: unknown field type: %s for %s' % \
|
||||||
|
(field.type, field.name)
|
||||||
|
else:
|
||||||
|
ftype = self.types[field.type]\
|
||||||
|
% dict(length=field.length)
|
||||||
|
if not field.type.startswith('id') and not field.type.startswith('reference'):
|
||||||
|
if field.notnull:
|
||||||
|
ftype += ' NOT NULL'
|
||||||
|
else:
|
||||||
|
ftype += self.ALLOW_NULL()
|
||||||
|
if field.unique:
|
||||||
|
ftype += ' UNIQUE'
|
||||||
|
|
||||||
|
# add to list of fields
|
||||||
|
sql_fields[field.name] = dict(sortable=sortable,
|
||||||
|
type=str(field.type),
|
||||||
|
sql=ftype)
|
||||||
|
|
||||||
|
if isinstance(field.default,(str,int,float)):
|
||||||
|
# Caveat: sql_fields and sql_fields_aux differ for default values.
|
||||||
|
# sql_fields is used to trigger migrations and sql_fields_aux
|
||||||
|
# is used for create tables.
|
||||||
|
# The reason is that we do not want to trigger a migration simply
|
||||||
|
# because a default value changes.
|
||||||
|
not_null = self.NOT_NULL(field.default, field.type)
|
||||||
|
ftype = ftype.replace('NOT NULL', not_null)
|
||||||
|
sql_fields_aux[field.name] = dict(sql=ftype)
|
||||||
|
fields.append('%s %s' % (field.name, ftype))
|
||||||
|
other = ';'
|
||||||
|
|
||||||
|
fields = ',\n '.join(fields)
|
||||||
|
for rtablename in TFK:
|
||||||
|
rfields = TFK[rtablename]
|
||||||
|
pkeys = table._db[rtablename]._primarykey
|
||||||
|
fkeys = [ rfields[k] for k in pkeys ]
|
||||||
|
fields = fields + ',\n ' + \
|
||||||
|
self.types['reference TFK'] %\
|
||||||
|
dict(table_name=tablename,
|
||||||
|
field_name=', '.join(fkeys),
|
||||||
|
foreign_table=rtablename,
|
||||||
|
foreign_key=', '.join(pkeys),
|
||||||
|
on_delete_action=field.ondelete)
|
||||||
|
|
||||||
|
if hasattr(table,'_primarykey'):
|
||||||
|
query = '''CREATE TABLE %s(\n %s,\n %s) %s''' % \
|
||||||
|
(tablename, fields, self.PRIMARY_KEY(', '.join(table._primarykey)),other)
|
||||||
|
else:
|
||||||
|
query = '''CREATE TABLE %s(\n %s\n)%s''' % \
|
||||||
|
(tablename, fields, other)
|
||||||
|
|
||||||
|
if self.uri.startswith('sqlite:///'):
|
||||||
|
path_encoding = sys.getfilesystemencoding() or locale.getdefaultlocale()[1] or 'utf8'
|
||||||
|
dbpath = self.uri[9:self.uri.rfind('/')].decode('utf8').encode(path_encoding)
|
||||||
|
else:
|
||||||
|
dbpath = self.folder
|
||||||
|
|
||||||
|
if not migrate:
|
||||||
|
return query
|
||||||
|
elif self.uri.startswith('sqlite:memory'):
|
||||||
|
table._dbt = None
|
||||||
|
elif isinstance(migrate, str):
|
||||||
|
table._dbt = os.path.join(dbpath, migrate)
|
||||||
|
else:
|
||||||
|
table._dbt = os.path.join(dbpath, '%s_%s.table' \
|
||||||
|
% (table._db._uri_hash, tablename))
|
||||||
|
if table._dbt:
|
||||||
|
table._loggername = os.path.join(dbpath, 'sql.log')
|
||||||
|
logfile = self.file_open(table._loggername, 'a')
|
||||||
|
else:
|
||||||
|
logfile = None
|
||||||
|
if not table._dbt or not self.file_exists(table._dbt):
|
||||||
|
if table._dbt:
|
||||||
|
logfile.write('timestamp: %s\n'
|
||||||
|
% datetime.datetime.today().isoformat())
|
||||||
|
logfile.write(query + '\n')
|
||||||
|
if not fake_migrate:
|
||||||
|
self.create_sequence_and_triggers(query,table)
|
||||||
|
table._db.commit()
|
||||||
|
if table._dbt:
|
||||||
|
tfile = self.file_open(table._dbt, 'w')
|
||||||
|
cPickle.dump(sql_fields, tfile)
|
||||||
|
self.file_close(tfile)
|
||||||
|
if fake_migrate:
|
||||||
|
logfile.write('faked!\n')
|
||||||
|
else:
|
||||||
|
logfile.write('success!\n')
|
||||||
|
else:
|
||||||
|
tfile = self.file_open(table._dbt, 'r')
|
||||||
|
try:
|
||||||
|
sql_fields_old = cPickle.load(tfile)
|
||||||
|
except EOFError:
|
||||||
|
self.file_close(tfile)
|
||||||
|
self.file_close(logfile)
|
||||||
|
raise RuntimeError, 'File %s appears corrupted' % table._dbt
|
||||||
|
self.file_close(tfile)
|
||||||
|
if sql_fields != sql_fields_old:
|
||||||
|
self.migrate_table(table,
|
||||||
|
sql_fields, sql_fields_old,
|
||||||
|
sql_fields_aux, logfile,
|
||||||
|
fake_migrate=fake_migrate)
|
||||||
|
self.file_close(logfile)
|
||||||
|
return query
|
||||||
|
|
||||||
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
|
def __init__(self,db,uri,pool_size=0,folder=None,db_codec ='UTF-8',
|
||||||
credential_decoder=lambda x:x, driver_args={},
|
credential_decoder=lambda x:x, driver_args={},
|
||||||
@@ -2796,6 +2956,13 @@ class TeradataAdapter(DB2Adapter):
|
|||||||
return self.driver.connect(cnxn,**driver_args)
|
return self.driver.connect(cnxn,**driver_args)
|
||||||
self.pool_connection(connect)
|
self.pool_connection(connect)
|
||||||
|
|
||||||
|
# Similar to MSSQL, Teradata can't specify a range (for Pageby)
|
||||||
|
def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby):
|
||||||
|
if limitby:
|
||||||
|
(lmin, lmax) = limitby
|
||||||
|
sql_s += ' TOP %i' % lmax
|
||||||
|
return 'SELECT %s %s FROM %s%s%s;' % (sql_s, sql_f, sql_t, sql_w, sql_o)
|
||||||
|
|
||||||
|
|
||||||
INGRES_SEQNAME='ii***lineitemsequence' # NOTE invalid database object name
|
INGRES_SEQNAME='ii***lineitemsequence' # NOTE invalid database object name
|
||||||
# (ANSI-SQL wants this form of name
|
# (ANSI-SQL wants this form of name
|
||||||
@@ -6289,7 +6456,6 @@ def index():
|
|||||||
self[tablename].import_from_csv_file(ifile, id_map, null,
|
self[tablename].import_from_csv_file(ifile, id_map, null,
|
||||||
unique, *args, **kwargs)
|
unique, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class SQLALL(object):
|
class SQLALL(object):
|
||||||
"""
|
"""
|
||||||
Helper class providing a comma-separated string having all the field names
|
Helper class providing a comma-separated string having all the field names
|
||||||
|
|||||||
Reference in new issue
Block a user