Skip to content

Commit 0573a33

Browse files
authored
feat: add SqlAuditMarkerDialectHelperTest and fix some upsert queries (#721)
1 parent 415bae3 commit 0573a33

File tree

8 files changed

+274
-13
lines changed

8 files changed

+274
-13
lines changed

.github/workflows/release.yml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,19 @@ jobs:
369369
FLAMINGOCK_JRELEASER_GPG_SECRET_KEY: ${{ secrets.FLAMINGOCK_JRELEASER_GPG_SECRET_KEY }}
370370
FLAMINGOCK_JRELEASER_GPG_PASSPHRASE: ${{ secrets.FLAMINGOCK_JRELEASER_GPG_PASSPHRASE }}
371371

372+
sql-util:
373+
needs: [ build ]
374+
uses: ./.github/workflows/module-release-graalvm.yml
375+
with:
376+
module: sql-util
377+
secrets:
378+
FLAMINGOCK_JRELEASER_GITHUB_TOKEN: ${{ secrets.FLAMINGOCK_JRELEASER_GITHUB_TOKEN }}
379+
FLAMINGOCK_JRELEASER_MAVENCENTRAL_USERNAME: ${{ secrets.FLAMINGOCK_JRELEASER_MAVENCENTRAL_USERNAME }}
380+
FLAMINGOCK_JRELEASER_MAVENCENTRAL_PASSWORD: ${{ secrets.FLAMINGOCK_JRELEASER_MAVENCENTRAL_PASSWORD }}
381+
FLAMINGOCK_JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.FLAMINGOCK_JRELEASER_GPG_PUBLIC_KEY }}
382+
FLAMINGOCK_JRELEASER_GPG_SECRET_KEY: ${{ secrets.FLAMINGOCK_JRELEASER_GPG_SECRET_KEY }}
383+
FLAMINGOCK_JRELEASER_GPG_PASSPHRASE: ${{ secrets.FLAMINGOCK_JRELEASER_GPG_PASSPHRASE }}
384+
372385
github-release:
373386
needs: [
374387
flamingock-core,
@@ -396,7 +409,8 @@ jobs:
396409
test-util,
397410
mongodb-util,
398411
dynamodb-util,
399-
couchbase-util
412+
couchbase-util,
413+
sql-util
400414
]
401415
uses: ./.github/workflows/github-release.yml
402416
secrets:

buildSrc/src/main/kotlin/flamingock.project-structure.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ val utilProjects = setOf(
4848
"test-util",
4949
"mongodb-util",
5050
"dynamodb-util",
51-
"couchbase-util"
51+
"couchbase-util",
52+
"sql-util"
5253
)
5354

5455
val allProjects = coreProjects + cloudProjects + communityProjects + pluginProjects + targetSystemProjects + templateProjects + utilProjects

core/target-systems/sql-target-system/build.gradle.kts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,19 @@ dependencies {
66
implementation(project(":utils:sql-util"))
77

88
//Test
9-
testImplementation("org.testcontainers:mysql:1.19.0")
9+
testImplementation("org.testcontainers:testcontainers-mysql:2.0.1")
10+
testImplementation("org.testcontainers:testcontainers-postgresql:2.0.1")
11+
testImplementation("org.testcontainers:testcontainers-mssqlserver:2.0.1")
12+
testImplementation("org.testcontainers:testcontainers-oracle-free:2.0.1")
1013
testImplementation("mysql:mysql-connector-java:8.0.33")
14+
testImplementation("org.postgresql:postgresql:42.7.8")
15+
testImplementation("com.microsoft.sqlserver:mssql-jdbc:13.2.1.jre8")
16+
testImplementation("com.oracle.database.jdbc:ojdbc8:23.2.0.0")
17+
testImplementation("org.xerial:sqlite-jdbc:3.50.3.0")
18+
testImplementation("com.h2database:h2:2.2.220")
19+
testImplementation("org.hsqldb:hsqldb:2.5.2")
20+
testImplementation("org.apache.derby:derbytools:10.15.2.0")
21+
testImplementation("org.firebirdsql.jdbc:jaybird:5.0.10.java8")
1122
testImplementation("org.testcontainers:junit-jupiter:1.18.3")
1223

1324
testImplementation(project(":cloud:flamingock-cloud"))

core/target-systems/sql-target-system/src/main/java/io/flamingock/targetsystem/sql/SqlAuditMarkerDialectHelper.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,19 +66,22 @@ public String getMarkSqlString(String tableName) {
6666
tableName);
6767
case DB2:
6868
return String.format(
69-
"MERGE INTO %s USING (SELECT ? AS task_id, ? AS operation FROM SYSIBM.SYSDUMMY1) AS src ON (%s.task_id = src.task_id) " +
70-
"WHEN MATCHED THEN UPDATE SET operation = src.operation WHEN NOT MATCHED THEN INSERT (task_id, operation) VALUES (src.task_id, src.operation)",
69+
"MERGE INTO %s AS t USING (SELECT ? AS task_id, ? AS operation FROM SYSIBM.SYSDUMMY1) AS s ON (t.task_id = s.task_id) " +
70+
"WHEN MATCHED THEN UPDATE SET operation = s.operation WHEN NOT MATCHED THEN INSERT (task_id, operation) VALUES (s.task_id, s.operation)",
7171
tableName, tableName);
7272
case FIREBIRD:
7373
return String.format(
7474
"UPDATE OR INSERT INTO %s (task_id, operation) VALUES (?, ?) MATCHING (task_id)",
7575
tableName);
7676
case H2:
77-
case HSQLDB:
78-
case DERBY:
7977
return String.format(
8078
"MERGE INTO %s (task_id, operation) KEY (task_id) VALUES (?, ?)",
8179
tableName);
80+
case HSQLDB:
81+
return String.format(
82+
"MERGE INTO %s AS t USING (VALUES(?,?)) AS s(task_id,operation) ON t.task_id = s.task_id " +
83+
"WHEN MATCHED THEN UPDATE SET t.operation = s.operation WHEN NOT MATCHED THEN INSERT (task_id, operation) VALUES (s.task_id, s.operation)",
84+
tableName);
8285
case INFORMIX:
8386
return String.format(
8487
"INSERT INTO %s (task_id, operation) VALUES (?, ?) ON DUPLICATE KEY UPDATE operation = ?",
@@ -96,7 +99,6 @@ public String getCreateTableSqlString(String tableName) {
9699
case SQLITE:
97100
case H2:
98101
case HSQLDB:
99-
case DERBY:
100102
case SQLSERVER:
101103
case SYBASE:
102104
case FIREBIRD:

core/target-systems/sql-target-system/src/test/java/io/flamingock/targetsystem/sql/MySQLTestHelper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public void checkEmptyTargetSystemAudiMarker() {
103103
checkOngoingTask(ongoingCount -> ongoingCount == 0);
104104
}
105105

106-
private void createOngoingTasksTableIfNotExists(Connection connection) throws SQLException {
106+
public void createOngoingTasksTableIfNotExists(Connection connection) throws SQLException {
107107
DatabaseMetaData meta = connection.getMetaData();
108108
ResultSet resultSet = meta.getTables(null, null, ONGOING_TASKS_TABLE, new String[]{"TABLE"});
109109
if (!resultSet.next()) {
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
/*
2+
* Copyright 2025 Flamingock (https://www.flamingock.io)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.flamingock.targetsystem.sql;
17+
18+
import com.zaxxer.hikari.HikariConfig;
19+
import com.zaxxer.hikari.HikariDataSource;
20+
import io.flamingock.internal.common.sql.SqlDialect;
21+
import io.flamingock.internal.core.targets.mark.TargetSystemAuditMark;
22+
import io.flamingock.internal.core.transaction.TransactionManager;
23+
import org.junit.jupiter.api.*;
24+
import org.junit.jupiter.params.ParameterizedTest;
25+
import org.junit.jupiter.params.provider.EnumSource;
26+
import org.testcontainers.containers.JdbcDatabaseContainer;
27+
import org.testcontainers.containers.MySQLContainer;
28+
import org.testcontainers.containers.PostgreSQLContainer;
29+
import org.testcontainers.containers.MSSQLServerContainer;
30+
import org.testcontainers.oracle.OracleContainer;
31+
import org.testcontainers.utility.DockerImageName;
32+
33+
import javax.sql.DataSource;
34+
import java.sql.*;
35+
import java.util.Set;
36+
37+
@TestInstance(TestInstance.Lifecycle.PER_METHOD)
38+
public class SqlAuditMarkerDialectHelperTest {
39+
40+
private static final String ONGOING_TASKS_TABLE = "FLAMINGOCK_ONGOING_TASKS";
41+
42+
private DataSource dataSource;
43+
private SqlTargetSystemAuditMarker sqlTargetSystemAuditMarker;
44+
private TransactionManager<Connection> txManager;
45+
46+
private JdbcDatabaseContainer<?> createContainerForDialect(SqlDialect dialect) {
47+
switch (dialect) {
48+
case MYSQL:
49+
case MARIADB:
50+
return new MySQLContainer<>(DockerImageName.parse("mysql:8.0"))
51+
.withDatabaseName("testdb")
52+
.withUsername("testuser")
53+
.withPassword("testpass");
54+
case POSTGRESQL:
55+
return new PostgreSQLContainer<>(DockerImageName.parse("postgres:14"))
56+
.withDatabaseName("testdb")
57+
.withUsername("testuser")
58+
.withPassword("testpass");
59+
case SQLSERVER:
60+
case SYBASE:
61+
return new MSSQLServerContainer<>(DockerImageName.parse("mcr.microsoft.com/mssql/server:2019-latest"))
62+
.withPassword("YourStrong!Passw0rd")
63+
.acceptLicense();
64+
case ORACLE:
65+
return new OracleContainer("gvenzl/oracle-free:slim-faststart")
66+
.withDatabaseName("testdb")
67+
.withUsername("testuser")
68+
.withPassword("testpass");
69+
default:
70+
return null;
71+
}
72+
}
73+
74+
private void initForDialect(SqlDialect dialect, JdbcDatabaseContainer<?> container) {
75+
if (container != null) {
76+
container.start();
77+
HikariConfig config = new HikariConfig();
78+
config.setJdbcUrl(container.getJdbcUrl());
79+
config.setUsername(container.getUsername());
80+
config.setPassword(container.getPassword());
81+
try {
82+
config.setDriverClassName(container.getDriverClassName());
83+
} catch (Exception ignored) {
84+
}
85+
dataSource = new HikariDataSource(config);
86+
} else {
87+
HikariConfig config = new HikariConfig();
88+
switch (dialect) {
89+
case H2:
90+
config.setJdbcUrl("jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1");
91+
config.setUsername("testuser");
92+
config.setPassword("");
93+
config.setDriverClassName("org.h2.Driver");
94+
break;
95+
case SQLITE:
96+
config.setJdbcUrl("jdbc:sqlite:file:memdb?mode=memory&cache=shared");
97+
config.setUsername("");
98+
config.setPassword("");
99+
config.setDriverClassName("org.sqlite.JDBC");
100+
break;
101+
case HSQLDB:
102+
config.setJdbcUrl("jdbc:hsqldb:mem:testdb");
103+
config.setUsername("testuser");
104+
config.setPassword("");
105+
config.setDriverClassName("org.hsqldb.jdbc.JDBCDriver");
106+
break;
107+
case DB2:
108+
config.setJdbcUrl("jdbc:h2:mem:testdb;MODE=DB2;DB_CLOSE_DELAY=-1");
109+
config.setUsername("testuser");
110+
config.setPassword("");
111+
config.setDriverClassName("org.h2.Driver");
112+
break;
113+
default:
114+
dataSource = null;
115+
break;
116+
}
117+
if (dataSource == null && config.getJdbcUrl() != null) {
118+
dataSource = new HikariDataSource(config);
119+
}
120+
}
121+
122+
txManager = new TransactionManager<>(() -> {
123+
try {
124+
return dataSource.getConnection();
125+
} catch (SQLException e) {
126+
throw new RuntimeException(e);
127+
}
128+
});
129+
130+
sqlTargetSystemAuditMarker = SqlTargetSystemAuditMarker.builder(dataSource, txManager)
131+
.withTableName(ONGOING_TASKS_TABLE)
132+
.build();
133+
}
134+
135+
private void dropTable() throws SQLException {
136+
try (Connection c = dataSource.getConnection(); Statement s = c.createStatement()) {
137+
s.execute("DROP TABLE IF EXISTS " + ONGOING_TASKS_TABLE);
138+
}
139+
}
140+
141+
@ParameterizedTest(name = "[{index}] dialect={0} - Should add and list two marks successfully")
142+
@EnumSource(SqlDialect.class)
143+
void addOngoingTaskMark(SqlDialect dialect) {
144+
JdbcDatabaseContainer<?> container = createContainerForDialect(dialect);
145+
Assumptions.assumeTrue(container != null ||
146+
(dialect != SqlDialect.FIREBIRD && dialect != SqlDialect.INFORMIX),
147+
"No Test support for " + dialect.name());
148+
149+
try {
150+
initForDialect(dialect, container);
151+
152+
// GIVEN
153+
String taskId1 = "test-task-id1";
154+
String taskId2 = "test-task-id2";
155+
io.flamingock.internal.common.cloud.vo.TargetSystemAuditMarkType operation = io.flamingock.internal.common.cloud.vo.TargetSystemAuditMarkType.ROLLBACK;
156+
157+
TargetSystemAuditMark mark1 = new TargetSystemAuditMark(taskId1, operation);
158+
TargetSystemAuditMark mark2 = new TargetSystemAuditMark(taskId2, operation);
159+
160+
// WHEN
161+
txManager.startSession(taskId1);
162+
sqlTargetSystemAuditMarker.mark(mark1);
163+
txManager.closeSession(taskId1);
164+
txManager.startSession(taskId2);
165+
sqlTargetSystemAuditMarker.mark(mark2);
166+
txManager.closeSession(taskId2);
167+
Set<TargetSystemAuditMark> marks = sqlTargetSystemAuditMarker.listAll();
168+
169+
// THEN
170+
Assertions.assertEquals(2, marks.size());
171+
} finally {
172+
if (dataSource != null) {
173+
try {
174+
dropTable();
175+
} catch (SQLException ignored) {
176+
}
177+
if (dataSource instanceof HikariDataSource) {
178+
((HikariDataSource) dataSource).close();
179+
}
180+
}
181+
if (container != null && container.isRunning()) {
182+
container.stop();
183+
}
184+
}
185+
}
186+
187+
@ParameterizedTest(name = "[{index}] dialect={0} - Should remove all marks successfully")
188+
@EnumSource(SqlDialect.class)
189+
void removeOngoingTaskMark(SqlDialect dialect) {
190+
JdbcDatabaseContainer<?> container = createContainerForDialect(dialect);
191+
Assumptions.assumeTrue(container != null ||
192+
(dialect != SqlDialect.FIREBIRD && dialect != SqlDialect.INFORMIX),
193+
"No Test support for " + dialect.name());
194+
195+
try {
196+
initForDialect(dialect, container);
197+
198+
// GIVEN
199+
String taskId1 = "test-task-id1";
200+
String taskId2 = "test-task-id2";
201+
io.flamingock.internal.common.cloud.vo.TargetSystemAuditMarkType operation = io.flamingock.internal.common.cloud.vo.TargetSystemAuditMarkType.ROLLBACK;
202+
203+
TargetSystemAuditMark mark1 = new TargetSystemAuditMark(taskId1, operation);
204+
TargetSystemAuditMark mark2 = new TargetSystemAuditMark(taskId2, operation);
205+
txManager.startSession(taskId1);
206+
sqlTargetSystemAuditMarker.mark(mark1);
207+
txManager.closeSession(taskId1);
208+
txManager.startSession(taskId2);
209+
sqlTargetSystemAuditMarker.mark(mark2);
210+
txManager.closeSession(taskId2);
211+
212+
Set<TargetSystemAuditMark> marks = sqlTargetSystemAuditMarker.listAll();
213+
214+
// WHEN
215+
for (TargetSystemAuditMark mark : marks) {
216+
sqlTargetSystemAuditMarker.clearMark(mark.getTaskId());
217+
}
218+
219+
// THEN
220+
Assertions.assertEquals(0, sqlTargetSystemAuditMarker.listAll().size());
221+
} finally {
222+
if (dataSource != null) {
223+
try {
224+
dropTable();
225+
} catch (SQLException ignored) {
226+
}
227+
if (dataSource instanceof HikariDataSource) {
228+
((HikariDataSource) dataSource).close();
229+
}
230+
}
231+
if (container != null && container.isRunning()) {
232+
container.stop();
233+
}
234+
}
235+
}
236+
}

utils/sql-util/src/main/java/io/flamingock/internal/common/sql/AbstractSqlDialectHelper.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,8 @@ private SqlDialect fromDatabaseProductName(String productName) {
4949
return SqlDialect.SQLITE;
5050
} else if (v.contains("h2")) {
5151
return SqlDialect.H2;
52-
} else if (v.contains("hsqldb")) {
52+
} else if (v.contains("hsql")) {
5353
return SqlDialect.HSQLDB;
54-
} else if (v.contains("derby")) {
55-
return SqlDialect.DERBY;
5654
} else if (v.contains("sql server")) {
5755
return SqlDialect.SQLSERVER;
5856
} else if (v.contains("sybase")) {

utils/sql-util/src/main/java/io/flamingock/internal/common/sql/SqlDialect.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ public enum SqlDialect {
2222
SQLITE,
2323
H2,
2424
HSQLDB,
25-
DERBY,
2625
SQLSERVER,
2726
SYBASE,
2827
FIREBIRD,

0 commit comments

Comments
 (0)