diff --git a/.github/workflows/multi-platform-test.yml b/.github/workflows/multi-platform-test.yml index f85636c9b..d923063fe 100644 --- a/.github/workflows/multi-platform-test.yml +++ b/.github/workflows/multi-platform-test.yml @@ -43,3 +43,5 @@ jobs: run: mvn clean test -Dprops.file=testconfig/ebean-sqlserver17.properties - name: mariadb run: mvn clean test -Dprops.file=testconfig/ebean-mariadb.properties + - name: db2 + run: mvn clean test -Dprops.file=testconfig/ebean-db2.properties diff --git a/ebean-api/pom.xml b/ebean-api/pom.xml index d11bb71f6..15021541b 100644 --- a/ebean-api/pom.xml +++ b/ebean-api/pom.xml @@ -49,7 +49,7 @@ io.ebean ebean-annotation - 7.3 + 7.5 diff --git a/ebean-api/src/main/java/io/ebean/config/dbplatform/db2/DB2Platform.java b/ebean-api/src/main/java/io/ebean/config/dbplatform/db2/BaseDB2Platform.java similarity index 88% rename from ebean-api/src/main/java/io/ebean/config/dbplatform/db2/DB2Platform.java rename to ebean-api/src/main/java/io/ebean/config/dbplatform/db2/BaseDB2Platform.java index 5f2e44df9..fb4fff30a 100644 --- a/ebean-api/src/main/java/io/ebean/config/dbplatform/db2/DB2Platform.java +++ b/ebean-api/src/main/java/io/ebean/config/dbplatform/db2/BaseDB2Platform.java @@ -15,15 +15,11 @@ import java.sql.Types; /** * DB2 specific platform. */ -public class DB2Platform extends DatabasePlatform { +public abstract class BaseDB2Platform extends DatabasePlatform { - public DB2Platform() { + public BaseDB2Platform() { super(); this.platform = Platform.DB2; - // Note: DB2 (at least LUW supports length up to 128) - // TOOD: Check if we need to introduce a new platform (DB2_LUW_11 ?) - this.maxTableNameLength = 18; - this.maxConstraintNameLength = 18; this.supportsNativeJavaTime = false; this.truncateTable = "truncate table %s reuse storage ignore delete triggers immediate"; this.sqlLimiter = new Db2SqlLimiter(); diff --git a/ebean-api/src/main/java/io/ebean/config/dbplatform/db2/DB2ForIPlatform.java b/ebean-api/src/main/java/io/ebean/config/dbplatform/db2/DB2ForIPlatform.java new file mode 100644 index 000000000..fa6028858 --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/config/dbplatform/db2/DB2ForIPlatform.java @@ -0,0 +1,39 @@ +package io.ebean.config.dbplatform.db2; + +import io.ebean.annotation.Platform; +import io.ebean.config.dbplatform.DbPlatformType; +import io.ebean.config.dbplatform.DbType; +import io.ebean.config.dbplatform.SqlErrorCodes; + +import java.sql.Types; + +/** + * DB2 specific platform for i Series. + * + * @author Cédric Sougné + */ +public class DB2ForIPlatform extends BaseDB2Platform { + + public DB2ForIPlatform() { + super(); + this.platform = Platform.DB2FORI; + // Note: IBM i from 7.1 allow up to to 128 + // TODO: Check if we need to introduce older platform (DB2ForI_6 ? but older + // documentation is not anymore published on ibm.com), + this.maxTableNameLength = 128; + this.maxConstraintNameLength = 128; + + this.dbIdentity.setSupportsIdentity(true); + + this.exceptionTranslator = new SqlErrorCodes().addAcquireLock("57033") // key -913 + .addDuplicateKey("23505") // -803 + // .addDataIntegrity("-407","-530","-531","-532","-543","-544","-545","-603","-667") + // we need SQLState, not code: + // https://www.ibm.com/support/knowledgecenter/en/SSEPEK_10.0.0/codes/src/tpc/db2z_n.html + .addDataIntegrity("23502", "23503", "23504", "23507", "23511", "23512", "23513", "42917", "23515") + .build(); + + booleanDbType = Types.SMALLINT; + dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("smallint default 0")); + } +} diff --git a/ebean-api/src/main/java/io/ebean/config/dbplatform/db2/DB2LegacyPlatform.java b/ebean-api/src/main/java/io/ebean/config/dbplatform/db2/DB2LegacyPlatform.java new file mode 100644 index 000000000..51f03c2a4 --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/config/dbplatform/db2/DB2LegacyPlatform.java @@ -0,0 +1,20 @@ +package io.ebean.config.dbplatform.db2; + +import io.ebean.annotation.Platform; + +/** + * DB2 specific platform for older DB2 versions. This platform is here for + * compatibility reasons. It uses a length limit of 18 chars for table and + * constraint names. Newer DB2 versions will support up to 128. It is strongly + * recommended to migrate to db2luw/DB2ForI or db2zos platform. + */ +public class DB2LegacyPlatform extends BaseDB2Platform { + public DB2LegacyPlatform() { + super(); + this.platform = Platform.DB2; + // Note: DB2 (at least LUW supports length up to 128) + // TOOD: Check if we need to introduce a new platform (DB2_LUW_11 ?) + this.maxTableNameLength = 18; + this.maxConstraintNameLength = 18; + } +} diff --git a/ebean-api/src/main/java/io/ebean/config/dbplatform/db2/DB2LuwPlatform.java b/ebean-api/src/main/java/io/ebean/config/dbplatform/db2/DB2LuwPlatform.java new file mode 100644 index 000000000..967cc2428 --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/config/dbplatform/db2/DB2LuwPlatform.java @@ -0,0 +1,17 @@ +package io.ebean.config.dbplatform.db2; + +import io.ebean.annotation.Platform; + +/** + * DB2 platform for Linux/Unix/Windows. + * @author Roland Praml, FOCONIS AG + * + */ +public class DB2LuwPlatform extends BaseDB2Platform { + public DB2LuwPlatform() { + super(); + this.platform = Platform.DB2LUW; + this.maxTableNameLength = 128; + this.maxConstraintNameLength = 128; + } +} diff --git a/ebean-api/src/main/java/io/ebean/config/dbplatform/db2/DB2ZosPlatform.java b/ebean-api/src/main/java/io/ebean/config/dbplatform/db2/DB2ZosPlatform.java new file mode 100644 index 000000000..2cb4cb35b --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/config/dbplatform/db2/DB2ZosPlatform.java @@ -0,0 +1,18 @@ +package io.ebean.config.dbplatform.db2; + +import io.ebean.annotation.Platform; + +/** + * DB2 platform for z/OS. + * Note: This platform is currently not tested! + * @author Roland Praml, FOCONIS AG + * + */ +public class DB2ZosPlatform extends BaseDB2Platform { + public DB2ZosPlatform() { + super(); + this.platform = Platform.DB2ZOS; + this.maxTableNameLength = 128; + this.maxConstraintNameLength = 128; + } +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DatabasePlatformFactory.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DatabasePlatformFactory.java index 96600609a..ad03fd45f 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DatabasePlatformFactory.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DatabasePlatformFactory.java @@ -4,7 +4,10 @@ import io.ebean.config.DatabaseConfig; import io.ebean.config.dbplatform.DatabasePlatform; import io.ebean.config.dbplatform.clickhouse.ClickHousePlatform; import io.ebean.config.dbplatform.cockroach.CockroachPlatform; -import io.ebean.config.dbplatform.db2.DB2Platform; +import io.ebean.config.dbplatform.db2.DB2ForIPlatform; +import io.ebean.config.dbplatform.db2.DB2LegacyPlatform; +import io.ebean.config.dbplatform.db2.DB2LuwPlatform; +import io.ebean.config.dbplatform.db2.DB2ZosPlatform; import io.ebean.config.dbplatform.h2.H2Platform; import io.ebean.config.dbplatform.hana.HanaPlatform; import io.ebean.config.dbplatform.hsqldb.HsqldbPlatform; @@ -103,7 +106,19 @@ public class DatabasePlatformFactory { return new SqlAnywherePlatform(); } if (dbName.equals("db2")) { - return new DB2Platform(); + throw new IllegalArgumentException("Please choose the more specific db2luw/db2zos/db2fori platform. Refer to issue #2514 for details"); + } + if (dbName.equals("db2legacy")) { + return new DB2LegacyPlatform(); + } + if (dbName.equals("db2zos")) { + return new DB2ZosPlatform(); + } + if (dbName.equals("db2fori")) { + return new DB2ForIPlatform(); + } + if (dbName.equals("db2luw")) { + return new DB2LuwPlatform(); } if (dbName.equals("clickhouse")) { return new ClickHousePlatform(); @@ -159,7 +174,7 @@ public class DatabasePlatformFactory { } else if (dbProductName.contains("sqlite")) { return new SQLitePlatform(); } else if (dbProductName.contains("db2")) { - return new DB2Platform(); + throw new IllegalArgumentException("For DB2 please explicitly choose either db2legacy/db2luw/db2zos/db2fori platform. Refer to issue #2514 for details"); } else if (dbProductName.contains("sql anywhere")) { return new SqlAnywherePlatform(); } else if (dbProductName.contains("hdb")) { diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DefaultDbMigration.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DefaultDbMigration.java index 4add6fe13..cdfa5a528 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DefaultDbMigration.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DefaultDbMigration.java @@ -24,7 +24,10 @@ import io.ebean.config.PropertiesWrapper; import io.ebean.config.dbplatform.DatabasePlatform; import io.ebean.config.dbplatform.clickhouse.ClickHousePlatform; import io.ebean.config.dbplatform.cockroach.CockroachPlatform; -import io.ebean.config.dbplatform.db2.DB2Platform; +import io.ebean.config.dbplatform.db2.DB2ForIPlatform; +import io.ebean.config.dbplatform.db2.DB2LegacyPlatform; +import io.ebean.config.dbplatform.db2.DB2LuwPlatform; +import io.ebean.config.dbplatform.db2.DB2ZosPlatform; import io.ebean.config.dbplatform.h2.H2Platform; import io.ebean.config.dbplatform.hana.HanaPlatform; import io.ebean.config.dbplatform.hsqldb.HsqldbPlatform; @@ -831,7 +834,14 @@ public class DefaultDbMigration implements DbMigration { case SQLSERVER: throw new IllegalArgumentException("Please choose the more specific SQLSERVER16 or SQLSERVER17 platform. Refer to issue #1340 for details"); case DB2: - return new DB2Platform(); + logger.warn("Using DB2LegacyPlatform. It is recommended to migrate to db2luw/db2zos/db2fori. Refer to issue #2514 for details"); + return new DB2LegacyPlatform(); + case DB2LUW: + return new DB2LuwPlatform(); + case DB2ZOS: + return new DB2ZosPlatform(); + case DB2FORI: + return new DB2ForIPlatform(); case SQLITE: return new SQLitePlatform(); case HANA: diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/PlatformDdlBuilder.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/PlatformDdlBuilder.java index 08d4e3b40..0133413e0 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/PlatformDdlBuilder.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/PlatformDdlBuilder.java @@ -31,6 +31,9 @@ public class PlatformDdlBuilder { case H2: return new H2Ddl(platform); case DB2: + case DB2LUW: + case DB2FORI: + case DB2ZOS: return new DB2Ddl(platform); case MARIADB: return new MariaDbDdl(platform); diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/DB2Ddl.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/DB2Ddl.java index d3cf92028..7ee9e1c30 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/DB2Ddl.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/DB2Ddl.java @@ -15,6 +15,8 @@ public class DB2Ddl extends PlatformDdl { this.dropConstraintIfExists = "drop constraint"; this.dropIndexIfExists = "drop index "; this.identitySuffix = " generated by default as identity"; + this.columnSetNull = "drop not null"; + this.columnSetType = "set data type "; this.inlineUniqueWhenNullable = false; } diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/1.1.sql b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/1.1.sql index 605a24af3..3d7f846c7 100644 --- a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/1.1.sql +++ b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/1.1.sql @@ -36,16 +36,16 @@ alter table migtest_e_basic alter column status set default 'A'; alter table migtest_e_basic alter column status set not null; alter table migtest_e_basic add constraint ck_mgtst__bsc_stts check ( status in ('N','A','I','?')); alter table migtest_e_basic drop constraint ck_mgtst__b_z543fg; -alter table migtest_e_basic alter column status2 varchar(127); +alter table migtest_e_basic alter column status2 set data type varchar(127); alter table migtest_e_basic alter column status2 drop default; -alter table migtest_e_basic alter column status2 set null; +alter table migtest_e_basic alter column status2 drop not null; -- rename all collisions; -- NOT SUPPORTED alter table migtest_e_basic add constraint uq_mgtst__b_vs45xo unique (description); insert into migtest_e_user (id) select distinct user_id from migtest_e_basic; alter table migtest_e_basic add constraint fk_mgtst__bsc_sr_d foreign key (user_id) references migtest_e_user (id) on delete restrict; -alter table migtest_e_basic alter column user_id set null; +alter table migtest_e_basic alter column user_id drop not null; alter table migtest_e_basic add column new_string_field varchar(255) default 'foo''bar' not null; alter table migtest_e_basic add column new_boolean_field boolean default true not null; update migtest_e_basic set new_boolean_field = old_boolean; @@ -63,7 +63,7 @@ alter table migtest_e_basic drop constraint uq_mgtst__b_4ayc02; -- NOT SUPPORTED alter table migtest_e_basic add constraint uq_mgtst__b_4ayc01 unique (indextest5); alter table migtest_e_enum drop constraint ck_mgtst__n_773sok; comment on column migtest_e_history.test_string is 'Column altered to long now'; -alter table migtest_e_history alter column test_string bigint; +alter table migtest_e_history alter column test_string set data type bigint; comment on table migtest_e_history is 'We have history now'; -- NOTE: table has @History - special migration may be necessary @@ -74,7 +74,7 @@ alter table migtest_e_history2 add column test_string2 varchar(255); alter table migtest_e_history2 add column test_string3 varchar(255) default 'unknown' not null; alter table migtest_e_history2 add column new_column varchar(20); -alter table migtest_e_history4 alter column test_number bigint; +alter table migtest_e_history4 alter column test_number set data type bigint; alter table migtest_e_history5 add column test_boolean boolean default false not null; @@ -82,7 +82,7 @@ alter table migtest_e_history5 add column test_boolean boolean default false not update migtest_e_history6 set test_number1 = 42 where test_number1 is null; alter table migtest_e_history6 alter column test_number1 set default 42; alter table migtest_e_history6 alter column test_number1 set not null; -alter table migtest_e_history6 alter column test_number2 set null; +alter table migtest_e_history6 alter column test_number2 drop not null; alter table migtest_e_softdelete add column deleted boolean default false not null; alter table migtest_oto_child add column master_id bigint; diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/1.3.sql b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/1.3.sql index 9489c8d98..47fc898ae 100644 --- a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/1.3.sql +++ b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/1.3.sql @@ -16,12 +16,12 @@ alter table migtest_fk_set_null drop constraint fk_mgtst_fk_wicx8x; alter table migtest_fk_set_null add constraint fk_mgtst_fk_wicx8x foreign key (one_id) references migtest_fk_one (id) on delete set null; alter table migtest_e_basic drop constraint ck_mgtst__bsc_stts; alter table migtest_e_basic alter column status drop default; -alter table migtest_e_basic alter column status set null; +alter table migtest_e_basic alter column status drop not null; alter table migtest_e_basic add constraint ck_mgtst__bsc_stts check ( status in ('N','A','I')); update migtest_e_basic set status2 = 'N' where status2 is null; alter table migtest_e_basic drop constraint ck_mgtst__b_z543fg; -alter table migtest_e_basic alter column status2 varchar(1); +alter table migtest_e_basic alter column status2 set data type varchar(1); alter table migtest_e_basic alter column status2 set default 'N'; alter table migtest_e_basic alter column status2 set not null; alter table migtest_e_basic add constraint ck_mgtst__b_z543fg check ( status2 in ('N','A','I')); @@ -46,13 +46,13 @@ alter table migtest_e_enum add constraint ck_mgtst__n_773sok check ( test_status comment on column migtest_e_history.test_string is ''; comment on table migtest_e_history is ''; alter table migtest_e_history2 alter column test_string drop default; -alter table migtest_e_history2 alter column test_string set null; +alter table migtest_e_history2 alter column test_string drop not null; alter table migtest_e_history2 add column obsolete_string1 varchar(255); alter table migtest_e_history2 add column obsolete_string2 varchar(255); -alter table migtest_e_history4 alter column test_number integer; +alter table migtest_e_history4 alter column test_number set data type integer; alter table migtest_e_history6 alter column test_number1 drop default; -alter table migtest_e_history6 alter column test_number1 set null; +alter table migtest_e_history6 alter column test_number1 drop not null; -- NOTE: table has @History - special migration may be necessary update migtest_e_history6 set test_number2 = 7 where test_number2 is null; diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/idx_db2.migrations b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/idx_db2.migrations index ec4208068..9c74dc16e 100644 --- a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/idx_db2.migrations +++ b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/idx_db2.migrations @@ -1,6 +1,6 @@ 441570368, 1.0__initial.sql --94595879, 1.1.sql +367602802, 1.1.sql 578073685, 1.2__dropsFor_1.1.sql --509420890, 1.3.sql +-478470986, 1.3.sql -1475628451, 1.4__dropsFor_1.3.sql diff --git a/ebean-test/testconfig/ebean-db2.properties b/ebean-test/testconfig/ebean-db2.properties index 2aa2944dd..40e375ef5 100644 --- a/ebean-test/testconfig/ebean-db2.properties +++ b/ebean-test/testconfig/ebean-db2.properties @@ -2,3 +2,4 @@ ebean.test.platform=db2 ebean.test.dbName=unit ebean.test.dbPassword=unit datasource.default=db2-11 +ebean.db2-11.databasePlatformName=db2luw