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
This commit is contained in:
hengsin 2024-01-03 10:02:59 +08:00 committed by Carlos Ruiz
parent 18146a57a6
commit 6f110b2d2f
10 changed files with 1481 additions and 181 deletions

View File

@ -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')
;

View File

@ -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')
;

View File

@ -26,6 +26,7 @@ package org.compiere.db.partition;
import org.compiere.model.MColumn; import org.compiere.model.MColumn;
import org.compiere.model.MTable; import org.compiere.model.MTable;
import org.compiere.model.X_AD_TablePartition;
import org.compiere.process.ProcessInfo; import org.compiere.process.ProcessInfo;
/** /**
@ -33,37 +34,69 @@ import org.compiere.process.ProcessInfo;
*/ */
public interface ITablePartitionService { public interface ITablePartitionService {
/** /**
* Is table already a partitioned table in DB
* @param table
* @param trxName
* @return true if table have been partition in DB * @return true if table have been partition in DB
*/ */
public boolean isPartitionedTable(MTable table, String trxName); public boolean isPartitionedTable(MTable table, String trxName);
/** /**
* Make existing table a partition table * Make existing table a partition table
* @param table
* @param trxName
* @param processInfo
* @return true if success * @return true if success
*/ */
public boolean createPartitionedTable(MTable table, String trxName, ProcessInfo processInfo); 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) * 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 * @return true if success
*/ */
public boolean addPartitionAndMigrateData(MTable table, String trxName, ProcessInfo processInfo); public boolean addPartitionAndMigrateData(MTable table, String trxName, ProcessInfo processInfo);
/** /**
* Run post partition process (if needed) * Run post partition process (if needed)
* @param table
* @param trxName
* @param processInfo
* @return true if success * @return true if success
*/ */
public boolean runPostPartitionProcess(MTable table, String trxName, ProcessInfo processInfo); public boolean runPostPartitionProcess(MTable table, String trxName, ProcessInfo processInfo);
/** /**
* Validate partition configuration for table object * Validate partition configuration for table object
* @param table
* @return String error-code - null if not error * @return String error-code - null if not error
*/ */
public String isValidConfiguration(MTable table); public String isValidConfiguration(MTable table);
/** /**
* Validate partition configuration for column object * Validate partition configuration for column object
* @param column
* @return String error-code - null if not error * @return String error-code - null if not error
*/ */
public String isValidConfiguration(MColumn column); 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);
} }

View File

@ -22,7 +22,7 @@ import org.compiere.util.KeyNamePair;
/** Generated Interface for AD_TablePartition /** Generated Interface for AD_TablePartition
* @author iDempiere (generated) * @author iDempiere (generated)
* @version Release 11 * @version Release 12
*/ */
public interface I_AD_TablePartition public interface I_AD_TablePartition
{ {
@ -156,6 +156,19 @@ public interface I_AD_TablePartition
*/ */
public boolean isActive(); 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 */ /** Column name Name */
public static final String COLUMNNAME_Name = "Name"; public static final String COLUMNNAME_Name = "Name";
@ -169,6 +182,30 @@ public interface I_AD_TablePartition
*/ */
public String getName(); 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 */ /** Column name Updated */
public static final String COLUMNNAME_Updated = "Updated"; public static final String COLUMNNAME_Updated = "Updated";

View File

@ -1019,12 +1019,28 @@ public class MTable extends X_AD_Table implements ImmutablePOSupport
* @return new X_AD_TablePartition record * @return new X_AD_TablePartition record
*/ */
public X_AD_TablePartition createTablePartition(String name, String expression, String trxName, MColumn column) 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); X_AD_TablePartition partition = new X_AD_TablePartition(Env.getCtx(), 0, trxName);
partition.setAD_Table_ID(getAD_Table_ID()); partition.setAD_Table_ID(getAD_Table_ID());
partition.setName(name); partition.setName(name);
partition.setExpressionPartition(expression); 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(); partition.saveEx();
return partition; return partition;
} }

View File

@ -22,7 +22,7 @@ import java.util.Properties;
/** Generated Model for AD_TablePartition /** Generated Model for AD_TablePartition
* @author iDempiere (generated) * @author iDempiere (generated)
* @version Release 11 - $Id$ */ * @version Release 12 - $Id$ */
@org.adempiere.base.Model(table="AD_TablePartition") @org.adempiere.base.Model(table="AD_TablePartition")
public class X_AD_TablePartition extends PO implements I_AD_TablePartition, I_Persistent 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 */ /** Standard Constructor */
public X_AD_TablePartition (Properties ctx, int AD_TablePartition_ID, String trxName) 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_Column_ID (0);
setAD_Table_ID (0); setAD_Table_ID (0);
setExpressionPartition (null); setExpressionPartition (null);
setIsPartitionAttached (true);
// Y
setName (null); setName (null);
} */ } */
} }
@ -54,6 +56,8 @@ public class X_AD_TablePartition extends PO implements I_AD_TablePartition, I_Pe
setAD_Column_ID (0); setAD_Column_ID (0);
setAD_Table_ID (0); setAD_Table_ID (0);
setExpressionPartition (null); setExpressionPartition (null);
setIsPartitionAttached (true);
// Y
setName (null); setName (null);
} */ } */
} }
@ -67,6 +71,8 @@ public class X_AD_TablePartition extends PO implements I_AD_TablePartition, I_Pe
setAD_Column_ID (0); setAD_Column_ID (0);
setAD_Table_ID (0); setAD_Table_ID (0);
setExpressionPartition (null); setExpressionPartition (null);
setIsPartitionAttached (true);
// Y
setName (null); setName (null);
} */ } */
} }
@ -80,6 +86,8 @@ public class X_AD_TablePartition extends PO implements I_AD_TablePartition, I_Pe
setAD_Column_ID (0); setAD_Column_ID (0);
setAD_Table_ID (0); setAD_Table_ID (0);
setExpressionPartition (null); setExpressionPartition (null);
setIsPartitionAttached (true);
// Y
setName (null); 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); 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. /** Set Name.
@param Name Alphanumeric identifier of the entity @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); 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;
}
} }

View File

@ -27,6 +27,7 @@ import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.logging.Level;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -38,8 +39,10 @@ import org.compiere.db.partition.RangePartitionInterval;
import org.compiere.model.MColumn; import org.compiere.model.MColumn;
import org.compiere.model.MTable; import org.compiere.model.MTable;
import org.compiere.model.Query; import org.compiere.model.Query;
import org.compiere.model.X_AD_Column;
import org.compiere.model.X_AD_TablePartition; import org.compiere.model.X_AD_TablePartition;
import org.compiere.process.ProcessInfo; import org.compiere.process.ProcessInfo;
import org.compiere.util.CLogger;
import org.compiere.util.DB; import org.compiere.util.DB;
import org.compiere.util.DisplayType; import org.compiere.util.DisplayType;
import org.compiere.util.Env; import org.compiere.util.Env;
@ -71,6 +74,9 @@ public class TablePartitionService implements ITablePartitionService {
.append(" MODIFY PARTITION BY "); .append(" MODIFY PARTITION BY ");
List<MColumn> partitionKeyColumns = table.getPartitionKeyColumns(false); List<MColumn> partitionKeyColumns = table.getPartitionKeyColumns(false);
MColumn partitionKeyColumn = partitionKeyColumns.get(0); MColumn partitionKeyColumn = partitionKeyColumns.get(0);
MColumn subPartitionColumn = null;
if (partitionKeyColumns.size() > 1)
subPartitionColumn = partitionKeyColumns.get(1);
String partitioningMethod = partitionKeyColumn.getPartitioningMethod(); String partitioningMethod = partitionKeyColumn.getPartitioningMethod();
if (partitioningMethod.equals(MColumn.PARTITIONINGMETHOD_List)) if (partitioningMethod.equals(MColumn.PARTITIONINGMETHOD_List))
alterStmt.append("List"); alterStmt.append("List");
@ -79,18 +85,28 @@ public class TablePartitionService implements ITablePartitionService {
else else
throw new IllegalArgumentException(Msg.getMsg(Env.getCtx(), "PartitioningMethodNotSupported", new Object[]{partitioningMethod})); 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) { 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 //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) { } else if (useIntervalPartition) {
alterStmt.append(" INTERVAL("); alterStmt.append(" INTERVAL(");
alterStmt.append(getIntervalExpression(partitionKeyColumn)); alterStmt.append(getIntervalExpression(partitionKeyColumn));
alterStmt.append(") (PARTITION default_partition "); alterStmt.append(") ");
} else {
alterStmt.append(" (PARTITION default_partition ");
} }
//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(); StringBuilder defaultExpression = new StringBuilder();
if (partitioningMethod.equals(MColumn.PARTITIONINGMETHOD_List)) { if (partitioningMethod.equals(MColumn.PARTITIONINGMETHOD_List)) {
defaultExpression.append("VALUES ("); defaultExpression.append("VALUES (");
@ -124,6 +140,10 @@ public class TablePartitionService implements ITablePartitionService {
return true; return true;
} }
/**
* @param partitionKeyColumn
* @return Oracle year/month interval expression (using NUMTOYMINTERVAL) for column range interval
*/
private String getIntervalExpression(MColumn partitionKeyColumn) { private String getIntervalExpression(MColumn partitionKeyColumn) {
if (DisplayType.isDate(partitionKeyColumn.getAD_Reference_ID()) || DisplayType.isTimestampWithTimeZone(partitionKeyColumn.getAD_Reference_ID())) { if (DisplayType.isDate(partitionKeyColumn.getAD_Reference_ID()) || DisplayType.isTimestampWithTimeZone(partitionKeyColumn.getAD_Reference_ID())) {
RangePartitionInterval.Interval interval = RangePartitionInterval.getInterval(partitionKeyColumn); RangePartitionInterval.Interval interval = RangePartitionInterval.getInterval(partitionKeyColumn);
@ -157,11 +177,14 @@ public class TablePartitionService implements ITablePartitionService {
List<MColumn> partitionKeyColumns = table.getPartitionKeyColumns(false); List<MColumn> partitionKeyColumns = table.getPartitionKeyColumns(false);
if (partitionKeyColumns.size() == 0) if (partitionKeyColumns.size() == 0)
return false; 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 sql = "SELECT Column_Name FROM User_Part_Key_Columns WHERE Name=? ORDER BY Column_Position";
String partKeyColumn = DB.getSQLValueString(trxName, sql, table.getTableName().toUpperCase()); String partKeyColumn = DB.getSQLValueString(trxName, sql, table.getTableName().toUpperCase());
if (!partitionKeyColumn.getColumnName().equalsIgnoreCase(partKeyColumn)) if (!partitionKeyColumn.getColumnName().equalsIgnoreCase(partKeyColumn))
return false; return false;
MColumn subPartitionColumn = null;
if (partitionKeyColumns.size() > 1)
subPartitionColumn = partitionKeyColumns.get(1);
String partitioningMethod = partitionKeyColumn.getPartitioningMethod(); String partitioningMethod = partitionKeyColumn.getPartitioningMethod();
if (partitioningMethod.equals(MColumn.PARTITIONINGMETHOD_List)) if (partitioningMethod.equals(MColumn.PARTITIONINGMETHOD_List))
{ {
@ -172,7 +195,7 @@ public class TablePartitionService implements ITablePartitionService {
} }
else else
{ {
isUpdated = addListPartition(table, partitionKeyColumn, trxName, processInfo); isUpdated = addListPartition(table, partitionKeyColumn, trxName, processInfo, "default_partition", null, null);
} }
} }
else if (partitioningMethod.equals(MColumn.PARTITIONINGMETHOD_Range)) else if (partitioningMethod.equals(MColumn.PARTITIONINGMETHOD_Range))
@ -193,16 +216,90 @@ public class TablePartitionService implements ITablePartitionService {
else else
{ {
syncRange(table, partitionKeyColumn, trxName, interval, processInfo); syncRange(table, partitionKeyColumn, trxName, interval, processInfo);
isUpdated = addRangePartition(table, partitionKeyColumn, trxName, processInfo); isUpdated = addRangePartition(table, partitionKeyColumn, trxName, processInfo, "default_partition", null, null);
} }
} }
} }
else else
throw new IllegalArgumentException(Msg.getMsg(Env.getCtx(), "PartitioningMethodNotSupported", new Object[]{partitioningMethod})); throw new IllegalArgumentException(Msg.getMsg(Env.getCtx(), "PartitioningMethodNotSupported", new Object[]{partitioningMethod}));
if (subPartitionColumn != null) {
List<X_AD_TablePartition> 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; 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<X_AD_TablePartition> 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 * Change partition method from list to range
* @param table * @param table
@ -215,7 +312,7 @@ public class TablePartitionService implements ITablePartitionService {
StringBuilder alterStmt = new StringBuilder("ALTER TABLE ") StringBuilder alterStmt = new StringBuilder("ALTER TABLE ")
.append(table.getTableName()) .append(table.getTableName())
.append(" MODIFY PARTITION BY RANGE ") .append(" MODIFY PARTITION BY RANGE ")
.append(" (" + partitionKeyColumn.getColumnName() + ")"); .append(" (").append(partitionKeyColumn.getColumnName()).append(")");
if (useIntervalPartition) { if (useIntervalPartition) {
alterStmt.append(" INTERVAL("); alterStmt.append(" INTERVAL(");
alterStmt.append(getIntervalExpression(partitionKeyColumn)); alterStmt.append(getIntervalExpression(partitionKeyColumn));
@ -264,7 +361,7 @@ public class TablePartitionService implements ITablePartitionService {
StringBuilder alterStmt = new StringBuilder("ALTER TABLE ") StringBuilder alterStmt = new StringBuilder("ALTER TABLE ")
.append(table.getTableName()) .append(table.getTableName())
.append(" MODIFY PARTITION BY List "); .append(" MODIFY PARTITION BY List ");
alterStmt.append(" (" + keyColumn + ")"); alterStmt.append(" (").append(keyColumn).append(")");
alterStmt.append(" AUTOMATIC (PARTITION default_partition VALUES ("); alterStmt.append(" AUTOMATIC (PARTITION default_partition VALUES (");
alterStmt.append("NULL"); alterStmt.append("NULL");
alterStmt.append("))"); alterStmt.append("))");
@ -321,7 +418,7 @@ public class TablePartitionService implements ITablePartitionService {
StringBuilder alterStmt = new StringBuilder("ALTER TABLE ") StringBuilder alterStmt = new StringBuilder("ALTER TABLE ")
.append(table.getTableName()) .append(table.getTableName())
.append(" MODIFY PARTITION BY Range "); .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(" (PARTITION default_partition VALUES LESS THAN (");
alterStmt.append("MAXVALUE"); alterStmt.append("MAXVALUE");
alterStmt.append("))"); alterStmt.append("))");
@ -345,7 +442,7 @@ public class TablePartitionService implements ITablePartitionService {
FROM User_Tab_Partitions FROM User_Tab_Partitions
WHERE Table_Name=? 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<X_AD_TablePartition> existingList = query.setParameters(table.getAD_Table_ID()).list(); List<X_AD_TablePartition> existingList = query.setParameters(table.getAD_Table_ID()).list();
List<Integer> matchedIds = new ArrayList<Integer>(); List<Integer> matchedIds = new ArrayList<Integer>();
List<MColumn> partitionKeyColumns = table.getPartitionKeyColumns(false); List<MColumn> partitionKeyColumns = table.getPartitionKeyColumns(false);
@ -429,15 +526,19 @@ public class TablePartitionService implements ITablePartitionService {
* @param partitionKeyColumn * @param partitionKeyColumn
* @param trxName * @param trxName
* @param pi * @param pi
* @param fromPartition name of default partition to select from
* @param partitionNamePrefix
* @param parentPartition
* @return true if new list partition added * @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; boolean isUpdated = false;
List<X_AD_TablePartition> partitions = new ArrayList<>(); List<X_AD_TablePartition> partitions = new ArrayList<>();
boolean subPartition = parentPartition != null;
StringBuilder sql = new StringBuilder(); StringBuilder sql = new StringBuilder();
sql.append("SELECT DISTINCT ").append(partitionKeyColumn.getColumnName()); 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()); sql.append("ORDER BY ").append(partitionKeyColumn.getColumnName());
try (PreparedStatement pstmt = DB.prepareStatement(sql.toString(), trxName)) try (PreparedStatement pstmt = DB.prepareStatement(sql.toString(), trxName))
@ -447,6 +548,8 @@ public class TablePartitionService implements ITablePartitionService {
{ {
StringBuilder name = new StringBuilder(); StringBuilder name = new StringBuilder();
StringBuilder expression = new StringBuilder("VALUES ("); StringBuilder expression = new StringBuilder("VALUES (");
if (!Util.isEmpty(partitionNamePrefix, true))
name.append(partitionNamePrefix).append("_");
String s = rs.getString(partitionKeyColumn.getColumnName()); String s = rs.getString(partitionKeyColumn.getColumnName());
name.append(s); name.append(s);
if (DisplayType.isText(partitionKeyColumn.getAD_Reference_ID())) if (DisplayType.isText(partitionKeyColumn.getAD_Reference_ID()))
@ -459,13 +562,12 @@ public class TablePartitionService implements ITablePartitionService {
expression.append(rs.getInt(partitionKeyColumn.getColumnName())); expression.append(rs.getInt(partitionKeyColumn.getColumnName()));
else if (DisplayType.isNumeric(partitionKeyColumn.getAD_Reference_ID())) else if (DisplayType.isNumeric(partitionKeyColumn.getAD_Reference_ID()))
expression.append(rs.getBigDecimal(partitionKeyColumn.getColumnName()).toPlainString()); expression.append(rs.getBigDecimal(partitionKeyColumn.getColumnName()).toPlainString());
expression.append(")"); expression.append(")");
if (Character.isDigit(name.charAt(0))) { if (Character.isDigit(name.charAt(0))) {
name.insert(0, "p"); 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); partitions.add(partition);
} }
} }
@ -477,10 +579,10 @@ public class TablePartitionService implements ITablePartitionService {
for(X_AD_TablePartition partition : partitions) for(X_AD_TablePartition partition : partitions)
{ {
StringBuilder alterStmt = new StringBuilder(); 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(partition.getExpressionPartition());
alterStmt.append(" INTO ( PARTITION ").append(partition.getName()).append(", "); alterStmt.append(" INTO ( ").append(subPartition ? "SUBPARTITION " : "PARTITION ").append(partition.getName()).append(", ");
alterStmt.append("PARTITION default_partition )"); alterStmt.append(subPartition ? "SubPartition " : "Partition ").append(fromPartition).append(" )");
int no = DB.executeUpdateEx(alterStmt.toString(), trxName); int no = DB.executeUpdateEx(alterStmt.toString(), trxName);
if (pi != null) if (pi != null)
pi.addLog(0, null, null, no + " " + alterStmt.toString()); pi.addLog(0, null, null, no + " " + alterStmt.toString());
@ -496,12 +598,16 @@ public class TablePartitionService implements ITablePartitionService {
* @param partitionKeyColumn * @param partitionKeyColumn
* @param trxName * @param trxName
* @param pi * @param pi
* @param fromPartition name of default partition to select from
* @param partitionNamePrefix
* @param parentPartition
* @return true if new range partition added * @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; boolean isUpdated = false;
X_AD_TablePartition partition = null; X_AD_TablePartition partition = null;
RangePartitionColumn rangePartitionColumn = null; RangePartitionColumn rangePartitionColumn = null;
boolean subPartition = parentPartition != null;
String partitionKeyColumnName = partitionKeyColumn.getColumnName(); String partitionKeyColumnName = partitionKeyColumn.getColumnName();
String partitionKeyColumnRangeIntervalPattern = partitionKeyColumn.getRangePartitionInterval(); String partitionKeyColumnRangeIntervalPattern = partitionKeyColumn.getRangePartitionInterval();
@ -509,7 +615,7 @@ public class TablePartitionService implements ITablePartitionService {
StringBuilder sql = new StringBuilder(); StringBuilder sql = new StringBuilder();
sql.append("SELECT MIN(").append(partitionKeyColumnName).append(") AS min_value, "); sql.append("SELECT MIN(").append(partitionKeyColumnName).append(") AS min_value, ");
sql.append("MAX(").append(partitionKeyColumnName).append(") AS max_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<Object> values = DB.getSQLValueObjectsEx(trxName, sql.toString()); List<Object> values = DB.getSQLValueObjectsEx(trxName, sql.toString());
if (values.get(0) != null && values.get(1) != null) if (values.get(0) != null && values.get(1) != null)
@ -529,17 +635,19 @@ public class TablePartitionService implements ITablePartitionService {
for(RangePartitionInterval rangePartitionInterval : rangePartitionIntervals) for(RangePartitionInterval rangePartitionInterval : rangePartitionIntervals)
{ {
StringBuilder name = new StringBuilder(); StringBuilder name = new StringBuilder();
if (!Util.isEmpty(partitionNamePrefix, true))
name.append(partitionNamePrefix).append("_");
name.append(rangePartitionInterval.getName()); name.append(rangePartitionInterval.getName());
StringBuilder countStmt = new StringBuilder("SELECT Count(*) FROM ") 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("WHERE ").append(" ")
.append(partitionKeyColumn.getColumnName()).append(" >= "); .append(partitionKeyColumn.getColumnName()).append(" >= ");
if (DisplayType.isDate(partitionKeyColumn.getAD_Reference_ID()) || DisplayType.isTimestampWithTimeZone(partitionKeyColumn.getAD_Reference_ID())) if (DisplayType.isDate(partitionKeyColumn.getAD_Reference_ID()) || DisplayType.isTimestampWithTimeZone(partitionKeyColumn.getAD_Reference_ID()))
countStmt.append("TO_DATE(").append(rangePartitionInterval.getFrom()).append(",'YYYY-MM-DD') "); countStmt.append("TO_DATE(").append(rangePartitionInterval.getFrom()).append(",'YYYY-MM-DD') ");
else else
countStmt.append(rangePartitionInterval.getFrom()).append(" "); 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())) if (DisplayType.isDate(partitionKeyColumn.getAD_Reference_ID()) || DisplayType.isTimestampWithTimeZone(partitionKeyColumn.getAD_Reference_ID()))
countStmt.append("TO_DATE(").append(rangePartitionInterval.getTo()).append(",'YYYY-MM-DD') "); countStmt.append("TO_DATE(").append(rangePartitionInterval.getTo()).append(",'YYYY-MM-DD') ");
else else
@ -574,14 +682,14 @@ public class TablePartitionService implements ITablePartitionService {
name.insert(0, "p"); name.insert(0, "p");
} }
if (!tablePartitionNames.contains(name.toString())) 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) if (partition != null)
{ {
StringBuilder alterStmt = new StringBuilder(); 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);
alterStmt.append(" INTO ( PARTITION ").append(partition.getName()).append(" ").append(partition.getExpressionPartition()).append(", "); alterStmt.append(" INTO ( ").append(subPartition ? "SUBPARTITION " : "PARTITION ").append(partition.getName()).append(" ").append(partition.getExpressionPartition()).append(", ");
alterStmt.append("PARTITION default_partition )"); alterStmt.append(subPartition ? "SubPartition " : "Partition ").append(fromPartition).append(" )");
int no = DB.executeUpdateEx(alterStmt.toString(), trxName); int no = DB.executeUpdateEx(alterStmt.toString(), trxName);
if (pi != null) if (pi != null)
pi.addLog(0, null, null, no + " " + alterStmt.toString()); pi.addLog(0, null, null, no + " " + alterStmt.toString());
@ -594,6 +702,53 @@ public class TablePartitionService implements ITablePartitionService {
@Override @Override
public boolean runPostPartitionProcess(MTable table, String trxName, ProcessInfo processInfo) { 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; return true;
} }
@ -621,28 +776,174 @@ public class TablePartitionService implements ITablePartitionService {
partitionKeyColumns.remove(column); partitionKeyColumns.remove(column);
} }
if (partitionKeyColumns.size() > 1) if (partitionKeyColumns.size() > 2)
return Msg.getMsg(Env.getCtx(), "OnlyOnePartitionKeyAllowed"); 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 //can't change partition key column for range partition
if ((!column.isPartitionKey() || !column.isActive()) && Boolean.TRUE.equals(column.get_ValueOld("IsPartitionKey"))) { 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"); 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 //can't change partition type to list for range partition
if (column.isActive() && column.isPartitionKey() && column.getPartitioningMethod().equals(MColumn.PARTITIONINGMETHOD_List)) { if (column.isActive() && column.isPartitionKey() && column.getPartitioningMethod().equals(MColumn.PARTITIONINGMETHOD_List)) {
if (isRangePartitionedTable(table, trxName)) if (isRangePartitionedTable(table, trxName))
return Msg.getMsg(Env.getCtx(), "PartitionConfigurationChanged"); 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; 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<X_AD_TablePartition> 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<X_AD_TablePartition> 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"));
}
}
}
} }

View File

@ -27,6 +27,7 @@ import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import org.adempiere.exceptions.AdempiereException; import org.adempiere.exceptions.AdempiereException;
@ -44,6 +45,7 @@ import org.compiere.util.DB;
import org.compiere.util.DisplayType; import org.compiere.util.DisplayType;
import org.compiere.util.Env; import org.compiere.util.Env;
import org.compiere.util.Msg; import org.compiere.util.Msg;
import org.compiere.util.Trx;
import org.compiere.util.Util; import org.compiere.util.Util;
public class TablePartitionService implements ITablePartitionService { public class TablePartitionService implements ITablePartitionService {
@ -76,7 +78,7 @@ public class TablePartitionService implements ITablePartitionService {
*/ */
private boolean renameOriginalTable(MTable table, String trxName, ProcessInfo processInfo) { private boolean renameOriginalTable(MTable table, String trxName, ProcessInfo processInfo) {
StringBuilder sql = new StringBuilder(); 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); int no = DB.executeUpdateEx(sql.toString(), trxName);
if (processInfo != null) if (processInfo != null)
processInfo.addLog(0, null, null, no + " " + sql.toString()); 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 ")) if (constraint_definition.startsWith("PRIMARY KEY ") || constraint_definition.startsWith("UNIQUE "))
{ {
StringBuilder alterStmt = new StringBuilder(); StringBuilder alterStmt = new StringBuilder();
alterStmt.append("ALTER TABLE " + getDefaultPartitionName(table) + " "); alterStmt.append("ALTER TABLE ").append(getDefaultPartitionName(table)).append(" ");
alterStmt.append("DROP CONSTRAINT " + constraint_name + " CASCADE"); alterStmt.append("DROP CONSTRAINT ").append(constraint_name).append(" CASCADE");
int no = DB.executeUpdateEx(alterStmt.toString(), trxName); int no = DB.executeUpdateEx(alterStmt.toString(), trxName);
if (pi != null) if (pi != null)
pi.addLog(0, null, null, no + " " + alterStmt.toString()); pi.addLog(0, null, null, no + " " + alterStmt.toString());
@ -141,15 +143,15 @@ public class TablePartitionService implements ITablePartitionService {
} }
alterStmt = new StringBuilder(); alterStmt = new StringBuilder();
alterStmt.append("ALTER TABLE " + table.getTableName() + " "); alterStmt.append("ALTER TABLE ").append(table.getTableName()).append(" ");
alterStmt.append("ADD CONSTRAINT " + constraint_name + " "); alterStmt.append("ADD CONSTRAINT ").append(constraint_name).append(" ");
alterStmt.append(constraint_definition.substring(0, constraint_definition.length()-1)); alterStmt.append(constraint_definition.substring(0, constraint_definition.length()-1));
for (int x = 0; x < lowerCasePartitionKeyColumnNames.size(); x++) for (int x = 0; x < lowerCasePartitionKeyColumnNames.size(); x++)
alterStmt.append(", " + lowerCasePartitionKeyColumnNames.get(x)); alterStmt.append(", ").append(lowerCasePartitionKeyColumnNames.get(x));
alterStmt.append(")"); alterStmt.append(")");
no = DB.executeUpdateEx(alterStmt.toString(), trxName); no = DB.executeUpdateEx(alterStmt.toString(), trxName);
if (pi != null) 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()); constraint_definition = constraint_definition.replace(getDefaultPartitionName(table).toLowerCase(), table.getTableName().toLowerCase());
} }
StringBuilder alterStmt = new StringBuilder(); StringBuilder alterStmt = new StringBuilder();
alterStmt.append("ALTER TABLE " + table.getTableName() + " "); alterStmt.append("ALTER TABLE ").append(table.getTableName()).append(" ");
alterStmt.append("ADD CONSTRAINT " + constraint_name + " "); alterStmt.append("ADD CONSTRAINT ").append(constraint_name).append(" ");
alterStmt.append(constraint_definition); alterStmt.append(constraint_definition);
int no = DB.executeUpdateEx(alterStmt.toString(), trxName); int no = DB.executeUpdateEx(alterStmt.toString(), trxName);
if (pi != null) if (pi != null)
@ -189,6 +191,108 @@ public class TablePartitionService implements ITablePartitionService {
return true; 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<String, List<String>> indexMap = new HashMap<String, List<String>>();
Map<String, List<String>> uniqueMap = new HashMap<String, List<String>>();
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<String> columns = new ArrayList<String>();
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<String> 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<String> 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<String> 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 * Attach renamed original table as default partition
* @param table * @param table
@ -211,8 +315,8 @@ public class TablePartitionService implements ITablePartitionService {
return true; return true;
StringBuilder alterStmt = new StringBuilder(); StringBuilder alterStmt = new StringBuilder();
alterStmt.append("ALTER TABLE " + table.getTableName() + " "); alterStmt.append("ALTER TABLE ").append(table.getTableName()).append(" ");
alterStmt.append("ATTACH PARTITION " + getDefaultPartitionName(table) + " DEFAULT"); alterStmt.append("ATTACH PARTITION ").append(getDefaultPartitionName(table)).append(" DEFAULT");
int no = DB.executeUpdateEx(alterStmt.toString(), trxName); int no = DB.executeUpdateEx(alterStmt.toString(), trxName);
if (pi != null) if (pi != null)
pi.addLog(0, null, null, no + " " + alterStmt.toString()); pi.addLog(0, null, null, no + " " + alterStmt.toString());
@ -255,24 +359,24 @@ public class TablePartitionService implements ITablePartitionService {
if (createStmt == null) if (createStmt == null)
{ {
createStmt = new StringBuilder(); createStmt = new StringBuilder();
createStmt.append("CREATE TABLE " + table_schema + "." + table.getTableName() + " ("); createStmt.append("CREATE TABLE ").append(table_schema).append(".").append(table.getTableName()).append(" (");
} }
else else
{ {
createStmt.append(", "); createStmt.append(", ");
} }
createStmt.append(column_name + " " + data_type); createStmt.append(column_name).append(" ").append(data_type);
if (data_type.equals("numeric") && numeric_precision > 0) 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) 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)) if ("NO".equals(is_nullable))
createStmt.append(" NOT NULL"); createStmt.append(" NOT NULL");
if (!Util.isEmpty(column_default)) if (!Util.isEmpty(column_default))
createStmt.append(" DEFAULT " + column_default); createStmt.append(" DEFAULT ").append(column_default);
} }
if (createStmt != null) if (createStmt != null)
@ -288,7 +392,7 @@ public class TablePartitionService implements ITablePartitionService {
else else
throw new IllegalArgumentException(Msg.getMsg(Env.getCtx(), "PartitioningMethodNotSupported", new Object[]{partitioningMethod})); 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); int no = DB.executeUpdateEx(createStmt.toString(), trxName);
if (processInfo != null) if (processInfo != null)
@ -297,6 +401,9 @@ public class TablePartitionService implements ITablePartitionService {
if (!migrateDBContrainsts(table, trxName, processInfo)) if (!migrateDBContrainsts(table, trxName, processInfo))
throw new AdempiereException(Msg.getMsg(Env.getCtx(), "FailedMigrateDatabaseConstraints")); 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)) if (!attachDefaultPartition(table, trxName, processInfo))
throw new AdempiereException(Msg.getMsg(Env.getCtx(), "FailedAttachDefaultPartition")); throw new AdempiereException(Msg.getMsg(Env.getCtx(), "FailedAttachDefaultPartition"));
@ -363,14 +470,17 @@ public class TablePartitionService implements ITablePartitionService {
List<MColumn> partitionKeyColumns = table.getPartitionKeyColumns(false); List<MColumn> partitionKeyColumns = table.getPartitionKeyColumns(false);
MColumn partitionKeyColumn = partitionKeyColumns.get(0); MColumn partitionKeyColumn = partitionKeyColumns.get(0);
MColumn subPartitionColumn = null;
if (partitionKeyColumns.size() > 1)
subPartitionColumn = partitionKeyColumns.get(1);
String partitioningMethod = partitionKeyColumn.getPartitioningMethod(); String partitioningMethod = partitionKeyColumn.getPartitioningMethod();
if (partitioningMethod.equals(MColumn.PARTITIONINGMETHOD_List)) 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)) else if (partitioningMethod.equals(MColumn.PARTITIONINGMETHOD_Range))
{ {
isUpdated = addRangePartition(table, partitionKeyColumn, trxName, pi); isUpdated = addRangePartition(table, partitionKeyColumn, trxName, pi, subPartitionColumn);
} }
else else
throw new IllegalArgumentException(Msg.getMsg(Env.getCtx(), "PartitioningMethodNotSupported", new Object[]{partitioningMethod})); throw new IllegalArgumentException(Msg.getMsg(Env.getCtx(), "PartitioningMethodNotSupported", new Object[]{partitioningMethod}));
@ -379,34 +489,144 @@ public class TablePartitionService implements ITablePartitionService {
} }
/** /**
* Add new range partition * Create new {@link RangePartitionColumn} instance
* @param table * @param fromTableName table name for FROM clause
* @param partitionKeyColumn * @param partitionKeyColumn
* @param trxName * @param trxName
* @param pi * @return new RangePartitionColumn instance
* @return true if new range partition added
*/ */
private boolean addRangePartition(MTable table, MColumn partitionKeyColumn, String trxName, ProcessInfo pi) { private RangePartitionColumn buildRangePartitionColumn(String fromTableName, MColumn partitionKeyColumn, String trxName) {
boolean isUpdated = false;
X_AD_TablePartition partition = null;
RangePartitionColumn rangePartitionColumn = null;
String partitionKeyColumnName = partitionKeyColumn.getColumnName(); String partitionKeyColumnName = partitionKeyColumn.getColumnName();
String partitionKeyColumnRangeIntervalPattern = partitionKeyColumn.getRangePartitionInterval(); String partitionKeyColumnRangeIntervalPattern = partitionKeyColumn.getRangePartitionInterval();
StringBuilder sql = new StringBuilder(); StringBuilder sql = new StringBuilder();
sql.append("SELECT MIN(").append(partitionKeyColumnName).append(") AS min_value, "); sql.append("SELECT MIN(").append(partitionKeyColumnName).append(") AS min_value, ");
sql.append("MAX(").append(partitionKeyColumnName).append(") AS max_value "); sql.append("MAX(").append(partitionKeyColumnName).append(") AS max_value ");
sql.append("FROM ").append(getDefaultPartitionName(table)); sql.append("FROM ").append(fromTableName);
List<Object> values = DB.getSQLValueObjectsEx(trxName, sql.toString()); List<Object> values = DB.getSQLValueObjectsEx(trxName, sql.toString());
if (values.get(0) != null && values.get(1) != null) if (values.get(0) != null && values.get(1) != null)
{ {
rangePartitionColumn = new RangePartitionColumn( return new RangePartitionColumn(
partitionKeyColumnName, partitionKeyColumnRangeIntervalPattern, partitionKeyColumnName, partitionKeyColumnRangeIntervalPattern,
values.get(0), values.get(1)); 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<String> 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) if (rangePartitionColumn == null)
return false; return false;
@ -415,111 +635,128 @@ public class TablePartitionService implements ITablePartitionService {
for (RangePartitionInterval rangePartitionInterval : rangePartitionIntervals) for (RangePartitionInterval rangePartitionInterval : rangePartitionIntervals)
{ {
StringBuilder name = new StringBuilder(); X_AD_TablePartition partition = createNewRangePartition(rangePartitionInterval, tablePartitionNames, table, partitionKeyColumn, table.getTableName().toLowerCase(),
name.append(table.getTableName().toLowerCase()); getDefaultPartitionName(table), null, trxName);
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);
if (partition != null) if (partition != null)
{ {
StringBuilder createStmt = new StringBuilder(); StringBuilder createStmt = new StringBuilder();
createStmt.append("CREATE TABLE ").append(partition.getName()).append(" (").append(DB_PostgreSQL.NATIVE_MARKER).append("LIKE "); createStmt.append("CREATE TABLE ").append(partition.getName()).append(" (").append(DB_PostgreSQL.NATIVE_MARKER).append("LIKE ");
createStmt.append(getDefaultPartitionName(table)).append(" INCLUDING ALL)"); 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); int no = DB.executeUpdateEx(createStmt.toString(), trxName);
if (pi != null) if (pi != null)
pi.addLog(0, null, null, no + " " + createStmt.toString()); pi.addLog(0, null, null, no + " " + createStmt.toString().replace(DB_PostgreSQL.NATIVE_MARKER, ""));
if (subPartitionColumn != null) {
StringBuilder updateStmt = new StringBuilder(); createSubDefaultPartition(table, subPartitionColumn, partition, pi, trxName);
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());
moveDefaultPartitionDataForRange(partition, partitionKeyColumn, table.getTableName(), getDefaultPartitionName(table), rangePartitionInterval, pi, trxName);
isUpdated = true; isUpdated = true;
} }
} }
if (subPartitionColumn != null) {
List<X_AD_TablePartition> 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<String, Object> subValues = new HashMap<>();
List<X_AD_TablePartition> 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; return isUpdated;
} }
/** /**
* Add new list partition * Generate new X_AD_TablePartition records
* @param table * @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 trxName
* @param pi * @return list of generated X_AD_TablePartition records
* @return true if new list partition added
*/ */
private boolean addListPartition(MTable table, MColumn partitionKeyColumn, String trxName, ProcessInfo pi) { private List<X_AD_TablePartition> generateListPartition(MTable table, String partitionNamePrefix, String fromPartitionTable, MColumn partitionKeyColumn, HashMap<String, Object> columnValues,
boolean isUpdated = false; X_AD_TablePartition parentPartition, String trxName) {
List<X_AD_TablePartition> partitions = new ArrayList<X_AD_TablePartition>(); List<X_AD_TablePartition> partitions = new ArrayList<X_AD_TablePartition>();
String nameColumn = "'" + table.getTableName().toLowerCase() + "_' || " + partitionKeyColumn.getColumnName(); String nameColumn = "'" + partitionNamePrefix + "_' || " + partitionKeyColumn.getColumnName();
String expressionColumn = "'FOR VALUES IN (''' || " + partitionKeyColumn.getColumnName() + " || ''')'"; String expressionColumn = "'FOR VALUES IN (''' || " + partitionKeyColumn.getColumnName() + " || ''')'";
StringBuilder sql = new StringBuilder(); StringBuilder sql = new StringBuilder();
sql.append("SELECT DISTINCT ").append(nameColumn).append(" AS name, "); sql.append("SELECT DISTINCT ").append(nameColumn).append(" AS name, ");
sql.append(expressionColumn).append(" AS expression, "); sql.append(expressionColumn).append(" AS expression, ");
sql.append(partitionKeyColumn.getColumnName()).append(" "); sql.append(partitionKeyColumn.getColumnName()).append(" ");
sql.append("FROM ").append(getDefaultPartitionName(table)).append(" "); sql.append("FROM ").append(fromPartitionTable).append(" ");
HashMap<String, Object> columnValues = new HashMap<>();
try (PreparedStatement pstmt = DB.prepareStatement(sql.toString(), trxName)) try (PreparedStatement pstmt = DB.prepareStatement(sql.toString(), trxName))
{ {
ResultSet rs = pstmt.executeQuery(); ResultSet rs = pstmt.executeQuery();
@ -532,7 +769,7 @@ public class TablePartitionService implements ITablePartitionService {
value = rs.getObject(partitionKeyColumn.getColumnName()); value = rs.getObject(partitionKeyColumn.getColumnName());
columnValues.put(name, value); 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); partitions.add(partition);
} }
} }
@ -540,7 +777,61 @@ public class TablePartitionService implements ITablePartitionService {
{ {
throw new DBException(e); 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<String, Object> columnValues = new HashMap<>();
List<X_AD_TablePartition> partitions = generateListPartition(table, table.getTableName().toLowerCase(), getDefaultPartitionName(table), partitionKeyColumn, columnValues, null, trxName);
for (X_AD_TablePartition partition : partitions) for (X_AD_TablePartition partition : partitions)
{ {
Object value = columnValues.get(partition.getName()); Object value = columnValues.get(partition.getName());
@ -548,41 +839,124 @@ public class TablePartitionService implements ITablePartitionService {
StringBuilder createStmt = new StringBuilder(); StringBuilder createStmt = new StringBuilder();
createStmt.append("CREATE TABLE ").append(partition.getName()).append(" (").append(DB_PostgreSQL.NATIVE_MARKER).append("LIKE "); createStmt.append("CREATE TABLE ").append(partition.getName()).append(" (").append(DB_PostgreSQL.NATIVE_MARKER).append("LIKE ");
createStmt.append(getDefaultPartitionName(table)).append(" INCLUDING ALL)"); 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); int no = DB.executeUpdateEx(createStmt.toString(), trxName);
if (pi != null) if (pi != null)
pi.addLog(0, null, null, no + " " + createStmt.toString()); pi.addLog(0, null, null, no + " " + createStmt.toString().replace(DB_PostgreSQL.NATIVE_MARKER, ""));
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(" ");
updateStmt.append("RETURNING *) "); if (subPartitionColumn != null) {
updateStmt.append("INSERT INTO ").append(partition.getName()).append(" "); createSubDefaultPartition(table, subPartitionColumn, partition, pi, trxName);
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(); moveDefaultPartitionDataForList(partition, partitionKeyColumn, table.getTableName(), getDefaultPartitionName(table), value, pi, trxName);
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());
isUpdated = true; isUpdated = true;
} }
if (subPartitionColumn != null) {
List<String> 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<String, Object> subValues = new HashMap<>();
List<X_AD_TablePartition> 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<RangePartitionInterval> 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; 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 @Override
public boolean runPostPartitionProcess(MTable table, String trxName, ProcessInfo processInfo) { public boolean runPostPartitionProcess(MTable table, String trxName, ProcessInfo processInfo) {
StringBuilder stmt = new StringBuilder(); StringBuilder stmt = new StringBuilder();
@ -627,8 +1001,8 @@ public class TablePartitionService implements ITablePartitionService {
if (partitionKeyColumns.contains(column)) if (partitionKeyColumns.contains(column))
partitionKeyColumns.remove(column); partitionKeyColumns.remove(column);
} }
if (partitionKeyColumns.size() > 1) if (partitionKeyColumns.size() > 2)
return Msg.getMsg(Env.getCtx(), "OnlyOnePartitionKeyAllowed"); return Msg.getMsg(Env.getCtx(), "OnlyTwoPartitionKeyAllowed");
if (column.isActive() && column.isPartitionKey() && column.getPartitioningMethod().equals(MColumn.PARTITIONINGMETHOD_Range)) { if (column.isActive() && column.isPartitionKey() && column.getPartitioningMethod().equals(MColumn.PARTITIONINGMETHOD_Range)) {
String error = RangePartitionInterval.validateIntervalPattern(column); String error = RangePartitionInterval.validateIntervalPattern(column);
@ -638,14 +1012,113 @@ public class TablePartitionService implements ITablePartitionService {
if (!isPartitionedTable(table, trxName)) if (!isPartitionedTable(table, trxName))
return null; return null;
if (column.is_ValueChanged(MColumn.COLUMNNAME_IsPartitionKey) if (column.is_ValueChanged(MColumn.COLUMNNAME_IsPartitionKey)
|| (column.isPartitionKey() && column.is_ValueChanged(MColumn.COLUMNNAME_IsActive)) || (column.isPartitionKey() && column.is_ValueChanged(MColumn.COLUMNNAME_IsActive))
|| (column.isPartitionKey() && column.is_ValueChanged(MColumn.COLUMNNAME_SeqNoPartition))) { || (column.isPartitionKey() && column.is_ValueChanged(MColumn.COLUMNNAME_PartitioningMethod))) {
return validateConfiguration(table, trxName); 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)) if (column.isPartitionKey() && column.is_ValueChanged(MColumn.COLUMNNAME_RangePartitionInterval))
return Msg.getMsg(Env.getCtx(), "PartitionConfigurationChanged") + " [" + MColumn.COLUMNNAME_RangePartitionInterval + "]"; return Msg.getMsg(Env.getCtx(), "PartitionConfigurationChanged") + " [" + MColumn.COLUMNNAME_RangePartitionInterval + "]";
return null; 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"));
}
}
}
} }

View File

@ -74,6 +74,7 @@ public class TablePartitionTask {
try try
{ {
table.set_TrxName(trxName);
addLog(Msg.getElement(Env.getCtx(), "TableName") + ": " + table.getTableName()); addLog(Msg.getElement(Env.getCtx(), "TableName") + ": " + table.getTableName());
List<MColumn> partitionKeyColumns = table.getPartitionKeyColumns(true); List<MColumn> partitionKeyColumns = table.getPartitionKeyColumns(true);

View File

@ -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());
}
}
}
}