Skip to content

Commit 8ffcfea

Browse files
bercianordieppa
authored andcommitted
feat: add SQL Test Kit (#870)
1 parent 23e32f3 commit 8ffcfea

File tree

15 files changed

+960
-304
lines changed

15 files changed

+960
-304
lines changed

.github/workflows/release.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,19 @@ jobs:
461461
FLAMINGOCK_JRELEASER_GPG_SECRET_KEY: ${{ secrets.FLAMINGOCK_JRELEASER_GPG_SECRET_KEY }}
462462
FLAMINGOCK_JRELEASER_GPG_PASSPHRASE: ${{ secrets.FLAMINGOCK_JRELEASER_GPG_PASSPHRASE }}
463463

464+
sql-test-kit:
465+
needs: [ build ]
466+
uses: ./.github/workflows/module-release-graalvm.yml
467+
with:
468+
module: sql-test-kit
469+
secrets:
470+
FLAMINGOCK_JRELEASER_GITHUB_TOKEN: ${{ secrets.FLAMINGOCK_JRELEASER_GITHUB_TOKEN }}
471+
FLAMINGOCK_JRELEASER_MAVENCENTRAL_USERNAME: ${{ secrets.FLAMINGOCK_JRELEASER_MAVENCENTRAL_USERNAME }}
472+
FLAMINGOCK_JRELEASER_MAVENCENTRAL_PASSWORD: ${{ secrets.FLAMINGOCK_JRELEASER_MAVENCENTRAL_PASSWORD }}
473+
FLAMINGOCK_JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.FLAMINGOCK_JRELEASER_GPG_PUBLIC_KEY }}
474+
FLAMINGOCK_JRELEASER_GPG_SECRET_KEY: ${{ secrets.FLAMINGOCK_JRELEASER_GPG_SECRET_KEY }}
475+
FLAMINGOCK_JRELEASER_GPG_PASSPHRASE: ${{ secrets.FLAMINGOCK_JRELEASER_GPG_PASSPHRASE }}
476+
464477
mongock-support:
465478
needs: [ build ]
466479
uses: ./.github/workflows/module-release-graalvm.yml
@@ -545,6 +558,7 @@ jobs:
545558
dynamodb-test-kit,
546559
couchbase-util,
547560
sql-util,
561+
sql-test-kit,
548562
mongock-support,
549563
mongock-importer-mongodb,
550564
mongock-importer-dynamodb,

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ val legacyProjects = setOf(
6565

6666
val testKitsProjects = setOf(
6767
"mongodb-test-kit",
68-
"dynamodb-test-kit"
68+
"dynamodb-test-kit",
69+
"sql-test-kit"
6970
)
7071

7172
val allProjects = coreProjects + cloudProjects + communityProjects + pluginProjects + targetSystemProjects + externalSystemProjects + utilProjects + legacyProjects + testKitsProjects

community/flamingock-auditstore-sql/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ dependencies {
2020
testImplementation("org.testcontainers:testcontainers-postgresql:2.0.2")
2121
testImplementation("org.testcontainers:testcontainers-mariadb:2.0.2")
2222
testImplementation(project(":utils:test-util"))
23+
testImplementation(project(":utils:sql-test-kit"))
2324
testImplementation("com.zaxxer:HikariCP:3.4.5")
2425
testImplementation("org.testcontainers:testcontainers-junit-jupiter:2.0.2")
2526
testImplementation("com.h2database:h2:2.2.224")

community/flamingock-auditstore-sql/src/main/java/io/flamingock/store/sql/internal/SqlAuditor.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import io.flamingock.internal.common.core.audit.AuditReader;
2020
import io.flamingock.internal.common.core.audit.AuditTxType;
2121
import io.flamingock.internal.common.sql.SqlDialect;
22+
import io.flamingock.internal.common.sql.dialectHelpers.SqlAuditorDialectHelper;
2223
import io.flamingock.internal.core.external.store.audit.LifecycleAuditWriter;
2324
import io.flamingock.internal.util.Result;
2425

community/flamingock-auditstore-sql/src/main/java/io/flamingock/store/sql/internal/SqlLockService.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package io.flamingock.store.sql.internal;
1717

1818
import io.flamingock.internal.common.sql.SqlDialect;
19+
import io.flamingock.internal.common.sql.dialectHelpers.SqlLockDialectHelper;
1920
import io.flamingock.internal.core.external.store.lock.LockAcquisition;
2021
import io.flamingock.internal.core.external.store.lock.LockKey;
2122
import io.flamingock.internal.core.external.store.lock.LockServiceException;

community/flamingock-auditstore-sql/src/test/java/io/flamingock/store/sql/SqlAuditStoreTest.java

Lines changed: 111 additions & 218 deletions
Large diffs are not rendered by default.

settings.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ include("utils:sql-util")
162162
project(":utils:sql-util").name = "sql-util"
163163
project(":utils:sql-util").projectDir = file("utils/sql-util")
164164

165+
include("utils:sql-test-kit")
166+
project(":utils:sql-test-kit").name = "sql-test-kit"
167+
project(":utils:sql-test-kit").projectDir = file("utils/sql-test-kit")
168+
165169
//////////////////////////////////////
166170
// LEGACY
167171
//////////////////////////////////////
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
dependencies {
2+
implementation(project(":core:flamingock-core"))
3+
implementation(project(":utils:sql-util"))
4+
implementation(project(":utils:test-util"))
5+
}
6+
7+
description = "SQL TestKit for Flamingock testing"
8+
9+
java {
10+
toolchain {
11+
languageVersion.set(JavaLanguageVersion.of(8))
12+
}
13+
}
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
/*
2+
* Copyright 2026 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.sql.kit;
17+
18+
import io.flamingock.core.kit.audit.AuditStorage;
19+
import io.flamingock.internal.common.core.audit.AuditEntry;
20+
import io.flamingock.internal.common.core.audit.AuditTxType;
21+
import io.flamingock.internal.common.sql.SqlDialect;
22+
import io.flamingock.internal.common.sql.dialectHelpers.SqlAuditorDialectHelper;
23+
24+
import javax.sql.DataSource;
25+
import java.sql.*;
26+
import java.util.ArrayList;
27+
import java.util.List;
28+
29+
import static io.flamingock.internal.util.constants.CommunityPersistenceConstants.DEFAULT_AUDIT_STORE_NAME;
30+
31+
/**
32+
* SQL implementation of AuditStorage for real database testing.
33+
* Only depends on SQL client/database and core Flamingock classes.
34+
* Does not depend on SQL-specific Flamingock components like SqlTargetSystem.
35+
*/
36+
public class SqlAuditStorage implements AuditStorage {
37+
38+
private final DataSource dataSource;
39+
private final SqlAuditorDialectHelper dialectHelper;
40+
private final String auditTableName;
41+
42+
public SqlAuditStorage(DataSource dataSource) throws SQLException {
43+
this(dataSource, DEFAULT_AUDIT_STORE_NAME);
44+
}
45+
46+
public SqlAuditStorage(DataSource dataSource, String tableName) throws SQLException {
47+
this.auditTableName = tableName;
48+
this.dataSource = dataSource;
49+
try (Connection conn = dataSource.getConnection()) {
50+
this.dialectHelper = new SqlAuditorDialectHelper(conn);
51+
}
52+
}
53+
54+
@Override
55+
public void addAuditEntry(AuditEntry auditEntry) {
56+
try (Connection connection = dataSource.getConnection()) {
57+
// For Informix, ensure autoCommit is enabled for audit writes
58+
if (dialectHelper.getSqlDialect() == SqlDialect.INFORMIX) {
59+
connection.setAutoCommit(true);
60+
}
61+
62+
try (PreparedStatement ps = connection.prepareStatement(
63+
dialectHelper.getInsertSqlString(auditTableName))) {
64+
ps.setString(1, auditEntry.getExecutionId());
65+
ps.setString(2, auditEntry.getStageId());
66+
ps.setString(3, auditEntry.getTaskId());
67+
ps.setString(4, auditEntry.getAuthor());
68+
ps.setTimestamp(5, Timestamp.valueOf(auditEntry.getCreatedAt()));
69+
ps.setString(6, auditEntry.getState() != null ? auditEntry.getState().name() : null);
70+
ps.setString(7, auditEntry.getClassName());
71+
ps.setString(8, auditEntry.getMethodName());
72+
ps.setString(9, auditEntry.getSourceFile());
73+
ps.setString(10, auditEntry.getMetadata() != null ? auditEntry.getMetadata().toString() : null);
74+
ps.setLong(11, auditEntry.getExecutionMillis());
75+
ps.setString(12, auditEntry.getExecutionHostname());
76+
ps.setString(13, auditEntry.getErrorTrace());
77+
ps.setString(14, auditEntry.getType() != null ? auditEntry.getType().name() : null);
78+
ps.setString(15, auditEntry.getTxType() != null ? auditEntry.getTxType().name() : null);
79+
ps.setString(16, auditEntry.getTargetSystemId());
80+
ps.setString(17, auditEntry.getOrder());
81+
ps.setString(18, auditEntry.getRecoveryStrategy() != null ? auditEntry.getRecoveryStrategy().name() : null);
82+
ps.setObject(19, auditEntry.getTransactionFlag());
83+
ps.setObject(20, auditEntry.getSystemChange());
84+
ps.executeUpdate();
85+
}
86+
} catch (SQLException e) {
87+
throw new RuntimeException("Failed to add audit entry", e);
88+
}
89+
// Log but don't throw
90+
}
91+
92+
@Override
93+
public List<AuditEntry> getAuditEntries() {
94+
List<AuditEntry> entries = new ArrayList<>();
95+
try (Connection connection = dataSource.getConnection();
96+
Statement stmt = connection.createStatement();
97+
ResultSet rs = stmt.executeQuery(dialectHelper.getSelectHistorySqlString(auditTableName))) {
98+
while (rs.next()) {
99+
AuditEntry entry = toAuditEntry(rs);
100+
entries.add(entry);
101+
}
102+
} catch (SQLException e) {
103+
throw new RuntimeException("Failed to read audit history", e);
104+
}
105+
return entries;
106+
}
107+
108+
@Override
109+
public List<AuditEntry> getAuditEntriesForChange(String changeId) {
110+
List<AuditEntry> entries = new ArrayList<>();
111+
try (Connection connection = dataSource.getConnection();
112+
PreparedStatement ps = connection.prepareStatement(dialectHelper.getSelectHistoryByChangeIdSqlString(auditTableName))) {
113+
ps.setString(1, changeId);
114+
try (ResultSet rs = ps.executeQuery()) {
115+
while (rs.next()) {
116+
AuditEntry entry = toAuditEntry(rs);
117+
entries.add(entry);
118+
}
119+
}
120+
} catch (SQLException e) {
121+
throw new RuntimeException("Failed to read audit history", e);
122+
}
123+
return entries;
124+
}
125+
126+
@Override
127+
public long countAuditEntriesWithStatus(AuditEntry.Status status) {
128+
try (Connection connection = dataSource.getConnection();
129+
PreparedStatement ps = connection.prepareStatement(dialectHelper.getCountByStatusSqlString(auditTableName))) {
130+
ps.setString(1, status.toString());
131+
try (ResultSet rs = ps.executeQuery()) {
132+
if (rs.next()) {
133+
return rs.getLong(1);
134+
}
135+
}
136+
} catch (SQLException e) {
137+
throw new RuntimeException("Failed to count audit entries with status: " + status, e);
138+
}
139+
return 0;
140+
}
141+
142+
@Override
143+
public boolean hasAuditEntries() {
144+
return !this.getAuditEntries().isEmpty();
145+
}
146+
147+
@Override
148+
public void clear() {
149+
try(Connection connection = dataSource.getConnection();
150+
Statement stmt = connection.createStatement()) {
151+
stmt.executeUpdate(String.format("DELETE FROM %s", auditTableName));
152+
} catch (SQLException e) {
153+
throw new RuntimeException("Failed to clear audit entries", e);
154+
}
155+
}
156+
157+
private AuditEntry toAuditEntry(ResultSet rs) throws SQLException {
158+
return new AuditEntry(
159+
rs.getString("execution_id"),
160+
rs.getString("stage_id"),
161+
rs.getString("change_id"),
162+
rs.getString("author"),
163+
rs.getTimestamp("created_at").toLocalDateTime(),
164+
rs.getString("state") != null ? AuditEntry.Status.valueOf(rs.getString("state")) : null,
165+
rs.getString("type") != null ? AuditEntry.ChangeType.valueOf(rs.getString("type")) : null,
166+
rs.getString("invoked_class"),
167+
rs.getString("invoked_method"),
168+
rs.getString("source_file"),
169+
rs.getLong("execution_millis"),
170+
rs.getString("execution_hostname"),
171+
rs.getString("metadata"),
172+
rs.getBoolean("system_change"),
173+
rs.getString("error_trace"),
174+
AuditTxType.fromString(rs.getString("tx_strategy")),
175+
rs.getString("target_system_id"),
176+
rs.getString("change_order"),
177+
rs.getString("recovery_strategy") != null ? io.flamingock.api.RecoveryStrategy.valueOf(rs.getString("recovery_strategy")) : null,
178+
rs.getObject("transaction_flag") != null ? rs.getBoolean("transaction_flag") : null
179+
);
180+
}
181+
}

0 commit comments

Comments
 (0)