From 6f110b2d2f3e2934c506d177a7bb9c289468e20a Mon Sep 17 00:00:00 2001 From: hengsin Date: Wed, 3 Jan 2024 10:02:59 +0800 Subject: [PATCH] IDEMPIERE-5963 Implement Sub-Partitioning and Detach Partition (#2150) * IDEMPIERE-5963 Implement Sub-Partitioning and Detach Partition Implement sub-partitioning for PostgreSQL Implement sub-partitioning for Oracle Implement detach/re-attach --- .../oracle/202312261050_IDEMPIERE-5963.sql | 137 ++++ .../202312271420_IDEMPIERE-5963.sql | 134 ++++ .../db/partition/ITablePartitionService.java | 33 + .../compiere/model/I_AD_TablePartition.java | 39 +- .../src/org/compiere/model/MTable.java | 18 +- .../compiere/model/X_AD_TablePartition.java | 85 +- .../partition/TablePartitionService.java | 375 ++++++++- .../partition/TablePartitionService.java | 753 ++++++++++++++---- .../tablepartition/TablePartitionTask.java | 1 + .../process/ChangePartitionStatus.java | 87 ++ 10 files changed, 1481 insertions(+), 181 deletions(-) create mode 100644 migration/iD11/oracle/202312261050_IDEMPIERE-5963.sql create mode 100644 migration/iD11/postgresql/202312271420_IDEMPIERE-5963.sql create mode 100644 org.idempiere.tablepartition/src/org/idempiere/tablepartition/process/ChangePartitionStatus.java diff --git a/migration/iD11/oracle/202312261050_IDEMPIERE-5963.sql b/migration/iD11/oracle/202312261050_IDEMPIERE-5963.sql new file mode 100644 index 0000000000..8fe3cd6c4e --- /dev/null +++ b/migration/iD11/oracle/202312261050_IDEMPIERE-5963.sql @@ -0,0 +1,137 @@ +-- IDEMPIERE-5963 Implement Sub-Partitioning and Detach Partition +SELECT register_migration_script('202312261050_IDEMPIERE-5963.sql') FROM dual; + +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Dec 26, 2023, 10:50:32 AM MYT +INSERT INTO AD_Process (AD_Process_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,Name,Description,Help,IsReport,Value,IsDirectPrint,Classname,AccessLevel,EntityType,Statistic_Count,Statistic_Seconds,IsBetaFunctionality,ShowHelp,CopyFromProcess,AD_Process_UU,AllowMultipleExecution) VALUES (200158,0,0,'Y',TO_TIMESTAMP('2023-12-26 10:50:30','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-12-26 10:50:30','YYYY-MM-DD HH24:MI:SS'),100,'Detach/re-attach Table Partition','Detach or re-attach table partition','Detach an attached table partition or re-attach a detached table partition','N','DetachOrReAttachPartition','N','org.idempiere.tablepartition.process.ChangePartitionStatus','4','D',0,0,'N','Y','N','d55b6a76-26f7-45a6-9117-a8a9cdb78e0f','PA') +; + +-- Dec 26, 2023, 10:52:22 AM MYT +INSERT INTO AD_Element (AD_Element_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,ColumnName,Name,Description,PrintName,EntityType,AD_Element_UU) VALUES (203890,0,0,'Y',TO_TIMESTAMP('2023-12-26 10:52:21','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-12-26 10:52:21','YYYY-MM-DD HH24:MI:SS'),100,'IsPartitionAttached','Attached','Partition attached to table','Attached','D','f2ba716f-605e-4f68-9ae7-96a384f5902c') +; + +-- Dec 26, 2023, 10:53:31 AM MYT +INSERT INTO AD_Column (AD_Column_ID,Version,Name,Description,AD_Table_ID,ColumnName,DefaultValue,FieldLength,IsKey,IsParent,IsMandatory,IsTranslated,IsIdentifier,SeqNo,IsEncrypted,AD_Reference_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Element_ID,IsUpdateable,IsSelectionColumn,EntityType,IsSyncDatabase,IsAlwaysUpdateable,IsAutocomplete,IsAllowLogging,AD_Column_UU,IsAllowCopy,SeqNoSelection,IsToolbarButton,IsSecure,IsHtml,IsPartitionKey) VALUES (216306,0,'Attached','Partition attached to table',200411,'IsPartitionAttached','Y',1,'N','N','Y','N','N',0,'N',20,0,0,'Y',TO_TIMESTAMP('2023-12-26 10:53:30','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-12-26 10:53:30','YYYY-MM-DD HH24:MI:SS'),100,203890,'Y','N','D','N','N','N','Y','9217549b-ee10-4ca0-9322-82822314c180','N',0,'N','N','N','N') +; + +-- Dec 26, 2023, 10:53:37 AM MYT +ALTER TABLE AD_TablePartition ADD IsPartitionAttached CHAR(1) DEFAULT 'Y' CHECK (IsPartitionAttached IN ('Y','N')) NOT NULL +; + +-- Dec 26, 2023, 11:01:05 AM MYT +INSERT INTO AD_Column (AD_Column_ID,Version,Name,AD_Table_ID,ColumnName,FieldLength,IsKey,IsParent,IsMandatory,IsTranslated,IsIdentifier,SeqNo,IsEncrypted,AD_Reference_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Element_ID,IsUpdateable,AD_Process_ID,IsSelectionColumn,EntityType,IsSyncDatabase,IsAlwaysUpdateable,IsAutocomplete,IsAllowLogging,AD_Column_UU,IsAllowCopy,SeqNoSelection,IsToolbarButton,IsSecure,IsHtml,IsPartitionKey) VALUES (216307,0,'Process Now',200411,'Processing',1,'N','N','N','N','N',0,'N',28,0,0,'Y',TO_TIMESTAMP('2023-12-26 11:01:04','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-12-26 11:01:04','YYYY-MM-DD HH24:MI:SS'),100,524,'Y',200158,'N','D','N','Y','N','Y','ddcbfcb0-e232-4ccd-876a-af4b89265ed1','N',0,'B','N','N','N') +; + +-- Dec 26, 2023, 11:01:10 AM MYT +ALTER TABLE AD_TablePartition ADD Processing CHAR(1) DEFAULT NULL +; + +-- Dec 26, 2023, 11:02:50 AM MYT +INSERT INTO AD_Field (AD_Field_ID,Name,Description,AD_Tab_ID,AD_Column_ID,IsDisplayed,DisplayLength,SeqNo,SortNo,IsSameLine,IsHeading,IsFieldOnly,IsEncrypted,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,IsReadOnly,IsCentrallyMaintained,EntityType,AD_Field_UU,IsDisplayedGrid,SeqNoGrid,XPosition,ColumnSpan,NumLines,IsQuickEntry,IsDefaultFocus,IsAdvancedField,IsQuickForm) VALUES (208096,'Attached','Partition attached to table',200381,216306,'Y',0,80,0,'N','N','N','N',0,0,'Y',TO_TIMESTAMP('2023-12-26 11:02:49','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-12-26 11:02:49','YYYY-MM-DD HH24:MI:SS'),100,'N','Y','D','32e3959f-1ddf-4764-b2d2-e706e4b5a1b6','Y',60,2,2,1,'N','N','N','N') +; + +-- Dec 26, 2023, 11:04:14 AM MYT +INSERT INTO AD_Field (AD_Field_ID,Name,AD_Tab_ID,AD_Column_ID,IsDisplayed,DisplayLength,SeqNo,SortNo,IsSameLine,IsHeading,IsFieldOnly,IsEncrypted,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,IsReadOnly,IsCentrallyMaintained,EntityType,AD_Field_UU,IsDisplayedGrid,SeqNoGrid,XPosition,ColumnSpan,NumLines,IsQuickEntry,IsDefaultFocus,IsAdvancedField,IsQuickForm) VALUES (208097,'Process Now',200381,216307,'Y',0,90,0,'N','N','N','N',0,0,'Y',TO_TIMESTAMP('2023-12-26 11:04:14','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-12-26 11:04:14','YYYY-MM-DD HH24:MI:SS'),100,'N','Y','D','8ade7442-7e4e-440a-8a79-86431c3164ac','Y',70,1,2,1,'N','N','N','N') +; + +-- Dec 27, 2023, 10:55:25 AM MYT +INSERT INTO AD_Element (AD_Element_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,ColumnName,Name,Description,PrintName,EntityType,AD_Element_UU) VALUES (203891,0,0,'Y',TO_TIMESTAMP('2023-12-27 10:55:24','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-12-27 10:55:24','YYYY-MM-DD HH24:MI:SS'),100,'Parent_TablePartition_ID','Parent Partition','Parent table partition','Parent Partition','D','b8a4d585-9114-43c9-b5cb-36a65783844e') +; + +-- Dec 27, 2023, 10:56:58 AM MYT +INSERT INTO AD_Reference (AD_Reference_ID,Name,Description,ValidationType,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,EntityType,IsOrderByValue,AD_Reference_UU,ShowInactive) VALUES (200262,'AD_TablePartition','Table Partition','T',0,0,'Y',TO_TIMESTAMP('2023-12-27 10:56:56','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-12-27 10:56:56','YYYY-MM-DD HH24:MI:SS'),100,'D','N','71d7e81f-de03-4329-86d4-41c0f07fa488','N') +; + +-- Dec 27, 2023, 11:00:04 AM MYT +INSERT INTO AD_Ref_Table (AD_Reference_ID,AD_Table_ID,AD_Key,AD_Display,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,IsValueDisplayed,EntityType,AD_Ref_Table_UU) VALUES (200262,200411,216289,216297,0,0,'Y',TO_TIMESTAMP('2023-12-27 11:00:04','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-12-27 11:00:04','YYYY-MM-DD HH24:MI:SS'),100,'N','D','e667c6e5-b53c-43c5-a85f-243d3b188458') +; + +-- Dec 27, 2023, 11:00:23 AM MYT +INSERT INTO AD_Column (AD_Column_ID,Version,Name,Description,AD_Table_ID,ColumnName,FieldLength,IsKey,IsParent,IsMandatory,IsTranslated,IsIdentifier,SeqNo,IsEncrypted,AD_Reference_ID,AD_Reference_Value_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Element_ID,IsUpdateable,IsSelectionColumn,EntityType,IsSyncDatabase,IsAlwaysUpdateable,IsAutocomplete,IsAllowLogging,AD_Column_UU,IsAllowCopy,SeqNoSelection,IsToolbarButton,IsSecure,IsHtml,IsPartitionKey) VALUES (216308,0,'Parent Partition','Parent table partition',200411,'Parent_TablePartition_ID',10,'N','N','N','N','N',0,'N',30,200262,0,0,'Y',TO_TIMESTAMP('2023-12-27 11:00:21','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-12-27 11:00:21','YYYY-MM-DD HH24:MI:SS'),100,203891,'N','N','D','N','N','N','Y','43a932ca-0f17-48b7-be99-e7b4327a7a22','N',0,'N','N','N','N') +; + +-- Dec 27, 2023, 11:00:28 AM MYT +UPDATE AD_Column SET FKConstraintName='ParentTablePartition_ADTablePa', FKConstraintType='N',Updated=TO_TIMESTAMP('2023-12-27 11:00:28','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Column_ID=216308 +; + +-- Dec 27, 2023, 11:00:28 AM MYT +ALTER TABLE AD_TablePartition ADD Parent_TablePartition_ID NUMBER(10) DEFAULT NULL +; + +-- Dec 27, 2023, 11:00:28 AM MYT +ALTER TABLE AD_TablePartition ADD CONSTRAINT ParentTablePartition_ADTablePa FOREIGN KEY (Parent_TablePartition_ID) REFERENCES ad_tablepartition(ad_tablepartition_id) DEFERRABLE INITIALLY DEFERRED +; + +-- Dec 27, 2023, 11:02:48 AM MYT +INSERT INTO AD_Field (AD_Field_ID,Name,Description,AD_Tab_ID,AD_Column_ID,IsDisplayed,DisplayLength,SeqNo,SortNo,IsSameLine,IsHeading,IsFieldOnly,IsEncrypted,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,IsReadOnly,IsCentrallyMaintained,EntityType,AD_Field_UU,IsDisplayedGrid,SeqNoGrid,XPosition,ColumnSpan,NumLines,IsQuickEntry,IsDefaultFocus,IsAdvancedField,IsQuickForm) VALUES (208098,'Parent Partition','Parent table partition',200381,216308,'Y',0,100,0,'N','N','N','N',0,0,'Y',TO_TIMESTAMP('2023-12-27 11:02:48','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-12-27 11:02:48','YYYY-MM-DD HH24:MI:SS'),100,'N','Y','D','cbd9fb43-e055-4e99-9d23-562d1ed515a6','Y',80,1,2,1,'N','N','N','N') +; + +-- Dec 27, 2023, 11:03:46 AM MYT +UPDATE AD_Field SET IsDisplayed='Y', SeqNo=70, XPosition=1,Updated=TO_TIMESTAMP('2023-12-27 11:03:46','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208098 +; + +-- Dec 27, 2023, 11:03:46 AM MYT +UPDATE AD_Field SET IsDisplayed='Y', SeqNo=90, XPosition=5,Updated=TO_TIMESTAMP('2023-12-27 11:03:46','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208097 +; + +-- Dec 27, 2023, 11:03:46 AM MYT +UPDATE AD_Field SET SeqNo=100,Updated=TO_TIMESTAMP('2023-12-27 11:03:46','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208091 +; + +-- Dec 27, 2023, 11:03:46 AM MYT +UPDATE AD_Field SET SeqNo=0,Updated=TO_TIMESTAMP('2023-12-27 11:03:46','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208089 +; + +-- Dec 27, 2023, 11:03:46 AM MYT +UPDATE AD_Field SET SeqNo=0,Updated=TO_TIMESTAMP('2023-12-27 11:03:46','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208088 +; + +-- Dec 27, 2023, 1:39:36 PM MYT +UPDATE AD_Column SET IsUpdateable='N', IsAllowCopy='N',Updated=TO_TIMESTAMP('2023-12-27 13:39:36','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Column_ID=216301 +; + +-- Dec 27, 2023, 1:39:43 PM MYT +UPDATE AD_Column SET IsMandatory='Y', IsUpdateable='N',Updated=TO_TIMESTAMP('2023-12-27 13:39:43','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Column_ID=216296 +; + +-- Dec 27, 2023, 1:39:49 PM MYT +UPDATE AD_Column SET IsMandatory='Y', IsUpdateable='N',Updated=TO_TIMESTAMP('2023-12-27 13:39:49','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Column_ID=216306 +; + +-- Dec 27, 2023, 1:39:55 PM MYT +UPDATE AD_Column SET IsUpdateable='N', IsAllowCopy='N',Updated=TO_TIMESTAMP('2023-12-27 13:39:55','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Column_ID=216297 +; + +-- Dec 27, 2023, 1:40:04 PM MYT +UPDATE AD_Column SET IsAlwaysUpdateable='N',Updated=TO_TIMESTAMP('2023-12-27 13:40:04','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Column_ID=216307 +; + +-- Dec 27, 2023, 1:40:52 PM MYT +UPDATE AD_Tab SET IsReadOnly='N',Updated=TO_TIMESTAMP('2023-12-27 13:40:52','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tab_ID=200381 +; + +-- Dec 27, 2023, 2:20:41 PM MYT +UPDATE AD_Column SET IsMandatory='Y', IsUpdateable='Y',Updated=TO_TIMESTAMP('2023-12-27 14:20:41','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Column_ID=216306 +; + +-- Dec 27, 2023, 2:20:59 PM MYT +UPDATE AD_Field SET IsReadOnly='Y',Updated=TO_TIMESTAMP('2023-12-27 14:20:59','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208096 +; + +-- Dec 28, 2023, 1:55:33 PM MYT +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('E','Can''t detach or re-attach default partition',0,0,'Y',TO_TIMESTAMP('2023-12-28 13:55:32','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-12-28 13:55:32','YYYY-MM-DD HH24:MI:SS'),100,200859,'CantDetachReattachDefaultPartition','D','375e3278-6231-4486-a8ef-8a978ce57698') +; + +-- Dec 28, 2023, 4:11:58 PM MYT +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('I','The partition {0} has been successfully detached from table {1}',0,0,'Y',TO_TIMESTAMP('2023-12-28 16:11:57','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-12-28 16:11:57','YYYY-MM-DD HH24:MI:SS'),100,200860,'PartitionDetachFromTable','D','3b92cf36-e6e4-401b-8663-5897e54b8966') +; + +-- Dec 28, 2023, 4:12:21 PM MYT +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('I','The partition {0} has been successfully re-attached to table {1}',0,0,'Y',TO_TIMESTAMP('2023-12-28 16:12:21','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-12-28 16:12:21','YYYY-MM-DD HH24:MI:SS'),100,200861,'PartitionReAttachToTable','D','4a3c84d0-fc35-4d62-b5ab-3ba018453783') +; + +-- Dec 29, 2023, 5:45:23 PM MYT +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('E','A maximum of two columns can be specified as partition keys. The first column defines the primary partition, and the second (optional) column defines the subpartition',0,0,'Y',TO_TIMESTAMP('2023-12-29 17:45:17','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-12-29 17:45:17','YYYY-MM-DD HH24:MI:SS'),100,200862,'OnlyTwoPartitionKeyAllowed','D','8b27d206-d764-4291-80ce-b00b135daa32') +; diff --git a/migration/iD11/postgresql/202312271420_IDEMPIERE-5963.sql b/migration/iD11/postgresql/202312271420_IDEMPIERE-5963.sql new file mode 100644 index 0000000000..f90114cffd --- /dev/null +++ b/migration/iD11/postgresql/202312271420_IDEMPIERE-5963.sql @@ -0,0 +1,134 @@ +-- IDEMPIERE-5963 Implement Sub-Partitioning and Detach Partition +SELECT register_migration_script('202312271420_IDEMPIERE-5963.sql') FROM dual; + +-- Dec 26, 2023, 10:50:32 AM MYT +INSERT INTO AD_Process (AD_Process_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,Name,Description,Help,IsReport,Value,IsDirectPrint,Classname,AccessLevel,EntityType,Statistic_Count,Statistic_Seconds,IsBetaFunctionality,ShowHelp,CopyFromProcess,AD_Process_UU,AllowMultipleExecution) VALUES (200158,0,0,'Y',TO_TIMESTAMP('2023-12-26 10:50:30','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-12-26 10:50:30','YYYY-MM-DD HH24:MI:SS'),100,'Detach/re-attach Table Partition','Detach or re-attach table partition','Detach an attached table partition or re-attach a detached table partition','N','DetachOrReAttachPartition','N','org.idempiere.tablepartition.process.ChangePartitionStatus','4','D',0,0,'N','Y','N','d55b6a76-26f7-45a6-9117-a8a9cdb78e0f','PA') +; + +-- Dec 26, 2023, 10:52:22 AM MYT +INSERT INTO AD_Element (AD_Element_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,ColumnName,Name,Description,PrintName,EntityType,AD_Element_UU) VALUES (203890,0,0,'Y',TO_TIMESTAMP('2023-12-26 10:52:21','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-12-26 10:52:21','YYYY-MM-DD HH24:MI:SS'),100,'IsPartitionAttached','Attached','Partition attached to table','Attached','D','f2ba716f-605e-4f68-9ae7-96a384f5902c') +; + +-- Dec 26, 2023, 10:53:31 AM MYT +INSERT INTO AD_Column (AD_Column_ID,Version,Name,Description,AD_Table_ID,ColumnName,DefaultValue,FieldLength,IsKey,IsParent,IsMandatory,IsTranslated,IsIdentifier,SeqNo,IsEncrypted,AD_Reference_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Element_ID,IsUpdateable,IsSelectionColumn,EntityType,IsSyncDatabase,IsAlwaysUpdateable,IsAutocomplete,IsAllowLogging,AD_Column_UU,IsAllowCopy,SeqNoSelection,IsToolbarButton,IsSecure,IsHtml,IsPartitionKey) VALUES (216306,0,'Attached','Partition attached to table',200411,'IsPartitionAttached','Y',1,'N','N','Y','N','N',0,'N',20,0,0,'Y',TO_TIMESTAMP('2023-12-26 10:53:30','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-12-26 10:53:30','YYYY-MM-DD HH24:MI:SS'),100,203890,'Y','N','D','N','N','N','Y','9217549b-ee10-4ca0-9322-82822314c180','N',0,'N','N','N','N') +; + +-- Dec 26, 2023, 10:53:37 AM MYT +ALTER TABLE AD_TablePartition ADD COLUMN IsPartitionAttached CHAR(1) DEFAULT 'Y' CHECK (IsPartitionAttached IN ('Y','N')) NOT NULL +; + +-- Dec 26, 2023, 11:01:05 AM MYT +INSERT INTO AD_Column (AD_Column_ID,Version,Name,AD_Table_ID,ColumnName,FieldLength,IsKey,IsParent,IsMandatory,IsTranslated,IsIdentifier,SeqNo,IsEncrypted,AD_Reference_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Element_ID,IsUpdateable,AD_Process_ID,IsSelectionColumn,EntityType,IsSyncDatabase,IsAlwaysUpdateable,IsAutocomplete,IsAllowLogging,AD_Column_UU,IsAllowCopy,SeqNoSelection,IsToolbarButton,IsSecure,IsHtml,IsPartitionKey) VALUES (216307,0,'Process Now',200411,'Processing',1,'N','N','N','N','N',0,'N',28,0,0,'Y',TO_TIMESTAMP('2023-12-26 11:01:04','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-12-26 11:01:04','YYYY-MM-DD HH24:MI:SS'),100,524,'Y',200158,'N','D','N','Y','N','Y','ddcbfcb0-e232-4ccd-876a-af4b89265ed1','N',0,'B','N','N','N') +; + +-- Dec 26, 2023, 11:01:10 AM MYT +ALTER TABLE AD_TablePartition ADD COLUMN Processing CHAR(1) DEFAULT NULL +; + +-- Dec 26, 2023, 11:02:50 AM MYT +INSERT INTO AD_Field (AD_Field_ID,Name,Description,AD_Tab_ID,AD_Column_ID,IsDisplayed,DisplayLength,SeqNo,SortNo,IsSameLine,IsHeading,IsFieldOnly,IsEncrypted,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,IsReadOnly,IsCentrallyMaintained,EntityType,AD_Field_UU,IsDisplayedGrid,SeqNoGrid,XPosition,ColumnSpan,NumLines,IsQuickEntry,IsDefaultFocus,IsAdvancedField,IsQuickForm) VALUES (208096,'Attached','Partition attached to table',200381,216306,'Y',0,80,0,'N','N','N','N',0,0,'Y',TO_TIMESTAMP('2023-12-26 11:02:49','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-12-26 11:02:49','YYYY-MM-DD HH24:MI:SS'),100,'N','Y','D','32e3959f-1ddf-4764-b2d2-e706e4b5a1b6','Y',60,2,2,1,'N','N','N','N') +; + +-- Dec 26, 2023, 11:04:14 AM MYT +INSERT INTO AD_Field (AD_Field_ID,Name,AD_Tab_ID,AD_Column_ID,IsDisplayed,DisplayLength,SeqNo,SortNo,IsSameLine,IsHeading,IsFieldOnly,IsEncrypted,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,IsReadOnly,IsCentrallyMaintained,EntityType,AD_Field_UU,IsDisplayedGrid,SeqNoGrid,XPosition,ColumnSpan,NumLines,IsQuickEntry,IsDefaultFocus,IsAdvancedField,IsQuickForm) VALUES (208097,'Process Now',200381,216307,'Y',0,90,0,'N','N','N','N',0,0,'Y',TO_TIMESTAMP('2023-12-26 11:04:14','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-12-26 11:04:14','YYYY-MM-DD HH24:MI:SS'),100,'N','Y','D','8ade7442-7e4e-440a-8a79-86431c3164ac','Y',70,1,2,1,'N','N','N','N') +; + +-- Dec 27, 2023, 10:55:25 AM MYT +INSERT INTO AD_Element (AD_Element_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,ColumnName,Name,Description,PrintName,EntityType,AD_Element_UU) VALUES (203891,0,0,'Y',TO_TIMESTAMP('2023-12-27 10:55:24','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-12-27 10:55:24','YYYY-MM-DD HH24:MI:SS'),100,'Parent_TablePartition_ID','Parent Partition','Parent table partition','Parent Partition','D','b8a4d585-9114-43c9-b5cb-36a65783844e') +; + +-- Dec 27, 2023, 10:56:58 AM MYT +INSERT INTO AD_Reference (AD_Reference_ID,Name,Description,ValidationType,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,EntityType,IsOrderByValue,AD_Reference_UU,ShowInactive) VALUES (200262,'AD_TablePartition','Table Partition','T',0,0,'Y',TO_TIMESTAMP('2023-12-27 10:56:56','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-12-27 10:56:56','YYYY-MM-DD HH24:MI:SS'),100,'D','N','71d7e81f-de03-4329-86d4-41c0f07fa488','N') +; + +-- Dec 27, 2023, 11:00:04 AM MYT +INSERT INTO AD_Ref_Table (AD_Reference_ID,AD_Table_ID,AD_Key,AD_Display,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,IsValueDisplayed,EntityType,AD_Ref_Table_UU) VALUES (200262,200411,216289,216297,0,0,'Y',TO_TIMESTAMP('2023-12-27 11:00:04','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-12-27 11:00:04','YYYY-MM-DD HH24:MI:SS'),100,'N','D','e667c6e5-b53c-43c5-a85f-243d3b188458') +; + +-- Dec 27, 2023, 11:00:23 AM MYT +INSERT INTO AD_Column (AD_Column_ID,Version,Name,Description,AD_Table_ID,ColumnName,FieldLength,IsKey,IsParent,IsMandatory,IsTranslated,IsIdentifier,SeqNo,IsEncrypted,AD_Reference_ID,AD_Reference_Value_ID,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Element_ID,IsUpdateable,IsSelectionColumn,EntityType,IsSyncDatabase,IsAlwaysUpdateable,IsAutocomplete,IsAllowLogging,AD_Column_UU,IsAllowCopy,SeqNoSelection,IsToolbarButton,IsSecure,IsHtml,IsPartitionKey) VALUES (216308,0,'Parent Partition','Parent table partition',200411,'Parent_TablePartition_ID',10,'N','N','N','N','N',0,'N',30,200262,0,0,'Y',TO_TIMESTAMP('2023-12-27 11:00:21','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-12-27 11:00:21','YYYY-MM-DD HH24:MI:SS'),100,203891,'N','N','D','N','N','N','Y','43a932ca-0f17-48b7-be99-e7b4327a7a22','N',0,'N','N','N','N') +; + +-- Dec 27, 2023, 11:00:28 AM MYT +UPDATE AD_Column SET FKConstraintName='ParentTablePartition_ADTablePa', FKConstraintType='N',Updated=TO_TIMESTAMP('2023-12-27 11:00:28','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Column_ID=216308 +; + +-- Dec 27, 2023, 11:00:28 AM MYT +ALTER TABLE AD_TablePartition ADD COLUMN Parent_TablePartition_ID NUMERIC(10) DEFAULT NULL +; + +-- Dec 27, 2023, 11:00:28 AM MYT +ALTER TABLE AD_TablePartition ADD CONSTRAINT ParentTablePartition_ADTablePa FOREIGN KEY (Parent_TablePartition_ID) REFERENCES ad_tablepartition(ad_tablepartition_id) DEFERRABLE INITIALLY DEFERRED +; + +-- Dec 27, 2023, 11:02:48 AM MYT +INSERT INTO AD_Field (AD_Field_ID,Name,Description,AD_Tab_ID,AD_Column_ID,IsDisplayed,DisplayLength,SeqNo,SortNo,IsSameLine,IsHeading,IsFieldOnly,IsEncrypted,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,IsReadOnly,IsCentrallyMaintained,EntityType,AD_Field_UU,IsDisplayedGrid,SeqNoGrid,XPosition,ColumnSpan,NumLines,IsQuickEntry,IsDefaultFocus,IsAdvancedField,IsQuickForm) VALUES (208098,'Parent Partition','Parent table partition',200381,216308,'Y',0,100,0,'N','N','N','N',0,0,'Y',TO_TIMESTAMP('2023-12-27 11:02:48','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-12-27 11:02:48','YYYY-MM-DD HH24:MI:SS'),100,'N','Y','D','cbd9fb43-e055-4e99-9d23-562d1ed515a6','Y',80,1,2,1,'N','N','N','N') +; + +-- Dec 27, 2023, 11:03:46 AM MYT +UPDATE AD_Field SET IsDisplayed='Y', SeqNo=70, XPosition=1,Updated=TO_TIMESTAMP('2023-12-27 11:03:46','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208098 +; + +-- Dec 27, 2023, 11:03:46 AM MYT +UPDATE AD_Field SET IsDisplayed='Y', SeqNo=90, XPosition=5,Updated=TO_TIMESTAMP('2023-12-27 11:03:46','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208097 +; + +-- Dec 27, 2023, 11:03:46 AM MYT +UPDATE AD_Field SET SeqNo=100,Updated=TO_TIMESTAMP('2023-12-27 11:03:46','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208091 +; + +-- Dec 27, 2023, 11:03:46 AM MYT +UPDATE AD_Field SET SeqNo=0,Updated=TO_TIMESTAMP('2023-12-27 11:03:46','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208089 +; + +-- Dec 27, 2023, 11:03:46 AM MYT +UPDATE AD_Field SET SeqNo=0,Updated=TO_TIMESTAMP('2023-12-27 11:03:46','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208088 +; + +-- Dec 27, 2023, 1:39:36 PM MYT +UPDATE AD_Column SET IsUpdateable='N', IsAllowCopy='N',Updated=TO_TIMESTAMP('2023-12-27 13:39:36','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Column_ID=216301 +; + +-- Dec 27, 2023, 1:39:43 PM MYT +UPDATE AD_Column SET IsMandatory='Y', IsUpdateable='N',Updated=TO_TIMESTAMP('2023-12-27 13:39:43','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Column_ID=216296 +; + +-- Dec 27, 2023, 1:39:49 PM MYT +UPDATE AD_Column SET IsMandatory='Y', IsUpdateable='N',Updated=TO_TIMESTAMP('2023-12-27 13:39:49','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Column_ID=216306 +; + +-- Dec 27, 2023, 1:39:55 PM MYT +UPDATE AD_Column SET IsUpdateable='N', IsAllowCopy='N',Updated=TO_TIMESTAMP('2023-12-27 13:39:55','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Column_ID=216297 +; + +-- Dec 27, 2023, 1:40:04 PM MYT +UPDATE AD_Column SET IsAlwaysUpdateable='N',Updated=TO_TIMESTAMP('2023-12-27 13:40:04','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Column_ID=216307 +; + +-- Dec 27, 2023, 1:40:52 PM MYT +UPDATE AD_Tab SET IsReadOnly='N',Updated=TO_TIMESTAMP('2023-12-27 13:40:52','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Tab_ID=200381 +; + +-- Dec 27, 2023, 2:20:41 PM MYT +UPDATE AD_Column SET IsMandatory='Y', IsUpdateable='Y',Updated=TO_TIMESTAMP('2023-12-27 14:20:41','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Column_ID=216306 +; + +-- Dec 27, 2023, 2:20:59 PM MYT +UPDATE AD_Field SET IsReadOnly='Y',Updated=TO_TIMESTAMP('2023-12-27 14:20:59','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=208096 +; + +-- Dec 28, 2023, 1:55:33 PM MYT +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('E','Can''t detach or re-attach default partition',0,0,'Y',TO_TIMESTAMP('2023-12-28 13:55:32','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-12-28 13:55:32','YYYY-MM-DD HH24:MI:SS'),100,200859,'CantDetachReattachDefaultPartition','D','375e3278-6231-4486-a8ef-8a978ce57698') +; + +-- Dec 28, 2023, 4:11:58 PM MYT +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('I','The partition {0} has been successfully detached from table {1}',0,0,'Y',TO_TIMESTAMP('2023-12-28 16:11:57','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-12-28 16:11:57','YYYY-MM-DD HH24:MI:SS'),100,200860,'PartitionDetachFromTable','D','3b92cf36-e6e4-401b-8663-5897e54b8966') +; + +-- Dec 28, 2023, 4:12:21 PM MYT +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('I','The partition {0} has been successfully re-attached to table {1}',0,0,'Y',TO_TIMESTAMP('2023-12-28 16:12:21','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-12-28 16:12:21','YYYY-MM-DD HH24:MI:SS'),100,200861,'PartitionReAttachToTable','D','4a3c84d0-fc35-4d62-b5ab-3ba018453783') +; + +-- Dec 29, 2023, 5:45:23 PM MYT +INSERT INTO AD_Message (MsgType,MsgText,AD_Client_ID,AD_Org_ID,IsActive,Created,CreatedBy,Updated,UpdatedBy,AD_Message_ID,Value,EntityType,AD_Message_UU) VALUES ('E','A maximum of two columns can be specified as partition keys. The first column defines the primary partition, and the second (optional) column defines the subpartition',0,0,'Y',TO_TIMESTAMP('2023-12-29 17:45:17','YYYY-MM-DD HH24:MI:SS'),100,TO_TIMESTAMP('2023-12-29 17:45:17','YYYY-MM-DD HH24:MI:SS'),100,200862,'OnlyTwoPartitionKeyAllowed','D','8b27d206-d764-4291-80ce-b00b135daa32') +; diff --git a/org.adempiere.base/src/org/compiere/db/partition/ITablePartitionService.java b/org.adempiere.base/src/org/compiere/db/partition/ITablePartitionService.java index 88c0315624..8f677395d0 100644 --- a/org.adempiere.base/src/org/compiere/db/partition/ITablePartitionService.java +++ b/org.adempiere.base/src/org/compiere/db/partition/ITablePartitionService.java @@ -26,6 +26,7 @@ package org.compiere.db.partition; import org.compiere.model.MColumn; import org.compiere.model.MTable; +import org.compiere.model.X_AD_TablePartition; import org.compiere.process.ProcessInfo; /** @@ -33,37 +34,69 @@ import org.compiere.process.ProcessInfo; */ public interface ITablePartitionService { /** + * Is table already a partitioned table in DB + * @param table + * @param trxName * @return true if table have been partition in DB */ public boolean isPartitionedTable(MTable table, String trxName); /** * Make existing table a partition table + * @param table + * @param trxName + * @param processInfo * @return true if success */ public boolean createPartitionedTable(MTable table, String trxName, ProcessInfo processInfo); /** * Add new partition for new data and migrate data to new partition (if needed by DB) + * @param table + * @param trxName + * @param processInfo * @return true if success */ public boolean addPartitionAndMigrateData(MTable table, String trxName, ProcessInfo processInfo); /** * Run post partition process (if needed) + * @param table + * @param trxName + * @param processInfo * @return true if success */ public boolean runPostPartitionProcess(MTable table, String trxName, ProcessInfo processInfo); /** * Validate partition configuration for table object + * @param table * @return String error-code - null if not error */ public String isValidConfiguration(MTable table); /** * Validate partition configuration for column object + * @param column * @return String error-code - null if not error */ public String isValidConfiguration(MColumn column); + + /** + * Detach an attached table partition + * @param table + * @param partition + * @param trxName + * @param processInfo + */ + public void detachPartition(MTable table, X_AD_TablePartition partition, String trxName, ProcessInfo processInfo); + + /** + * Re-attach a detached table partition + * @param table + * @param partition + * @param trxName + * @param processInfo + */ + public void reattachPartition(MTable table, X_AD_TablePartition partition, String trxName, ProcessInfo processInfo); } diff --git a/org.adempiere.base/src/org/compiere/model/I_AD_TablePartition.java b/org.adempiere.base/src/org/compiere/model/I_AD_TablePartition.java index 8444e67578..1657a425a9 100644 --- a/org.adempiere.base/src/org/compiere/model/I_AD_TablePartition.java +++ b/org.adempiere.base/src/org/compiere/model/I_AD_TablePartition.java @@ -22,7 +22,7 @@ import org.compiere.util.KeyNamePair; /** Generated Interface for AD_TablePartition * @author iDempiere (generated) - * @version Release 11 + * @version Release 12 */ public interface I_AD_TablePartition { @@ -156,6 +156,19 @@ public interface I_AD_TablePartition */ public boolean isActive(); + /** Column name IsPartitionAttached */ + public static final String COLUMNNAME_IsPartitionAttached = "IsPartitionAttached"; + + /** Set Attached. + * Partition attached to table + */ + public void setIsPartitionAttached (boolean IsPartitionAttached); + + /** Get Attached. + * Partition attached to table + */ + public boolean isPartitionAttached(); + /** Column name Name */ public static final String COLUMNNAME_Name = "Name"; @@ -169,6 +182,30 @@ public interface I_AD_TablePartition */ public String getName(); + /** Column name Parent_TablePartition_ID */ + public static final String COLUMNNAME_Parent_TablePartition_ID = "Parent_TablePartition_ID"; + + /** Set Parent Partition. + * Parent table partition + */ + public void setParent_TablePartition_ID (int Parent_TablePartition_ID); + + /** Get Parent Partition. + * Parent table partition + */ + public int getParent_TablePartition_ID(); + + public org.compiere.model.I_AD_TablePartition getParent_TablePartition() throws RuntimeException; + + /** Column name Processing */ + public static final String COLUMNNAME_Processing = "Processing"; + + /** Set Process Now */ + public void setProcessing (boolean Processing); + + /** Get Process Now */ + public boolean isProcessing(); + /** Column name Updated */ public static final String COLUMNNAME_Updated = "Updated"; diff --git a/org.adempiere.base/src/org/compiere/model/MTable.java b/org.adempiere.base/src/org/compiere/model/MTable.java index e0c9e25b23..d5d4e890fd 100644 --- a/org.adempiere.base/src/org/compiere/model/MTable.java +++ b/org.adempiere.base/src/org/compiere/model/MTable.java @@ -1019,12 +1019,28 @@ public class MTable extends X_AD_Table implements ImmutablePOSupport * @return new X_AD_TablePartition record */ public X_AD_TablePartition createTablePartition(String name, String expression, String trxName, MColumn column) + { + return createTablePartition(name, expression, trxName, column, null); + } + + /** + * Create and save new X_AD_TablePartition record. + * @param name + * @param expression + * @param trxName + * @param column + * @param parentPartition + * @return new X_AD_TablePartition record + */ + public X_AD_TablePartition createTablePartition(String name, String expression, String trxName, MColumn column, X_AD_TablePartition parentPartition) { X_AD_TablePartition partition = new X_AD_TablePartition(Env.getCtx(), 0, trxName); partition.setAD_Table_ID(getAD_Table_ID()); partition.setName(name); partition.setExpressionPartition(expression); - partition.setAD_Column_ID(column.getAD_Column_ID()); + partition.setAD_Column_ID(column.getAD_Column_ID()); + if (parentPartition != null) + partition.setParent_TablePartition_ID(parentPartition.getAD_TablePartition_ID()); partition.saveEx(); return partition; } diff --git a/org.adempiere.base/src/org/compiere/model/X_AD_TablePartition.java b/org.adempiere.base/src/org/compiere/model/X_AD_TablePartition.java index ff530cf793..d662d8f96d 100644 --- a/org.adempiere.base/src/org/compiere/model/X_AD_TablePartition.java +++ b/org.adempiere.base/src/org/compiere/model/X_AD_TablePartition.java @@ -22,7 +22,7 @@ import java.util.Properties; /** Generated Model for AD_TablePartition * @author iDempiere (generated) - * @version Release 11 - $Id$ */ + * @version Release 12 - $Id$ */ @org.adempiere.base.Model(table="AD_TablePartition") public class X_AD_TablePartition extends PO implements I_AD_TablePartition, I_Persistent { @@ -30,7 +30,7 @@ public class X_AD_TablePartition extends PO implements I_AD_TablePartition, I_Pe /** * */ - private static final long serialVersionUID = 20231222L; + private static final long serialVersionUID = 20231227L; /** Standard Constructor */ public X_AD_TablePartition (Properties ctx, int AD_TablePartition_ID, String trxName) @@ -41,6 +41,8 @@ public class X_AD_TablePartition extends PO implements I_AD_TablePartition, I_Pe setAD_Column_ID (0); setAD_Table_ID (0); setExpressionPartition (null); + setIsPartitionAttached (true); +// Y setName (null); } */ } @@ -54,6 +56,8 @@ public class X_AD_TablePartition extends PO implements I_AD_TablePartition, I_Pe setAD_Column_ID (0); setAD_Table_ID (0); setExpressionPartition (null); + setIsPartitionAttached (true); +// Y setName (null); } */ } @@ -67,6 +71,8 @@ public class X_AD_TablePartition extends PO implements I_AD_TablePartition, I_Pe setAD_Column_ID (0); setAD_Table_ID (0); setExpressionPartition (null); + setIsPartitionAttached (true); +// Y setName (null); } */ } @@ -80,6 +86,8 @@ public class X_AD_TablePartition extends PO implements I_AD_TablePartition, I_Pe setAD_Column_ID (0); setAD_Table_ID (0); setExpressionPartition (null); + setIsPartitionAttached (true); +// Y setName (null); } */ } @@ -221,6 +229,29 @@ public class X_AD_TablePartition extends PO implements I_AD_TablePartition, I_Pe return (String)get_Value(COLUMNNAME_ExpressionPartition); } + /** Set Attached. + @param IsPartitionAttached Partition attached to table + */ + public void setIsPartitionAttached (boolean IsPartitionAttached) + { + set_Value (COLUMNNAME_IsPartitionAttached, Boolean.valueOf(IsPartitionAttached)); + } + + /** Get Attached. + @return Partition attached to table + */ + public boolean isPartitionAttached() + { + Object oo = get_Value(COLUMNNAME_IsPartitionAttached); + if (oo != null) + { + if (oo instanceof Boolean) + return ((Boolean)oo).booleanValue(); + return "Y".equals(oo); + } + return false; + } + /** Set Name. @param Name Alphanumeric identifier of the entity */ @@ -236,4 +267,54 @@ public class X_AD_TablePartition extends PO implements I_AD_TablePartition, I_Pe { return (String)get_Value(COLUMNNAME_Name); } + + public org.compiere.model.I_AD_TablePartition getParent_TablePartition() throws RuntimeException + { + return (org.compiere.model.I_AD_TablePartition)MTable.get(getCtx(), org.compiere.model.I_AD_TablePartition.Table_ID) + .getPO(getParent_TablePartition_ID(), get_TrxName()); + } + + /** Set Parent Partition. + @param Parent_TablePartition_ID Parent table partition + */ + public void setParent_TablePartition_ID (int Parent_TablePartition_ID) + { + if (Parent_TablePartition_ID < 1) + set_ValueNoCheck (COLUMNNAME_Parent_TablePartition_ID, null); + else + set_ValueNoCheck (COLUMNNAME_Parent_TablePartition_ID, Integer.valueOf(Parent_TablePartition_ID)); + } + + /** Get Parent Partition. + @return Parent table partition + */ + public int getParent_TablePartition_ID() + { + Integer ii = (Integer)get_Value(COLUMNNAME_Parent_TablePartition_ID); + if (ii == null) + return 0; + return ii.intValue(); + } + + /** Set Process Now. + @param Processing Process Now + */ + public void setProcessing (boolean Processing) + { + set_Value (COLUMNNAME_Processing, Boolean.valueOf(Processing)); + } + + /** Get Process Now. + @return Process Now */ + public boolean isProcessing() + { + Object oo = get_Value(COLUMNNAME_Processing); + if (oo != null) + { + if (oo instanceof Boolean) + return ((Boolean)oo).booleanValue(); + return "Y".equals(oo); + } + return false; + } } \ No newline at end of file diff --git a/org.compiere.db.oracle.provider/src/org/adempiere/db/oracle/partition/TablePartitionService.java b/org.compiere.db.oracle.provider/src/org/adempiere/db/oracle/partition/TablePartitionService.java index 6310d9ed97..1668d370ec 100644 --- a/org.compiere.db.oracle.provider/src/org/adempiere/db/oracle/partition/TablePartitionService.java +++ b/org.compiere.db.oracle.provider/src/org/adempiere/db/oracle/partition/TablePartitionService.java @@ -27,6 +27,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -38,8 +39,10 @@ import org.compiere.db.partition.RangePartitionInterval; import org.compiere.model.MColumn; import org.compiere.model.MTable; import org.compiere.model.Query; +import org.compiere.model.X_AD_Column; import org.compiere.model.X_AD_TablePartition; import org.compiere.process.ProcessInfo; +import org.compiere.util.CLogger; import org.compiere.util.DB; import org.compiere.util.DisplayType; import org.compiere.util.Env; @@ -71,6 +74,9 @@ public class TablePartitionService implements ITablePartitionService { .append(" MODIFY PARTITION BY "); List partitionKeyColumns = table.getPartitionKeyColumns(false); MColumn partitionKeyColumn = partitionKeyColumns.get(0); + MColumn subPartitionColumn = null; + if (partitionKeyColumns.size() > 1) + subPartitionColumn = partitionKeyColumns.get(1); String partitioningMethod = partitionKeyColumn.getPartitioningMethod(); if (partitioningMethod.equals(MColumn.PARTITIONINGMETHOD_List)) alterStmt.append("List"); @@ -79,18 +85,28 @@ public class TablePartitionService implements ITablePartitionService { else throw new IllegalArgumentException(Msg.getMsg(Env.getCtx(), "PartitioningMethodNotSupported", new Object[]{partitioningMethod})); - alterStmt.append(" (" + partitionKeyColumn.getColumnName() + ")"); + alterStmt.append(" (").append(partitionKeyColumn.getColumnName()).append(")"); if (partitioningMethod.equals(MColumn.PARTITIONINGMETHOD_List) && useAutomaticListPartition) { //with automatic, we still need to create at least one partition and we will create a null partition to fulfill the requirement - alterStmt.append(" AUTOMATIC (PARTITION default_partition "); + alterStmt.append(" AUTOMATIC "); } else if (useIntervalPartition) { alterStmt.append(" INTERVAL("); alterStmt.append(getIntervalExpression(partitionKeyColumn)); - alterStmt.append(") (PARTITION default_partition "); - } else { - alterStmt.append(" (PARTITION default_partition "); + alterStmt.append(") "); } + //oracle will auto create default partition for sub-partition + if (subPartitionColumn != null) { + alterStmt.append("SUBPARTITION BY "); + if (subPartitionColumn.getPartitioningMethod().equals(MColumn.PARTITIONINGMETHOD_List)) + alterStmt.append("List"); + else if (subPartitionColumn.getPartitioningMethod().equals(MColumn.PARTITIONINGMETHOD_Range)) + alterStmt.append("Range"); + else + throw new IllegalArgumentException(Msg.getMsg(Env.getCtx(), "PartitioningMethodNotSupported", new Object[]{subPartitionColumn.getPartitioningMethod()})); + alterStmt.append(" (").append(subPartitionColumn.getColumnName()).append(")"); + } + alterStmt.append(" (PARTITION default_partition "); StringBuilder defaultExpression = new StringBuilder(); if (partitioningMethod.equals(MColumn.PARTITIONINGMETHOD_List)) { defaultExpression.append("VALUES ("); @@ -124,6 +140,10 @@ public class TablePartitionService implements ITablePartitionService { return true; } + /** + * @param partitionKeyColumn + * @return Oracle year/month interval expression (using NUMTOYMINTERVAL) for column range interval + */ private String getIntervalExpression(MColumn partitionKeyColumn) { if (DisplayType.isDate(partitionKeyColumn.getAD_Reference_ID()) || DisplayType.isTimestampWithTimeZone(partitionKeyColumn.getAD_Reference_ID())) { RangePartitionInterval.Interval interval = RangePartitionInterval.getInterval(partitionKeyColumn); @@ -157,11 +177,14 @@ public class TablePartitionService implements ITablePartitionService { List partitionKeyColumns = table.getPartitionKeyColumns(false); if (partitionKeyColumns.size() == 0) return false; - MColumn partitionKeyColumn = partitionKeyColumns.get(0); + MColumn partitionKeyColumn = partitionKeyColumns.get(0); String sql = "SELECT Column_Name FROM User_Part_Key_Columns WHERE Name=? ORDER BY Column_Position"; String partKeyColumn = DB.getSQLValueString(trxName, sql, table.getTableName().toUpperCase()); if (!partitionKeyColumn.getColumnName().equalsIgnoreCase(partKeyColumn)) return false; + MColumn subPartitionColumn = null; + if (partitionKeyColumns.size() > 1) + subPartitionColumn = partitionKeyColumns.get(1); String partitioningMethod = partitionKeyColumn.getPartitioningMethod(); if (partitioningMethod.equals(MColumn.PARTITIONINGMETHOD_List)) { @@ -172,7 +195,7 @@ public class TablePartitionService implements ITablePartitionService { } else { - isUpdated = addListPartition(table, partitionKeyColumn, trxName, processInfo); + isUpdated = addListPartition(table, partitionKeyColumn, trxName, processInfo, "default_partition", null, null); } } else if (partitioningMethod.equals(MColumn.PARTITIONINGMETHOD_Range)) @@ -193,16 +216,90 @@ public class TablePartitionService implements ITablePartitionService { else { syncRange(table, partitionKeyColumn, trxName, interval, processInfo); - isUpdated = addRangePartition(table, partitionKeyColumn, trxName, processInfo); + isUpdated = addRangePartition(table, partitionKeyColumn, trxName, processInfo, "default_partition", null, null); } } } else throw new IllegalArgumentException(Msg.getMsg(Env.getCtx(), "PartitioningMethodNotSupported", new Object[]{partitioningMethod})); + if (subPartitionColumn != null) { + List primaryPartitions = getPrimaryPartitions(table, partitionKeyColumn, trxName); + if (subPartitionColumn.getPartitioningMethod().equals(MColumn.PARTITIONINGMETHOD_List)) + { + for(X_AD_TablePartition primaryPartition : primaryPartitions) + { + String defaultSubPartition = getDefaultSubPartitionName(table, primaryPartition, false, trxName); + if (defaultSubPartition == null) + continue; + isUpdated = addListPartition(table, subPartitionColumn, trxName, processInfo, defaultSubPartition, primaryPartition.getName(), primaryPartition); + } + } + else if (subPartitionColumn.getPartitioningMethod().equals(MColumn.PARTITIONINGMETHOD_Range)) + { + for(X_AD_TablePartition primaryPartition : primaryPartitions) + { + String defaultSubPartition = getDefaultSubPartitionName(table, primaryPartition, true, trxName); + if (defaultSubPartition == null) + continue; + isUpdated = addRangePartition(table, subPartitionColumn, trxName, processInfo, defaultSubPartition, primaryPartition.getName(), primaryPartition); + } + } + else + throw new IllegalArgumentException(Msg.getMsg(Env.getCtx(), "PartitioningMethodNotSupported", new Object[]{subPartitionColumn.getPartitioningMethod()})); + } return isUpdated; } + /** + * Find name of default sub-partition auto created by Oracle + * @param table + * @param primaryPartition + * @param range true if sub-partition is range, false for list + * @param trxName + * @return name of default sub-partition + */ + private String getDefaultSubPartitionName(MTable table, X_AD_TablePartition primaryPartition, boolean range, String trxName) { + String sql = + """ + SELECT High_Value, SubPartition_Name + FROM User_Tab_SubPartitions + WHERE Table_Name=? AND Partition_Name=? + """; + try(PreparedStatement stmt = DB.prepareStatement(sql, trxName)) { + stmt.setString(1, table.getTableName().toUpperCase()); + stmt.setString(2, primaryPartition.getName().toUpperCase()); + ResultSet rs = stmt.executeQuery(); + while(rs.next()) { + String expression = rs.getString(1); + if (range) { + if ("MAXVALUE".equals(expression)) + return rs.getString(2); + } else { + if ("DEFAULT".equals(expression)) { + return rs.getString(2); + } + } + } + } catch (SQLException e) { + throw new DBException(e); + } + return null; + } + + /** + * Find the list of partition for partitionKeyColumn + * @param table + * @param partitionKeyColumn + * @param trxName + * @return list of partition for column + */ + private List getPrimaryPartitions(MTable table, MColumn partitionKeyColumn, String trxName) { + String whereClause = "AD_Table_ID=? AND AD_Column_ID=? And Name != 'DEFAULT_PARTITION' AND IsPartitionAttached='Y' AND IsActive='Y'"; + Query query = new Query(Env.getCtx(), MTable.get(Env.getCtx(), X_AD_TablePartition.Table_ID), whereClause, trxName); + return query.setParameters(table.getAD_Table_ID(), partitionKeyColumn.getAD_Column_ID()).list(); + } + /** * Change partition method from list to range * @param table @@ -215,7 +312,7 @@ public class TablePartitionService implements ITablePartitionService { StringBuilder alterStmt = new StringBuilder("ALTER TABLE ") .append(table.getTableName()) .append(" MODIFY PARTITION BY RANGE ") - .append(" (" + partitionKeyColumn.getColumnName() + ")"); + .append(" (").append(partitionKeyColumn.getColumnName()).append(")"); if (useIntervalPartition) { alterStmt.append(" INTERVAL("); alterStmt.append(getIntervalExpression(partitionKeyColumn)); @@ -264,7 +361,7 @@ public class TablePartitionService implements ITablePartitionService { StringBuilder alterStmt = new StringBuilder("ALTER TABLE ") .append(table.getTableName()) .append(" MODIFY PARTITION BY List "); - alterStmt.append(" (" + keyColumn + ")"); + alterStmt.append(" (").append(keyColumn).append(")"); alterStmt.append(" AUTOMATIC (PARTITION default_partition VALUES ("); alterStmt.append("NULL"); alterStmt.append("))"); @@ -321,7 +418,7 @@ public class TablePartitionService implements ITablePartitionService { StringBuilder alterStmt = new StringBuilder("ALTER TABLE ") .append(table.getTableName()) .append(" MODIFY PARTITION BY Range "); - alterStmt.append(" (" + partitionKeyColumn.getColumnName() + ")"); + alterStmt.append(" (").append(partitionKeyColumn.getColumnName()).append(")"); alterStmt.append(" (PARTITION default_partition VALUES LESS THAN ("); alterStmt.append("MAXVALUE"); alterStmt.append("))"); @@ -345,7 +442,7 @@ public class TablePartitionService implements ITablePartitionService { FROM User_Tab_Partitions WHERE Table_Name=? """; - Query query = new Query(Env.getCtx(), X_AD_TablePartition.Table_Name, "AD_Table_ID=?", trxName); + Query query = new Query(Env.getCtx(), X_AD_TablePartition.Table_Name, "AD_Table_ID=? AND Parent_TablePartition_ID IS NULL", trxName); List existingList = query.setParameters(table.getAD_Table_ID()).list(); List matchedIds = new ArrayList(); List partitionKeyColumns = table.getPartitionKeyColumns(false); @@ -429,15 +526,19 @@ public class TablePartitionService implements ITablePartitionService { * @param partitionKeyColumn * @param trxName * @param pi + * @param fromPartition name of default partition to select from + * @param partitionNamePrefix + * @param parentPartition * @return true if new list partition added */ - private boolean addListPartition(MTable table, MColumn partitionKeyColumn, String trxName, ProcessInfo pi) { + private boolean addListPartition(MTable table, MColumn partitionKeyColumn, String trxName, ProcessInfo pi, String fromPartition, String partitionNamePrefix, X_AD_TablePartition parentPartition) { boolean isUpdated = false; List partitions = new ArrayList<>(); + boolean subPartition = parentPartition != null; StringBuilder sql = new StringBuilder(); sql.append("SELECT DISTINCT ").append(partitionKeyColumn.getColumnName()); - sql.append(" FROM ").append(table.getTableName()).append(" PARTITION (default_partition) "); + sql.append(" FROM ").append(table.getTableName()).append(" ").append(subPartition ? "SubPartition(" : "Partition(").append(fromPartition).append(") "); sql.append("ORDER BY ").append(partitionKeyColumn.getColumnName()); try (PreparedStatement pstmt = DB.prepareStatement(sql.toString(), trxName)) @@ -447,6 +548,8 @@ public class TablePartitionService implements ITablePartitionService { { StringBuilder name = new StringBuilder(); StringBuilder expression = new StringBuilder("VALUES ("); + if (!Util.isEmpty(partitionNamePrefix, true)) + name.append(partitionNamePrefix).append("_"); String s = rs.getString(partitionKeyColumn.getColumnName()); name.append(s); if (DisplayType.isText(partitionKeyColumn.getAD_Reference_ID())) @@ -459,13 +562,12 @@ public class TablePartitionService implements ITablePartitionService { expression.append(rs.getInt(partitionKeyColumn.getColumnName())); else if (DisplayType.isNumeric(partitionKeyColumn.getAD_Reference_ID())) expression.append(rs.getBigDecimal(partitionKeyColumn.getColumnName()).toPlainString()); - - + expression.append(")"); if (Character.isDigit(name.charAt(0))) { name.insert(0, "p"); } - X_AD_TablePartition partition = table.createTablePartition(name.toString(), expression.toString(), trxName, partitionKeyColumn); + X_AD_TablePartition partition = table.createTablePartition(name.toString(), expression.toString(), trxName, partitionKeyColumn, parentPartition); partitions.add(partition); } } @@ -477,10 +579,10 @@ public class TablePartitionService implements ITablePartitionService { for(X_AD_TablePartition partition : partitions) { StringBuilder alterStmt = new StringBuilder(); - alterStmt.append("ALTER TABLE " + table.getTableName() + " SPLIT PARTITION default_partition "); + alterStmt.append("ALTER TABLE ").append(table.getTableName()).append(" SPLIT ").append(subPartition ? "SubPartition " : "Partition ").append(fromPartition).append(" "); alterStmt.append(partition.getExpressionPartition()); - alterStmt.append(" INTO ( PARTITION ").append(partition.getName()).append(", "); - alterStmt.append("PARTITION default_partition )"); + alterStmt.append(" INTO ( ").append(subPartition ? "SUBPARTITION " : "PARTITION ").append(partition.getName()).append(", "); + alterStmt.append(subPartition ? "SubPartition " : "Partition ").append(fromPartition).append(" )"); int no = DB.executeUpdateEx(alterStmt.toString(), trxName); if (pi != null) pi.addLog(0, null, null, no + " " + alterStmt.toString()); @@ -496,12 +598,16 @@ public class TablePartitionService implements ITablePartitionService { * @param partitionKeyColumn * @param trxName * @param pi + * @param fromPartition name of default partition to select from + * @param partitionNamePrefix + * @param parentPartition * @return true if new range partition added */ - private boolean addRangePartition(MTable table, MColumn partitionKeyColumn, String trxName, ProcessInfo pi) { + private boolean addRangePartition(MTable table, MColumn partitionKeyColumn, String trxName, ProcessInfo pi, String fromPartition, String partitionNamePrefix, X_AD_TablePartition parentPartition) { boolean isUpdated = false; X_AD_TablePartition partition = null; RangePartitionColumn rangePartitionColumn = null; + boolean subPartition = parentPartition != null; String partitionKeyColumnName = partitionKeyColumn.getColumnName(); String partitionKeyColumnRangeIntervalPattern = partitionKeyColumn.getRangePartitionInterval(); @@ -509,7 +615,7 @@ public class TablePartitionService implements ITablePartitionService { StringBuilder sql = new StringBuilder(); sql.append("SELECT MIN(").append(partitionKeyColumnName).append(") AS min_value, "); sql.append("MAX(").append(partitionKeyColumnName).append(") AS max_value "); - sql.append("FROM ").append(table.getTableName()).append(" PARTITION (default_partition) "); + sql.append("FROM ").append(table.getTableName()).append(" ").append(subPartition ? "SubPartition(" : "Partition(").append(fromPartition).append(")"); List values = DB.getSQLValueObjectsEx(trxName, sql.toString()); if (values.get(0) != null && values.get(1) != null) @@ -529,17 +635,19 @@ public class TablePartitionService implements ITablePartitionService { for(RangePartitionInterval rangePartitionInterval : rangePartitionIntervals) { StringBuilder name = new StringBuilder(); + if (!Util.isEmpty(partitionNamePrefix, true)) + name.append(partitionNamePrefix).append("_"); name.append(rangePartitionInterval.getName()); StringBuilder countStmt = new StringBuilder("SELECT Count(*) FROM ") - .append(table.getTableName()).append(" ") + .append(table.getTableName()).append(" ").append(subPartition ? "SubPartition(" : "Partition(").append(fromPartition).append(") ") .append("WHERE ").append(" ") .append(partitionKeyColumn.getColumnName()).append(" >= "); if (DisplayType.isDate(partitionKeyColumn.getAD_Reference_ID()) || DisplayType.isTimestampWithTimeZone(partitionKeyColumn.getAD_Reference_ID())) countStmt.append("TO_DATE(").append(rangePartitionInterval.getFrom()).append(",'YYYY-MM-DD') "); else countStmt.append(rangePartitionInterval.getFrom()).append(" "); - countStmt.append("AND " + partitionKeyColumn.getColumnName()).append(" < "); + countStmt.append("AND ").append(partitionKeyColumn.getColumnName()).append(" < "); if (DisplayType.isDate(partitionKeyColumn.getAD_Reference_ID()) || DisplayType.isTimestampWithTimeZone(partitionKeyColumn.getAD_Reference_ID())) countStmt.append("TO_DATE(").append(rangePartitionInterval.getTo()).append(",'YYYY-MM-DD') "); else @@ -574,14 +682,14 @@ public class TablePartitionService implements ITablePartitionService { name.insert(0, "p"); } if (!tablePartitionNames.contains(name.toString())) - partition = table.createTablePartition(name.toString(), expression.toString(), trxName, partitionKeyColumn); + partition = table.createTablePartition(name.toString(), expression.toString(), trxName, partitionKeyColumn, parentPartition); if (partition != null) { StringBuilder alterStmt = new StringBuilder(); - alterStmt.append("ALTER TABLE " + table.getTableName() + " SPLIT PARTITION default_partition "); - alterStmt.append(" INTO ( PARTITION ").append(partition.getName()).append(" ").append(partition.getExpressionPartition()).append(", "); - alterStmt.append("PARTITION default_partition )"); + alterStmt.append("ALTER TABLE ").append(table.getTableName()).append(" SPLIT ").append(subPartition ? "SubPartition " : "Partition ").append(fromPartition); + alterStmt.append(" INTO ( ").append(subPartition ? "SUBPARTITION " : "PARTITION ").append(partition.getName()).append(" ").append(partition.getExpressionPartition()).append(", "); + alterStmt.append(subPartition ? "SubPartition " : "Partition ").append(fromPartition).append(" )"); int no = DB.executeUpdateEx(alterStmt.toString(), trxName); if (pi != null) pi.addLog(0, null, null, no + " " + alterStmt.toString()); @@ -594,6 +702,53 @@ public class TablePartitionService implements ITablePartitionService { @Override public boolean runPostPartitionProcess(MTable table, String trxName, ProcessInfo processInfo) { + String sql = + """ + select index_name, partition_name, 'partition' ddl_type + from user_ind_partitions + where (index_name) in + ( select index_name + from user_indexes + where table_name = ? + ) + and status = 'UNUSABLE' + union all + select index_name, subpartition_name, 'subpartition' ddl_type + from user_ind_subpartitions + where (index_name) in + ( select index_name + from user_indexes + where table_name = ? + ) + and status = 'UNUSABLE' + union all + select index_name, null, null + from user_indexes + where table_name = ? + and status = 'UNUSABLE' + """; + + try (PreparedStatement stmt = DB.prepareStatement(sql, trxName)) { + stmt.setString(1, table.getTableName().toUpperCase()); + stmt.setString(2, table.getTableName().toUpperCase()); + stmt.setString(3, table.getTableName().toUpperCase()); + ResultSet rs = stmt.executeQuery(); + while(rs.next()) { + StringBuilder alter = new StringBuilder("ALTER INDEX ") + .append(rs.getString(1)) + .append(" REBUILD "); + String ddlType = rs.getString(3); + if (!Util.isEmpty(ddlType, true) ) { + alter.append(ddlType).append(" ").append(rs.getString(2)); + } + int no = DB.executeUpdateEx(alter.toString(), trxName); + if (processInfo != null) + processInfo.addLog(0, null, null, no + " " + alter.toString()); + } + } catch (SQLException e) { + CLogger.getCLogger(getClass()).log(Level.SEVERE, e.getMessage(), e); + } + return true; } @@ -621,28 +776,174 @@ public class TablePartitionService implements ITablePartitionService { partitionKeyColumns.remove(column); } - if (partitionKeyColumns.size() > 1) - return Msg.getMsg(Env.getCtx(), "OnlyOnePartitionKeyAllowed"); + if (partitionKeyColumns.size() > 2) + return Msg.getMsg(Env.getCtx(), "OnlyTwoPartitionKeyAllowed"); + + if (column.isActive() && column.isPartitionKey() && column.getPartitioningMethod().equals(MColumn.PARTITIONINGMETHOD_Range)) { + String error = RangePartitionInterval.validateIntervalPattern(column); + if (!Util.isEmpty(error)) + return error; + } + + if (!isPartitionedTable(table, trxName)) + return null; //can't change partition key column for range partition if ((!column.isPartitionKey() || !column.isActive()) && Boolean.TRUE.equals(column.get_ValueOld("IsPartitionKey"))) { - if (isRangePartitionedTable(table, trxName)) + if (partitionKeyColumns.size() == 2 + || (partitionKeyColumns.size() == 1 && !column.isPartitionKey()) + || isRangePartitionedTable(table, trxName)) return Msg.getMsg(Env.getCtx(), "PartitionConfigurationChanged"); } + if (column.isActive() && column.isPartitionKey() && partitionKeyColumns.size() == 2) { + if (column.is_ValueChanged(MColumn.COLUMNNAME_PartitioningMethod)) { + return Msg.getMsg(Env.getCtx(), "PartitionConfigurationChanged"); + } else if (column.is_ValueChanged(MColumn.COLUMNNAME_SeqNoPartition)) { + int oldSeq = column.get_ValueOldAsInt(MColumn.COLUMNNAME_SeqNoPartition); + int newSeq = column.getSeqNoPartition(); + int otherSeq = partitionKeyColumns.get(0).getAD_Column_ID() == column.getAD_Column_ID() + ? partitionKeyColumns.get(1).getSeqNoPartition() + : partitionKeyColumns.get(0).getSeqNoPartition(); + if (!(((newSeq < otherSeq) && (oldSeq < otherSeq)) || ((oldSeq > otherSeq) && (newSeq > otherSeq)))) + return Msg.getMsg(Env.getCtx(), "PartitionConfigurationChanged"); + } + } + //can't change partition type to list for range partition if (column.isActive() && column.isPartitionKey() && column.getPartitioningMethod().equals(MColumn.PARTITIONINGMETHOD_List)) { if (isRangePartitionedTable(table, trxName)) return Msg.getMsg(Env.getCtx(), "PartitionConfigurationChanged"); } - if (column.isActive() && column.isPartitionKey() && column.getPartitioningMethod().equals(MColumn.PARTITIONINGMETHOD_Range)) { - String error = RangePartitionInterval.validateIntervalPattern(column); - if (!Util.isEmpty(error)) - return error; - } - return null; } + @Override + public void detachPartition(MTable table, X_AD_TablePartition partition, String trxName, + ProcessInfo processInfo) { + if (partition.isPartitionAttached()) { + String defaultPartitionName = "default_partition"; + if (partition.getParent_TablePartition_ID() > 0) { + X_AD_TablePartition parentPartition = new X_AD_TablePartition(Env.getCtx(), partition.getParent_TablePartition_ID(), trxName); + MColumn partitionKeyColumn = MColumn.get(partition.getAD_Column_ID()); + defaultPartitionName = getDefaultSubPartitionName(table, parentPartition, X_AD_Column.PARTITIONINGMETHOD_Range.equals(partitionKeyColumn.getPartitioningMethod()), trxName); + } + if (!defaultPartitionName.equalsIgnoreCase(partition.getName())) { + StringBuilder exchangeTableName = new StringBuilder(table.getTableName()) + .append("_") + .append(partition.getName()); + StringBuilder alter = null; + Query query = new Query(Env.getCtx(), X_AD_TablePartition.Table_Name, "Parent_TablePartition_ID=? And IsActive='Y' AND IsPartitionAttached='Y'", trxName); + List subPartitions = query.setParameters(partition.getAD_TablePartition_ID()) + .setOrderBy("Name") + .list(); + if (subPartitions.size() > 0) { + alter = new StringBuilder("CREATE TABLE ") + .append(exchangeTableName) + .append(" AS SELECT * FROM ") + .append(table.getTableName()) + .append(" WHERE ROWNUM=-1 "); + + int no = DB.executeUpdateEx(alter.toString(), trxName); + if (processInfo != null) + processInfo.addLog(0, null, null, no + " " + alter.toString()); + + alter = new StringBuilder("ALTER TABLE ") + .append(exchangeTableName) + .append(" MODIFY PARTITION BY "); + MColumn subKeyColumn = MColumn.get(subPartitions.get(0).getAD_Column_ID()); + if (MColumn.PARTITIONINGMETHOD_Range.equals(subKeyColumn.getPartitioningMethod())) + alter.append("RANGE (").append(subKeyColumn.getColumnName()).append(") "); + else + alter.append("LIST (").append(subKeyColumn.getColumnName()).append(") "); + alter.append("("); + for(X_AD_TablePartition subPartition : subPartitions) { + alter.append("PARTITION ").append(subPartition.getExpressionPartition()).append(","); + } + if (MColumn.PARTITIONINGMETHOD_Range.equals(subKeyColumn.getPartitioningMethod())) + alter.append("PARTITION VALUES LESS THAN (MAXVALUE))"); + else + alter.append("PARTITION VALUES (DEFAULT))"); + no = DB.executeUpdateEx(alter.toString(), trxName); + if (processInfo != null) + processInfo.addLog(0, null, null, no + " " + alter.toString()); + } else { + alter = new StringBuilder("CREATE TABLE ") + .append(exchangeTableName) + .append(" FOR EXCHANGE WITH TABLE ") + .append(table.getTableName()); + + int no = DB.executeUpdateEx(alter.toString(), trxName); + if (processInfo != null) + processInfo.addLog(0, null, null, no + " " + alter.toString()); + } + + alter = new StringBuilder("ALTER TABLE ") + .append(table.getTableName()); + if (partition.getParent_TablePartition_ID() > 0) + alter.append(" EXCHANGE SUBPARTITION "); + else + alter.append(" EXCHANGE PARTITION "); + alter.append(partition.getName()) + .append(" WITH TABLE ") + .append(exchangeTableName); + int no = DB.executeUpdateEx(alter.toString(), trxName); + if (processInfo != null) + processInfo.addLog(0, null, null, no + " " + alter.toString()); + partition.setIsPartitionAttached(false); + partition.saveEx(); + } else { + throw new AdempiereException(Msg.getMsg(Env.getCtx(), "CantDetachReattachDefaultPartition")); + } + } + } + + @Override + public void reattachPartition(MTable table, X_AD_TablePartition partition, String trxName, + ProcessInfo processInfo) { + if (!partition.isPartitionAttached()) { + String defaultPartitionName = "default_partition"; + if (partition.getParent_TablePartition_ID() > 0) { + X_AD_TablePartition parentPartition = new X_AD_TablePartition(Env.getCtx(), partition.getParent_TablePartition_ID(), trxName); + MColumn partitionKeyColumn = MColumn.get(partition.getAD_Column_ID()); + defaultPartitionName = getDefaultSubPartitionName(table, parentPartition, X_AD_Column.PARTITIONINGMETHOD_Range.equals(partitionKeyColumn.getPartitioningMethod()), trxName); + } + if (!defaultPartitionName.equalsIgnoreCase(partition.getName())) { + StringBuilder exchangeTableName = new StringBuilder(table.getTableName()) + .append("_") + .append(partition.getName()); + StringBuilder updateStmt = new StringBuilder(); + updateStmt.append("INSERT INTO ").append(table.getTableName()).append(" "); + updateStmt.append("SELECT * FROM ").append(exchangeTableName); + int no = DB.executeUpdateEx(updateStmt.toString(), trxName); + if (processInfo != null) + processInfo.addLog(0, null, null, no + " " + updateStmt.toString()); + + updateStmt = new StringBuilder("DELETE FROM ").append(exchangeTableName); + no = DB.executeUpdateEx(updateStmt.toString(), trxName); + if (processInfo != null) + processInfo.addLog(0, null, null, no + " " + updateStmt.toString()); + + Query query = new Query(Env.getCtx(), X_AD_TablePartition.Table_Name, "Parent_TablePartition_ID=?", trxName); + List subPartitions = query.setParameters(partition.getAD_TablePartition_ID()).list(); + for(X_AD_TablePartition subPartition : subPartitions) { + subPartition.deleteEx(true); + } + + partition.deleteEx(true); + table.getTablePartitions(true, trxName); + addPartitionAndMigrateData(table, trxName, processInfo); + + updateStmt = new StringBuilder("DROP TABLE ") + .append(exchangeTableName); + no = DB.executeUpdateEx(updateStmt.toString(), trxName); + if (processInfo != null) + processInfo.addLog(0, null, null, no + " " + updateStmt.toString()); + } else { + throw new AdempiereException(Msg.getMsg(Env.getCtx(), "CantDetachReattachDefaultPartition")); + } + } + } + } diff --git a/org.compiere.db.postgresql.provider/src/org/adempiere/db/postgresql/partition/TablePartitionService.java b/org.compiere.db.postgresql.provider/src/org/adempiere/db/postgresql/partition/TablePartitionService.java index 52bca8f8b1..115beb839e 100644 --- a/org.compiere.db.postgresql.provider/src/org/adempiere/db/postgresql/partition/TablePartitionService.java +++ b/org.compiere.db.postgresql.provider/src/org/adempiere/db/postgresql/partition/TablePartitionService.java @@ -27,6 +27,7 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.StringTokenizer; import org.adempiere.exceptions.AdempiereException; @@ -44,6 +45,7 @@ import org.compiere.util.DB; import org.compiere.util.DisplayType; import org.compiere.util.Env; import org.compiere.util.Msg; +import org.compiere.util.Trx; import org.compiere.util.Util; public class TablePartitionService implements ITablePartitionService { @@ -76,7 +78,7 @@ public class TablePartitionService implements ITablePartitionService { */ private boolean renameOriginalTable(MTable table, String trxName, ProcessInfo processInfo) { StringBuilder sql = new StringBuilder(); - sql.append("ALTER TABLE " + table.getTableName() + " RENAME TO " + getDefaultPartitionName(table)); + sql.append("ALTER TABLE ").append(table.getTableName()).append(" RENAME TO ").append(getDefaultPartitionName(table)); int no = DB.executeUpdateEx(sql.toString(), trxName); if (processInfo != null) processInfo.addLog(0, null, null, no + " " + sql.toString()); @@ -123,8 +125,8 @@ public class TablePartitionService implements ITablePartitionService { if (constraint_definition.startsWith("PRIMARY KEY ") || constraint_definition.startsWith("UNIQUE ")) { StringBuilder alterStmt = new StringBuilder(); - alterStmt.append("ALTER TABLE " + getDefaultPartitionName(table) + " "); - alterStmt.append("DROP CONSTRAINT " + constraint_name + " CASCADE"); + alterStmt.append("ALTER TABLE ").append(getDefaultPartitionName(table)).append(" "); + alterStmt.append("DROP CONSTRAINT ").append(constraint_name).append(" CASCADE"); int no = DB.executeUpdateEx(alterStmt.toString(), trxName); if (pi != null) pi.addLog(0, null, null, no + " " + alterStmt.toString()); @@ -141,15 +143,15 @@ public class TablePartitionService implements ITablePartitionService { } alterStmt = new StringBuilder(); - alterStmt.append("ALTER TABLE " + table.getTableName() + " "); - alterStmt.append("ADD CONSTRAINT " + constraint_name + " "); + alterStmt.append("ALTER TABLE ").append(table.getTableName()).append(" "); + alterStmt.append("ADD CONSTRAINT ").append(constraint_name).append(" "); alterStmt.append(constraint_definition.substring(0, constraint_definition.length()-1)); for (int x = 0; x < lowerCasePartitionKeyColumnNames.size(); x++) - alterStmt.append(", " + lowerCasePartitionKeyColumnNames.get(x)); + alterStmt.append(", ").append(lowerCasePartitionKeyColumnNames.get(x)); alterStmt.append(")"); no = DB.executeUpdateEx(alterStmt.toString(), trxName); if (pi != null) - pi.addLog(0, null, null, no + " " + alterStmt.toString()); + pi.addLog(0, null, null, no + " " + alterStmt.toString()); } } } @@ -173,8 +175,8 @@ public class TablePartitionService implements ITablePartitionService { constraint_definition = constraint_definition.replace(getDefaultPartitionName(table).toLowerCase(), table.getTableName().toLowerCase()); } StringBuilder alterStmt = new StringBuilder(); - alterStmt.append("ALTER TABLE " + table.getTableName() + " "); - alterStmt.append("ADD CONSTRAINT " + constraint_name + " "); + alterStmt.append("ALTER TABLE ").append(table.getTableName()).append(" "); + alterStmt.append("ADD CONSTRAINT ").append(constraint_name).append(" "); alterStmt.append(constraint_definition); int no = DB.executeUpdateEx(alterStmt.toString(), trxName); if (pi != null) @@ -189,6 +191,108 @@ public class TablePartitionService implements ITablePartitionService { return true; } + /** + * Re-create indexes from the renamed table + * @param table + * @param trxName + * @param pi + * @return true if success + */ + private boolean migrateDBIndexes(MTable table, String trxName, ProcessInfo pi) { + String indexs = + """ + select indexname + from pg_indexes + where schemaname='adempiere' + and tablename=?; + """; + + String sql = + """ + select a.attname, i.indisunique + from pg_index i + join pg_attribute a on (a.attrelid=i.indexrelid) + where i.indrelid::regclass = ?::regclass + and i.indexrelid::regclass = ?::regclass + order by a.attnum; + """; + + Map> indexMap = new HashMap>(); + Map> uniqueMap = new HashMap>(); + try (PreparedStatement stmt = DB.prepareStatement(indexs, trxName)) { + stmt.setString(1, getDefaultPartitionName(table).toLowerCase()); + ResultSet rs = stmt.executeQuery(); + while(rs.next()) { + String indexName = rs.getString(1); + boolean unique = false; + List columns = new ArrayList(); + try(PreparedStatement stmt1 = DB.prepareStatement(sql, trxName)) { + stmt1.setString(1, getDefaultPartitionName(table).toLowerCase()); + stmt1.setString(2, indexName); + ResultSet rs1 = stmt1.executeQuery(); + while(rs1.next()) { + String columnName = rs1.getString(1); + unique = rs1.getBoolean(2); + columns.add(columnName.toLowerCase()); + } + } + if (unique) + uniqueMap.put(indexName, columns); + else + indexMap.put(indexName, columns); + } + } catch (SQLException e) { + throw new DBException(e); + } + + List partitionKeyColumnNames = table.getPartitionKeyColumnNames(); + for(String indexName : uniqueMap.keySet()) { + String consql = "select conindid::regclass from pg_constraint where conrelid = ?::regclass and conindid = ?::regclass"; + String conindid = DB.getSQLValueString(trxName, consql, table.getTableName().toLowerCase(), indexName.toLowerCase()); + if (conindid != null && conindid.equalsIgnoreCase(indexName)) + continue; + + //unique index must include partition key column + List columns = uniqueMap.get(indexName); + for(String partitionKey : partitionKeyColumnNames) { + if (!columns.contains(partitionKey.toLowerCase())) + columns.add(partitionKey.toLowerCase()); + } + StringBuilder alter = new StringBuilder("DROP INDEX ").append(indexName); + DB.executeUpdateEx(alter.toString(), trxName); + alter = new StringBuilder("CREATE UNIQUE INDEX ") + .append(indexName) + .append(" ") + .append("ON ") + .append(table.getTableName()) + .append("(") + .append(String.join(",", columns)) + .append(")"); + DB.executeUpdateEx(alter.toString(), trxName); + } + + for(String indexName : indexMap.keySet()) { + String consql = "select conindid::regclass from pg_constraint where conrelid = ?::regclass and conindid = ?::regclass"; + String conindid = DB.getSQLValueString(trxName, consql, table.getTableName().toLowerCase(), indexName.toLowerCase()); + if (conindid != null && conindid.equalsIgnoreCase(indexName)) + continue; + + List columns = indexMap.get(indexName); + StringBuilder alter = new StringBuilder("DROP INDEX ").append(indexName); + DB.executeUpdateEx(alter.toString(), trxName); + alter = new StringBuilder("CREATE INDEX ") + .append(indexName) + .append(" ") + .append("ON ") + .append(table.getTableName()) + .append("(") + .append(String.join(",", columns)) + .append(")"); + DB.executeUpdateEx(alter.toString(), trxName); + } + return true; + } + /** * Attach renamed original table as default partition * @param table @@ -211,8 +315,8 @@ public class TablePartitionService implements ITablePartitionService { return true; StringBuilder alterStmt = new StringBuilder(); - alterStmt.append("ALTER TABLE " + table.getTableName() + " "); - alterStmt.append("ATTACH PARTITION " + getDefaultPartitionName(table) + " DEFAULT"); + alterStmt.append("ALTER TABLE ").append(table.getTableName()).append(" "); + alterStmt.append("ATTACH PARTITION ").append(getDefaultPartitionName(table)).append(" DEFAULT"); int no = DB.executeUpdateEx(alterStmt.toString(), trxName); if (pi != null) pi.addLog(0, null, null, no + " " + alterStmt.toString()); @@ -255,24 +359,24 @@ public class TablePartitionService implements ITablePartitionService { if (createStmt == null) { createStmt = new StringBuilder(); - createStmt.append("CREATE TABLE " + table_schema + "." + table.getTableName() + " ("); + createStmt.append("CREATE TABLE ").append(table_schema).append(".").append(table.getTableName()).append(" ("); } else { createStmt.append(", "); } - createStmt.append(column_name + " " + data_type); + createStmt.append(column_name).append(" ").append(data_type); if (data_type.equals("numeric") && numeric_precision > 0) - createStmt.append("(" + numeric_precision + "," + numeric_scale + ")"); + createStmt.append("(").append(numeric_precision).append(",").append(numeric_scale).append(")"); else if (data_type.startsWith("character") && character_maximum_length > 0) - createStmt.append("(" + character_maximum_length + ")"); + createStmt.append("(").append(character_maximum_length).append(")"); if ("NO".equals(is_nullable)) createStmt.append(" NOT NULL"); if (!Util.isEmpty(column_default)) - createStmt.append(" DEFAULT " + column_default); + createStmt.append(" DEFAULT ").append(column_default); } if (createStmt != null) @@ -288,7 +392,7 @@ public class TablePartitionService implements ITablePartitionService { else throw new IllegalArgumentException(Msg.getMsg(Env.getCtx(), "PartitioningMethodNotSupported", new Object[]{partitioningMethod})); - createStmt.append(" (" + partitionKeyColumn.getColumnName() + ")"); + createStmt.append(" (").append(partitionKeyColumn.getColumnName()).append(")"); int no = DB.executeUpdateEx(createStmt.toString(), trxName); if (processInfo != null) @@ -297,6 +401,9 @@ public class TablePartitionService implements ITablePartitionService { if (!migrateDBContrainsts(table, trxName, processInfo)) throw new AdempiereException(Msg.getMsg(Env.getCtx(), "FailedMigrateDatabaseConstraints")); + if (!migrateDBIndexes(table, trxName, processInfo)) + throw new AdempiereException(Msg.getMsg(Env.getCtx(), "FailedMigrateDatabaseConstraints")); + if (!attachDefaultPartition(table, trxName, processInfo)) throw new AdempiereException(Msg.getMsg(Env.getCtx(), "FailedAttachDefaultPartition")); @@ -363,14 +470,17 @@ public class TablePartitionService implements ITablePartitionService { List partitionKeyColumns = table.getPartitionKeyColumns(false); MColumn partitionKeyColumn = partitionKeyColumns.get(0); + MColumn subPartitionColumn = null; + if (partitionKeyColumns.size() > 1) + subPartitionColumn = partitionKeyColumns.get(1); String partitioningMethod = partitionKeyColumn.getPartitioningMethod(); if (partitioningMethod.equals(MColumn.PARTITIONINGMETHOD_List)) { - isUpdated = addListPartition(table, partitionKeyColumn, trxName, pi); + isUpdated = addListPartition(table, partitionKeyColumn, trxName, pi, subPartitionColumn); } else if (partitioningMethod.equals(MColumn.PARTITIONINGMETHOD_Range)) { - isUpdated = addRangePartition(table, partitionKeyColumn, trxName, pi); + isUpdated = addRangePartition(table, partitionKeyColumn, trxName, pi, subPartitionColumn); } else throw new IllegalArgumentException(Msg.getMsg(Env.getCtx(), "PartitioningMethodNotSupported", new Object[]{partitioningMethod})); @@ -379,34 +489,144 @@ public class TablePartitionService implements ITablePartitionService { } /** - * Add new range partition - * @param table - * @param partitionKeyColumn + * Create new {@link RangePartitionColumn} instance + * @param fromTableName table name for FROM clause + * @param partitionKeyColumn * @param trxName - * @param pi - * @return true if new range partition added + * @return new RangePartitionColumn instance */ - private boolean addRangePartition(MTable table, MColumn partitionKeyColumn, String trxName, ProcessInfo pi) { - boolean isUpdated = false; - X_AD_TablePartition partition = null; - RangePartitionColumn rangePartitionColumn = null; - + private RangePartitionColumn buildRangePartitionColumn(String fromTableName, MColumn partitionKeyColumn, String trxName) { String partitionKeyColumnName = partitionKeyColumn.getColumnName(); String partitionKeyColumnRangeIntervalPattern = partitionKeyColumn.getRangePartitionInterval(); StringBuilder sql = new StringBuilder(); sql.append("SELECT MIN(").append(partitionKeyColumnName).append(") AS min_value, "); sql.append("MAX(").append(partitionKeyColumnName).append(") AS max_value "); - sql.append("FROM ").append(getDefaultPartitionName(table)); + sql.append("FROM ").append(fromTableName); List values = DB.getSQLValueObjectsEx(trxName, sql.toString()); if (values.get(0) != null && values.get(1) != null) { - rangePartitionColumn = new RangePartitionColumn( + return new RangePartitionColumn( partitionKeyColumnName, partitionKeyColumnRangeIntervalPattern, values.get(0), values.get(1)); } + return null; + } + + /** + * Create new X_AD_TablePartition record for a range partition interval + * @param rangePartitionInterval + * @param tablePartitionNames existing partition names + * @param table + * @param partitionKeyColumn + * @param partitionNamePrefix Prefix for the new range partition name + * @param defaultPartitionName default partition name to select from + * @param parentPartition + * @param trxName + * @return new X_AD_TablePartition record or null (if 0 record in defaultPartitionName for rangePartitionInterval) + */ + private X_AD_TablePartition createNewRangePartition(RangePartitionInterval rangePartitionInterval, List tablePartitionNames, MTable table, MColumn partitionKeyColumn, + String partitionNamePrefix, String defaultPartitionName, X_AD_TablePartition parentPartition, String trxName) { + X_AD_TablePartition partition = null; + StringBuilder name = new StringBuilder(); + name.append(partitionNamePrefix); + name.append("_"); + name.append(rangePartitionInterval.getName()); + + StringBuilder expression = new StringBuilder(); + expression.append("FOR VALUES FROM ("); + expression.append(rangePartitionInterval.getFrom()); + expression.append(") TO ("); + expression.append(rangePartitionInterval.getTo()); + expression.append(")"); + + StringBuilder countStmt = new StringBuilder("SELECT Count(*) FROM ") + .append(defaultPartitionName).append(" ") + .append("WHERE ").append(" ") + .append(partitionKeyColumn.getColumnName()).append(" >= "); + if (DisplayType.isDate(partitionKeyColumn.getAD_Reference_ID()) || DisplayType.isTimestampWithTimeZone(partitionKeyColumn.getAD_Reference_ID())) + countStmt.append("TO_DATE(").append(rangePartitionInterval.getFrom()).append(",'yyyy-MM-dd') "); + else + countStmt.append(rangePartitionInterval.getFrom()).append(" "); + countStmt.append("AND ").append(partitionKeyColumn.getColumnName()).append(" < "); + if (DisplayType.isDate(partitionKeyColumn.getAD_Reference_ID()) || DisplayType.isTimestampWithTimeZone(partitionKeyColumn.getAD_Reference_ID())) + countStmt.append("TO_DATE(").append(rangePartitionInterval.getTo()).append(",'yyyy-MM-dd') "); + else + countStmt.append(rangePartitionInterval.getTo()).append(" "); + int recordCount = DB.getSQLValueEx(trxName, countStmt.toString()); + if (recordCount == 0) { + if (tablePartitionNames.contains(name.toString())) { + Query query = new Query(Env.getCtx(), X_AD_TablePartition.Table_Name, "AD_Table_ID=? AND Name=?", trxName); + X_AD_TablePartition toDelete = query.setParameters(table.getAD_Table_ID(), name.toString()).first(); + if (toDelete != null) + toDelete.deleteEx(true); + } + return null; + } + + if (!tablePartitionNames.contains(name.toString())) { + partition = table.createTablePartition(name.toString(), expression.toString(), trxName, partitionKeyColumn, parentPartition); + tablePartitionNames.add(name.toString()); + } + return partition; + } + + /** + * Move data from default partition to a range partition + * @param partition range partition + * @param partitionKeyColumn + * @param tableName table name to attached partition + * @param defaultPartitionName name of default partition + * @param rangePartitionInterval + * @param pi + * @param trxName + */ + private void moveDefaultPartitionDataForRange(X_AD_TablePartition partition, MColumn partitionKeyColumn, String tableName, + String defaultPartitionName, RangePartitionInterval rangePartitionInterval, ProcessInfo pi, String trxName) { + StringBuilder updateStmt = new StringBuilder(); + updateStmt.append("WITH x AS ( "); + updateStmt.append("DELETE FROM ").append(defaultPartitionName).append(" "); + updateStmt.append("WHERE ").append(" "); + updateStmt.append(partitionKeyColumn.getColumnName()).append(" >= "); + if (DisplayType.isDate(partitionKeyColumn.getAD_Reference_ID()) || DisplayType.isTimestampWithTimeZone(partitionKeyColumn.getAD_Reference_ID())) + updateStmt.append("TO_DATE(").append(rangePartitionInterval.getFrom()).append(",'yyyy-MM-dd') "); + else + updateStmt.append(rangePartitionInterval.getFrom()).append(" "); + updateStmt.append("AND ").append(partitionKeyColumn.getColumnName()).append(" < "); + if (DisplayType.isDate(partitionKeyColumn.getAD_Reference_ID()) || DisplayType.isTimestampWithTimeZone(partitionKeyColumn.getAD_Reference_ID())) + updateStmt.append("TO_DATE(").append(rangePartitionInterval.getTo()).append(",'yyyy-MM-dd') "); + else + updateStmt.append(rangePartitionInterval.getTo()).append(" "); + + updateStmt.append("RETURNING *) "); + updateStmt.append("INSERT INTO ").append(partition.getName()).append(" "); + updateStmt.append("SELECT * FROM x"); + int no = DB.executeUpdateEx(updateStmt.toString(), trxName); + if (pi != null) + pi.addLog(0, null, null, no + " " + updateStmt.toString()); + + StringBuilder alterStmt = new StringBuilder(); + alterStmt.append("ALTER TABLE ").append(tableName).append(" "); + alterStmt.append("ATTACH PARTITION ").append(partition.getName()).append(" ").append(partition.getExpressionPartition()); + no = DB.executeUpdateEx(alterStmt.toString(), trxName); + if (pi != null) + pi.addLog(0, null, null, no + " " + alterStmt.toString()); + } + + /** + * Add new range partition + * @param table + * @param partitionKeyColumn + * @param trxName + * @param pi + * @param subPartitionColumn + * @return true if new range partition added + */ + private boolean addRangePartition(MTable table, MColumn partitionKeyColumn, String trxName, ProcessInfo pi, MColumn subPartitionColumn) { + boolean isUpdated = false; + RangePartitionColumn rangePartitionColumn = buildRangePartitionColumn(getDefaultPartitionName(table), partitionKeyColumn, trxName); if (rangePartitionColumn == null) return false; @@ -415,111 +635,128 @@ public class TablePartitionService implements ITablePartitionService { for (RangePartitionInterval rangePartitionInterval : rangePartitionIntervals) { - StringBuilder name = new StringBuilder(); - name.append(table.getTableName().toLowerCase()); - name.append("_"); - name.append(rangePartitionInterval.getName()); - - StringBuilder expression = new StringBuilder(); - expression.append("FOR VALUES FROM ("); - expression.append(rangePartitionInterval.getFrom()); - expression.append(") TO ("); - expression.append(rangePartitionInterval.getTo()); - expression.append(")"); - - StringBuilder countStmt = new StringBuilder("SELECT Count(*) FROM ") - .append(getDefaultPartitionName(table)).append(" ") - .append("WHERE ").append(" ") - .append(partitionKeyColumn.getColumnName()).append(" >= "); - if (DisplayType.isDate(partitionKeyColumn.getAD_Reference_ID()) || DisplayType.isTimestampWithTimeZone(partitionKeyColumn.getAD_Reference_ID())) - countStmt.append("TO_DATE(").append(rangePartitionInterval.getFrom()).append(",'yyyy-MM-dd') "); - else - countStmt.append(rangePartitionInterval.getFrom()).append(" "); - countStmt.append("AND " + partitionKeyColumn.getColumnName()).append(" < "); - if (DisplayType.isDate(partitionKeyColumn.getAD_Reference_ID()) || DisplayType.isTimestampWithTimeZone(partitionKeyColumn.getAD_Reference_ID())) - countStmt.append("TO_DATE(").append(rangePartitionInterval.getTo()).append(",'yyyy-MM-dd') "); - else - countStmt.append(rangePartitionInterval.getTo()).append(" "); - int recordCount = DB.getSQLValueEx(trxName, countStmt.toString()); - - if (recordCount == 0) { - if (tablePartitionNames.contains(name.toString())) { - Query query = new Query(Env.getCtx(), X_AD_TablePartition.Table_Name, "AD_Table_ID=? AND Name=?", trxName); - X_AD_TablePartition toDelete = query.setParameters(table.getAD_Table_ID(), name.toString()).first(); - if (toDelete != null) - toDelete.deleteEx(true); - } - continue; - } - - if (!tablePartitionNames.contains(name.toString())) - partition = table.createTablePartition(name.toString(), expression.toString(), trxName, partitionKeyColumn); - + X_AD_TablePartition partition = createNewRangePartition(rangePartitionInterval, tablePartitionNames, table, partitionKeyColumn, table.getTableName().toLowerCase(), + getDefaultPartitionName(table), null, trxName); if (partition != null) { StringBuilder createStmt = new StringBuilder(); createStmt.append("CREATE TABLE ").append(partition.getName()).append(" (").append(DB_PostgreSQL.NATIVE_MARKER).append("LIKE "); createStmt.append(getDefaultPartitionName(table)).append(" INCLUDING ALL)"); + if (subPartitionColumn != null) { + createStmt.append(" PARTITION BY "); + if (MColumn.PARTITIONINGMETHOD_List.equals(subPartitionColumn.getPartitioningMethod())) + createStmt.append(" LIST("); + else if (MColumn.PARTITIONINGMETHOD_Range.equals(subPartitionColumn.getPartitioningMethod())) + createStmt.append(" RANGE("); + else + throw new IllegalArgumentException(Msg.getMsg(Env.getCtx(), "PartitioningMethodNotSupported", new Object[]{subPartitionColumn.getPartitioningMethod()})); + createStmt.append(subPartitionColumn.getColumnName()); + createStmt.append(")"); + } int no = DB.executeUpdateEx(createStmt.toString(), trxName); if (pi != null) - pi.addLog(0, null, null, no + " " + createStmt.toString()); - - StringBuilder updateStmt = new StringBuilder(); - updateStmt.append("WITH x AS ( "); - updateStmt.append("DELETE FROM ").append(getDefaultPartitionName(table)).append(" "); - updateStmt.append("WHERE ").append(" "); - updateStmt.append(partitionKeyColumn.getColumnName()).append(" >= "); - if (DisplayType.isDate(partitionKeyColumn.getAD_Reference_ID()) || DisplayType.isTimestampWithTimeZone(partitionKeyColumn.getAD_Reference_ID())) - updateStmt.append("TO_DATE(").append(rangePartitionInterval.getFrom()).append(",'yyyy-MM-dd') "); - else - updateStmt.append(rangePartitionInterval.getFrom()).append(" "); - updateStmt.append("AND ").append(partitionKeyColumn.getColumnName()).append(" < "); - if (DisplayType.isDate(partitionKeyColumn.getAD_Reference_ID()) || DisplayType.isTimestampWithTimeZone(partitionKeyColumn.getAD_Reference_ID())) - updateStmt.append("TO_DATE(").append(rangePartitionInterval.getTo()).append(",'yyyy-MM-dd') "); - else - updateStmt.append(rangePartitionInterval.getTo()).append(" "); - - updateStmt.append("RETURNING *) "); - updateStmt.append("INSERT INTO ").append(partition.getName()).append(" "); - updateStmt.append("SELECT * FROM x"); - no = DB.executeUpdateEx(updateStmt.toString(), trxName); - if (pi != null) - pi.addLog(0, null, null, no + " " + updateStmt.toString()); - - StringBuilder alterStmt = new StringBuilder(); - alterStmt.append("ALTER TABLE ").append(table.getTableName()).append(" "); - alterStmt.append("ATTACH PARTITION ").append(partition.getName()).append(" ").append(partition.getExpressionPartition()); - no = DB.executeUpdateEx(alterStmt.toString(), trxName); - if (pi != null) - pi.addLog(0, null, null, no + " " + alterStmt.toString()); + pi.addLog(0, null, null, no + " " + createStmt.toString().replace(DB_PostgreSQL.NATIVE_MARKER, "")); + if (subPartitionColumn != null) { + createSubDefaultPartition(table, subPartitionColumn, partition, pi, trxName); + } + moveDefaultPartitionDataForRange(partition, partitionKeyColumn, table.getTableName(), getDefaultPartitionName(table), rangePartitionInterval, pi, trxName); isUpdated = true; } } + + if (subPartitionColumn != null) { + List partitions = new ArrayList<>(); + tablePartitionNames = new ArrayList<>(); + try (PreparedStatement stmt = DB.prepareStatement("SELECT * FROM AD_TablePartition WHERE IsActive='Y' AND AD_Table_ID=? AND AD_Column_ID=? AND IsPartitionAttached='Y'", trxName)) { + stmt.setInt(1, table.getAD_Table_ID()); + stmt.setInt(2, partitionKeyColumn.getAD_Column_ID()); + ResultSet rs = stmt.executeQuery(); + while(rs.next()) { + X_AD_TablePartition partition = new X_AD_TablePartition(Env.getCtx(), rs, trxName); + if (partition.getName().toLowerCase().endsWith("_default_partition")) + continue; + partitions.add(partition); + tablePartitionNames.add(partition.getName()); + } + } catch (SQLException e) { + throw new DBException(e); + } + for(X_AD_TablePartition partition : partitions) { + String subDefaultPartition = partition.getName() + "_default_partition"; + String sql = + """ + SELECT COUNT(*) + FROM information_schema.columns + WHERE table_name = LOWER(?) + """; + int count = DB.getSQLValueEx(trxName, sql, subDefaultPartition); + if (count <= 0) + continue; + if (MColumn.PARTITIONINGMETHOD_List.equals(subPartitionColumn.getPartitioningMethod())) { + HashMap subValues = new HashMap<>(); + List subPartitions = generateListPartition(table, partition.getName(), subDefaultPartition, subPartitionColumn, subValues, partition, trxName); + for(X_AD_TablePartition subPartition : subPartitions) { + StringBuilder createStmt = new StringBuilder(); + createStmt.append("CREATE TABLE ").append(subPartition.getName()).append(" (").append(DB_PostgreSQL.NATIVE_MARKER).append("LIKE "); + createStmt.append(subDefaultPartition).append(" INCLUDING ALL)"); + int no = DB.executeUpdateEx(createStmt.toString(), trxName); + if (pi != null) + pi.addLog(0, null, null, no + " " + createStmt.toString().replace(DB_PostgreSQL.NATIVE_MARKER, "")); + Object subValue = subValues.get(subPartition.getName()); + moveDefaultPartitionDataForList(subPartition, subPartitionColumn, partition.getName(), subDefaultPartition, subValue, pi, trxName); + } + } else if (MColumn.PARTITIONINGMETHOD_Range.equals(subPartitionColumn.getPartitioningMethod())) { + rangePartitionColumn = buildRangePartitionColumn(partition.getName(), subPartitionColumn, trxName); + if (rangePartitionColumn != null) + { + rangePartitionIntervals = RangePartitionInterval.createInterval(table, rangePartitionColumn, trxName); + for (RangePartitionInterval rangePartitionInterval : rangePartitionIntervals) + { + X_AD_TablePartition subPartition = createNewRangePartition(rangePartitionInterval, tablePartitionNames, table, subPartitionColumn, partition.getName(), + subDefaultPartition, partition, trxName); + if (subPartition != null) + { + StringBuilder createStmt = new StringBuilder(); + createStmt.append("CREATE TABLE ").append(subPartition.getName()).append(" (").append(DB_PostgreSQL.NATIVE_MARKER).append("LIKE "); + createStmt.append(subDefaultPartition).append(" INCLUDING ALL)"); + int no = DB.executeUpdateEx(createStmt.toString(), trxName); + if (pi != null) + pi.addLog(0, null, null, no + " " + createStmt.toString().replace(DB_PostgreSQL.NATIVE_MARKER, "")); + moveDefaultPartitionDataForRange(subPartition, subPartitionColumn, partition.getName(), subDefaultPartition, rangePartitionInterval, pi, trxName); + } + } + } + } + } + } + return isUpdated; } /** - * Add new list partition + * Generate new X_AD_TablePartition records * @param table - * @param partitionKeyColumn + * @param partitionNamePrefix name prefix for new list partition + * @param fromPartitionTable table name for FROM clause + * @param partitionKeyColumn + * @param columnValues List Partition Name:List Value map + * @param parentPartition * @param trxName - * @param pi - * @return true if new list partition added + * @return list of generated X_AD_TablePartition records */ - private boolean addListPartition(MTable table, MColumn partitionKeyColumn, String trxName, ProcessInfo pi) { - boolean isUpdated = false; + private List generateListPartition(MTable table, String partitionNamePrefix, String fromPartitionTable, MColumn partitionKeyColumn, HashMap columnValues, + X_AD_TablePartition parentPartition, String trxName) { List partitions = new ArrayList(); - String nameColumn = "'" + table.getTableName().toLowerCase() + "_' || " + partitionKeyColumn.getColumnName(); + String nameColumn = "'" + partitionNamePrefix + "_' || " + partitionKeyColumn.getColumnName(); String expressionColumn = "'FOR VALUES IN (''' || " + partitionKeyColumn.getColumnName() + " || ''')'"; StringBuilder sql = new StringBuilder(); sql.append("SELECT DISTINCT ").append(nameColumn).append(" AS name, "); sql.append(expressionColumn).append(" AS expression, "); sql.append(partitionKeyColumn.getColumnName()).append(" "); - sql.append("FROM ").append(getDefaultPartitionName(table)).append(" "); + sql.append("FROM ").append(fromPartitionTable).append(" "); - HashMap columnValues = new HashMap<>(); try (PreparedStatement pstmt = DB.prepareStatement(sql.toString(), trxName)) { ResultSet rs = pstmt.executeQuery(); @@ -532,7 +769,7 @@ public class TablePartitionService implements ITablePartitionService { value = rs.getObject(partitionKeyColumn.getColumnName()); columnValues.put(name, value); - X_AD_TablePartition partition = table.createTablePartition(name, expression, trxName, partitionKeyColumn); + X_AD_TablePartition partition = table.createTablePartition(name, expression, trxName, partitionKeyColumn, parentPartition); partitions.add(partition); } } @@ -540,7 +777,61 @@ public class TablePartitionService implements ITablePartitionService { { throw new DBException(e); } + return partitions; + } + + /** + * Move records from default partition to a list partition + * @param partition list partition + * @param partitionKeyColumn + * @param tableName table name to attached list partition + * @param defaultPartitionName name of default partition + * @param listValue key value of list partition + * @param pi + * @param trxName + */ + private void moveDefaultPartitionDataForList(X_AD_TablePartition partition, MColumn partitionKeyColumn, String tableName, + String defaultPartitionName, Object listValue, ProcessInfo pi, String trxName) { + StringBuilder updateStmt = new StringBuilder(); + updateStmt.append("WITH x AS ( "); + updateStmt.append("DELETE FROM ").append(defaultPartitionName).append(" "); + updateStmt.append("WHERE ").append(" "); + + updateStmt.append(partitionKeyColumn.getColumnName()).append("="); + + if (DisplayType.isText(partitionKeyColumn.getAD_Reference_ID())) + updateStmt.append("'").append(listValue).append("' "); + else + updateStmt.append(listValue).append(" "); + updateStmt.append("RETURNING *) "); + updateStmt.append("INSERT INTO ").append(partition.getName()).append(" "); + updateStmt.append("SELECT * FROM x"); + int no = DB.executeUpdateEx(updateStmt.toString(), trxName); + if (pi != null) + pi.addLog(0, null, null, no + " " + updateStmt.toString()); + + StringBuilder alterStmt = new StringBuilder(); + alterStmt.append("ALTER TABLE ").append(tableName).append(" "); + alterStmt.append("ATTACH PARTITION ").append(partition.getName()).append(" ").append(partition.getExpressionPartition()); + no = DB.executeUpdateEx(alterStmt.toString(), trxName); + if (pi != null) + pi.addLog(0, null, null, no + " " + alterStmt.toString()); + } + + /** + * Add new list partition + * @param table + * @param partitionKeyColumn + * @param trxName + * @param pi + * @param subPartitionColumn + * @return true if new list partition added + */ + private boolean addListPartition(MTable table, MColumn partitionKeyColumn, String trxName, ProcessInfo pi, MColumn subPartitionColumn) { + boolean isUpdated = false; + HashMap columnValues = new HashMap<>(); + List partitions = generateListPartition(table, table.getTableName().toLowerCase(), getDefaultPartitionName(table), partitionKeyColumn, columnValues, null, trxName); for (X_AD_TablePartition partition : partitions) { Object value = columnValues.get(partition.getName()); @@ -548,41 +839,124 @@ public class TablePartitionService implements ITablePartitionService { StringBuilder createStmt = new StringBuilder(); createStmt.append("CREATE TABLE ").append(partition.getName()).append(" (").append(DB_PostgreSQL.NATIVE_MARKER).append("LIKE "); createStmt.append(getDefaultPartitionName(table)).append(" INCLUDING ALL)"); + if (subPartitionColumn != null) { + createStmt.append(" PARTITION BY "); + if (MColumn.PARTITIONINGMETHOD_List.equals(subPartitionColumn.getPartitioningMethod())) + createStmt.append(" LIST("); + else if (MColumn.PARTITIONINGMETHOD_Range.equals(subPartitionColumn.getPartitioningMethod())) + createStmt.append(" RANGE("); + else + throw new IllegalArgumentException(Msg.getMsg(Env.getCtx(), "PartitioningMethodNotSupported", new Object[]{subPartitionColumn.getPartitioningMethod()})); + createStmt.append(subPartitionColumn.getColumnName()); + createStmt.append(")"); + } int no = DB.executeUpdateEx(createStmt.toString(), trxName); if (pi != null) - pi.addLog(0, null, null, no + " " + createStmt.toString()); - - StringBuilder updateStmt = new StringBuilder(); - updateStmt.append("WITH x AS ( "); - updateStmt.append("DELETE FROM ").append(getDefaultPartitionName(table)).append(" "); - updateStmt.append("WHERE ").append(" "); - - updateStmt.append(partitionKeyColumn.getColumnName()).append("="); - - if (DisplayType.isText(partitionKeyColumn.getAD_Reference_ID())) - updateStmt.append("'").append(value).append("' "); - else - updateStmt.append(value).append(" "); + pi.addLog(0, null, null, no + " " + createStmt.toString().replace(DB_PostgreSQL.NATIVE_MARKER, "")); - updateStmt.append("RETURNING *) "); - updateStmt.append("INSERT INTO ").append(partition.getName()).append(" "); - updateStmt.append("SELECT * FROM x"); - no = DB.executeUpdateEx(updateStmt.toString(), trxName); - if (pi != null) - pi.addLog(0, null, null, no + " " + updateStmt.toString()); + if (subPartitionColumn != null) { + createSubDefaultPartition(table, subPartitionColumn, partition, pi, trxName); + } - StringBuilder alterStmt = new StringBuilder(); - alterStmt.append("ALTER TABLE ").append(table.getTableName()).append(" "); - alterStmt.append("ATTACH PARTITION ").append(partition.getName()).append(" ").append(partition.getExpressionPartition()); - no = DB.executeUpdateEx(alterStmt.toString(), trxName); - if (pi != null) - pi.addLog(0, null, null, no + " " + alterStmt.toString()); + moveDefaultPartitionDataForList(partition, partitionKeyColumn, table.getTableName(), getDefaultPartitionName(table), value, pi, trxName); isUpdated = true; } + + if (subPartitionColumn != null) { + List tablePartitionNames = new ArrayList<>(); + partitions = new ArrayList<>(); + try (PreparedStatement stmt = DB.prepareStatement("SELECT * FROM AD_TablePartition WHERE IsActive='Y' AND AD_Table_ID=? AND AD_Column_ID=? AND IsPartitionAttached='Y'", trxName)) { + stmt.setInt(1, table.getAD_Table_ID()); + stmt.setInt(2, partitionKeyColumn.getAD_Column_ID()); + ResultSet rs = stmt.executeQuery(); + while(rs.next()) { + X_AD_TablePartition partition = new X_AD_TablePartition(Env.getCtx(), rs, trxName); + if (partition.getName().toLowerCase().endsWith("_default_partition")) + continue; + partitions.add(partition); + tablePartitionNames.add(partition.getName()); + } + } catch (SQLException e) { + throw new DBException(e); + } + for(X_AD_TablePartition partition : partitions) { + String subDefaultPartition = partition.getName() + "_default_partition"; + String sql = + """ + SELECT COUNT(*) + FROM information_schema.columns + WHERE table_name = LOWER(?) + """; + int count = DB.getSQLValueEx(trxName, sql, subDefaultPartition); + if (count <= 0) + continue; + if (MColumn.PARTITIONINGMETHOD_List.equals(subPartitionColumn.getPartitioningMethod())) { + HashMap subValues = new HashMap<>(); + List subPartitions = generateListPartition(table, partition.getName(), subDefaultPartition, subPartitionColumn, subValues, partition, trxName); + for(X_AD_TablePartition subPartition : subPartitions) { + StringBuilder createStmt = new StringBuilder(); + createStmt.append("CREATE TABLE ").append(subPartition.getName()).append(" (").append(DB_PostgreSQL.NATIVE_MARKER).append("LIKE "); + createStmt.append(subDefaultPartition).append(" INCLUDING ALL)"); + int no = DB.executeUpdateEx(createStmt.toString(), trxName); + if (pi != null) + pi.addLog(0, null, null, no + " " + createStmt.toString().replace(DB_PostgreSQL.NATIVE_MARKER, "")); + Object subValue = subValues.get(subPartition.getName()); + moveDefaultPartitionDataForList(subPartition, subPartitionColumn, partition.getName(), subDefaultPartition, subValue, pi, trxName); + } + } else if (MColumn.PARTITIONINGMETHOD_Range.equals(subPartitionColumn.getPartitioningMethod())) { + RangePartitionColumn rangePartitionColumn = buildRangePartitionColumn(partition.getName(), subPartitionColumn, trxName); + if (rangePartitionColumn != null) + { + List rangePartitionIntervals = RangePartitionInterval.createInterval(table, rangePartitionColumn, trxName); + for (RangePartitionInterval rangePartitionInterval : rangePartitionIntervals) + { + X_AD_TablePartition subPartition = createNewRangePartition(rangePartitionInterval, tablePartitionNames, table, subPartitionColumn, partition.getName(), + subDefaultPartition, partition, trxName); + if (subPartition != null) + { + StringBuilder createStmt = new StringBuilder(); + createStmt.append("CREATE TABLE ").append(subPartition.getName()).append(" (").append(DB_PostgreSQL.NATIVE_MARKER).append("LIKE "); + createStmt.append(subDefaultPartition).append(" INCLUDING ALL)"); + int no = DB.executeUpdateEx(createStmt.toString(), trxName); + if (pi != null) + pi.addLog(0, null, null, no + " " + createStmt.toString().replace(DB_PostgreSQL.NATIVE_MARKER, "")); + moveDefaultPartitionDataForRange(subPartition, subPartitionColumn, partition.getName(), subDefaultPartition, rangePartitionInterval, pi, trxName); + } + } + } + } + } + } return isUpdated; } + /** + * Create default partition table for sub-partition + * @param table + * @param subPartitionColumn sub-partition column + * @param partition parent partition + * @param pi + * @param trxName + */ + private void createSubDefaultPartition(MTable table, MColumn subPartitionColumn, X_AD_TablePartition partition, ProcessInfo pi, String trxName) { + StringBuilder subStmt = new StringBuilder("CREATE TABLE ") + .append(partition.getName()).append("_default_partition (").append(DB_PostgreSQL.NATIVE_MARKER).append("LIKE ") + .append(partition.getName()) + .append(" INCLUDING ALL)"); + int no = DB.executeUpdateEx(subStmt.toString(), trxName); + if (pi != null) + pi.addLog(0, null, null, no + " " + subStmt.toString()); + subStmt = new StringBuilder("ALTER TABLE ") + .append(partition.getName()) + .append(" ATTACH PARTITION ") + .append(partition.getName()).append("_default_partition DEFAULT "); + no = DB.executeUpdateEx(subStmt.toString(), trxName); + if (pi != null) + pi.addLog(0, null, null, no + " " + subStmt.toString().replace(DB_PostgreSQL.NATIVE_MARKER, "")); + table.createTablePartition(partition.getName()+"_default_partition", "DEFAULT", trxName, subPartitionColumn, partition); + } + @Override public boolean runPostPartitionProcess(MTable table, String trxName, ProcessInfo processInfo) { StringBuilder stmt = new StringBuilder(); @@ -627,8 +1001,8 @@ public class TablePartitionService implements ITablePartitionService { if (partitionKeyColumns.contains(column)) partitionKeyColumns.remove(column); } - if (partitionKeyColumns.size() > 1) - return Msg.getMsg(Env.getCtx(), "OnlyOnePartitionKeyAllowed"); + if (partitionKeyColumns.size() > 2) + return Msg.getMsg(Env.getCtx(), "OnlyTwoPartitionKeyAllowed"); if (column.isActive() && column.isPartitionKey() && column.getPartitioningMethod().equals(MColumn.PARTITIONINGMETHOD_Range)) { String error = RangePartitionInterval.validateIntervalPattern(column); @@ -638,14 +1012,113 @@ public class TablePartitionService implements ITablePartitionService { if (!isPartitionedTable(table, trxName)) return null; + if (column.is_ValueChanged(MColumn.COLUMNNAME_IsPartitionKey) || (column.isPartitionKey() && column.is_ValueChanged(MColumn.COLUMNNAME_IsActive)) - || (column.isPartitionKey() && column.is_ValueChanged(MColumn.COLUMNNAME_SeqNoPartition))) { - return validateConfiguration(table, trxName); + || (column.isPartitionKey() && column.is_ValueChanged(MColumn.COLUMNNAME_PartitioningMethod))) { + if (partitionKeyColumns.size() == 2 || (partitionKeyColumns.size()==1 && !column.isPartitionKey() && column.is_ValueChanged(MColumn.COLUMNNAME_IsPartitionKey))) + return Msg.getMsg(Env.getCtx(), "PartitionConfigurationChanged"); + else + return validateConfiguration(table, trxName); } + + if (column.isPartitionKey() && column.is_ValueChanged(MColumn.COLUMNNAME_SeqNoPartition) && partitionKeyColumns.size() == 2) { + int oldSeq = column.get_ValueOldAsInt(MColumn.COLUMNNAME_SeqNoPartition); + int newSeq = column.getSeqNoPartition(); + int otherSeq = partitionKeyColumns.get(0).getAD_Column_ID() == column.getAD_Column_ID() + ? partitionKeyColumns.get(1).getSeqNoPartition() + : partitionKeyColumns.get(0).getSeqNoPartition(); + if (!(((newSeq < otherSeq) && (oldSeq < otherSeq)) || ((oldSeq > otherSeq) && (newSeq > otherSeq)))) + return Msg.getMsg(Env.getCtx(), "PartitionConfigurationChanged"); + } + if (column.isPartitionKey() && column.is_ValueChanged(MColumn.COLUMNNAME_RangePartitionInterval)) return Msg.getMsg(Env.getCtx(), "PartitionConfigurationChanged") + " [" + MColumn.COLUMNNAME_RangePartitionInterval + "]"; return null; } + @Override + public void detachPartition(MTable table, X_AD_TablePartition partition, String trxName, + ProcessInfo processInfo) { + if (partition.isPartitionAttached()) { + if (!"default".equalsIgnoreCase(partition.getExpressionPartition())) { + StringBuilder alter = new StringBuilder("ALTER TABLE "); + if (partition.getParent_TablePartition_ID() > 0) { + X_AD_TablePartition parentPartition = new X_AD_TablePartition(Env.getCtx(), partition.getParent_TablePartition_ID(), trxName); + alter.append(parentPartition.getName()).append(" "); + } else { + alter.append(table.getTableName()).append(" "); + } + alter.append("DETACH PARTITION ").append(partition.getName()); + int no = DB.executeUpdateEx(alter.toString(), trxName); + if (processInfo != null) + processInfo.addLog(0, null, null, no + " " + alter.toString()); + partition.setIsPartitionAttached(false); + partition.saveEx(); + } else { + throw new AdempiereException(Msg.getMsg(Env.getCtx(), "CantDetachReattachDefaultPartition")); + } + } + } + + @Override + public void reattachPartition(MTable table, X_AD_TablePartition partition, String trxName, + ProcessInfo processInfo) { + if (!partition.isPartitionAttached()) { + if (!"default".equalsIgnoreCase(partition.getExpressionPartition())) { + StringBuilder alter = new StringBuilder("ALTER TABLE "); + if (partition.getParent_TablePartition_ID() > 0) { + X_AD_TablePartition parentPartition = new X_AD_TablePartition(Env.getCtx(), partition.getParent_TablePartition_ID(), trxName); + alter.append(parentPartition.getName()).append(" "); + } else { + alter.append(table.getTableName()).append(" "); + } + alter.append("ATTACH PARTITION ") + .append(partition.getName()) + .append(" ") + .append(partition.getExpressionPartition()); + boolean success = true; + try { + int no = DB.executeUpdateEx(alter.toString(), trxName); + if (processInfo != null) + processInfo.addLog(0, null, null, no + " " + alter.toString()); + } catch (RuntimeException e) { + success = false; + Trx.get(trxName, false).rollback(); + } + + if (success) { + partition.setIsPartitionAttached(true); + partition.saveEx(); + } else { + //fallback to insert and delete + StringBuilder updateStmt = new StringBuilder(); + updateStmt.append("WITH x AS ( "); + updateStmt.append("DELETE FROM ").append(partition.getName()).append(" "); + updateStmt.append("RETURNING *) "); + updateStmt.append("INSERT INTO ").append(table.getTableName()).append(" "); + updateStmt.append("SELECT * FROM x"); + int no = DB.executeUpdateEx(updateStmt.toString(), trxName); + if (processInfo != null) + processInfo.addLog(0, null, null, no + " " + updateStmt.toString()); + alter = new StringBuilder("DROP TABLE ").append(partition.getName()); + no = DB.executeUpdateEx(alter.toString(), trxName); + if (processInfo != null) + processInfo.addLog(0, null, null, no + " " + alter.toString()); + try { + Trx.get(trxName, false).commit(true); + } catch (SQLException e) { + throw new DBException(e); + } + + partition.deleteEx(true); + table.getTablePartitions(true, trxName); + addPartitionAndMigrateData(table, trxName, processInfo); + } + } else { + throw new AdempiereException(Msg.getMsg(Env.getCtx(), "CantDetachReattachDefaultPartition")); + } + } + } + } diff --git a/org.idempiere.tablepartition/src/org/idempiere/tablepartition/TablePartitionTask.java b/org.idempiere.tablepartition/src/org/idempiere/tablepartition/TablePartitionTask.java index 538b9dd9fb..279613eadc 100644 --- a/org.idempiere.tablepartition/src/org/idempiere/tablepartition/TablePartitionTask.java +++ b/org.idempiere.tablepartition/src/org/idempiere/tablepartition/TablePartitionTask.java @@ -74,6 +74,7 @@ public class TablePartitionTask { try { + table.set_TrxName(trxName); addLog(Msg.getElement(Env.getCtx(), "TableName") + ": " + table.getTableName()); List partitionKeyColumns = table.getPartitionKeyColumns(true); diff --git a/org.idempiere.tablepartition/src/org/idempiere/tablepartition/process/ChangePartitionStatus.java b/org.idempiere.tablepartition/src/org/idempiere/tablepartition/process/ChangePartitionStatus.java new file mode 100644 index 0000000000..dcabe5a083 --- /dev/null +++ b/org.idempiere.tablepartition/src/org/idempiere/tablepartition/process/ChangePartitionStatus.java @@ -0,0 +1,87 @@ +/*********************************************************************** + * This file is part of iDempiere ERP Open Source * + * http://www.idempiere.org * + * * + * Copyright (C) Contributors * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * 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. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * + * MA 02110-1301, USA. * + **********************************************************************/ +package org.idempiere.tablepartition.process; + +import org.compiere.db.partition.ITablePartitionService; +import org.compiere.model.MTable; +import org.compiere.model.X_AD_TablePartition; +import org.compiere.process.SvrProcess; +import org.compiere.util.DB; +import org.compiere.util.Env; +import org.compiere.util.Msg; + +@org.adempiere.base.annotation.Process +public class ChangePartitionStatus extends SvrProcess { + + private int p_record_ID = 0; + private MTable table = null; + + @Override + protected void prepare() { + p_record_ID = getRecord_ID(); + } + + @Override + protected String doIt() throws Exception { + ITablePartitionService service = DB.getDatabase().getTablePartitionService(); + if (service == null) { + return "@Error@ " + Msg.getMsg(getCtx(), "DBAdapterNoTablePartitionSupport"); + } + + if (p_record_ID <= 0) + return "@Error@ " + Msg.getMsg(getCtx(), "FillMandatory", new Object[]{"Record ID"}); + + X_AD_TablePartition partition = new X_AD_TablePartition(Env.getCtx(), p_record_ID, get_TrxName()); + if (partition.get_ID() != p_record_ID) + return "@Error@ " + Msg.getMsg(getCtx(), "FillMandatory", new Object[]{"Record ID"}); + + table = new MTable(Env.getCtx(), partition.getAD_Table_ID(), get_TrxName()); + if (partition.isPartitionAttached()) + return detachPartition(partition, service); + else + return reattachPartition(partition, service); + } + + private String reattachPartition(X_AD_TablePartition partition, ITablePartitionService service) { + String partitionName = partition.getName(); + service.reattachPartition(table, partition, get_TrxName(), getProcessInfo()); + return Msg.getMsg(getCtx(), "PartitionReAttachToTable", new Object[] {partitionName, table.getTableName()}); + } + + private String detachPartition(X_AD_TablePartition partition, ITablePartitionService service) { + String partitionName = partition.getName(); + service.detachPartition(table, partition, get_TrxName(), getProcessInfo()); + return Msg.getMsg(getCtx(), "PartitionDetachFromTable", new Object[] {partitionName, table.getTableName()}); + } + + @Override + protected void postProcess(boolean success) { + if (success) { + ITablePartitionService service = DB.getDatabase().getTablePartitionService(); + if (service != null) { + service.runPostPartitionProcess(table, null, getProcessInfo()); + } + } + } + + +}