diff --git a/pydal/adapters/base.py b/pydal/adapters/base.py index b83346772..81eb799b6 100644 --- a/pydal/adapters/base.py +++ b/pydal/adapters/base.py @@ -346,19 +346,6 @@ def drop_table(self, table, mode=''): def rowslice(self, rows, minimum=0, maximum=None): return rows - def alias(self, table, alias): - other = copy.copy(table) - other['_ot'] = other._ot or other.sqlsafe - other['ALL'] = SQLALL(other) - other['_tablename'] = alias - for fieldname in other.fields: - other[fieldname] = copy.copy(other[fieldname]) - other[fieldname]._tablename = alias - other[fieldname].tablename = alias - other[fieldname].table = other - table._db[alias] = other - return other - class DebugHandler(ExecutionHandler): def before_execute(self, command): @@ -423,14 +410,10 @@ def execute(self, *args, **kwargs): def _expand(self, expression, field_type=None, colnames=False, query_env={}): if isinstance(expression, Field): - et = expression.table if not colnames: - table_rname = et.query_alias - rv = '%s.%s' % (table_rname, expression._rname or - (self.dialect.quote(expression.name))) + rv = expression.sqlsafe else: - rv = '%s.%s' % (self.dialect.quote(et._tablename), - self.dialect.quote(expression.name)) + rv = expression.longname if field_type == 'string' and expression.type not in ( 'string', 'text', 'json', 'password'): rv = self.dialect.cast(rv, self.types['text'], query_env) @@ -465,7 +448,7 @@ def _expand(self, expression, field_type=None, colnames=False, def _expand_for_index(self, expression, field_type=None, colnames=False, query_env={}): if isinstance(expression, Field): - return expression._rname or self.dialect.quote(expression.name) + return expression._rname return self._expand(expression, field_type, colnames, query_env) @contextmanager @@ -480,10 +463,10 @@ def lastrowid(self, table): def _insert(self, table, fields): if fields: return self.dialect.insert( - table.sqlsafe, - ','.join(el[0].sqlsafe_name for el in fields), + table._rname, + ','.join(el[0]._rname for el in fields), ','.join(self.expand(v, f.type) for f, v in fields)) - return self.dialect.insert_empty(table.sqlsafe) + return self.dialect.insert_empty(table._rname) def insert(self, table, fields): query = self._insert(table, fields) @@ -511,17 +494,16 @@ def insert(self, table, fields): def _update(self, table, query, fields): sql_q = '' - tablename = table.sqlsafe query_env = dict(current_scope=[table._tablename]) if query: if use_common_filters(query): query = self.common_filter(query, [table]) sql_q = self.expand(query, query_env=query_env) sql_v = ','.join([ - '%s=%s' % (field.sqlsafe_name, + '%s=%s' % (field._rname, self.expand(value, field.type, query_env=query_env)) for (field, value) in fields]) - return self.dialect.update(tablename, sql_v, sql_q) + return self.dialect.update(table, sql_v, sql_q) def update(self, table, query, fields): sql = self._update(table, query, fields) @@ -539,13 +521,12 @@ def update(self, table, query, fields): def _delete(self, table, query): sql_q = '' - tablename = table.sqlsafe query_env = dict(current_scope=[table._tablename]) if query: if use_common_filters(query): query = self.common_filter(query, [table]) sql_q = self.expand(query, query_env=query_env) - return self.dialect.delete(tablename, sql_q) + return self.dialect.delete(table, sql_q) def delete(self, table, query): sql = self._delete(table, query) @@ -703,7 +684,7 @@ def _select_wcols(self, query, fields, left=False, join=False, if (limitby and not groupby and query_tables and orderby_on_limitby and not orderby): sql_ord = ', '.join([ - tablemap[t].sqlsafe + '.' + tablemap[t][x].sqlsafe_name + tablemap[t][x].sqlsafe for t in query_tables if not isinstance(tablemap[t], Select) for x in (hasattr(tablemap[t], '_primarykey') and tablemap[t]._primarykey or ['_id']) @@ -838,7 +819,7 @@ def truncate(self, table, mode=''): def create_index(self, table, index_name, *fields, **kwargs): expressions = [ - field.sqlsafe_name if isinstance(field, Field) else field + field._rname if isinstance(field, Field) else field for field in fields] sql = self.dialect.create_index( index_name, table, expressions, **kwargs) diff --git a/pydal/adapters/firebird.py b/pydal/adapters/firebird.py index 67a440db5..cee01923d 100644 --- a/pydal/adapters/firebird.py +++ b/pydal/adapters/firebird.py @@ -51,7 +51,7 @@ def lastrowid(self, table): return long(self.cursor.fetchone()[0]) def create_sequence_and_triggers(self, query, table, **args): - tablename = table._tablename + tablename = table._rname sequence_name = table._sequence_name trigger_name = table._trigger_name self.execute(query) diff --git a/pydal/adapters/google.py b/pydal/adapters/google.py index e3b4bd586..3c8381450 100644 --- a/pydal/adapters/google.py +++ b/pydal/adapters/google.py @@ -374,7 +374,7 @@ def select(self, query, fields, attributes): (t.name == 'nativeRef' and item) or getattr(item, t.name) for t in fields ] for item in items] - colnames = ['%s.%s' % (table._tablename, t.name) for t in fields] + colnames = [t.longname for t in fields] processor = attributes.get('processor', self.parse) return processor(rows, fields, colnames, False) diff --git a/pydal/adapters/ingres.py b/pydal/adapters/ingres.py index 898fe8203..0cf76a24d 100644 --- a/pydal/adapters/ingres.py +++ b/pydal/adapters/ingres.py @@ -34,16 +34,16 @@ def create_sequence_and_triggers(self, query, table, **args): # Older Ingres releases could use rule/trigger like Oracle above. if hasattr(table, '_primarykey'): modify_tbl_sql = 'modify %s to btree unique on %s' % \ - (table._tablename, + (table._rname, ', '.join(["'%s'" % x for x in table.primarykey])) self.execute(modify_tbl_sql) else: - tmp_seqname = '%s_iisq' % table._tablename + tmp_seqname = '%s_iisq' % table._raw_rname query = query.replace(self.dialect.INGRES_SEQNAME, tmp_seqname) self.execute('create sequence %s' % tmp_seqname) self.execute(query) self.execute( - 'modify %s to btree unique on %s' % (table._tablename, 'id')) + 'modify %s to btree unique on %s' % (table._rname, 'id')) @adapters.register_for('ingresu') diff --git a/pydal/adapters/mongo.py b/pydal/adapters/mongo.py index 02bf973a3..6c77e2b3c 100644 --- a/pydal/adapters/mongo.py +++ b/pydal/adapters/mongo.py @@ -326,7 +326,7 @@ def __select(self, query, fields, left=False, join=False, distinct=False, # Mongodb reserved uuid key colname = (tablename + '.' + 'id', '_id') else: - colname = (tablename + '.' + field.name, field.name) + colname = (field.longname, field.name) elif not isinstance(query, Expression): colname = (field.name, field.name) colnames.append(colname[1]) diff --git a/pydal/adapters/oracle.py b/pydal/adapters/oracle.py index 22a594de6..2f6a8df6d 100644 --- a/pydal/adapters/oracle.py +++ b/pydal/adapters/oracle.py @@ -60,8 +60,8 @@ def lastrowid(self, table): return long(self.cursor.fetchone()[0]) def create_sequence_and_triggers(self, query, table, **args): - tablename = table._rname or table._tablename - id_name = table._id.name + tablename = table._rname + id_name = table._id._rname sequence_name = table._sequence_name trigger_name = table._trigger_name self.execute(query) @@ -94,21 +94,21 @@ def sqlsafe_table(self, tablename, original_tablename=None): def _build_value_for_insert(self, field, value, r_values): if field.type is 'text': - r_values[':' + field.sqlsafe_name] = self.expand(value, field.type) - return ':' + field.sqlsafe_name + r_values[':' + field._rname] = self.expand(value, field.type) + return ':' + field._rname return self.expand(value, field.type) def _insert(self, table, fields): if fields: r_values = {} return self.dialect.insert( - table.sqlsafe, - ','.join(el[0].sqlsafe_name for el in fields), + table._rname, + ','.join(el[0]._rname for el in fields), ','.join( self._build_value_for_insert(f, v, r_values) for f, v in fields) ), r_values - return self.dialect.insert_empty(table.sqlsafe), None + return self.dialect.insert_empty(table._rname), None def insert(self, table, fields): query, values = self._insert(table, fields) diff --git a/pydal/adapters/postgres.py b/pydal/adapters/postgres.py index e132330e9..100b2d5fa 100644 --- a/pydal/adapters/postgres.py +++ b/pydal/adapters/postgres.py @@ -116,13 +116,13 @@ def _insert(self, table, fields): retval = None if hasattr(table, '_id'): self._last_insert = (table._id, 1) - retval = table._id.name + retval = table._id._rname return self.dialect.insert( - table.sqlsafe, - ','.join(el[0].sqlsafe_name for el in fields), + table._rname, + ','.join(el[0]._rname for el in fields), ','.join(self.expand(v, f.type) for f, v in fields), retval) - return self.dialect.insert_empty(table.sqlsafe) + return self.dialect.insert_empty(table._rname) @with_connection def prepare(self, key): diff --git a/pydal/adapters/sap.py b/pydal/adapters/sap.py index adc330965..046bf95a2 100644 --- a/pydal/adapters/sap.py +++ b/pydal/adapters/sap.py @@ -45,5 +45,5 @@ def create_sequence_and_triggers(self, query, table, **args): self.execute('CREATE SEQUENCE %s;' % table._sequence_name) self.execute( "ALTER TABLE %s ALTER COLUMN %s SET DEFAULT NEXTVAL('%s');" % - (table._tablename, table._id.name, table._sequence_name)) + (table._rname, table._id._rname, table._sequence_name)) self.execute(query) diff --git a/pydal/adapters/sqlite.py b/pydal/adapters/sqlite.py index 0068695a5..36f79c37a 100644 --- a/pydal/adapters/sqlite.py +++ b/pydal/adapters/sqlite.py @@ -87,7 +87,7 @@ def delete(self, table, query): counter = super(SQLite, self).delete(table, query) if counter: for field in table._referenced_by: - if field.type == 'reference ' + table._tablename \ + if field.type == 'reference ' + table._dalname \ and field.ondelete == 'CASCADE': db(field.belongs(deleted)).delete() return counter diff --git a/pydal/contrib/imap_adapter.py b/pydal/contrib/imap_adapter.py index c84e0c6d9..c3486c881 100644 --- a/pydal/contrib/imap_adapter.py +++ b/pydal/contrib/imap_adapter.py @@ -524,7 +524,7 @@ def select(self, query, fields, attributes): fetch_results = list() if isinstance(query, Query): - tablename = self.get_table(query) + tablename = self.get_table(query)._dalname mailbox = self.connection.mailbox_names.get(tablename, None) if mailbox is None: raise ValueError("Mailbox name not found: %s" % mailbox) @@ -601,7 +601,7 @@ def select(self, query, fields, attributes): if allfields: colnames = ["%s.%s" % (tablename, field) for field in self.search_fields.keys()] else: - colnames = ["%s.%s" % (tablename, field.name) for field in fields] + colnames = [field.longname for field in fields] for k in colnames: imapfields_dict[k] = k @@ -800,10 +800,11 @@ def add_payload(message, obj): else: raise NotImplementedError("IMAP empty insert is not implemented") - def update(self, tablename, query, fields): + def update(self, table, query, fields): # TODO: the adapter should implement an .expand method commands = list() rowcount = 0 + tablename = table._dalname if use_common_filters(query): query = self.common_filter(query, [tablename,]) mark = [] @@ -855,8 +856,9 @@ def count(self,query,distinct=None): counter = len(store_list) return counter - def delete(self, tablename, query): + def delete(self, table, query): counter = 0 + tablename = table._dalname if query: if use_common_filters(query): query = self.common_filter(query, [tablename,]) diff --git a/pydal/dialects/base.py b/pydal/dialects/base.py index 03bfbb628..6495145c8 100644 --- a/pydal/dialects/base.py +++ b/pydal/dialects/base.py @@ -148,13 +148,15 @@ def insert_empty(self, table): def where(self, query): return 'WHERE %s' % query - def update(self, tablename, values, where=None): + def update(self, table, values, where=None): + tablename = self.writing_alias(table) whr = '' if where: whr = ' %s' % self.where(where) return 'UPDATE %s SET %s%s;' % (tablename, values, whr) - def delete(self, tablename, where=None): + def delete(self, table, where=None): + tablename = self.writing_alias(table) whr = '' if where: whr = ' %s' % self.where(where) @@ -470,18 +472,18 @@ def primary_key(self, key): return 'PRIMARY KEY(%s)' % key def drop_table(self, table, mode): - return ['DROP TABLE %s;' % table.sqlsafe] + return ['DROP TABLE %s;' % table._rname] def truncate(self, table, mode=''): if mode: mode = " %s" % mode - return ['TRUNCATE TABLE %s%s;' % (table.sqlsafe, mode)] + return ['TRUNCATE TABLE %s%s;' % (table._rname, mode)] def create_index(self, name, table, expressions, unique=False): uniq = ' UNIQUE' if unique else '' with self.adapter.index_expander(): rv = 'CREATE%s INDEX %s ON %s (%s);' % ( - uniq, self.quote(name), table.sqlsafe, ','.join( + uniq, self.quote(name), table._rname, ','.join( self.expand(field) for field in expressions)) return rv @@ -494,6 +496,9 @@ def constraint_name(self, table, fieldname): def concat_add(self, tablename): return ', ADD ' + def writing_alias(self, table): + return table.sql_fullref + class NoSQLDialect(CommonDialect): @sqltype_for('string') diff --git a/pydal/dialects/firebird.py b/pydal/dialects/firebird.py index bccf5f588..2cacd2c0c 100644 --- a/pydal/dialects/firebird.py +++ b/pydal/dialects/firebird.py @@ -105,10 +105,10 @@ def select(self, fields, tables, where=None, groupby=None, having=None, def drop_table(self, table, mode): sequence_name = table._sequence_name return [ - 'DROP TABLE %s %s;' % (table.sqlsafe, mode), + 'DROP TABLE %s %s;' % (table._rname, mode), 'DROP GENERATOR %s;' % sequence_name] def truncate(self, table, mode=''): return [ - 'DELETE FROM %s;' % table._tablename, + 'DELETE FROM %s;' % table._rname, 'SET GENERATOR %s TO 0;' % table._sequence_name] diff --git a/pydal/dialects/mssql.py b/pydal/dialects/mssql.py index 9c2536d71..907ab3d26 100644 --- a/pydal/dialects/mssql.py +++ b/pydal/dialects/mssql.py @@ -75,9 +75,9 @@ def type_reference_fk(self): @sqltype_for('reference TFK') def type_reference_tfk(self): - return ' CONSTRAINT FK_%(foreign_table)s_PK FOREIGN KEY ' + \ + return ' CONSTRAINT FK_%(constraint_name)s_PK FOREIGN KEY ' + \ '(%(field_name)s) REFERENCES %(foreign_table)s ' + \ - '(%(foreign_key)s) ON DELETE %(on_delete_action)s', + '(%(foreign_key)s) ON DELETE %(on_delete_action)s' @sqltype_for('geometry') def type_geometry(self): @@ -90,6 +90,21 @@ def type_geography(self): def varquote(self, val): return varquote_aux(val, '[%s]') + def update(self, table, values, where=None): + tablename = self.writing_alias(table) + whr = '' + if where: + whr = ' %s' % self.where(where) + return 'UPDATE %s SET %s FROM %s%s;' % ( + table.sql_shortref, values, tablename, whr) + + def delete(self, table, where=None): + tablename = self.writing_alias(table) + whr = '' + if where: + whr = ' %s' % self.where(where) + return 'DELETE %s FROM %s%s;' % (table.sql_shortref, tablename, whr) + def select(self, fields, tables, where=None, groupby=None, having=None, orderby=None, limitby=None, distinct=False, for_update=False): dst, whr, grp, order, limit, offset, upd = '', '', '', '', '', '', '' @@ -180,7 +195,7 @@ def concat_add(self, tablename): return '; ALTER TABLE %s ADD ' % tablename def drop_index(self, name, table): - return 'DROP INDEX %s ON %s;' % (self.quote(name), table.sqlsafe) + return 'DROP INDEX %s ON %s;' % (self.quote(name), table._rname) def st_astext(self, first, query_env={}): return '%s.STAsText()' % self.expand(first, query_env=query_env) @@ -393,7 +408,7 @@ def extract(self, first, what, query_env={}): def truncate(self, table, mode=''): if mode: mode = " %s" % mode - return ['TRUNCATE %s%s;' % (table.sqlsafe, mode)] + return ['TRUNCATE %s%s;' % (table._rname, mode)] def select(self, *args, **kwargs): return SQLDialect.select(self, *args, **kwargs) diff --git a/pydal/dialects/mysql.py b/pydal/dialects/mysql.py index 088480079..f9b0e3519 100644 --- a/pydal/dialects/mysql.py +++ b/pydal/dialects/mysql.py @@ -56,6 +56,13 @@ def varquote(self, val): def insert_empty(self, table): return 'INSERT INTO %s VALUES (DEFAULT);' % table + def delete(self, table, where=None): + tablename = self.writing_alias(table) + whr = '' + if where: + whr = ' %s' % self.where(where) + return 'DELETE %s FROM %s%s;' % (table.sql_shortref, tablename, whr) + @property def random(self): return 'RAND()' @@ -86,8 +93,8 @@ def cast(self, first, second, query_env={}): def drop_table(self, table, mode): # breaks db integrity but without this mysql does not drop table return [ - 'SET FOREIGN_KEY_CHECKS=0;', 'DROP TABLE %s;' % table.sqlsafe, + 'SET FOREIGN_KEY_CHECKS=0;', 'DROP TABLE %s;' % table._rname, 'SET FOREIGN_KEY_CHECKS=1;'] def drop_index(self, name, table): - return 'DROP INDEX %s ON %s;' % (self.quote(name), table.sqlsafe) + return 'DROP INDEX %s ON %s;' % (self.quote(name), table._rname) diff --git a/pydal/dialects/oracle.py b/pydal/dialects/oracle.py index 3452b5977..2c4f72b0e 100644 --- a/pydal/dialects/oracle.py +++ b/pydal/dialects/oracle.py @@ -115,5 +115,5 @@ def select(self, fields, tables, where=None, groupby=None, having=None, def drop_table(self, table, mode): sequence_name = table._sequence_name return [ - 'DROP TABLE %s %s;' % (table.sqlsafe, mode), + 'DROP TABLE %s %s;' % (table._rname, mode), 'DROP SEQUENCE %s;' % sequence_name] diff --git a/pydal/dialects/postgre.py b/pydal/dialects/postgre.py index 514a9d9b8..4f7b7e61b 100644 --- a/pydal/dialects/postgre.py +++ b/pydal/dialects/postgre.py @@ -58,7 +58,7 @@ def sequence_name(self, tablename): def insert(self, table, fields, values, returning=None): ret = '' if returning: - ret = 'RETURNING %s' % self.quote(returning) + ret = 'RETURNING %s' % returning return 'INSERT INTO %s(%s) VALUES (%s)%s;' % ( table, fields, values, ret) @@ -115,7 +115,7 @@ def ilike(self, first, second, escape=None, query_env={}): def drop_table(self, table, mode): if mode not in ['restrict', 'cascade', '']: raise ValueError('Invalid mode: %s' % mode) - return ['DROP TABLE ' + table.sqlsafe + ' ' + mode + ';'] + return ['DROP TABLE ' + table._rname + ' ' + mode + ';'] def create_index(self, name, table, expressions, unique=False, where=None): uniq = ' UNIQUE' if unique else '' @@ -124,7 +124,7 @@ def create_index(self, name, table, expressions, unique=False, where=None): whr = ' %s' % self.where(where) with self.adapter.index_expander(): rv = 'CREATE%s INDEX %s ON %s (%s)%s;' % ( - uniq, self.quote(name), table.sqlsafe, ','.join( + uniq, self.quote(name), table._rname, ','.join( self.expand(field) for field in expressions), whr) return rv diff --git a/pydal/dialects/sqlite.py b/pydal/dialects/sqlite.py index f168a0ea1..5094efc1e 100644 --- a/pydal/dialects/sqlite.py +++ b/pydal/dialects/sqlite.py @@ -40,10 +40,16 @@ def select(self, fields, tables, where=None, groupby=None, having=None, for_update) def truncate(self, table, mode=''): - tablename = table._tablename + tablename = self.adapter.expand(table._raw_rname, 'string') return [ - self.delete(tablename), - self.delete('sqlite_sequence', "name='%s'" % tablename)] + self.delete(table), + "DELETE FROM sqlite_sequence WHERE name=%s" % tablename] + + def writing_alias(self, table): + if table._dalname != table._tablename: + raise SyntaxError( + 'SQLite does not support UPDATE/DELETE on aliased table') + return table._rname @dialects.register_for(Spatialite) diff --git a/pydal/dialects/teradata.py b/pydal/dialects/teradata.py index 25c074d30..cf96a0249 100644 --- a/pydal/dialects/teradata.py +++ b/pydal/dialects/teradata.py @@ -97,4 +97,4 @@ def select(self, fields, tables, where=None, groupby=None, having=None, dst, limit, fields, tables, whr, grp, order, offset, upd) def truncate(self, table, mode=''): - return ['DELETE FROM %s ALL;' % table._tablename] + return ['DELETE FROM %s ALL;' % table._rname] diff --git a/pydal/migrator.py b/pydal/migrator.py index 759de6c0a..f45c2bb1a 100644 --- a/pydal/migrator.py +++ b/pydal/migrator.py @@ -52,7 +52,7 @@ def create_table(self, table, migrate=True, fake_migrate=False, if referenced == '.': referenced = tablename constraint_name = self.dialect.constraint_name( - tablename, field_name) + table._raw_rname, field._raw_rname) # if not '.' in referenced \ # and referenced != tablename \ # and hasattr(table,'_primarykey'): @@ -87,21 +87,21 @@ def create_table(self, table, migrate=True, fake_migrate=False, TFK[rtablename] = {} TFK[rtablename][rfieldname] = field_name else: - fk = rtable.sqlsafe + ' (' + rfield.sqlsafe_name + ')' + fk = rtable._rname + ' (' + rfield._rname + ')' ftype = ftype + \ types['reference FK'] % dict( # should be quoted constraint_name=constraint_name, foreign_key=fk, - table_name=table.sqlsafe, - field_name=field.sqlsafe_name, + table_name=table._rname, + field_name=field._rname, on_delete_action=field.ondelete) else: # make a guess here for circular references if referenced in db: - id_fieldname = db[referenced]._id.sqlsafe_name + id_fieldname = db[referenced]._id._rname elif referenced == tablename: - id_fieldname = table._id.sqlsafe_name + id_fieldname = table._id._rname else: # make a guess id_fieldname = self.dialect.quote('id') #gotcha: the referenced table must be defined before @@ -111,18 +111,18 @@ def create_table(self, table, migrate=True, fake_migrate=False, #migrations and model relationship work also if tables #are not defined in order if referenced == tablename: - real_referenced = db[referenced].sqlsafe + real_referenced = db[referenced]._rname else: real_referenced = ( - referenced in db and db[referenced].sqlsafe or + referenced in db and db[referenced]._rname or referenced) rfield = db[referenced]._id ftype_info = dict( - index_name=self.dialect.quote(field_name+'__idx'), - field_name=field.sqlsafe_name, + index_name=self.dialect.quote(field._raw_rname+'__idx'), + field_name=field._rname, constraint_name=self.dialect.quote(constraint_name), foreign_key='%s (%s)' % ( - real_referenced, rfield.sqlsafe_name), + real_referenced, rfield._rname), on_delete_action=field.ondelete) ftype_info['null'] = ' NOT NULL' if field.notnull else \ self.dialect.allow_null @@ -158,8 +158,8 @@ def create_table(self, table, migrate=True, fake_migrate=False, schema = parms[0] ftype = "SELECT AddGeometryColumn ('%%(schema)s', '%%(tablename)s', '%%(fieldname)s', %%(srid)s, '%s', %%(dimension)s);" % types[geotype] ftype = ftype % dict(schema=schema, - tablename=tablename, - fieldname=field_name, srid=srid, + tablename=table._raw_rname, + fieldname=field._raw_rname, srid=srid, dimension=dimension) postcreation_fields.append(ftype) elif field_type not in types: @@ -185,7 +185,9 @@ def create_table(self, table, migrate=True, fake_migrate=False, notnull=field.notnull, sortable=sortable, type=str(field_type), - sql=ftype) + sql=ftype, + rname=field._rname, + raw_rname=field._raw_rname) if field.notnull and field.default is not None: # Caveat: sql_fields and sql_fields_aux @@ -201,40 +203,46 @@ def create_table(self, table, migrate=True, fake_migrate=False, # geometry fields are added after the table has been created, not now if not (self.dbengine == 'postgres' and field_type.startswith('geom')): - fields.append('%s %s' % (field.sqlsafe_name, ftype)) + fields.append('%s %s' % (field._rname, ftype)) other = ';' # backend-specific extensions to fields if self.dbengine == 'mysql': if not hasattr(table, "_primarykey"): - fields.append('PRIMARY KEY (%s)' % ( - self.dialect.quote(table._id.name))) + fields.append('PRIMARY KEY (%s)' % (table._id._rname)) engine = self.adapter.adapter_args.get('engine', 'InnoDB') other = ' ENGINE=%s CHARACTER SET utf8;' % engine fields = ',\n '.join(fields) for rtablename in TFK: + rtable = db[rtablename] rfields = TFK[rtablename] - pkeys = [ - self.dialect.quote(pk) for pk in db[rtablename]._primarykey] - fkeys = [self.dialect.quote(rfields[k].name) for k in pkeys] + pkeys = [rtable[pk]._rname for pk in rtable._primarykey] + fk_fields = [table[rfields[k]] for k in rtable._primarykey] + fkeys = [f._rname for f in fk_fields] + constraint_name = self.dialect.constraint_name( + table._raw_rname, '_'.join(f._raw_rname for f in fk_fields)) + on_delete = list(set(f.ondelete for f in fk_fields)) + if len(on_delete) > 1: + raise SyntaxError('Table %s has incompatible ON DELETE actions in multi-field foreign key.' % table._dalname) fields = fields + ',\n ' + \ types['reference TFK'] % dict( - table_name=table.sqlsafe, + constraint_name=constraint_name, + table_name=table._rname, field_name=', '.join(fkeys), - foreign_table=table.sqlsafe, + foreign_table=rtable._rname, foreign_key=', '.join(pkeys), - on_delete_action=field.ondelete) + on_delete_action=on_delete[0]) if getattr(table, '_primarykey', None): query = "CREATE TABLE %s(\n %s,\n %s) %s" % \ - (table.sqlsafe, fields, + (table._rname, fields, self.dialect.primary_key(', '.join([ - self.dialect.quote(pk) + table[pk]._rname for pk in table._primarykey])), other) else: query = "CREATE TABLE %s(\n %s\n)%s" % \ - (table.sqlsafe, fields, other) + (table._rname, fields, other) uri = self.adapter.uri if uri.startswith('sqlite:///') \ @@ -289,6 +297,15 @@ def create_table(self, table, migrate=True, fake_migrate=False, self.file_close(tfile) raise RuntimeError('File %s appears corrupted' % table._dbt) self.file_close(tfile) + # add missing rnames + for key, item in sql_fields_old.items(): + tmp = sql_fields.get(key) + if tmp: + item.setdefault('rname', tmp['rname']) + item.setdefault('raw_rname', tmp['raw_rname']) + else: + item.setdefault('rname', self.dialect.quote(key)) + item.setdefault('raw_rname', key) if sql_fields != sql_fields_old: self.migrate_table( table, @@ -312,8 +329,14 @@ def migrate_table(self, table, sql_fields, sql_fields_old, sql_fields_aux, db = table._db db._migrated.append(table._tablename) tablename = table._tablename + if self.dbengine in ('firebird',): + drop_expr = 'ALTER TABLE %s DROP %s;' + else: + drop_expr = 'ALTER TABLE %s DROP COLUMN %s;' + field_types = dict((x.lower(), table[x].type) + for x in sql_fields.keys() if x in table) # make sure all field names are lower case to avoid - # migrations because of case cahnge + # migrations because of case change sql_fields = dict(map(self._fix, iteritems(sql_fields))) sql_fields_old = dict(map(self._fix, iteritems(sql_fields_old))) sql_fields_aux = dict(map(self._fix, iteritems(sql_fields_aux))) @@ -325,7 +348,7 @@ def migrate_table(self, table, sql_fields, sql_fields_old, sql_fields_aux, for key in sql_fields_old: if key not in keys: keys.append(key) - new_add = self.dialect.concat_add(tablename) + new_add = self.dialect.concat_add(table._rname) metadata_change = False sql_fields_current = copy.copy(sql_fields_old) @@ -339,12 +362,22 @@ def migrate_table(self, table, sql_fields, sql_fields_old, sql_fields_aux, query = [sql_fields[key]['sql']] else: query = ['ALTER TABLE %s ADD %s %s;' % ( - table.sqlsafe, self.dialect.quote(key), + table._rname, sql_fields[key]['rname'], sql_fields_aux[key]['sql'].replace(', ', new_add))] metadata_change = True elif self.dbengine in ('sqlite', 'spatialite'): if key in sql_fields: sql_fields_current[key] = sql_fields[key] + # Field rname has changes, add new column + if (sql_fields[key]['raw_rname'].lower() != + sql_fields_old[key]['raw_rname'].lower()): + tt = sql_fields_aux[key]['sql'].replace(', ', new_add) + query = [ + 'ALTER TABLE %s ADD %s %s;' % ( + table._rname, sql_fields[key]['rname'], tt), + 'UPDATE %s SET %s=%s;' % ( + table._rname, sql_fields[key]['rname'], + sql_fields_old[key]['rname'])] metadata_change = True elif key not in sql_fields: del sql_fields_current[key] @@ -355,48 +388,45 @@ def migrate_table(self, table, sql_fields, sql_fields_old, sql_fields_aux, schema = parms.split(',')[0] query = ["SELECT DropGeometryColumn ('%(schema)s', \ '%(table)s', '%(field)s');" % dict( - schema=schema, table=tablename, field=key)] - elif self.dbengine in ('firebird',): - query = ['ALTER TABLE %s DROP %s;' % ( - self.dialect.quote(tablename), - self.dialect.quote(key))] + schema=schema, table=table._raw_rname, + field=sql_fields_old[key]['raw_rname'])] else: - query = ['ALTER TABLE %s DROP COLUMN %s;' % ( - self.dialect.quote(tablename), - self.dialect.quote(key))] + query = [drop_expr % ( + table._rname, sql_fields_old[key]['rname'])] + metadata_change = True + # The field has a new rname, temp field is not needed + elif (sql_fields[key]['raw_rname'].lower() != + sql_fields_old[key]['raw_rname'].lower()): + sql_fields_current[key] = sql_fields[key] + tt = sql_fields_aux[key]['sql'].replace(', ', new_add) + query = [ + 'ALTER TABLE %s ADD %s %s;' % ( + table._rname, sql_fields[key]['rname'], tt), + 'UPDATE %s SET %s=%s;' % ( + table._rname, sql_fields[key]['rname'], + sql_fields_old[key]['rname']), + drop_expr % (table._rname, sql_fields_old[key]['rname']), + ] metadata_change = True elif ( sql_fields[key]['sql'] != sql_fields_old[key]['sql'] and not - (key in table.fields and - isinstance(table[key].type, SQLCustomType)) and not + isinstance(field_types.get(key), SQLCustomType) and not sql_fields[key]['type'].startswith('reference') and not sql_fields[key]['type'].startswith('double') and not sql_fields[key]['type'].startswith('id')): sql_fields_current[key] = sql_fields[key] - t = tablename tt = sql_fields_aux[key]['sql'].replace(', ', new_add) - if self.dbengine in ('firebird',): - drop_expr = 'ALTER TABLE %s DROP %s;' - else: - drop_expr = 'ALTER TABLE %s DROP COLUMN %s;' - key_tmp = key + '__tmp' + key_tmp = self.dialect.quote(key + '__tmp') query = [ - 'ALTER TABLE %s ADD %s %s;' % ( - self.dialect.quote(t), self.dialect.quote(key_tmp), - tt), + 'ALTER TABLE %s ADD %s %s;' % (table._rname, key_tmp, tt), 'UPDATE %s SET %s=%s;' % ( - self.dialect.quote(t), self.dialect.quote(key_tmp), - self.dialect.quote(key)), - drop_expr % ( - self.dialect.quote(t), self.dialect.quote(key)), + table._rname, key_tmp, sql_fields_old[key]['rname']), + drop_expr % (table._rname, sql_fields_old[key]['rname']), 'ALTER TABLE %s ADD %s %s;' % ( - self.dialect.quote(t), - self.dialect.quote(key), tt), + table._rname, sql_fields[key]['rname'], tt), 'UPDATE %s SET %s=%s;' % ( - self.dialect.quote(t), self.dialect.quote(key), - self.dialect.quote(key_tmp)), - drop_expr % ( - self.dialect.quote(t), self.dialect.quote(key_tmp)) + table._rname, sql_fields[key]['rname'], key_tmp), + drop_expr % (table._rname, key_tmp) ] metadata_change = True elif sql_fields[key]['type'] != sql_fields_old[key]['type']: diff --git a/pydal/objects.py b/pydal/objects.py index fdc56524c..69086a502 100644 --- a/pydal/objects.py +++ b/pydal/objects.py @@ -34,6 +34,7 @@ attempt_upload_on_insert, attempt_upload_on_update, delete_uploaded_files ) from .helpers.serializers import serializers +from .utils import deprecated DEFAULTLENGTH = {'string': 512, 'password': 512, 'upload': 512, 'text': 2**15, @@ -221,16 +222,17 @@ def __init__(self, db, tablename, *fields, **args): super(Table, self).__init__() self._actual = False # set to True by define_table() self._db = db - self._tablename = tablename + self._tablename = self._dalname = tablename if not isinstance(tablename, str) or hasattr(DAL, tablename) or not \ REGEX_VALID_TB_FLD.match(tablename) or \ REGEX_PYTHON_KEYWORDS.match(tablename): raise SyntaxError('Field: invalid table name: %s, ' 'use rname for "funny" names' % tablename) - self._ot = None - self._rname = args.get('rname') + self._rname = args.get('rname') or \ + db and db._adapter.dialect.quote(tablename) + self._raw_rname = args.get('rname') or db and tablename self._sequence_name = args.get('sequence_name') or \ - db and db._adapter.dialect.sequence_name(self._rname or tablename) + db and db._adapter.dialect.sequence_name(self._raw_rname) self._trigger_name = args.get('trigger_name') or \ db and db._adapter.dialect.trigger_name(tablename) self._common_filter = args.get('common_filter') @@ -346,9 +348,7 @@ def check_reserved(field_name): self[field_name] = field if field.type == 'id': self['id'] = field - field.tablename = field._tablename = tablename - field.table = field._table = self - field.db = field._db = db + field.bind(self) self.ALL = SQLALL(self) if _primarykey is not None: @@ -381,7 +381,7 @@ def _enable_record_versioning(self, current_record_label=None): db = self._db archive_db = archive_db or db - archive_name = archive_name % dict(tablename=self._tablename) + archive_name = archive_name % dict(tablename=self._dalname) if archive_name in archive_db.tables(): return # do not try define the archive if already exists fieldnames = self.fields() @@ -408,7 +408,7 @@ def _enable_record_versioning(self, AND, [ tab.is_active == True for tab in db._adapter.tables(query).values() - if tab.real_name == self.real_name] + if tab._raw_rname == self._raw_rname] ) query = self._common_filter if query: @@ -624,38 +624,34 @@ def __repr__(self): return '