diff --git a/trytond/CHANGELOG b/trytond/CHANGELOG index e86346a935..a782f008b4 100644 --- a/trytond/CHANGELOG +++ b/trytond/CHANGELOG @@ -1,3 +1,4 @@ +* Use IDENTITY column for id columns in the postgresql backend * Use no breaking space to format number and date * Fallback to code to format currency without symbol * Add sort method to order like ModelStorage.search diff --git a/trytond/trytond/backend/postgresql/database.py b/trytond/trytond/backend/postgresql/database.py index 871f60b246..e156c68af5 100644 --- a/trytond/trytond/backend/postgresql/database.py +++ b/trytond/trytond/backend/postgresql/database.py @@ -416,17 +416,22 @@ class Database(DatabaseInterface): def nextid(self, connection, table): cursor = connection.cursor() - cursor.execute("SELECT NEXTVAL(%s)", (table + '_id_seq',)) + cursor.execute( + "SELECT NEXTVAL(pg_get_serial_sequence(%s, %s))", (table, 'id')) return cursor.fetchone()[0] def setnextid(self, connection, table, value): cursor = connection.cursor() - cursor.execute("SELECT SETVAL(%s, %s)", (table + '_id_seq', value)) + cursor.execute( + "SELECT SETVAL(pg_get_serial_sequence(%s, %s), %s)", + (table, 'id', value)) def currid(self, connection, table): cursor = connection.cursor() + cursor.execute("SELECT pg_get_serial_sequence(%s, %s)", (table, 'id')) + sequence_name, = cursor.fetchone() cursor.execute(SQL("SELECT last_value FROM {}").format( - Identifier(table + '_id_seq'))) + Identifier(*sequence_name.split('.')))) return cursor.fetchone()[0] def lock(self, connection, table): diff --git a/trytond/trytond/backend/postgresql/init.sql b/trytond/trytond/backend/postgresql/init.sql index 184a6235c4..26cde97e00 100644 --- a/trytond/trytond/backend/postgresql/init.sql +++ b/trytond/trytond/backend/postgresql/init.sql @@ -1,16 +1,12 @@ -CREATE SEQUENCE ir_configuration_id_seq; - CREATE TABLE ir_configuration ( - id INTEGER DEFAULT NEXTVAL('ir_configuration_id_seq') NOT NULL, + id INTEGER GENERATED BY DEFAULT AS IDENTITY, language VARCHAR, hostname VARCHAR, PRIMARY KEY(id) ); -CREATE SEQUENCE ir_model_id_seq; - CREATE TABLE ir_model ( - id INTEGER DEFAULT NEXTVAL('ir_model_id_seq') NOT NULL, + id INTEGER GENERATED BY DEFAULT AS IDENTITY, model VARCHAR NOT NULL, name VARCHAR, info TEXT, @@ -18,10 +14,8 @@ CREATE TABLE ir_model ( PRIMARY KEY(id) ); -CREATE SEQUENCE ir_model_field_id_seq; - CREATE TABLE ir_model_field ( - id INTEGER DEFAULT NEXTVAL('ir_model_field_id_seq') NOT NULL, + id INTEGER GENERATED BY DEFAULT AS IDENTITY, model INTEGER, name VARCHAR NOT NULL, relation VARCHAR, @@ -34,10 +28,8 @@ CREATE TABLE ir_model_field ( FOREIGN KEY (model) REFERENCES ir_model(id) ON DELETE CASCADE ); -CREATE SEQUENCE ir_ui_view_id_seq; - CREATE TABLE ir_ui_view ( - id INTEGER DEFAULT NEXTVAL('ir_ui_view_id_seq') NOT NULL, + id INTEGER GENERATED BY DEFAULT AS IDENTITY, model VARCHAR NOT NULL, "type" VARCHAR, data TEXT NOT NULL, @@ -46,10 +38,8 @@ CREATE TABLE ir_ui_view ( PRIMARY KEY(id) ); -CREATE SEQUENCE ir_ui_menu_id_seq; - CREATE TABLE ir_ui_menu ( - id INTEGER DEFAULT NEXTVAL('ir_ui_menu_id_seq') NOT NULL, + id INTEGER GENERATED BY DEFAULT AS IDENTITY, parent INTEGER, name VARCHAR NOT NULL, icon VARCHAR, @@ -57,10 +47,8 @@ CREATE TABLE ir_ui_menu ( FOREIGN KEY (parent) REFERENCES ir_ui_menu (id) ON DELETE SET NULL ); -CREATE SEQUENCE ir_translation_id_seq; - CREATE TABLE ir_translation ( - id INTEGER DEFAULT NEXTVAL('ir_translation_id_seq') NOT NULL, + id INTEGER GENERATED BY DEFAULT AS IDENTITY, lang VARCHAR, src TEXT, name VARCHAR NOT NULL, @@ -72,10 +60,8 @@ CREATE TABLE ir_translation ( PRIMARY KEY(id) ); -CREATE SEQUENCE ir_lang_id_seq; - CREATE TABLE ir_lang ( - id INTEGER DEFAULT NEXTVAL('ir_lang_id_seq') NOT NULL, + id INTEGER GENERATED BY DEFAULT AS IDENTITY, name VARCHAR NOT NULL, code VARCHAR NOT NULL, translatable BOOLEAN NOT NULL, @@ -85,10 +71,8 @@ CREATE TABLE ir_lang ( PRIMARY KEY(id) ); -CREATE SEQUENCE res_user_id_seq; - CREATE TABLE res_user ( - id INTEGER DEFAULT NEXTVAL('res_user_id_seq') NOT NULL, + id INTEGER GENERATED BY DEFAULT AS IDENTITY, name VARCHAR NOT NULL, active BOOLEAN NOT NULL, login VARCHAR NOT NULL, @@ -100,18 +84,14 @@ ALTER TABLE res_user ADD CONSTRAINT res_user_login_key UNIQUE (login); INSERT INTO res_user (id, login, password, name, active) VALUES (0, 'root', NULL, 'Root', False); -CREATE SEQUENCE res_group_id_seq; - CREATE TABLE res_group ( - id INTEGER DEFAULT NEXTVAL('res_group_id_seq') NOT NULL, + id INTEGER GENERATED BY DEFAULT AS IDENTITY, name VARCHAR NOT NULL, PRIMARY KEY(id) ); -CREATE SEQUENCE "res_user-res_group_id_seq"; - CREATE TABLE "res_user-res_group" ( - id INTEGER DEFAULT NEXTVAL('res_user-res_group_id_seq') NOT NULL, + id INTEGER GENERATED BY DEFAULT AS IDENTITY, "user" INTEGER NOT NULL, "group" INTEGER NOT NULL, FOREIGN KEY ("user") REFERENCES res_user (id) ON DELETE CASCADE, @@ -119,10 +99,8 @@ CREATE TABLE "res_user-res_group" ( PRIMARY KEY(id) ); -CREATE SEQUENCE ir_module_id_seq; - CREATE TABLE ir_module ( - id INTEGER DEFAULT NEXTVAL('ir_module_id_seq') NOT NULL, + id INTEGER GENERATED BY DEFAULT AS IDENTITY, create_uid INTEGER NOT NULL, create_date TIMESTAMP WITHOUT TIME ZONE NOT NULL, write_date TIMESTAMP WITHOUT TIME ZONE, @@ -136,10 +114,8 @@ CREATE TABLE ir_module ( ALTER TABLE ir_module ADD CONSTRAINT ir_module_name_uniq UNIQUE (name); -CREATE SEQUENCE ir_module_dependency_id_seq; - CREATE TABLE ir_module_dependency ( - id INTEGER DEFAULT NEXTVAL('ir_module_dependency_id_seq') NOT NULL, + id INTEGER GENERATED BY DEFAULT AS IDENTITY, create_uid INTEGER NOT NULL, create_date TIMESTAMP WITHOUT TIME ZONE NOT NULL, write_date TIMESTAMP WITHOUT TIME ZONE, @@ -152,10 +128,8 @@ CREATE TABLE ir_module_dependency ( FOREIGN KEY (module) REFERENCES ir_module ON DELETE CASCADE ); -CREATE SEQUENCE ir_cache_id_seq; - CREATE TABLE ir_cache ( - id INTEGER DEFAULT NEXTVAL('ir_cache_id_seq') NOT NULL, + id INTEGER GENERATED BY DEFAULT AS IDENTITY, name VARCHAR NOT NULL, "timestamp" TIMESTAMP WITHOUT TIME ZONE ); diff --git a/trytond/trytond/backend/postgresql/table.py b/trytond/trytond/backend/postgresql/table.py index 6ffbb1a685..e3bef8c247 100644 --- a/trytond/trytond/backend/postgresql/table.py +++ b/trytond/trytond/backend/postgresql/table.py @@ -28,11 +28,6 @@ class TableHandler(TableHandlerInterface): transaction = Transaction() cursor = transaction.connection.cursor() - # Create sequence if necessary - if not transaction.database.sequence_exist( - transaction.connection, self.sequence_name): - transaction.database.sequence_create( - transaction.connection, self.sequence_name) # Create new table if necessary if not self.table_exist(self.table_name): @@ -51,50 +46,72 @@ class TableHandler(TableHandlerInterface): Identifier(self.table_name)), (model.__doc__,)) + def migrate_to_identity(table, column): + previous_seq_name = f"{table}_{column}_seq" + cursor.execute( + "SELECT NEXTVAL(%s)", (previous_seq_name,)) + next_val, = cursor.fetchone() + cursor.execute( + "SELECT seqincrement, seqmax, seqmin, seqcache " + "FROM pg_sequence WHERE seqrelid = %s::regclass", + (previous_seq_name,)) + increment, s_max, s_min, cache = cursor.fetchone() + # Previously created sequences were setting bigint values for those + # identity column mimic the type of the underlying column + if (s_max > 2 ** 31 - 1 + and self._columns[column]['typname'] != 'int8'): + s_max = 2 ** 31 - 1 + if (s_min < -(2 ** 31) + and self._columns[column]['typname'] != 'int8'): + s_min = -(2 ** 31) + cursor.execute( + SQL("ALTER TABLE {} ALTER COLUMN {} DROP DEFAULT").format( + Identifier(table), Identifier(column))) + cursor.execute( + SQL("DROP SEQUENCE {}").format( + Identifier(previous_seq_name))) + cursor.execute( + SQL("ALTER TABLE {} ALTER COLUMN {} " + "ADD GENERATED BY DEFAULT AS IDENTITY").format( + Identifier(table), Identifier(column))) + cursor.execute( + "SELECT pg_get_serial_sequence(%s, %s)", (table, column)) + serial_seq_name, = cursor.fetchone() + cursor.execute( + (f"ALTER SEQUENCE {serial_seq_name} INCREMENT BY %s " + "MINVALUE %s MAXVALUE %s RESTART WITH %s CACHE %s"), + (increment, s_min, s_max, next_val, cache)) + + update_definitions = False if 'id' not in self._columns: + update_definitions = True if not self.history: cursor.execute( SQL( "ALTER TABLE {} ADD COLUMN id INTEGER " - "DEFAULT nextval(%s) NOT NULL").format( - Identifier(self.table_name)), - (self.sequence_name,)) - cursor.execute( - SQL('ALTER TABLE {} ADD PRIMARY KEY(id)') - .format(Identifier(self.table_name))) + "GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY").format( + Identifier(self.table_name))) else: cursor.execute( SQL('ALTER TABLE {} ADD COLUMN id INTEGER') .format(Identifier(self.table_name))) - self._update_definitions(columns=True) + else: + if not self.history and not self.__columns['id']['identity']: + update_definitions = True + migrate_to_identity(self.table_name, 'id') if self.history and '__id' not in self._columns: + update_definitions = True cursor.execute( SQL( "ALTER TABLE {} ADD COLUMN __id INTEGER " - "DEFAULT nextval(%s) NOT NULL").format( - Identifier(self.table_name)), - (self.sequence_name,)) - cursor.execute( - SQL('ALTER TABLE {} ADD PRIMARY KEY(__id)') - .format(Identifier(self.table_name))) + "GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY").format( + Identifier(self.table_name))) + elif self.history: + if not self.__columns['__id']['identity']: + update_definitions = True + migrate_to_identity(self.table_name, '__id') + if update_definitions: self._update_definitions(columns=True) - else: - default = "nextval('%s'::regclass)" % self.sequence_name - if self.history: - if self._columns['__id']['default'] != default: - cursor.execute( - SQL("ALTER TABLE {} " - "ALTER __id SET DEFAULT nextval(%s::regclass)") - .format(Identifier(self.table_name)), - (self.sequence_name,)) - self._update_definitions(columns=True) - if self._columns['id']['default'] != default: - cursor.execute( - SQL("ALTER TABLE {} " - "ALTER id SET DEFAULT nextval(%s::regclass)") - .format(Identifier(self.table_name)), - (self.sequence_name,)) - self._update_definitions(columns=True) @classmethod def table_exist(cls, table_name): @@ -111,11 +128,6 @@ class TableHandler(TableHandlerInterface): and not cls.table_exist(new_name)): cursor.execute(SQL('ALTER TABLE {} RENAME TO {}').format( Identifier(old_name), Identifier(new_name))) - # Rename sequence - old_sequence = old_name + '_id_seq' - new_sequence = new_name + '_id_seq' - transaction.database.sequence_rename( - transaction.connection, old_sequence, new_sequence) # Rename history table old_history = old_name + "__history" new_history = new_name + "__history" @@ -151,16 +163,17 @@ class TableHandler(TableHandlerInterface): cursor.execute('SELECT ' 'column_name, udt_name, is_nullable, ' 'character_maximum_length, ' - 'column_default ' + 'column_default, is_identity ' 'FROM information_schema.columns ' 'WHERE table_name = %s AND table_schema = %s', (self.table_name, self.table_schema)) - for column, typname, nullable, size, default in cursor: + for column, typname, nullable, size, default, identity in cursor: self.__columns[column] = { 'typname': typname, 'notnull': True if nullable == 'NO' else False, 'size': size, 'default': default, + 'identity': False if identity == 'NO' else True, } return self.__columns