diff --git a/community/flamingock-auditstore-sql/build.gradle.kts b/community/flamingock-auditstore-sql/build.gradle.kts new file mode 100644 index 000000000..235028362 --- /dev/null +++ b/community/flamingock-auditstore-sql/build.gradle.kts @@ -0,0 +1,34 @@ +dependencies { + api(project(":core:flamingock-core")) + api(project(":core:target-systems:sql-target-system")) + implementation(project(":utils:sql-util")) + + testImplementation("mysql:mysql-connector-java:8.0.33") + testImplementation("com.microsoft.sqlserver:mssql-jdbc:12.4.2.jre8") + testImplementation("com.oracle.database.jdbc:ojdbc8:21.9.0.0") + testImplementation("org.postgresql:postgresql:42.7.3") + testImplementation("org.mariadb.jdbc:mariadb-java-client:3.3.2") + testImplementation("org.testcontainers:mysql:1.21.3") + testImplementation("org.testcontainers:mssqlserver:1.21.3") + testImplementation("org.testcontainers:oracle-xe:1.21.3") + testImplementation("org.testcontainers:postgresql:1.21.3") + testImplementation("org.testcontainers:mariadb:1.21.3") + testImplementation(project(":utils:test-util")) + testImplementation("com.zaxxer:HikariCP:3.4.5") + testImplementation("org.testcontainers:junit-jupiter:1.21.3") + testImplementation("com.h2database:h2:2.2.224") + testImplementation("org.mockito:mockito-inline:4.11.0") + testImplementation("org.xerial:sqlite-jdbc:3.41.2.1") +} + +description = "SQL audit store implementation for distributed change auditing" + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(8)) + } +} + +configurations.testImplementation { + extendsFrom(configurations.compileOnly.get()) +} diff --git a/community/flamingock-auditstore-sql/src/main/java/io/flamingock/community/sql/driver/SqlAuditStore.java b/community/flamingock-auditstore-sql/src/main/java/io/flamingock/community/sql/driver/SqlAuditStore.java new file mode 100644 index 000000000..c1d907139 --- /dev/null +++ b/community/flamingock-auditstore-sql/src/main/java/io/flamingock/community/sql/driver/SqlAuditStore.java @@ -0,0 +1,84 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.driver; + +import io.flamingock.internal.core.store.CommunityAuditStore; +import io.flamingock.internal.core.store.audit.community.CommunityAuditPersistence; +import io.flamingock.internal.core.store.lock.community.CommunityLockService; +import io.flamingock.internal.common.core.context.ContextResolver; +import io.flamingock.internal.core.configuration.community.CommunityConfigurable; +import io.flamingock.internal.util.constants.CommunityPersistenceConstants; +import io.flamingock.internal.util.id.RunnerId; +import io.flamingock.community.sql.internal.SqlAuditPersistence; +import io.flamingock.community.sql.internal.SqlLockService; + +import javax.sql.DataSource; + +public class SqlAuditStore implements CommunityAuditStore { + + private final DataSource dataSource; + private CommunityConfigurable communityConfiguration; + private RunnerId runnerId; + private SqlAuditPersistence persistence; + private SqlLockService lockService; + private String auditRepositoryName = CommunityPersistenceConstants.DEFAULT_AUDIT_STORE_NAME; + private String lockRepositoryName = CommunityPersistenceConstants.DEFAULT_LOCK_STORE_NAME; + private boolean autoCreate = true; + + public SqlAuditStore(DataSource dataSource) { + this.dataSource = dataSource; + } + + public SqlAuditStore withAuditRepositoryName(String auditRepositoryName) { + this.auditRepositoryName = auditRepositoryName; + return this; + } + + public SqlAuditStore withLockRepositoryName(String lockRepositoryName) { + this.lockRepositoryName = lockRepositoryName; + return this; + } + + public SqlAuditStore withAutoCreate(boolean autoCreate) { + this.autoCreate = autoCreate; + return this; + } + + @Override + public void initialize(ContextResolver baseContext) { + runnerId = baseContext.getRequiredDependencyValue(RunnerId.class); + communityConfiguration = baseContext.getRequiredDependencyValue(CommunityConfigurable.class); + } + + @Override + public synchronized CommunityAuditPersistence getPersistence() { + if (persistence == null) { + persistence = new SqlAuditPersistence(communityConfiguration, dataSource, auditRepositoryName, autoCreate); + persistence.initialize(runnerId); + } + return persistence; + } + + + @Override + public synchronized CommunityLockService getLockService() { + if (lockService == null) { + lockService = new SqlLockService(dataSource, lockRepositoryName); + lockService.initialize(autoCreate); + } + return lockService; + } +} diff --git a/community/flamingock-auditstore-sql/src/main/java/io/flamingock/community/sql/internal/SqlAuditPersistence.java b/community/flamingock-auditstore-sql/src/main/java/io/flamingock/community/sql/internal/SqlAuditPersistence.java new file mode 100644 index 000000000..fbdca83e3 --- /dev/null +++ b/community/flamingock-auditstore-sql/src/main/java/io/flamingock/community/sql/internal/SqlAuditPersistence.java @@ -0,0 +1,59 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.internal; + +import io.flamingock.internal.core.configuration.community.CommunityConfigurable; +import io.flamingock.internal.core.store.audit.community.AbstractCommunityAuditPersistence; +import io.flamingock.internal.common.core.audit.AuditEntry; +import io.flamingock.internal.util.Result; +import io.flamingock.internal.util.id.RunnerId; + +import javax.sql.DataSource; +import java.util.List; + +public class SqlAuditPersistence extends AbstractCommunityAuditPersistence { + + private final DataSource dataSource; + private final String auditRepositoryName; + private final boolean autoCreate; + private SqlAuditor auditor; + + public SqlAuditPersistence(CommunityConfigurable localConfiguration, + DataSource dataSource, + String auditRepositoryName, + boolean autoCreate) { + super(localConfiguration); + this.dataSource = dataSource; + this.auditRepositoryName = auditRepositoryName; + this.autoCreate = autoCreate; + } + + @Override + protected void doInitialize(RunnerId runnerId) { + auditor = new SqlAuditor(dataSource, auditRepositoryName, autoCreate); + auditor.initialize(); + } + + @Override + public List getAuditHistory() { + return auditor.getAuditHistory(); + } + + @Override + public Result writeEntry(AuditEntry auditEntry) { + return auditor.writeEntry(auditEntry); + } +} diff --git a/community/flamingock-auditstore-sql/src/main/java/io/flamingock/community/sql/internal/SqlAuditor.java b/community/flamingock-auditstore-sql/src/main/java/io/flamingock/community/sql/internal/SqlAuditor.java new file mode 100644 index 000000000..b21f1441e --- /dev/null +++ b/community/flamingock-auditstore-sql/src/main/java/io/flamingock/community/sql/internal/SqlAuditor.java @@ -0,0 +1,120 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.internal; + +import io.flamingock.internal.common.core.audit.AuditEntry; +import io.flamingock.internal.common.core.audit.AuditReader; +import io.flamingock.internal.common.core.audit.AuditTxType; +import io.flamingock.internal.core.store.audit.LifecycleAuditWriter; +import io.flamingock.internal.util.Result; + +import javax.sql.DataSource; +import java.sql.*; +import java.util.ArrayList; +import java.util.List; + +public class SqlAuditor implements LifecycleAuditWriter, AuditReader { + + private final DataSource dataSource; + private final String auditTableName; + private final boolean autoCreate; + private final SqlAuditorDialectHelper dialectHelper; + + public SqlAuditor(DataSource dataSource, String auditTableName, boolean autoCreate) { + this.dataSource = dataSource; + this.auditTableName = auditTableName; + this.autoCreate = autoCreate; + this.dialectHelper = new SqlAuditorDialectHelper(dataSource); + } + + public void initialize() { + if (autoCreate) { + try (Connection conn = dataSource.getConnection(); + Statement stmt = conn.createStatement()) { + stmt.executeUpdate(dialectHelper.getCreateTableSqlString(auditTableName)); + } catch (SQLException e) { + throw new RuntimeException("Failed to initialize audit table", e); + } + } + } + + @Override + public Result writeEntry(AuditEntry auditEntry) { + try (Connection conn = dataSource.getConnection(); + PreparedStatement ps = conn.prepareStatement( + dialectHelper.getInsertSqlString(auditTableName))) { + ps.setString(1, auditEntry.getExecutionId()); + ps.setString(2, auditEntry.getStageId()); + ps.setString(3, auditEntry.getTaskId()); + ps.setString(4, auditEntry.getAuthor()); + ps.setTimestamp(5, Timestamp.valueOf(auditEntry.getCreatedAt())); + ps.setString(6, auditEntry.getState() != null ? auditEntry.getState().name() : null); + ps.setString(7, auditEntry.getClassName()); + ps.setString(8, auditEntry.getMethodName()); + ps.setString(9, auditEntry.getMetadata() != null ? auditEntry.getMetadata().toString() : null); + ps.setLong(10, auditEntry.getExecutionMillis()); + ps.setString(11, auditEntry.getExecutionHostname()); + ps.setString(12, auditEntry.getErrorTrace()); + ps.setString(13, auditEntry.getType() != null ? auditEntry.getType().name() : null); + ps.setString(14, auditEntry.getTxType() != null ? auditEntry.getTxType().name() : null); + ps.setString(15, auditEntry.getTargetSystemId()); + ps.setString(16, auditEntry.getOrder()); + ps.setString(17, auditEntry.getRecoveryStrategy() != null ? auditEntry.getRecoveryStrategy().name() : null); + ps.setObject(18, auditEntry.getTransactionFlag()); + ps.setObject(19, auditEntry.getSystemChange()); + ps.executeUpdate(); + return Result.OK(); + } catch (SQLException e) { + return new Result.Error(e); + } + } + + @Override + public List getAuditHistory() { + List entries = new ArrayList<>(); + try (Connection conn = dataSource.getConnection(); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(dialectHelper.getSelectHistorySqlString(auditTableName))) { + while (rs.next()) { + AuditEntry entry = new AuditEntry( + rs.getString("execution_id"), + rs.getString("stage_id"), + rs.getString("task_id"), + rs.getString("author"), + rs.getTimestamp("created_at").toLocalDateTime(), + rs.getString("state") != null ? AuditEntry.Status.valueOf(rs.getString("state")) : null, + rs.getString("type") != null ? AuditEntry.ExecutionType.valueOf(rs.getString("type")) : null, + rs.getString("class_name"), + rs.getString("method_name"), + rs.getLong("execution_millis"), + rs.getString("execution_hostname"), + rs.getString("metadata"), + rs.getBoolean("system_change"), + rs.getString("error_trace"), + AuditTxType.fromString(rs.getString("tx_type")), + rs.getString("target_system_id"), + rs.getString("order_col"), + rs.getString("recovery_strategy") != null ? io.flamingock.api.RecoveryStrategy.valueOf(rs.getString("recovery_strategy")) : null, + rs.getObject("transaction_flag") != null ? rs.getBoolean("transaction_flag") : null + ); + entries.add(entry); + } + } catch (SQLException e) { + throw new RuntimeException("Failed to read audit history", e); + } + return entries; + } +} diff --git a/community/flamingock-auditstore-sql/src/main/java/io/flamingock/community/sql/internal/SqlAuditorDialectHelper.java b/community/flamingock-auditstore-sql/src/main/java/io/flamingock/community/sql/internal/SqlAuditorDialectHelper.java new file mode 100644 index 000000000..8bd3de305 --- /dev/null +++ b/community/flamingock-auditstore-sql/src/main/java/io/flamingock/community/sql/internal/SqlAuditorDialectHelper.java @@ -0,0 +1,290 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.internal; + +import io.flamingock.internal.common.sql.AbstractSqlDialectHelper; +import io.flamingock.internal.common.sql.SqlDialect; + +import javax.sql.DataSource; + +public final class SqlAuditorDialectHelper extends AbstractSqlDialectHelper { + + public SqlAuditorDialectHelper(DataSource dataSource) { + super(dataSource); + } + + public SqlAuditorDialectHelper(SqlDialect dialect) { + super(dialect); + } + + public String getCreateTableSqlString(String tableName) { + switch (sqlDialect) { + case MYSQL: + case MARIADB: + case H2: + case HSQLDB: + case FIREBIRD: + case INFORMIX: + return String.format( + "CREATE TABLE IF NOT EXISTS %s (" + + "id %s PRIMARY KEY, " + + "execution_id VARCHAR(255), " + + "stage_id VARCHAR(255), " + + "task_id VARCHAR(255), " + + "author VARCHAR(255), " + + "created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, " + + "state VARCHAR(255), " + + "class_name VARCHAR(255), " + + "method_name VARCHAR(255), " + + "metadata %s, " + + "execution_millis %s, " + + "execution_hostname VARCHAR(255), " + + "error_trace %s, " + + "type VARCHAR(50), " + + "tx_type VARCHAR(50), " + + "target_system_id VARCHAR(255), " + + "order_col VARCHAR(50), " + + "recovery_strategy VARCHAR(50), " + + "transaction_flag %s, " + + "system_change %s" + + ")", tableName, getAutoIncrementType(), getClobType(), getBigIntType(), getClobType(), getBooleanType(), getBooleanType()); + case POSTGRESQL: + return String.format( + "CREATE TABLE IF NOT EXISTS %s (" + + "id SERIAL PRIMARY KEY," + + "execution_id VARCHAR(255)," + + "stage_id VARCHAR(255)," + + "task_id VARCHAR(255)," + + "author VARCHAR(255)," + + "created_at TIMESTAMP," + + "state VARCHAR(32)," + + "class_name VARCHAR(255)," + + "method_name VARCHAR(255)," + + "metadata TEXT," + + "execution_millis BIGINT," + + "execution_hostname VARCHAR(255)," + + "error_trace TEXT," + + "type VARCHAR(32)," + + "tx_type VARCHAR(32)," + + "target_system_id VARCHAR(255)," + + "order_col VARCHAR(255)," + + "recovery_strategy VARCHAR(32)," + + "transaction_flag BOOLEAN," + + "system_change BOOLEAN" + + ")", tableName); + + case SQLSERVER: + case SYBASE: + return String.format( + "IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='%s' AND xtype='U') " + + "CREATE TABLE %s (" + + "id %s PRIMARY KEY, " + + "execution_id VARCHAR(255), " + + "stage_id VARCHAR(255), " + + "task_id VARCHAR(255), " + + "author VARCHAR(255), " + + "created_at DATETIME DEFAULT GETDATE(), " + + "state VARCHAR(255), " + + "class_name VARCHAR(255), " + + "method_name VARCHAR(255), " + + "metadata %s, " + + "execution_millis %s, " + + "execution_hostname VARCHAR(255), " + + "error_trace %s, " + + "type VARCHAR(50), " + + "tx_type VARCHAR(50), " + + "target_system_id VARCHAR(255), " + + "order_col VARCHAR(50), " + + "recovery_strategy VARCHAR(50), " + + "transaction_flag %s, " + + "system_change %s" + + ")", tableName, tableName, getAutoIncrementType(), getClobType(), getBigIntType(), getClobType(), getBooleanType(), getBooleanType()); + case ORACLE: + return String.format( + "BEGIN EXECUTE IMMEDIATE 'CREATE TABLE %s (" + + "id %s PRIMARY KEY, " + + "execution_id VARCHAR2(255), " + + "stage_id VARCHAR2(255), " + + "task_id VARCHAR2(255), " + + "author VARCHAR2(255), " + + "created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, " + + "state VARCHAR2(255), " + + "class_name VARCHAR2(255), " + + "method_name VARCHAR2(255), " + + "metadata %s, " + + "execution_millis %s, " + + "execution_hostname VARCHAR2(255), " + + "error_trace %s, " + + "type VARCHAR2(50), " + + "tx_type VARCHAR2(50), " + + "target_system_id VARCHAR2(255), " + + "order_col VARCHAR2(50), " + + "recovery_strategy VARCHAR2(50), " + + "transaction_flag %s, " + + "system_change %s" + + ")'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -955 THEN RAISE; END IF; END;", tableName, getAutoIncrementType(), getClobType(), getBigIntType(), getClobType(), getBooleanType(), getBooleanType()); + case DB2: + return String.format( + "CREATE TABLE %s (" + + "id %s PRIMARY KEY, " + + "execution_id VARCHAR(255), " + + "stage_id VARCHAR(255), " + + "task_id VARCHAR(255), " + + "author VARCHAR(255), " + + "created_at TIMESTAMP DEFAULT CURRENT TIMESTAMP, " + + "state VARCHAR(255), " + + "class_name VARCHAR(255), " + + "method_name VARCHAR(255), " + + "metadata %s, " + + "execution_millis %s, " + + "execution_hostname VARCHAR(255), " + + "error_trace %s, " + + "type VARCHAR(50), " + + "tx_type VARCHAR(50), " + + "target_system_id VARCHAR(255), " + + "order_col VARCHAR(50), " + + "recovery_strategy VARCHAR(50), " + + "transaction_flag %s, " + + "system_change %s" + + ")", tableName, getAutoIncrementType(), getClobType(), getBigIntType(), getClobType(), getBooleanType(), getBooleanType()); + case SQLITE: + return String.format( + "CREATE TABLE IF NOT EXISTS %s (" + + "id INTEGER PRIMARY KEY, " + + "execution_id TEXT, " + + "stage_id TEXT, " + + "task_id TEXT, " + + "author TEXT, " + + "created_at DATETIME, " + + "state TEXT, " + + "class_name TEXT, " + + "method_name TEXT, " + + "metadata TEXT, " + + "execution_millis INTEGER, " + + "execution_hostname TEXT, " + + "error_trace TEXT, " + + "type TEXT, " + + "tx_type TEXT, " + + "target_system_id TEXT, " + + "order_col TEXT, " + + "recovery_strategy TEXT, " + + "transaction_flag INTEGER, " + + "system_change INTEGER" + + ")", tableName); + default: + throw new UnsupportedOperationException("Dialect not supported for CREATE TABLE: " + sqlDialect.name()); + } + } + + public String getInsertSqlString(String tableName) { + return String.format( + "INSERT INTO %s (" + + "execution_id, stage_id, task_id, author, created_at, state, class_name, method_name, metadata, " + + "execution_millis, execution_hostname, error_trace, type, tx_type, target_system_id, order_col, recovery_strategy, transaction_flag, system_change" + + ") VALUES (" + + "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?" + + ")", tableName); + } + + public String getSelectHistorySqlString(String tableName) { + return String.format( + "SELECT execution_id, stage_id, task_id, author, created_at, state, type, class_name, method_name, " + + "execution_millis, execution_hostname, metadata, system_change, error_trace, tx_type, target_system_id, order_col, recovery_strategy, transaction_flag " + + "FROM %s ORDER BY id ASC", tableName); + } + + private String getAutoIncrementType() { + switch (sqlDialect) { + case POSTGRESQL: + return "BIGSERIAL"; + case SQLITE: + case H2: + case HSQLDB: + case DB2: + case FIREBIRD: + return "BIGINT GENERATED BY DEFAULT AS IDENTITY"; + case SQLSERVER: + case SYBASE: + return "BIGINT IDENTITY(1,1)"; + case ORACLE: + return "NUMBER GENERATED BY DEFAULT AS IDENTITY"; + case INFORMIX: + return "SERIAL8"; + case MYSQL: + case MARIADB: + default: + return "BIGINT AUTO_INCREMENT"; + } + } + + private String getClobType() { + switch (sqlDialect) { + case MYSQL: + case MARIADB: + return "LONGTEXT"; + case SQLITE: + case H2: + case HSQLDB: + case FIREBIRD: + case INFORMIX: + case ORACLE: + case DB2: + return "CLOB"; + case SQLSERVER: + case SYBASE: + return "NVARCHAR(MAX)"; + case POSTGRESQL: + default: + return "TEXT"; + } + } + + private String getBigIntType() { + switch (sqlDialect) { + case ORACLE: + return "NUMBER(19)"; + default: + return "BIGINT"; + } + } + + private String getBooleanType() { + switch (sqlDialect) { + case MYSQL: + case MARIADB: + return "TINYINT(1)"; + case POSTGRESQL: + case H2: + case HSQLDB: + case FIREBIRD: + case INFORMIX: + return "BOOLEAN"; + case SQLITE: + return "INTEGER"; + case SQLSERVER: + case SYBASE: + return "BIT"; + case ORACLE: + return "NUMBER(1)"; + default: + return "SMALLINT"; + } + } + + public SqlDialect getSqlDialect() { + return sqlDialect; + } +} diff --git a/community/flamingock-auditstore-sql/src/main/java/io/flamingock/community/sql/internal/SqlLockDialectHelper.java b/community/flamingock-auditstore-sql/src/main/java/io/flamingock/community/sql/internal/SqlLockDialectHelper.java new file mode 100644 index 000000000..416c671d1 --- /dev/null +++ b/community/flamingock-auditstore-sql/src/main/java/io/flamingock/community/sql/internal/SqlLockDialectHelper.java @@ -0,0 +1,201 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.internal; + +import io.flamingock.internal.common.sql.AbstractSqlDialectHelper; +import io.flamingock.internal.common.sql.SqlDialect; +import io.flamingock.internal.core.store.lock.LockStatus; + +import javax.sql.DataSource; +import java.sql.*; +import java.time.LocalDateTime; +import java.util.Objects; + +public final class SqlLockDialectHelper extends AbstractSqlDialectHelper { + + public SqlLockDialectHelper(DataSource dataSource) { + super(dataSource); + } + + public SqlLockDialectHelper(SqlDialect dialect) { + super(dialect); + } + + public String getCreateTableSqlString(String tableName) { + switch (sqlDialect) { + case POSTGRESQL: + return String.format( + "CREATE TABLE IF NOT EXISTS %s (" + + "\"key\" VARCHAR(255) PRIMARY KEY," + + "status VARCHAR(32)," + + "owner VARCHAR(255)," + + "expires_at TIMESTAMP" + + ")", tableName); + case MYSQL: + case MARIADB: + case SQLITE: + case H2: + case HSQLDB: + case FIREBIRD: + case INFORMIX: + return String.format( + "CREATE TABLE IF NOT EXISTS %s (" + + "`key` VARCHAR(255) PRIMARY KEY," + + "status VARCHAR(32)," + + "owner VARCHAR(255)," + + "expires_at TIMESTAMP" + + ")", tableName); + case SQLSERVER: + case SYBASE: + return String.format( + "IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='%s' AND xtype='U') " + + "CREATE TABLE %s (" + + "[key] VARCHAR(255) PRIMARY KEY," + + "status VARCHAR(32)," + + "owner VARCHAR(255)," + + "expires_at DATETIME" + + ")", tableName, tableName); + case ORACLE: + return String.format( + "BEGIN EXECUTE IMMEDIATE 'CREATE TABLE %s (" + + "\"key\" VARCHAR2(255) PRIMARY KEY," + + "status VARCHAR2(32)," + + "owner VARCHAR2(255)," + + "expires_at TIMESTAMP" + + ")'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -955 THEN RAISE; END IF; END;", tableName); + case DB2: + return String.format( + "CREATE TABLE %s (" + + "\"key\" VARCHAR(255) PRIMARY KEY," + + "status VARCHAR(32)," + + "owner VARCHAR(255)," + + "expires_at TIMESTAMP" + + ")", tableName); + default: + throw new UnsupportedOperationException("Dialect not supported for CREATE TABLE: " + sqlDialect.name()); + } + } + + public String getSelectLockSqlString(String tableName) { + switch (sqlDialect) { + case POSTGRESQL: + return String.format("SELECT \"key\", status, owner, expires_at FROM %s WHERE \"key\" = ?", tableName); + case SQLSERVER: + case SYBASE: + return String.format("SELECT [key], status, owner, expires_at FROM %s WITH (UPDLOCK, ROWLOCK) WHERE [key] = ?", tableName); + case ORACLE: + return String.format("SELECT \"key\", status, owner, expires_at FROM %s WHERE \"key\" = ? FOR UPDATE", tableName); + default: + return String.format("SELECT `key`, status, owner, expires_at FROM %s WHERE `key` = ?", tableName); + } + } + + public String getInsertOrUpdateLockSqlString(String tableName) { + switch (sqlDialect) { + case MYSQL: + case MARIADB: + case INFORMIX: + return String.format( + "INSERT INTO %s (`key`, status, owner, expires_at) VALUES (?, ?, ?, ?) " + + "ON DUPLICATE KEY UPDATE status = VALUES(status), owner = VALUES(owner), expires_at = VALUES(expires_at)", + tableName); + case POSTGRESQL: + return String.format( + "INSERT INTO %s (\"key\", status, owner, expires_at) VALUES (?, ?, ?, ?) " + + "ON CONFLICT (\"key\") DO UPDATE SET status = EXCLUDED.status, owner = EXCLUDED.owner, expires_at = EXCLUDED.expires_at", + tableName); + case SQLITE: + return String.format( + "INSERT OR REPLACE INTO %s (`key`, status, owner, expires_at) VALUES (?, ?, ?, ?)", + tableName); + case SQLSERVER: + case SYBASE: + return String.format( + "BEGIN TRANSACTION; " + + "UPDATE %s SET status = ?, owner = ?, expires_at = ? WHERE [key] = ?; " + + "IF @@ROWCOUNT = 0 " + + "BEGIN " + + "INSERT INTO %s ([key], status, owner, expires_at) VALUES (?, ?, ?, ?) " + + "END; " + + "COMMIT TRANSACTION;", + tableName, tableName); + case ORACLE: + return String.format( + "MERGE INTO %s t USING (SELECT ? AS \"key\", ? AS status, ? AS owner, ? AS expires_at FROM dual) s " + + "ON (t.\"key\" = s.\"key\") " + + "WHEN MATCHED THEN UPDATE SET t.status = s.status, t.owner = s.owner, t.expires_at = s.expires_at " + + "WHEN NOT MATCHED THEN INSERT (\"key\", status, owner, expires_at) VALUES (s.\"key\", s.status, s.owner, s.expires_at)", + tableName); + case H2: + case HSQLDB: + return String.format( + "MERGE INTO %s (`key`, status, owner, expires_at) KEY (`key`) VALUES (?, ?, ?, ?)", + tableName); + case DB2: + return String.format( + "MERGE INTO %s USING (SELECT ? AS \"key\", ? AS status, ? AS owner, ? AS expires_at FROM SYSIBM.SYSDUMMY1) AS src " + + "ON (%s.\"key\" = src.\"key\") " + + "WHEN MATCHED THEN UPDATE SET status = src.status, owner = src.owner, expires_at = src.expires_at " + + "WHEN NOT MATCHED THEN INSERT (\"key\", status, owner, expires_at) VALUES (src.\"key\", src.status, src.owner, src.expires_at)", + tableName, tableName); + case FIREBIRD: + return String.format( + "UPDATE OR INSERT INTO %s (`key`, status, owner, expires_at) VALUES (?, ?, ?, ?) MATCHING (`key`)", + tableName); + default: + throw new UnsupportedOperationException("Dialect not supported for upsert: " + sqlDialect.name()); + } + } + + public String getDeleteLockSqlString(String tableName) { + if (Objects.requireNonNull(sqlDialect) == SqlDialect.POSTGRESQL) { + return String.format("DELETE FROM %s WHERE \"key\" = ?", tableName); + } + return String.format("DELETE FROM %s WHERE `key` = ?", tableName); + } + + public void upsertLockEntry(Connection conn, String tableName, String key, String owner, LocalDateTime expiresAt) throws SQLException { + String sql = getInsertOrUpdateLockSqlString(tableName); + + if (getSqlDialect() == SqlDialect.SQLSERVER || getSqlDialect() == SqlDialect.SYBASE) { + // For SQL Server/Sybase, use Statement and format SQL + try (Statement stmt = conn.createStatement()) { + String formattedSql = sql + .replaceFirst("\\?", "'" + LockStatus.LOCK_HELD.name() + "'") + .replaceFirst("\\?", "'" + owner + "'") + .replaceFirst("\\?", "'" + Timestamp.valueOf(expiresAt) + "'") + .replaceFirst("\\?", "'" + key + "'") + .replaceFirst("\\?", "'" + key + "'") + .replaceFirst("\\?", "'" + LockStatus.LOCK_HELD.name() + "'") + .replaceFirst("\\?", "'" + owner + "'") + .replaceFirst("\\?", "'" + Timestamp.valueOf(expiresAt) + "'"); + stmt.execute(formattedSql); + } + } else { + try (PreparedStatement ps = conn.prepareStatement(sql)) { + ps.setString(1, key); + ps.setString(2, LockStatus.LOCK_HELD.name()); + ps.setString(3, owner); + ps.setTimestamp(4, Timestamp.valueOf(expiresAt)); + ps.executeUpdate(); + } + } + } + + public SqlDialect getSqlDialect() { + return sqlDialect; + } +} diff --git a/community/flamingock-auditstore-sql/src/main/java/io/flamingock/community/sql/internal/SqlLockService.java b/community/flamingock-auditstore-sql/src/main/java/io/flamingock/community/sql/internal/SqlLockService.java new file mode 100644 index 000000000..1123be744 --- /dev/null +++ b/community/flamingock-auditstore-sql/src/main/java/io/flamingock/community/sql/internal/SqlLockService.java @@ -0,0 +1,179 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.internal; + +import io.flamingock.internal.common.sql.SqlDialect; +import io.flamingock.internal.core.store.lock.community.CommunityLockService; +import io.flamingock.internal.core.store.lock.community.CommunityLockEntry; +import io.flamingock.internal.core.store.lock.LockAcquisition; +import io.flamingock.internal.core.store.lock.LockKey; +import io.flamingock.internal.core.store.lock.LockServiceException; +import io.flamingock.internal.core.store.lock.LockStatus; +import io.flamingock.internal.util.id.RunnerId; + +import javax.sql.DataSource; +import java.sql.*; +import java.time.LocalDateTime; + +public class SqlLockService implements CommunityLockService { + + private final DataSource dataSource; + private final String lockRepositoryName; + private final SqlLockDialectHelper dialectHelper; + + public SqlLockService(DataSource dataSource, String lockRepositoryName) { + this.dataSource = dataSource; + this.lockRepositoryName = lockRepositoryName; + this.dialectHelper = new SqlLockDialectHelper(dataSource); + } + + public void initialize(boolean autoCreate) { + if (autoCreate) { + try (Connection conn = dataSource.getConnection(); + Statement stmt = conn.createStatement()) { + stmt.executeUpdate(dialectHelper.getCreateTableSqlString(lockRepositoryName)); + } catch (SQLException e) { + throw new RuntimeException("Failed to initialize lock table", e); + } + } + } + + + @Override + public LockAcquisition upsert(LockKey key, RunnerId owner, long leaseMillis) { + String keyStr = key.toString(); + LocalDateTime expiresAt = LocalDateTime.now().plusNanos(leaseMillis * 1_000_000); + + try (Connection conn = dataSource.getConnection()) { + conn.setAutoCommit(false); + try { + CommunityLockEntry existing = getLockEntry(conn, keyStr); + if (existing == null || + owner.toString().equals(existing.getOwner()) || + LocalDateTime.now().isAfter(existing.getExpiresAt())) { + upsertLockEntry(conn, keyStr, owner.toString(), expiresAt); + if (dialectHelper.getSqlDialect() != SqlDialect.SQLSERVER && dialectHelper.getSqlDialect() != SqlDialect.SYBASE) { + conn.commit(); + } + } else { + conn.rollback(); + throw new LockServiceException("upsert", keyStr, + "Still locked by " + existing.getOwner() + " until " + existing.getExpiresAt()); + } + } catch (Exception e) { + conn.rollback(); + throw e; + } finally { + conn.setAutoCommit(true); + } + } catch (SQLException e) { + throw new LockServiceException("upsert", keyStr, e.getMessage()); + } + return new LockAcquisition(owner, leaseMillis); + } + + @Override + public LockAcquisition extendLock(LockKey key, RunnerId owner, long leaseMillis) throws LockServiceException { + String keyStr = key.toString(); + LocalDateTime expiresAt = LocalDateTime.now().plusNanos(leaseMillis * 1_000_000); + + try (Connection conn = dataSource.getConnection()) { + conn.setAutoCommit(false); + try { + CommunityLockEntry existing = getLockEntry(conn, keyStr); + if (existing != null && owner.toString().equals(existing.getOwner())) { + upsertLockEntry(conn, keyStr, owner.toString(), expiresAt); + if (dialectHelper.getSqlDialect() != SqlDialect.SQLSERVER && dialectHelper.getSqlDialect() != SqlDialect.SYBASE) { + conn.commit(); + } + } else { + conn.rollback(); + throw new LockServiceException("extendLock", keyStr, + "Lock belongs to " + (existing != null ? existing.getOwner() : "none")); + } + } catch (Exception e) { + conn.rollback(); + throw e; + } finally { + conn.setAutoCommit(true); + } + } catch (SQLException e) { + throw new LockServiceException("extendLock", keyStr, e.getMessage()); + } + return new LockAcquisition(owner, leaseMillis); + } + + @Override + public LockAcquisition getLock(LockKey lockKey) { + String keyStr = lockKey.toString(); + try (Connection conn = dataSource.getConnection()) { + CommunityLockEntry entry = getLockEntry(conn, keyStr); + if (entry != null) { + return new LockAcquisition(RunnerId.fromString(entry.getOwner()), + java.sql.Timestamp.valueOf(entry.getExpiresAt()).getTime() - System.currentTimeMillis()); + } + } catch (SQLException e) { + // ignore + } + return null; + } + + @Override + public void releaseLock(LockKey lockKey, RunnerId owner) { + String keyStr = lockKey.toString(); + try (Connection conn = dataSource.getConnection(); + PreparedStatement ps = conn.prepareStatement( + "SELECT owner FROM " + lockRepositoryName + " WHERE `key` = ?")) { + ps.setString(1, keyStr); + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + String existingOwner = rs.getString("owner"); + if (existingOwner.equals(owner.toString())) { + try (PreparedStatement delete = conn.prepareStatement( + dialectHelper.getDeleteLockSqlString(lockRepositoryName))) { + delete.setString(1, keyStr); + delete.executeUpdate(); + } + } + } + } + } catch (SQLException e) { + // ignore + } + } + + private CommunityLockEntry getLockEntry(Connection conn, String key) throws SQLException { + try (PreparedStatement ps = conn.prepareStatement( + dialectHelper.getSelectLockSqlString(lockRepositoryName))) { + ps.setString(1, key); + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + return new CommunityLockEntry( + rs.getString(1), // key column + LockStatus.valueOf(rs.getString("status")), + rs.getString("owner"), + rs.getTimestamp("expires_at").toLocalDateTime() + ); + } + } + } + return null; + } + + private void upsertLockEntry(Connection conn, String key, String owner, LocalDateTime expiresAt) throws SQLException { + dialectHelper.upsertLockEntry(conn, lockRepositoryName, key, owner, expiresAt); + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/PipelineTestHelper.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/PipelineTestHelper.java new file mode 100644 index 000000000..617020b8a --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/PipelineTestHelper.java @@ -0,0 +1,160 @@ +/* + * Copyright 2023 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql; + +import io.flamingock.api.StageType; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.TargetSystem; +import io.flamingock.internal.common.core.preview.CodePreviewChange; +import io.flamingock.internal.common.core.preview.PreviewMethod; +import io.flamingock.internal.common.core.preview.PreviewPipeline; +import io.flamingock.internal.common.core.preview.PreviewStage; +import io.flamingock.internal.common.core.task.RecoveryDescriptor; +import io.flamingock.internal.common.core.task.TargetSystemDescriptor; +import io.flamingock.internal.core.task.loaded.ChangeOrderUtil; +import io.flamingock.internal.util.Pair; +import io.flamingock.internal.util.Trio; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class PipelineTestHelper { + + private static final Function, ChangeInfo> infoExtractor = c -> { + Change ann = c.getAnnotation(Change.class); + TargetSystem targetSystemAnn = c.getAnnotation(TargetSystem.class); + String targetSystemId = targetSystemAnn != null ? targetSystemAnn.id() : null; + String changeId = ann.id(); + String order = ChangeOrderUtil.getMatchedOrderFromClassName(changeId, null, c.getName()); + return new ChangeInfo(changeId, order, ann.author(), targetSystemId, ann.transactional()); + }; + + @NotNull + private static List getParameterTypes(List> second) { + return second + .stream() + .map(Class::getName) + .collect(Collectors.toList()); + } + + /** + * Builds a {@link PreviewPipeline} composed of a single {@link PreviewStage} containing one or more {@link CodePreviewChange}s. + *

+ * Each change is derived from a {@link Pair} where: + *

    + *
  • The first item is the {@link Class} annotated with {@link Change}
  • + *
  • The second item is a {@link List} of parameter types (as {@link Class}) expected by the method annotated with {@code @Execution}
  • + *
  • The third item is a {@link List} of parameter types (as {@link Class}) expected by the method annotated with {@code @RollbackExecution}
  • + *
+ * + * @param changeDefinitions varargs of pairs containing change classes and their execution method parameters + * @return a {@link PreviewPipeline} ready for preview or testing + */ + @SafeVarargs + public static PreviewPipeline getPreviewPipeline(String stageName, Trio, List>, List>>... changeDefinitions) { + + List tasks = Arrays.stream(changeDefinitions) + .map(trio -> { + ChangeInfo changeInfo = infoExtractor.apply(trio.getFirst()); + PreviewMethod rollback = null; + PreviewMethod rollbackBeforeExecution = null; + if (trio.getThird() != null) { + rollback = new PreviewMethod("rollbackExecution", getParameterTypes(trio.getThird())); + rollbackBeforeExecution = new PreviewMethod("rollbackBeforeExecution", getParameterTypes(trio.getThird())); + } + + List changes = new ArrayList<>(); + changes.add(new CodePreviewChange( + changeInfo.getChangeId(), + changeInfo.getOrder(), + changeInfo.getAuthor(), + trio.getFirst().getName(), + new PreviewMethod("execution", getParameterTypes(trio.getSecond())), + rollback, + new PreviewMethod("beforeExecution", getParameterTypes(trio.getSecond())), + rollbackBeforeExecution, + false, + changeInfo.transactional, + false, + changeInfo.targetSystem, + RecoveryDescriptor.getDefault() + )); + return changes; + }) + .flatMap(List::stream) + .collect(Collectors.toList()); + + PreviewStage stage = new PreviewStage( + stageName, + StageType.DEFAULT, + "some description", + null, + null, + tasks + ); + + return new PreviewPipeline(Collections.singletonList(stage)); + } + + @SafeVarargs + public static PreviewPipeline getPreviewPipeline(Trio, List>, List>>... changeDefinitions) { + return getPreviewPipeline("default-stage-name", changeDefinitions); + } + + + + static class ChangeInfo { + private final String changeId; + private final String order; + private final String author; + private final TargetSystemDescriptor targetSystem; + private final boolean transactional; + + public ChangeInfo(String changeId, String order, String author, String targetSystemId, boolean transactional) { + this.changeId = changeId; + this.order = order; + this.author = author; + this.targetSystem = new TargetSystemDescriptor(targetSystemId); + this.transactional = transactional; + } + + public String getChangeId() { + return changeId; + } + + public String getOrder() { + return order; + } + + public String getAuthor() { + return author; + } + + public TargetSystemDescriptor getTargetSystem() { + return targetSystem; + } + + public boolean isTransactional() { + return transactional; + } + } + +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/SqlAuditStoreTest.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/SqlAuditStoreTest.java new file mode 100644 index 000000000..4e9893b1a --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/SqlAuditStoreTest.java @@ -0,0 +1,516 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import io.flamingock.community.sql.driver.SqlAuditStore; +import io.flamingock.core.processor.util.Deserializer; +import io.flamingock.internal.common.sql.SqlDialect; +import io.flamingock.internal.core.builder.FlamingockFactory; +import io.flamingock.internal.core.runner.PipelineExecutionException; +import io.flamingock.internal.util.Trio; +import io.flamingock.internal.util.constants.CommunityPersistenceConstants; +import io.flamingock.targetsystem.sql.SqlTargetSystem; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.sqlite.SQLiteDataSource; +import org.testcontainers.containers.*; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import javax.sql.DataSource; +import java.sql.*; +import java.util.Collections; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +@Testcontainers +class SqlAuditStoreTest { + + static Stream dialectProvider() { + return Stream.of( + Arguments.of(SqlDialect.MYSQL, "mysql"), + Arguments.of(SqlDialect.SQLSERVER, "sqlserver"), + Arguments.of(SqlDialect.ORACLE, "oracle"), + Arguments.of(SqlDialect.POSTGRESQL, "postgresql"), + Arguments.of(SqlDialect.MARIADB, "mariadb"), + Arguments.of(SqlDialect.H2, "h2"), + Arguments.of(SqlDialect.SQLITE, "sqlite") + ); + } + + private TestContext setupTest(SqlDialect sqlDialect, String dialectName) throws SQLException { + if ("h2".equals(dialectName)) { + HikariConfig config = new HikariConfig(); + config.setJdbcUrl("jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1"); + config.setUsername("sa"); + config.setPassword(""); + config.setDriverClassName("org.h2.Driver"); + DataSource dataSource = new HikariDataSource(config); + + SqlAuditTestHelper.createTables(dataSource, sqlDialect); + + return new TestContext(dataSource, null, sqlDialect); + } + + if ("sqlite".equals(dialectName)) { + String dbFile = "test_" + System.currentTimeMillis() + ".db"; + + // Use a shared in-memory DB or file DB, but single connection + String jdbcUrl = "jdbc:sqlite:" + dbFile; + + // Create a single-connection DataSource for SQLite + SQLiteDataSource ds = new SQLiteDataSource(); + ds.setUrl(jdbcUrl); + + try (Connection conn = ds.getConnection(); + Statement stmt = conn.createStatement()) { + stmt.execute("PRAGMA journal_mode=WAL;"); + stmt.execute("PRAGMA busy_timeout=5000;"); + } + + // Run table creation with this same DataSource + SqlAuditTestHelper.createTables(ds, sqlDialect); + + return new TestContext(ds, null, SqlDialect.SQLITE); + } + + JdbcDatabaseContainer container = SqlAuditTestHelper.createContainer(dialectName); + container.start(); + + HikariConfig config = new HikariConfig(); + config.setJdbcUrl(container.getJdbcUrl()); + config.setUsername(container.getUsername()); + config.setPassword(container.getPassword()); + config.setDriverClassName(container.getDriverClassName()); + DataSource dataSource = new HikariDataSource(config); + + SqlAuditTestHelper.createTables(dataSource, sqlDialect); + + return new TestContext(dataSource, container, sqlDialect); + } + + private void tearDown(TestContext context) throws SQLException { + if (context.dataSource instanceof HikariDataSource) { + ((HikariDataSource) context.dataSource).close(); + } + if (context.container != null) { + context.container.stop(); + } + } + + private JdbcDatabaseContainer createContainer(String dialectName) { + switch (dialectName) { + case "mysql": + return new MySQLContainer<>("mysql:8.0") + .withDatabaseName("testdb") + .withUsername("testuser") + .withPassword("testpass"); + case "sqlserver": + return new MSSQLServerContainer<>("mcr.microsoft.com/mssql/server:2019-CU18-ubuntu-20.04") + .acceptLicense() + .withPassword("TestPass123!"); + case "oracle": + OracleContainer oracleContainer = new OracleContainer( + DockerImageName.parse("gvenzl/oracle-free:23-slim-faststart") + .asCompatibleSubstituteFor("gvenzl/oracle-xe")) + .withPassword("oracle123") + .withSharedMemorySize(1073741824L) + .withStartupTimeoutSeconds(900) + .withEnv("ORACLE_CHARACTERSET", "AL32UTF8"); + + return new OracleContainer( + DockerImageName.parse("gvenzl/oracle-free:23-slim-faststart") + .asCompatibleSubstituteFor("gvenzl/oracle-xe")) { + @Override + public String getDatabaseName() { + return "FREEPDB1"; + } + } + .withPassword("oracle123") + .withSharedMemorySize(1073741824L) + .withStartupTimeoutSeconds(900) + .withEnv("ORACLE_CHARACTERSET", "AL32UTF8"); + case "postgresql": + return new PostgreSQLContainer<>(DockerImageName.parse("postgres:15")) + .withDatabaseName("testdb") + .withUsername("test") + .withPassword("test"); + case "mariadb": + return new MariaDBContainer<>("mariadb:11.3.2") + .withDatabaseName("testdb") + .withUsername("testuser") + .withPassword("testpass"); + default: + throw new IllegalArgumentException("Unsupported dialect: " + dialectName); + } + } + + private Class[] getChangeClasses(String dialectName, String scenario) { + switch (dialectName) { + case "mysql": + case "mariadb": + case "sqlite": + case "h2": + if ("happyPath".equals(scenario)) { + return new Class[]{ + io.flamingock.community.sql.changes.mysql.happyPath._001__create_index.class, + io.flamingock.community.sql.changes.mysql.happyPath._002__insert_document.class, + io.flamingock.community.sql.changes.mysql.happyPath._003__insert_another_document.class + }; + } else if ("failedWithRollback".equals(scenario)) { + return new Class[]{ + io.flamingock.community.sql.changes.mysql.failedWithRollback._001__create_index.class, + io.flamingock.community.sql.changes.mysql.failedWithRollback._002__insert_document.class, + io.flamingock.community.sql.changes.mysql.failedWithRollback._003__execution_with_exception.class + }; + } else if ("failedWithoutRollback".equals(scenario)) { + return new Class[]{ + io.flamingock.community.sql.changes.mysql.failedWithoutRollback._001__create_index.class, + io.flamingock.community.sql.changes.mysql.failedWithoutRollback._002__insert_document.class, + io.flamingock.community.sql.changes.mysql.failedWithoutRollback._003__execution_with_exception.class + }; + } + break; + case "sqlserver": + if ("happyPath".equals(scenario)) { + return new Class[]{ + io.flamingock.community.sql.changes.sqlserver.happyPath._001__create_index.class, + io.flamingock.community.sql.changes.sqlserver.happyPath._002__insert_document.class, + io.flamingock.community.sql.changes.sqlserver.happyPath._003__insert_another_document.class + }; + } else if ("failedWithRollback".equals(scenario)) { + return new Class[]{ + io.flamingock.community.sql.changes.sqlserver.failedWithRollback._001__create_index.class, + io.flamingock.community.sql.changes.sqlserver.failedWithRollback._002__insert_document.class, + io.flamingock.community.sql.changes.sqlserver.failedWithRollback._003__execution_with_exception.class + }; + } else if ("failedWithoutRollback".equals(scenario)) { + return new Class[]{ + io.flamingock.community.sql.changes.sqlserver.failedWithoutRollback._001__create_index.class, + io.flamingock.community.sql.changes.sqlserver.failedWithoutRollback._002__insert_document.class, + io.flamingock.community.sql.changes.sqlserver.failedWithoutRollback._003__execution_with_exception.class + }; + } + break; + case "oracle": + if ("happyPath".equals(scenario)) { + return new Class[]{ + io.flamingock.community.sql.changes.oracle.happyPath._001__create_index.class, + io.flamingock.community.sql.changes.oracle.happyPath._002__insert_document.class, + io.flamingock.community.sql.changes.oracle.happyPath._003__insert_another_document.class + }; + } else if ("failedWithRollback".equals(scenario)) { + return new Class[]{ + io.flamingock.community.sql.changes.oracle.failedWithRollback._001__create_index.class, + io.flamingock.community.sql.changes.oracle.failedWithRollback._002__insert_document.class, + io.flamingock.community.sql.changes.oracle.failedWithRollback._003__execution_with_exception.class + }; + } else if ("failedWithoutRollback".equals(scenario)) { + return new Class[]{ + io.flamingock.community.sql.changes.oracle.failedWithoutRollback._001__create_index.class, + io.flamingock.community.sql.changes.oracle.failedWithoutRollback._002__insert_document.class, + io.flamingock.community.sql.changes.oracle.failedWithoutRollback._003__execution_with_exception.class + }; + } + break; + case "postgresql": + if ("happyPath".equals(scenario)) { + return new Class[]{ + io.flamingock.community.sql.changes.postgresql.happyPath._001__create_index.class, + io.flamingock.community.sql.changes.postgresql.happyPath._002__insert_document.class, + io.flamingock.community.sql.changes.postgresql.happyPath._003__insert_another_document.class + }; + } else if ("failedWithRollback".equals(scenario)) { + return new Class[]{ + io.flamingock.community.sql.changes.postgresql.failedWithRollback._001__create_index.class, + io.flamingock.community.sql.changes.postgresql.failedWithRollback._002__insert_document.class, + io.flamingock.community.sql.changes.postgresql.failedWithRollback._003__execution_with_exception.class + }; + } else if ("failedWithoutRollback".equals(scenario)) { + return new Class[]{ + io.flamingock.community.sql.changes.postgresql.failedWithoutRollback._001__create_index.class, + io.flamingock.community.sql.changes.postgresql.failedWithoutRollback._002__insert_document.class, + io.flamingock.community.sql.changes.postgresql.failedWithoutRollback._003__execution_with_exception.class + }; + } + break; + } + throw new IllegalArgumentException("Unsupported dialect/scenario: " + dialectName + "/" + scenario); + } + + @ParameterizedTest + @MethodSource("dialectProvider") + @DisplayName("When standalone runs the driver should persist the audit logs and the test data") + void happyPathWithMockedPipeline(SqlDialect sqlDialect, String dialectName) throws Exception { + TestContext context = setupTest(sqlDialect, dialectName); + try { + try (MockedStatic mocked = Mockito.mockStatic(Deserializer.class)) { + Class[] changeClasses = getChangeClasses(dialectName, "happyPath"); + + mocked.when(Deserializer::readPreviewPipelineFromFile).thenReturn(PipelineTestHelper.getPreviewPipeline( + new Trio<>(changeClasses[0], Collections.singletonList(Connection.class), null), + new Trio<>(changeClasses[1], Collections.singletonList(Connection.class), null), + new Trio<>(changeClasses[2], Collections.singletonList(Connection.class), null) + )); + + SqlAuditStore auditStore = new SqlAuditStore(context.dataSource); + SqlTargetSystem targetSystem = new SqlTargetSystem("sql", context.dataSource); + + FlamingockFactory.getCommunityBuilder() + .setAuditStore(auditStore) + .addTargetSystem(targetSystem) + .build() + .run(); + } + + // Verify audit logs + try (Connection conn = context.dataSource.getConnection(); + PreparedStatement ps = conn.prepareStatement( + "SELECT task_id, state FROM " + CommunityPersistenceConstants.DEFAULT_AUDIT_STORE_NAME + " ORDER BY id ASC"); + ResultSet rs = ps.executeQuery()) { + + String[] expectedTaskIds = {"create-index", "insert-document", "insert-another-document"}; + int recordCount = 0; + int startedCount = 0; + int appliedCount = 0; + + while (rs.next()) { + String taskId = rs.getString("task_id"); + String state = rs.getString("state"); + assertTrue( + java.util.Arrays.asList(expectedTaskIds).contains(taskId), + "Unexpected task_id: " + taskId + ); + assertTrue( + state.equals("STARTED") || state.equals("APPLIED"), + "Unexpected state: " + state + ); + if (state.equals("STARTED")) startedCount++; + if (state.equals("APPLIED")) appliedCount++; + recordCount++; + } + + assertEquals(6, recordCount, "Audit log should have 6 records"); + assertEquals(3, startedCount, "Should have 3 STARTED records"); + assertEquals(3, appliedCount, "Should have 3 APPLIED records"); + } + + // Verify test data + try (Connection conn = context.dataSource.getConnection(); + PreparedStatement ps = conn.prepareStatement("SELECT name FROM test_table WHERE id = ?")) { + ps.setString(1, "test-client-Federico"); + try (ResultSet rs = ps.executeQuery()) { + assertTrue(rs.next()); + assertEquals("Federico", rs.getString("name")); + } + ps.setString(1, "test-client-Jorge"); + try (ResultSet rs = ps.executeQuery()) { + assertTrue(rs.next()); + assertEquals("Jorge", rs.getString("name")); + } + } + } finally { + tearDown(context); + } + } + + @ParameterizedTest + @MethodSource("dialectProvider") + @DisplayName("When standalone runs the driver and execution fails (with rollback method) should persist all the audit logs up to the failed one (ROLLED_BACK)") + void failedWithRollback(SqlDialect sqlDialect, String dialectName) throws Exception { + TestContext context = setupTest(sqlDialect, dialectName); + try { + try (MockedStatic mocked = Mockito.mockStatic(Deserializer.class)) { + Class[] changeClasses = getChangeClasses(dialectName, "failedWithRollback"); + + mocked.when(Deserializer::readPreviewPipelineFromFile).thenReturn(PipelineTestHelper.getPreviewPipeline( + new Trio<>(changeClasses[0], Collections.singletonList(Connection.class), null), + new Trio<>(changeClasses[1], Collections.singletonList(Connection.class), null), + new Trio<>(changeClasses[2], Collections.singletonList(Connection.class), null) + )); + + SqlAuditStore auditStore = new SqlAuditStore(context.dataSource); + SqlTargetSystem targetSystem = new SqlTargetSystem("sql", context.dataSource); + + assertThrows(PipelineExecutionException.class, () -> { + FlamingockFactory.getCommunityBuilder() + .setAuditStore(auditStore) + .addTargetSystem(targetSystem) + .build() + .run(); + }); + + // Verify audit sequence + try (Connection conn = context.dataSource.getConnection(); + PreparedStatement ps = conn.prepareStatement( + "SELECT task_id, state FROM " + CommunityPersistenceConstants.DEFAULT_AUDIT_STORE_NAME + " ORDER BY id ASC"); + ResultSet rs = ps.executeQuery()) { + + assertTrue(rs.next()); + assertEquals("create-index", rs.getString("task_id")); + assertEquals("STARTED", rs.getString("state")); + + assertTrue(rs.next()); + assertEquals("create-index", rs.getString("task_id")); + assertEquals("APPLIED", rs.getString("state")); + + assertTrue(rs.next()); + assertEquals("insert-document", rs.getString("task_id")); + assertEquals("STARTED", rs.getString("state")); + + assertTrue(rs.next()); + assertEquals("insert-document", rs.getString("task_id")); + assertEquals("APPLIED", rs.getString("state")); + + assertTrue(rs.next()); + assertEquals("execution-with-exception", rs.getString("task_id")); + assertEquals("STARTED", rs.getString("state")); + + assertTrue(rs.next()); + assertEquals("execution-with-exception", rs.getString("task_id")); + assertEquals("FAILED", rs.getString("state")); + + assertTrue(rs.next()); + assertEquals("execution-with-exception", rs.getString("task_id")); + assertEquals("ROLLED_BACK", rs.getString("state")); + + assertFalse(rs.next()); + } + + // Verify index exists + SqlAuditTestHelper.verifyIndexExists(context); + + // Verify partial data + try (Connection conn = context.dataSource.getConnection(); + PreparedStatement ps = conn.prepareStatement("SELECT name FROM test_table WHERE id = ?")) { + ps.setString(1, "test-client-Federico"); + try (ResultSet rs = ps.executeQuery()) { + assertTrue(rs.next()); + assertEquals("Federico", rs.getString("name")); + } + } + + try (Connection conn = context.dataSource.getConnection(); + PreparedStatement ps = conn.prepareStatement("SELECT name FROM test_table WHERE id = ?")) { + ps.setString(1, "test-client-Jorge"); + try (ResultSet rs = ps.executeQuery()) { + assertFalse(rs.next()); + } + } + } + } finally { + tearDown(context); + } + } + + @ParameterizedTest + @MethodSource("dialectProvider") + @DisplayName("When standalone runs the driver and execution fails (without rollback method) should persist all the audit logs up to the failed one (FAILED)") + void failedWithoutRollback(SqlDialect sqlDialect, String dialectName) throws Exception { + TestContext context = setupTest(sqlDialect, dialectName); + try { + try (MockedStatic mocked = Mockito.mockStatic(Deserializer.class)) { + Class[] changeClasses = getChangeClasses(dialectName, "failedWithoutRollback"); + + mocked.when(Deserializer::readPreviewPipelineFromFile).thenReturn(PipelineTestHelper.getPreviewPipeline( + new Trio<>(changeClasses[0], Collections.singletonList(Connection.class), null), + new Trio<>(changeClasses[1], Collections.singletonList(Connection.class), null), + new Trio<>(changeClasses[2], Collections.singletonList(Connection.class), null) + )); + + SqlAuditStore auditStore = new SqlAuditStore(context.dataSource); + SqlTargetSystem targetSystem = new SqlTargetSystem("sql", context.dataSource); + + assertThrows(PipelineExecutionException.class, () -> { + FlamingockFactory.getCommunityBuilder() + .setAuditStore(auditStore) + .addTargetSystem(targetSystem) + .build() + .run(); + }); + + // Verify audit sequence + try (Connection conn = context.dataSource.getConnection(); + PreparedStatement ps = conn.prepareStatement( + "SELECT task_id, state FROM " + CommunityPersistenceConstants.DEFAULT_AUDIT_STORE_NAME + " ORDER BY id ASC"); + ResultSet rs = ps.executeQuery()) { + + assertTrue(rs.next()); + assertEquals("create-index", rs.getString("task_id")); + assertEquals("STARTED", rs.getString("state")); + + assertTrue(rs.next()); + assertEquals("create-index", rs.getString("task_id")); + assertEquals("APPLIED", rs.getString("state")); + + assertTrue(rs.next()); + assertEquals("insert-document", rs.getString("task_id")); + assertEquals("STARTED", rs.getString("state")); + + assertTrue(rs.next()); + assertEquals("insert-document", rs.getString("task_id")); + assertEquals("APPLIED", rs.getString("state")); + + assertTrue(rs.next()); + assertEquals("execution-with-exception", rs.getString("task_id")); + assertEquals("STARTED", rs.getString("state")); + + assertTrue(rs.next()); + assertEquals("execution-with-exception", rs.getString("task_id")); + assertEquals("FAILED", rs.getString("state")); + + assertTrue(rs.next()); + assertEquals("execution-with-exception", rs.getString("task_id")); + assertEquals("ROLLED_BACK", rs.getString("state")); + + assertFalse(rs.next()); + } + + // Verify index exists and data state + SqlAuditTestHelper.verifyIndexExists(context); + verifyPartialDataState(context); + } + } finally { + tearDown(context); + } + } + + private void verifyPartialDataState(TestContext context) throws SQLException { + try (Connection conn = context.dataSource.getConnection(); + PreparedStatement ps = conn.prepareStatement("SELECT name FROM test_table WHERE id = ?")) { + ps.setString(1, "test-client-Federico"); + try (ResultSet rs = ps.executeQuery()) { + assertTrue(rs.next()); + assertEquals("Federico", rs.getString("name")); + } + } + + try (Connection conn = context.dataSource.getConnection(); + PreparedStatement ps = conn.prepareStatement("SELECT name FROM test_table WHERE id = ?")) { + ps.setString(1, "test-client-Jorge"); + try (ResultSet rs = ps.executeQuery()) { + assertFalse(rs.next()); + } + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/SqlAuditTestHelper.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/SqlAuditTestHelper.java new file mode 100644 index 000000000..71d635aea --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/SqlAuditTestHelper.java @@ -0,0 +1,263 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import io.flamingock.internal.common.sql.SqlDialect; +import org.testcontainers.containers.*; +import org.testcontainers.utility.DockerImageName; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SqlAuditTestHelper { + + public static JdbcDatabaseContainer createContainer(String dialectName) { + switch (dialectName) { + case "mysql": + return new MySQLContainer<>("mysql:8.0") + .withDatabaseName("testdb") + .withUsername("testuser") + .withPassword("testpass"); + case "sqlserver": + return new MSSQLServerContainer<>("mcr.microsoft.com/mssql/server:2019-CU18-ubuntu-20.04") + .acceptLicense() + .withPassword("TestPass123!"); + case "oracle": + OracleContainer oracleContainer = new OracleContainer( + DockerImageName.parse("gvenzl/oracle-free:23-slim-faststart") + .asCompatibleSubstituteFor("gvenzl/oracle-xe")) + .withPassword("oracle123") + .withSharedMemorySize(1073741824L) + .withStartupTimeoutSeconds(900) + .withEnv("ORACLE_CHARACTERSET", "AL32UTF8"); + + return new OracleContainer( + DockerImageName.parse("gvenzl/oracle-free:23-slim-faststart") + .asCompatibleSubstituteFor("gvenzl/oracle-xe")) { + @Override + public String getDatabaseName() { + return "FREEPDB1"; + } + } + .withPassword("oracle123") + .withSharedMemorySize(1073741824L) + .withStartupTimeoutSeconds(900) + .withEnv("ORACLE_CHARACTERSET", "AL32UTF8"); + case "postgresql": + return new PostgreSQLContainer<>(DockerImageName.parse("postgres:15")) + .withDatabaseName("testdb") + .withUsername("test") + .withPassword("test"); + case "mariadb": + return new MariaDBContainer<>("mariadb:11.3.2") + .withDatabaseName("testdb") + .withUsername("testuser") + .withPassword("testpass"); + default: + throw new IllegalArgumentException("Unsupported dialect: " + dialectName); + } + } + + public static void createTables(DataSource dataSource, SqlDialect dialect) throws SQLException { + try (Connection conn = dataSource.getConnection()) { + dropTablesIfExist(conn, dialect); + + String createTestTable = getCreateTestTableSql(dialect); + conn.createStatement().execute(createTestTable); + + String createLockTable = getCreateLockTableSql(dialect); + conn.createStatement().execute(createLockTable); + } + } + + private static void dropTablesIfExist(Connection conn, SqlDialect dialect) throws SQLException { + String[] tables = {"flamingockAuditLog", "test_table", "flamingockLock"}; + for (String table : tables) { + try { + String dropSql = getDropTableSql(table, dialect); + conn.createStatement().execute(dropSql); + } catch (SQLException e) { + // Ignore if table doesn't exist + } + } + } + + private static String getDropTableSql(String tableName, SqlDialect dialect) { + if (dialect == SqlDialect.ORACLE) { + return "DROP TABLE " + tableName + " CASCADE CONSTRAINTS"; + } + return "DROP TABLE IF EXISTS " + tableName; + } + + private static String getCreateTestTableSql(SqlDialect dialect) { + switch (dialect) { + case MYSQL: + case SQLSERVER: + case MARIADB: + case SQLITE: + case H2: + case HSQLDB: + return "CREATE TABLE test_table (" + + "id VARCHAR(255) PRIMARY KEY, " + + "name VARCHAR(255), " + + "field1 VARCHAR(255), " + + "field2 VARCHAR(255))"; + case POSTGRESQL: + return "CREATE TABLE test_table (" + + "id VARCHAR(255) PRIMARY KEY," + + "name VARCHAR(255)," + + "field1 VARCHAR(255)," + + "field2 VARCHAR(255))"; + case ORACLE: + return "CREATE TABLE test_table (" + + "id VARCHAR2(255) PRIMARY KEY, " + + "name VARCHAR2(255), " + + "field1 VARCHAR2(255), " + + "field2 VARCHAR2(255))"; + default: + throw new UnsupportedOperationException("Dialect not supported: " + dialect); + } + } + + private static String getCreateLockTableSql(SqlDialect dialect) { + switch (dialect) { + case MYSQL: + case MARIADB: + case SQLITE: + case H2: + case HSQLDB: + case FIREBIRD: + case INFORMIX: + return "CREATE TABLE flamingockLock (" + + "`key` VARCHAR(255) PRIMARY KEY, " + + "status VARCHAR(32), " + + "owner VARCHAR(255), " + + "expires_at TIMESTAMP)"; + case POSTGRESQL: + return "CREATE TABLE flamingockLock (" + + "\"key\" VARCHAR(255) PRIMARY KEY," + + "status VARCHAR(32)," + + "owner VARCHAR(255)," + + "expires_at TIMESTAMP)"; + case SQLSERVER: + return "CREATE TABLE flamingockLock (" + + "[key] VARCHAR(255) PRIMARY KEY, " + + "status VARCHAR(32), " + + "owner VARCHAR(255), " + + "expires_at DATETIME)"; + case ORACLE: + return "CREATE TABLE flamingockLock (" + + "\"key\" VARCHAR2(255) PRIMARY KEY, " + + "status VARCHAR2(32), " + + "owner VARCHAR2(255), " + + "expires_at TIMESTAMP)"; + default: + throw new UnsupportedOperationException("Dialect not supported: " + dialect); + } + } + + public static DataSource createDataSource(JdbcDatabaseContainer container) { + HikariConfig config = new HikariConfig(); + config.setJdbcUrl(container.getJdbcUrl()); + config.setUsername(container.getUsername()); + config.setPassword(container.getPassword()); + config.setDriverClassName(container.getDriverClassName()); + return new HikariDataSource(config); + } + + public static void verifyPartialDataState(DataSource dataSource) throws SQLException { + try (Connection conn = dataSource.getConnection(); + PreparedStatement ps = conn.prepareStatement("SELECT name FROM test_table WHERE id = ?")) { + ps.setString(1, "test-client-Federico"); + try (ResultSet rs = ps.executeQuery()) { + if (!rs.next() || !"Federico".equals(rs.getString("name"))) { + throw new AssertionError("Federico not found"); + } + } + ps.setString(1, "test-client-Jorge"); + try (ResultSet rs = ps.executeQuery()) { + if (!rs.next() || !"Jorge".equals(rs.getString("name"))) { + throw new AssertionError("Jorge not found"); + } + } + } + } + + private static String getIndexCheckSql(SqlDialect dialect) { + switch (dialect) { + case POSTGRESQL: + return "SELECT indexname FROM pg_indexes WHERE indexname = ?"; + case MYSQL: + case MARIADB: + return "SELECT INDEX_NAME FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_NAME = ? AND INDEX_NAME = ?"; + case ORACLE: + return "SELECT INDEX_NAME FROM USER_INDEXES WHERE INDEX_NAME = ? AND TABLE_NAME = ?"; + case SQLSERVER: + case SYBASE: + return "SELECT name FROM sys.indexes WHERE name = ?"; + case SQLITE: + // SQLite uses sqlite_master for indexes + return "SELECT name FROM sqlite_master WHERE type='index' AND name = ?"; + case H2: + case HSQLDB: + default: + return "SELECT INDEX_NAME FROM INFORMATION_SCHEMA.INDEXES WHERE INDEX_NAME = ?"; + + } + } + + public static void verifyIndexExists(TestContext context) throws SQLException { + try (Connection conn = context.dataSource.getConnection()) { + String indexCheckSql = getIndexCheckSql(context.dialect); + try (PreparedStatement ps = conn.prepareStatement(indexCheckSql)) { + switch (context.dialect) { + case ORACLE: + ps.setString(1, "IDX_STANDALONE_INDEX"); + ps.setString(2, "TEST_TABLE"); + break; + case MYSQL: + case MARIADB: + ps.setString(1, "test_table"); + ps.setString(2, "idx_standalone_index"); + break; + case H2: + case HSQLDB: + ps.setString(1, "IDX_STANDALONE_INDEX"); + break; + case POSTGRESQL: + case SQLSERVER: + case SYBASE: + case SQLITE: + default: + ps.setString(1, "idx_standalone_index"); + break; + } + + try (ResultSet rs = ps.executeQuery()) { + boolean indexExists = rs.next(); + assertTrue(indexExists, "Index idx_standalone_index should exist"); + } + } + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/TestContext.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/TestContext.java new file mode 100644 index 000000000..c8cab1942 --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/TestContext.java @@ -0,0 +1,33 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql; + +import io.flamingock.internal.common.sql.SqlDialect; +import org.testcontainers.containers.JdbcDatabaseContainer; + +import javax.sql.DataSource; + +public class TestContext { + final DataSource dataSource; + final JdbcDatabaseContainer container; + final SqlDialect dialect; + + TestContext(DataSource dataSource, JdbcDatabaseContainer container, SqlDialect dialect) { + this.dataSource = dataSource; + this.container = container; + this.dialect = dialect; + } +} \ No newline at end of file diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/mysql/failedWithRollback/_001__create_index.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/mysql/failedWithRollback/_001__create_index.java new file mode 100644 index 000000000..325ab7048 --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/mysql/failedWithRollback/_001__create_index.java @@ -0,0 +1,36 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.mysql.failedWithRollback; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +@TargetSystem(id = "sql") +@Change(id = "create-index", transactional = false, author = "aperezdieppa") +public class _001__create_index { + + @Apply + public void execution(Connection connection) throws SQLException { + try (Statement stmt = connection.createStatement()) { + stmt.executeUpdate("CREATE INDEX idx_standalone_index ON test_table(field1, field2)"); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/mysql/failedWithRollback/_002__insert_document.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/mysql/failedWithRollback/_002__insert_document.java new file mode 100644 index 000000000..f56d7092a --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/mysql/failedWithRollback/_002__insert_document.java @@ -0,0 +1,49 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.mysql.failedWithRollback; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.Rollback; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +@TargetSystem(id = "sql") +@Change(id = "insert-document", transactional = false, author = "aperezdieppa") +public class _002__insert_document { + + @Apply + public void execution(Connection connection) throws SQLException { + try (PreparedStatement ps = connection.prepareStatement( + "INSERT INTO test_table (id, name) VALUES (?, ?)")) { + ps.setString(1, "test-client-Federico"); + ps.setString(2, "Federico"); + ps.executeUpdate(); + } + } + + @Rollback + public void rollbackExecution(Connection connection) throws SQLException { + try (PreparedStatement ps = connection.prepareStatement( + "DELETE FROM test_table WHERE id = ?")) { + ps.setString(1, "test-client-Federico"); + ps.executeUpdate(); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/mysql/failedWithRollback/_003__execution_with_exception.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/mysql/failedWithRollback/_003__execution_with_exception.java new file mode 100644 index 000000000..f6ee4ea10 --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/mysql/failedWithRollback/_003__execution_with_exception.java @@ -0,0 +1,50 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.mysql.failedWithRollback; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.Rollback; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +@TargetSystem(id = "sql") +@Change(id = "execution-with-exception", transactional = false, author = "aperezdieppa") +public class _003__execution_with_exception { + + @Apply + public void execution(Connection connection) throws SQLException { + try (PreparedStatement ps = connection.prepareStatement( + "INSERT INTO test_table (id, name) VALUES (?, ?)")) { + ps.setString(1, "test-client-Jorge"); + ps.setString(2, "Jorge"); + ps.executeUpdate(); + } + throw new RuntimeException("test"); + } + + @Rollback + public void rollbackExecution(Connection connection) throws SQLException { + try (PreparedStatement ps = connection.prepareStatement( + "DELETE FROM test_table WHERE id = ?")) { + ps.setString(1, "test-client-Jorge"); + ps.executeUpdate(); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/mysql/failedWithoutRollback/_001__create_index.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/mysql/failedWithoutRollback/_001__create_index.java new file mode 100644 index 000000000..f58de2853 --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/mysql/failedWithoutRollback/_001__create_index.java @@ -0,0 +1,36 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.mysql.failedWithoutRollback; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +@TargetSystem(id = "sql") +@Change(id = "create-index", transactional = false, author = "aperezdieppa") +public class _001__create_index { + + @Apply + public void execution(Connection connection) throws SQLException { + try (Statement stmt = connection.createStatement()) { + stmt.executeUpdate("CREATE INDEX idx_standalone_index ON test_table(field1, field2)"); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/mysql/failedWithoutRollback/_002__insert_document.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/mysql/failedWithoutRollback/_002__insert_document.java new file mode 100644 index 000000000..c93302ebf --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/mysql/failedWithoutRollback/_002__insert_document.java @@ -0,0 +1,39 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.mysql.failedWithoutRollback; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +@TargetSystem(id = "sql") +@Change(id = "insert-document", author = "aperezdieppa") +public class _002__insert_document { + + @Apply + public void execution(Connection connection) throws SQLException { + try (PreparedStatement ps = connection.prepareStatement( + "INSERT INTO test_table (id, name) VALUES (?, ?)")) { + ps.setString(1, "test-client-Federico"); + ps.setString(2, "Federico"); + ps.executeUpdate(); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/mysql/failedWithoutRollback/_003__execution_with_exception.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/mysql/failedWithoutRollback/_003__execution_with_exception.java new file mode 100644 index 000000000..1016279cc --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/mysql/failedWithoutRollback/_003__execution_with_exception.java @@ -0,0 +1,40 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.mysql.failedWithoutRollback; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +@TargetSystem(id = "sql") +@Change(id = "execution-with-exception", author = "aperezdieppa") +public class _003__execution_with_exception { + + @Apply + public void execution(Connection connection) throws SQLException { + try (PreparedStatement ps = connection.prepareStatement( + "INSERT INTO test_table (id, name) VALUES (?, ?)")) { + ps.setString(1, "test-client-Jorge"); + ps.setString(2, "Jorge"); + ps.executeUpdate(); + } + throw new RuntimeException("test"); + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/mysql/happyPath/_001__create_index.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/mysql/happyPath/_001__create_index.java new file mode 100644 index 000000000..deacee1aa --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/mysql/happyPath/_001__create_index.java @@ -0,0 +1,35 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.mysql.happyPath; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.Statement; + +@TargetSystem(id = "sql") +@Change(id = "create-index", transactional = false, author = "aperezdieppa") +public class _001__create_index { + + @Apply + public void execution(Connection connection) throws Exception { + try (Statement stmt = connection.createStatement()) { + stmt.execute("CREATE INDEX idx_standalone_index ON test_table(field1, field2)"); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/mysql/happyPath/_002__insert_document.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/mysql/happyPath/_002__insert_document.java new file mode 100644 index 000000000..09b5fbb34 --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/mysql/happyPath/_002__insert_document.java @@ -0,0 +1,38 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.mysql.happyPath; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.PreparedStatement; + +@TargetSystem(id = "sql") +@Change(id = "insert-document", author = "aperezdieppa") +public class _002__insert_document { + + @Apply + public void execution(Connection connection) throws Exception { + try (PreparedStatement ps = connection.prepareStatement( + "INSERT INTO test_table (id, name) VALUES (?, ?)")) { + ps.setString(1, "test-client-Federico"); + ps.setString(2, "Federico"); + ps.executeUpdate(); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/mysql/happyPath/_003__insert_another_document.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/mysql/happyPath/_003__insert_another_document.java new file mode 100644 index 000000000..987bd6765 --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/mysql/happyPath/_003__insert_another_document.java @@ -0,0 +1,38 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.mysql.happyPath; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.PreparedStatement; + +@TargetSystem(id = "sql") +@Change(id = "insert-another-document", author = "aperezdieppa") +public class _003__insert_another_document { + + @Apply + public void execution(Connection connection) throws Exception { + try (PreparedStatement ps = connection.prepareStatement( + "INSERT INTO test_table (id, name) VALUES (?, ?)")) { + ps.setString(1, "test-client-Jorge"); + ps.setString(2, "Jorge"); + ps.executeUpdate(); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/oracle/failedWithRollback/_001__create_index.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/oracle/failedWithRollback/_001__create_index.java new file mode 100644 index 000000000..28b427e3b --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/oracle/failedWithRollback/_001__create_index.java @@ -0,0 +1,36 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.oracle.failedWithRollback; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +@TargetSystem(id = "sql") +@Change(id = "create-index", transactional = false, author = "aperezdieppa") +public class _001__create_index { + + @Apply + public void execution(Connection connection) throws SQLException { + try (Statement stmt = connection.createStatement()) { + stmt.execute("CREATE INDEX idx_standalone_index ON test_table(field1, field2)"); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/oracle/failedWithRollback/_002__insert_document.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/oracle/failedWithRollback/_002__insert_document.java new file mode 100644 index 000000000..7db36db79 --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/oracle/failedWithRollback/_002__insert_document.java @@ -0,0 +1,49 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.oracle.failedWithRollback; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.Rollback; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +@TargetSystem(id = "sql") +@Change(id = "insert-document", transactional = false, author = "aperezdieppa") +public class _002__insert_document { + + @Apply + public void execution(Connection connection) throws SQLException { + try (PreparedStatement ps = connection.prepareStatement( + "INSERT INTO test_table (id, name) VALUES (?, ?)")) { + ps.setString(1, "test-client-Federico"); + ps.setString(2, "Federico"); + ps.executeUpdate(); + } + } + + @Rollback + public void rollback(Connection connection) throws Exception { + try (PreparedStatement ps = connection.prepareStatement( + "DELETE FROM test_table WHERE id = ?")) { + ps.setString(1, "test-client-Federico"); + ps.executeUpdate(); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/oracle/failedWithRollback/_003__execution_with_exception.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/oracle/failedWithRollback/_003__execution_with_exception.java new file mode 100644 index 000000000..3eb0e3a28 --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/oracle/failedWithRollback/_003__execution_with_exception.java @@ -0,0 +1,50 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.oracle.failedWithRollback; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.Rollback; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +@TargetSystem(id = "sql") +@Change(id = "execution-with-exception", transactional = false, author = "aperezdieppa") +public class _003__execution_with_exception { + + @Apply + public void execution(Connection connection) throws SQLException { + try (PreparedStatement ps = connection.prepareStatement( + "INSERT INTO test_table (id, name) VALUES (?, ?)")) { + ps.setString(1, "test-client-Jorge"); + ps.setString(2, "Jorge"); + ps.executeUpdate(); + } + throw new RuntimeException("test"); + } + + @Rollback + public void rollbackExecution(Connection connection) throws SQLException { + try (PreparedStatement ps = connection.prepareStatement( + "DELETE FROM test_table WHERE id = ?")) { + ps.setString(1, "test-client-Jorge"); + ps.executeUpdate(); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/oracle/failedWithoutRollback/_001__create_index.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/oracle/failedWithoutRollback/_001__create_index.java new file mode 100644 index 000000000..955581255 --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/oracle/failedWithoutRollback/_001__create_index.java @@ -0,0 +1,36 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.oracle.failedWithoutRollback; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +@TargetSystem(id = "sql") +@Change(id = "create-index", transactional = false, author = "aperezdieppa") +public class _001__create_index { + + @Apply + public void execution(Connection connection) throws SQLException { + try (Statement stmt = connection.createStatement()) { + stmt.execute("CREATE INDEX idx_standalone_index ON test_table(field1, field2)"); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/oracle/failedWithoutRollback/_002__insert_document.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/oracle/failedWithoutRollback/_002__insert_document.java new file mode 100644 index 000000000..b2bec2056 --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/oracle/failedWithoutRollback/_002__insert_document.java @@ -0,0 +1,39 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.oracle.failedWithoutRollback; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +@TargetSystem(id = "sql") +@Change(id = "insert-document", author = "aperezdieppa") +public class _002__insert_document { + + @Apply + public void execution(Connection connection) throws SQLException { + try (PreparedStatement ps = connection.prepareStatement( + "INSERT INTO test_table (id, name) VALUES (?, ?)")) { + ps.setString(1, "test-client-Federico"); + ps.setString(2, "Federico"); + ps.executeUpdate(); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/oracle/failedWithoutRollback/_003__execution_with_exception.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/oracle/failedWithoutRollback/_003__execution_with_exception.java new file mode 100644 index 000000000..ffaf1b6b6 --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/oracle/failedWithoutRollback/_003__execution_with_exception.java @@ -0,0 +1,40 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.oracle.failedWithoutRollback; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +@TargetSystem(id = "sql") +@Change(id = "execution-with-exception", author = "aperezdieppa") +public class _003__execution_with_exception { + + @Apply + public void execution(Connection connection) throws SQLException { + try (PreparedStatement ps = connection.prepareStatement( + "INSERT INTO test_table (id, name) VALUES (?, ?)")) { + ps.setString(1, "test-client-Jorge"); + ps.setString(2, "Jorge"); + ps.executeUpdate(); + } + throw new RuntimeException("test"); + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/oracle/happyPath/_001__create_index.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/oracle/happyPath/_001__create_index.java new file mode 100644 index 000000000..fc724efe4 --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/oracle/happyPath/_001__create_index.java @@ -0,0 +1,35 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.oracle.happyPath; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.Statement; + +@TargetSystem(id = "sql") +@Change(id = "create-index", transactional = false, author = "aperezdieppa") +public class _001__create_index { + + @Apply + public void execution(Connection connection) throws Exception { + try (Statement stmt = connection.createStatement()) { + stmt.execute("CREATE INDEX idx_standalone_index ON test_table(field1, field2)"); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/oracle/happyPath/_002__insert_document.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/oracle/happyPath/_002__insert_document.java new file mode 100644 index 000000000..ef8fd54ec --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/oracle/happyPath/_002__insert_document.java @@ -0,0 +1,38 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.oracle.happyPath; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.PreparedStatement; + +@TargetSystem(id = "sql") +@Change(id = "insert-document", author = "aperezdieppa") +public class _002__insert_document { + + @Apply + public void execution(Connection connection) throws Exception { + try (PreparedStatement ps = connection.prepareStatement( + "INSERT INTO test_table (id, name) VALUES (?, ?)")) { + ps.setString(1, "test-client-Federico"); + ps.setString(2, "Federico"); + ps.executeUpdate(); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/oracle/happyPath/_003__insert_another_document.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/oracle/happyPath/_003__insert_another_document.java new file mode 100644 index 000000000..610a0e5f1 --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/oracle/happyPath/_003__insert_another_document.java @@ -0,0 +1,38 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.oracle.happyPath; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.PreparedStatement; + +@TargetSystem(id = "sql") +@Change(id = "insert-another-document", author = "aperezdieppa") +public class _003__insert_another_document { + + @Apply + public void execution(Connection connection) throws Exception { + try (PreparedStatement ps = connection.prepareStatement( + "INSERT INTO test_table (id, name) VALUES (?, ?)")) { + ps.setString(1, "test-client-Jorge"); + ps.setString(2, "Jorge"); + ps.executeUpdate(); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/postgresql/failedWithRollback/_001__create_index.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/postgresql/failedWithRollback/_001__create_index.java new file mode 100644 index 000000000..7219f2dad --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/postgresql/failedWithRollback/_001__create_index.java @@ -0,0 +1,36 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.postgresql.failedWithRollback; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +@TargetSystem(id = "sql") +@Change(id = "create-index", transactional = false, author = "aperezdieppa") +public class _001__create_index { + + @Apply + public void execution(Connection connection) throws SQLException { + try (Statement stmt = connection.createStatement()) { + stmt.executeUpdate("CREATE INDEX IF NOT EXISTS idx_standalone_index ON test_table(field1, field2)"); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/postgresql/failedWithRollback/_002__insert_document.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/postgresql/failedWithRollback/_002__insert_document.java new file mode 100644 index 000000000..c21dff498 --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/postgresql/failedWithRollback/_002__insert_document.java @@ -0,0 +1,49 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.postgresql.failedWithRollback; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.Rollback; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +@TargetSystem(id = "sql") +@Change(id = "insert-document", transactional = false, author = "aperezdieppa") +public class _002__insert_document { + + @Apply + public void execution(Connection connection) throws SQLException { + try (PreparedStatement ps = connection.prepareStatement( + "INSERT INTO test_table (id, name) VALUES (?, ?)")) { + ps.setString(1, "test-client-Federico"); + ps.setString(2, "Federico"); + ps.executeUpdate(); + } + } + + @Rollback + public void rollbackExecution(Connection connection) throws SQLException { + try (PreparedStatement ps = connection.prepareStatement( + "DELETE FROM test_table WHERE id = ?")) { + ps.setString(1, "test-client-Federico"); + ps.executeUpdate(); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/postgresql/failedWithRollback/_003__execution_with_exception.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/postgresql/failedWithRollback/_003__execution_with_exception.java new file mode 100644 index 000000000..147208bd3 --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/postgresql/failedWithRollback/_003__execution_with_exception.java @@ -0,0 +1,50 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.postgresql.failedWithRollback; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.Rollback; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +@TargetSystem(id = "sql") +@Change(id = "execution-with-exception", transactional = false, author = "aperezdieppa") +public class _003__execution_with_exception { + + @Apply + public void execution(Connection connection) throws SQLException { + try (PreparedStatement ps = connection.prepareStatement( + "INSERT INTO test_table (id, name) VALUES (?, ?)")) { + ps.setString(1, "test-client-Jorge"); + ps.setString(2, "Jorge"); + ps.executeUpdate(); + } + throw new RuntimeException("test"); + } + + @Rollback + public void rollbackExecution(Connection connection) throws SQLException { + try (PreparedStatement ps = connection.prepareStatement( + "DELETE FROM test_table WHERE id = ?")) { + ps.setString(1, "test-client-Jorge"); + ps.executeUpdate(); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/postgresql/failedWithoutRollback/_001__create_index.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/postgresql/failedWithoutRollback/_001__create_index.java new file mode 100644 index 000000000..526ec3719 --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/postgresql/failedWithoutRollback/_001__create_index.java @@ -0,0 +1,36 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.postgresql.failedWithoutRollback; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +@TargetSystem(id = "sql") +@Change(id = "create-index", transactional = false, author = "aperezdieppa") +public class _001__create_index { + + @Apply + public void execution(Connection connection) throws SQLException { + try (Statement stmt = connection.createStatement()) { + stmt.executeUpdate("CREATE INDEX IF NOT EXISTS idx_standalone_index ON test_table(field1, field2)"); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/postgresql/failedWithoutRollback/_002__insert_document.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/postgresql/failedWithoutRollback/_002__insert_document.java new file mode 100644 index 000000000..5db818f03 --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/postgresql/failedWithoutRollback/_002__insert_document.java @@ -0,0 +1,39 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.postgresql.failedWithoutRollback; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +@TargetSystem(id = "sql") +@Change(id = "insert-document", author = "aperezdieppa") +public class _002__insert_document { + + @Apply + public void execution(Connection connection) throws SQLException { + try (PreparedStatement ps = connection.prepareStatement( + "INSERT INTO test_table (id, name) VALUES (?, ?)")) { + ps.setString(1, "test-client-Federico"); + ps.setString(2, "Federico"); + ps.executeUpdate(); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/postgresql/failedWithoutRollback/_003__execution_with_exception.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/postgresql/failedWithoutRollback/_003__execution_with_exception.java new file mode 100644 index 000000000..6ae6c99f0 --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/postgresql/failedWithoutRollback/_003__execution_with_exception.java @@ -0,0 +1,40 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.postgresql.failedWithoutRollback; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +@TargetSystem(id = "sql") +@Change(id = "execution-with-exception", author = "aperezdieppa") +public class _003__execution_with_exception { + + @Apply + public void execution(Connection connection) throws SQLException { + try (PreparedStatement ps = connection.prepareStatement( + "INSERT INTO test_table (id, name) VALUES (?, ?)")) { + ps.setString(1, "test-client-Jorge"); + ps.setString(2, "Jorge"); + ps.executeUpdate(); + } + throw new RuntimeException("test"); + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/postgresql/happyPath/_001__create_index.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/postgresql/happyPath/_001__create_index.java new file mode 100644 index 000000000..8db5bb0de --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/postgresql/happyPath/_001__create_index.java @@ -0,0 +1,35 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.postgresql.happyPath; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.Statement; + +@TargetSystem(id = "sql") +@Change(id = "create-index", transactional = false, author = "aperezdieppa") +public class _001__create_index { + + @Apply + public void execution(Connection connection) throws Exception { + try (Statement stmt = connection.createStatement()) { + stmt.executeUpdate("CREATE INDEX IF NOT EXISTS idx_standalone_index ON test_table(field1, field2)"); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/postgresql/happyPath/_002__insert_document.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/postgresql/happyPath/_002__insert_document.java new file mode 100644 index 000000000..c3a044c1d --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/postgresql/happyPath/_002__insert_document.java @@ -0,0 +1,38 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.postgresql.happyPath; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.PreparedStatement; + +@TargetSystem(id = "sql") +@Change(id = "insert-document", author = "aperezdieppa") +public class _002__insert_document { + + @Apply + public void execution(Connection connection) throws Exception { + try (PreparedStatement ps = connection.prepareStatement( + "INSERT INTO test_table (id, name) VALUES (?, ?)")) { + ps.setString(1, "test-client-Federico"); + ps.setString(2, "Federico"); + ps.executeUpdate(); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/postgresql/happyPath/_003__insert_another_document.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/postgresql/happyPath/_003__insert_another_document.java new file mode 100644 index 000000000..4fb3eff0b --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/postgresql/happyPath/_003__insert_another_document.java @@ -0,0 +1,38 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.postgresql.happyPath; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.PreparedStatement; + +@TargetSystem(id = "sql") +@Change(id = "insert-another-document", author = "aperezdieppa") +public class _003__insert_another_document { + + @Apply + public void execution(Connection connection) throws Exception { + try (PreparedStatement ps = connection.prepareStatement( + "INSERT INTO test_table (id, name) VALUES (?, ?)")) { + ps.setString(1, "test-client-Jorge"); + ps.setString(2, "Jorge"); + ps.executeUpdate(); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/sqlserver/failedWithRollback/_001__create_index.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/sqlserver/failedWithRollback/_001__create_index.java new file mode 100644 index 000000000..c2c23dbf6 --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/sqlserver/failedWithRollback/_001__create_index.java @@ -0,0 +1,36 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.sqlserver.failedWithRollback; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +@TargetSystem(id = "sql") +@Change(id = "create-index", transactional = false, author = "aperezdieppa") +public class _001__create_index { + + @Apply + public void execution(Connection connection) throws SQLException { + try (Statement stmt = connection.createStatement()) { + stmt.execute("CREATE INDEX idx_standalone_index ON test_table(field1, field2)"); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/sqlserver/failedWithRollback/_002__insert_document.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/sqlserver/failedWithRollback/_002__insert_document.java new file mode 100644 index 000000000..d7bfa9cf6 --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/sqlserver/failedWithRollback/_002__insert_document.java @@ -0,0 +1,49 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.sqlserver.failedWithRollback; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.Rollback; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +@TargetSystem(id = "sql") +@Change(id = "insert-document", transactional = false, author = "aperezdieppa") +public class _002__insert_document { + + @Apply + public void execution(Connection connection) throws SQLException { + try (PreparedStatement ps = connection.prepareStatement( + "INSERT INTO test_table (id, name) VALUES (?, ?)")) { + ps.setString(1, "test-client-Federico"); + ps.setString(2, "Federico"); + ps.executeUpdate(); + } + } + + @Rollback + public void rollbackExecution(Connection connection) throws SQLException { + try (PreparedStatement ps = connection.prepareStatement( + "DELETE FROM test_table WHERE id = ?")) { + ps.setString(1, "test-client-Federico"); + ps.executeUpdate(); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/sqlserver/failedWithRollback/_003__execution_with_exception.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/sqlserver/failedWithRollback/_003__execution_with_exception.java new file mode 100644 index 000000000..d9ef56d0f --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/sqlserver/failedWithRollback/_003__execution_with_exception.java @@ -0,0 +1,50 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.sqlserver.failedWithRollback; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.Rollback; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +@TargetSystem(id = "sql") +@Change(id = "execution-with-exception", transactional = false, author = "aperezdieppa") +public class _003__execution_with_exception { + + @Apply + public void execution(Connection connection) throws SQLException { + try (PreparedStatement ps = connection.prepareStatement( + "INSERT INTO test_table (id, name) VALUES (?, ?)")) { + ps.setString(1, "test-client-Jorge"); + ps.setString(2, "Jorge"); + ps.executeUpdate(); + } + throw new RuntimeException("test"); + } + + @Rollback + public void rollbackExecution(Connection connection) throws SQLException { + try (PreparedStatement ps = connection.prepareStatement( + "DELETE FROM test_table WHERE id = ?")) { + ps.setString(1, "test-client-Jorge"); + ps.executeUpdate(); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/sqlserver/failedWithoutRollback/_001__create_index.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/sqlserver/failedWithoutRollback/_001__create_index.java new file mode 100644 index 000000000..a287dd942 --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/sqlserver/failedWithoutRollback/_001__create_index.java @@ -0,0 +1,36 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.sqlserver.failedWithoutRollback; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +@TargetSystem(id = "sql") +@Change(id = "create-index", transactional = false, author = "aperezdieppa") +public class _001__create_index { + + @Apply + public void execution(Connection connection) throws SQLException { + try (Statement stmt = connection.createStatement()) { + stmt.execute("CREATE INDEX idx_standalone_index ON test_table(field1, field2)"); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/sqlserver/failedWithoutRollback/_002__insert_document.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/sqlserver/failedWithoutRollback/_002__insert_document.java new file mode 100644 index 000000000..1c50c5a6c --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/sqlserver/failedWithoutRollback/_002__insert_document.java @@ -0,0 +1,39 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.sqlserver.failedWithoutRollback; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +@TargetSystem(id = "sql") +@Change(id = "insert-document", author = "aperezdieppa") +public class _002__insert_document { + + @Apply + public void execution(Connection connection) throws SQLException { + try (PreparedStatement ps = connection.prepareStatement( + "INSERT INTO test_table (id, name) VALUES (?, ?)")) { + ps.setString(1, "test-client-Federico"); + ps.setString(2, "Federico"); + ps.executeUpdate(); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/sqlserver/failedWithoutRollback/_003__execution_with_exception.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/sqlserver/failedWithoutRollback/_003__execution_with_exception.java new file mode 100644 index 000000000..affff9f77 --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/sqlserver/failedWithoutRollback/_003__execution_with_exception.java @@ -0,0 +1,40 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.sqlserver.failedWithoutRollback; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +@TargetSystem(id = "sql") +@Change(id = "execution-with-exception", author = "aperezdieppa") +public class _003__execution_with_exception { + + @Apply + public void execution(Connection connection) throws SQLException { + try (PreparedStatement ps = connection.prepareStatement( + "INSERT INTO test_table (id, name) VALUES (?, ?)")) { + ps.setString(1, "test-client-Jorge"); + ps.setString(2, "Jorge"); + ps.executeUpdate(); + } + throw new RuntimeException("test"); + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/sqlserver/happyPath/_001__create_index.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/sqlserver/happyPath/_001__create_index.java new file mode 100644 index 000000000..5d8d2d4cc --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/sqlserver/happyPath/_001__create_index.java @@ -0,0 +1,35 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.sqlserver.happyPath; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.Statement; + +@TargetSystem(id = "sql") +@Change(id = "create-index", transactional = false, author = "aperezdieppa") +public class _001__create_index { + + @Apply + public void execution(Connection connection) throws Exception { + try (Statement stmt = connection.createStatement()) { + stmt.execute("CREATE INDEX idx_standalone_index ON test_table(field1, field2)"); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/sqlserver/happyPath/_002__insert_document.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/sqlserver/happyPath/_002__insert_document.java new file mode 100644 index 000000000..db4269da0 --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/sqlserver/happyPath/_002__insert_document.java @@ -0,0 +1,38 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.sqlserver.happyPath; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.PreparedStatement; + +@TargetSystem(id = "sql") +@Change(id = "insert-document", author = "aperezdieppa") +public class _002__insert_document { + + @Apply + public void execution(Connection connection) throws Exception { + try (PreparedStatement ps = connection.prepareStatement( + "INSERT INTO test_table (id, name) VALUES (?, ?)")) { + ps.setString(1, "test-client-Federico"); + ps.setString(2, "Federico"); + ps.executeUpdate(); + } + } +} diff --git a/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/sqlserver/happyPath/_003__insert_another_document.java b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/sqlserver/happyPath/_003__insert_another_document.java new file mode 100644 index 000000000..9a86c938d --- /dev/null +++ b/community/flamingock-auditstore-sql/src/test/java/io/flamingock/community/sql/changes/sqlserver/happyPath/_003__insert_another_document.java @@ -0,0 +1,38 @@ +/* + * Copyright 2025 Flamingock (https://www.flamingock.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.flamingock.community.sql.changes.sqlserver.happyPath; + +import io.flamingock.api.annotations.Apply; +import io.flamingock.api.annotations.Change; +import io.flamingock.api.annotations.TargetSystem; + +import java.sql.Connection; +import java.sql.PreparedStatement; + +@TargetSystem(id = "sql") +@Change(id = "insert-another-document", author = "aperezdieppa") +public class _003__insert_another_document { + + @Apply + public void execution(Connection connection) throws Exception { + try (PreparedStatement ps = connection.prepareStatement( + "INSERT INTO test_table (id, name) VALUES (?, ?)")) { + ps.setString(1, "test-client-Jorge"); + ps.setString(2, "Jorge"); + ps.executeUpdate(); + } + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 82b086daf..5e50bab90 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -63,6 +63,10 @@ include("community:flamingock-auditstore-dynamodb") project(":community:flamingock-auditstore-dynamodb").name = "flamingock-auditstore-dynamodb" project(":community:flamingock-auditstore-dynamodb").projectDir = file("community/flamingock-auditstore-dynamodb") +include("community:flamingock-auditstore-sql") +project(":community:flamingock-auditstore-sql").name = "flamingock-auditstore-sql" +project(":community:flamingock-auditstore-sql").projectDir = file("community/flamingock-auditstore-sql") + ////////////////////////////////////// // PLUGINS //////////////////////////////////////