feat: add shadow-only SQL Server package

This commit is contained in:
zgw
2026-08-11 19:16:07 +08:00
parent aab6b8e601
commit 8b050ac9d6
10 changed files with 928 additions and 2413 deletions

View File

@@ -0,0 +1,143 @@
package com.ruoyi.algorithm.repository;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.assertj.core.api.Assertions.assertThat;
class ShadowSqlServerMigrationScriptTest {
private static final Set<String> ALLOWED_INSTALL_OBJECTS = Set.of(
"table alg.warehouse_policy",
"table alg.project_policy",
"table alg.space_role",
"table alg.space_topology",
"table alg.inventory_item_snapshot",
"table alg.space_inventory_summary",
"table alg.lane_state",
"table alg.reservation",
"table alg.deep_workflow_lock",
"table alg.decision_log",
"view alg.v_inventory_item",
"view alg.v_active_job",
"view alg.v_space_state",
"procedure alg.usp_rebuild_space_topology",
"procedure alg.usp_sync_inventory_snapshot",
"procedure alg.usp_compute_lane_semantic_fingerprint",
"procedure alg.usp_rebuild_lane_state",
"procedure alg.usp_refresh_dirty_lanes");
@Test
void installCreatesOnlyAllowListedShadowObjects() throws IOException {
String install = resource("db/sqlserver/install-shadow.sql");
assertThat(createdObjects(install)).containsExactlyInAnyOrderElementsOf(ALLOWED_INSTALL_OBJECTS);
assertThat(install.toLowerCase(Locale.ROOT)).doesNotContain(
"usp_try_reserve_inbound",
"usp_release_reservation",
"usp_acquire_deep_workflow_lock",
"usp_release_deep_workflow_lock",
"usp_complete_move_job",
"usp_sync_replay_sequences",
"tidy_",
"outbound_workflow",
"proc_yk_complete");
}
@Test
void installNeverMutatesLegacyBusinessTables() throws IOException {
String install = normalize(resource("db/sqlserver/install-shadow.sql"));
assertThat(install).doesNotContain(
"insert into dbo.",
"update dbo.",
"delete from dbo.",
"merge dbo.",
"create trigger",
"on dbo.",
"dirty_lane_queue");
}
@Test
void installAndUninstallOwnExactlyTheSameObjects() throws IOException {
Set<String> installed = createdObjects(resource("db/sqlserver/install-shadow.sql"));
Set<String> removed = droppedObjects(resource("db/sqlserver/uninstall-shadow.sql"));
assertThat(removed).containsExactlyInAnyOrderElementsOf(installed);
}
@Test
void refreshPerformsFullSynchronizationWithoutLegacyTriggers() throws IOException {
String install = normalize(resource("db/sqlserver/install-shadow.sql"));
String refresh = install.substring(install.indexOf("create procedure alg.usp_refresh_dirty_lanes"));
assertThat(refresh).contains(
"exec alg.usp_rebuild_space_topology",
"exec alg.usp_sync_inventory_snapshot",
"exec alg.usp_rebuild_lane_state");
assertThat(refresh).doesNotContain("dirty_lane_queue", "tidy_lane_queue");
assertThat(install).contains(
"exec alg.usp_rebuild_space_topology",
"exec alg.usp_sync_inventory_snapshot",
"exec alg.usp_rebuild_lane_state");
}
@Test
void diagnosticsSupportsSafetyChecksAndDecisionExport() throws IOException {
String diagnostics = normalize(resource("db/sqlserver/diagnostics-shadow.sql"));
assertThat(diagnostics).contains(
"from alg.decision_log",
"inbound_compare",
"outbound_compare",
"from sys.dm_tran_locks",
"from dbo.bas_cargo_space",
"from dbo.bas_stock",
"from dbo.mydo_job");
assertThat(diagnostics).doesNotContain("insert ", "update ", "delete ", "merge ");
}
@Test
void installationAndDiagnosticsReadLegacyWithoutTakingSharedLocks() throws IOException {
String readUncommitted = "set transaction isolation level read uncommitted";
assertThat(normalize(resource("db/sqlserver/install-shadow.sql"))).contains(readUncommitted);
assertThat(normalize(resource("db/sqlserver/diagnostics-shadow.sql"))).contains(readUncommitted);
}
private Set<String> createdObjects(String sql) {
return objects(sql, Pattern.compile(
"(?i)create\\s+(table|view|procedure|trigger)\\s+([a-z0-9_]+\\.[a-z0-9_]+)"));
}
private Set<String> droppedObjects(String sql) {
return objects(sql, Pattern.compile(
"(?i)drop\\s+(table|view|procedure|trigger)\\s+([a-z0-9_]+\\.[a-z0-9_]+)"));
}
private Set<String> objects(String sql, Pattern pattern) {
Matcher matcher = pattern.matcher(sql);
Set<String> objects = new LinkedHashSet<>();
while (matcher.find()) {
objects.add((matcher.group(1) + " " + matcher.group(2)).toLowerCase(Locale.ROOT));
}
return objects;
}
private String normalize(String sql) {
return sql.toLowerCase(Locale.ROOT).replaceAll("\\s+", " ");
}
private String resource(String name) throws IOException {
try (var stream = getClass().getClassLoader().getResourceAsStream(name)) {
assertThat(stream).as("resource %s", name).isNotNull();
return new String(stream.readAllBytes(), StandardCharsets.UTF_8);
}
}
}

View File

@@ -1,200 +0,0 @@
package com.ruoyi.algorithm.repository;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import static org.assertj.core.api.Assertions.assertThat;
class SqlServerMigrationScriptTest {
@Test
void installDefinesPersistentOutboundWorkflowSchema() throws IOException {
String install = resource("db/sqlserver/install.sql");
assertThat(install).contains(
"CREATE TABLE alg.outbound_workflow (",
"CREATE TABLE alg.outbound_workflow_step (",
"CREATE TABLE alg.outbound_workflow_space (",
"CK_alg_outbound_workflow_status",
"CK_alg_outbound_workflow_step_phase",
"CK_alg_outbound_workflow_step_status",
"UX_alg_outbound_workflow_active_request",
"UX_alg_outbound_workflow_step_wms_job",
"UX_alg_outbound_workflow_space_active");
}
@Test
void installDefinesDirtyLaneQueueRefreshAndAllMutationSources() throws IOException {
String install = resource("db/sqlserver/install.sql");
assertThat(install).contains(
"CREATE TABLE alg.dirty_lane_queue (",
"CREATE PROCEDURE alg.usp_mark_dirty_lane",
"CREATE PROCEDURE alg.usp_refresh_dirty_lanes",
"CREATE TRIGGER dbo.trg_alg_dirty_bas_stock",
"CREATE TRIGGER dbo.trg_alg_dirty_bas_cargo_space",
"CREATE TRIGGER dbo.trg_alg_dirty_mydo_job",
"CREATE TRIGGER alg.trg_alg_dirty_space_role",
"CREATE TRIGGER alg.trg_alg_dirty_project_policy",
"CREATE TRIGGER alg.trg_alg_dirty_warehouse_policy");
}
@Test
void installDefinesPersistentTidyRuntimeSchemaAndConstraints() throws IOException {
String install = resource("db/sqlserver/install.sql");
assertThat(install).contains(
"CREATE TABLE alg.tidy_mode (",
"CREATE TABLE alg.tidy_lane_queue (",
"CREATE TABLE alg.tidy_workflow (",
"CREATE TABLE alg.tidy_step (",
"CREATE TABLE alg.tidy_space_lock (",
"CREATE TABLE alg.tidy_callback_event (",
"CK_alg_tidy_mode_effective_state",
"CK_alg_tidy_workflow_origin",
"CK_alg_tidy_workflow_status",
"CK_alg_tidy_space_lock_resource_type",
"UX_alg_tidy_space_lock_active",
"UX_alg_tidy_callback_event_key",
"UX_alg_tidy_workflow_active_auto",
"FK_alg_tidy_callback_event_step_workflow");
}
@Test
void installDefinesVersionedTidyLaneClaimAndSequenceSynchronization() throws IOException {
String install = resource("db/sqlserver/install.sql");
assertThat(install).contains(
"CREATE PROCEDURE alg.usp_claim_tidy_lane",
"@eligible_lane_codes varchar(max) = NULL",
"STRING_SPLIT(@eligible_lane_codes, '','')",
"CREATE PROCEDURE alg.usp_release_tidy_lane",
"CREATE PROCEDURE alg.usp_sync_replay_sequences",
"sp_getapplock",
"CK202607210002");
}
@Test
void installCompletesAlgorithmOwnedMoveJobsWithoutChangingTheLegacyProcedure() throws IOException {
String install = resource("db/sqlserver/install.sql");
assertThat(install).contains(
"CREATE PROCEDURE alg.usp_complete_move_job",
"in_or_out = ''28''",
"UPDATE dbo.bas_stock",
"INSERT INTO dbo.mydo_job_finish",
"DELETE FROM dbo.mydo_job");
assertThat(install).doesNotContain("ALTER PROCEDURE dbo.proc_yk_complete");
}
@Test
void dirtyLaneRefreshPublishesRebuiltLanesToPersistentTidyQueue() throws IOException {
String install = resource("db/sqlserver/install.sql");
String procedure = install.substring(
install.indexOf("CREATE PROCEDURE alg.usp_refresh_dirty_lanes"),
install.indexOf("IF OBJECT_ID(N'alg.usp_try_reserve_inbound'"));
int rebuildLaneState = procedure.lastIndexOf("EXEC alg.usp_rebuild_lane_state");
int publishTidyLane = procedure.indexOf("MERGE alg.tidy_lane_queue WITH (HOLDLOCK)");
assertThat(rebuildLaneState).isGreaterThanOrEqualTo(0);
assertThat(publishTidyLane).isGreaterThan(rebuildLaneState);
assertThat(procedure).contains(
"dirty_version = target.dirty_version + 1",
"queue_status = ''PENDING''");
}
@Test
void dirtyLaneRefreshReleasesQueueLocksBeforeReadingWarehouseTablesAndRequeuesFailures()
throws IOException {
String install = resource("db/sqlserver/install.sql");
String procedure = install.substring(
install.indexOf("CREATE PROCEDURE alg.usp_refresh_dirty_lanes"),
install.indexOf("IF OBJECT_ID(N'alg.usp_try_reserve_inbound'"));
int claimDelete = procedure.indexOf("DELETE queue");
int claimCommit = procedure.indexOf("COMMIT TRANSACTION;", claimDelete);
int warehouseRead = procedure.indexOf("FROM dbo.bas_cargo_space", claimDelete);
assertThat(claimDelete).isGreaterThanOrEqualTo(0);
assertThat(claimCommit).isBetween(claimDelete + 1, warehouseRead - 1);
assertThat(procedure).contains(
"BEGIN TRY",
"BEGIN CATCH",
"MERGE alg.dirty_lane_queue WITH (HOLDLOCK) AS target",
"THROW;");
}
@Test
void topologyRebuildExcludesUnconfiguredCargoSpacesAndDiagnosticsReportsThem()
throws IOException {
String install = resource("db/sqlserver/install.sql");
String diagnostics = resource("db/sqlserver/diagnostics.sql");
String procedure = install.substring(
install.indexOf("CREATE PROCEDURE alg.usp_rebuild_space_topology"),
install.indexOf("IF OBJECT_ID(N'alg.usp_sync_inventory_snapshot'"));
assertThat(procedure).contains(
"NULLIF(LTRIM(RTRIM(group_coding_no)), '''') IS NOT NULL",
"TRY_CONVERT(int, group_coding) IS NOT NULL");
assertThat(diagnostics).contains(
"excluded_from_algorithm_topology_count",
"group_coding_no IS NULL OR LTRIM(RTRIM(group_coding_no)) = ''",
"TRY_CONVERT(int, group_coding) IS NULL");
}
@Test
void diagnosticsAndUninstallCoverNewRuntimeObjects() throws IOException {
String diagnostics = resource("db/sqlserver/diagnostics.sql");
String uninstall = resource("db/sqlserver/uninstall.sql");
assertThat(diagnostics).contains(
"FROM alg.dirty_lane_queue",
"FROM alg.outbound_workflow",
"FROM alg.tidy_mode",
"FROM alg.tidy_lane_queue",
"FROM alg.tidy_workflow",
"FROM alg.tidy_space_lock",
"PAUSED_RECOVERY");
assertThat(uninstall).contains(
"DROP TRIGGER dbo.trg_alg_dirty_bas_stock",
"DROP PROCEDURE alg.usp_refresh_dirty_lanes",
"DROP PROCEDURE alg.usp_claim_tidy_lane",
"DROP PROCEDURE alg.usp_release_tidy_lane",
"DROP PROCEDURE alg.usp_sync_replay_sequences",
"DROP TABLE alg.tidy_callback_event",
"DROP TABLE alg.tidy_space_lock",
"DROP TABLE alg.tidy_step",
"DROP TABLE alg.tidy_workflow",
"DROP TABLE alg.tidy_lane_queue",
"DROP TABLE alg.tidy_mode",
"DROP TABLE alg.outbound_workflow_space",
"DROP TABLE alg.outbound_workflow_step",
"DROP TABLE alg.outbound_workflow",
"DROP TABLE alg.dirty_lane_queue");
}
@Test
void repeatedInstallDoesNotDiscardPendingDirtyLanes() throws IOException {
String install = resource("db/sqlserver/install.sql");
String initialization = install.substring(install.lastIndexOf("IF NOT EXISTS (SELECT 1 FROM alg.space_topology)"));
assertThat(initialization).contains(
"AND NOT EXISTS (SELECT 1 FROM alg.inventory_item_snapshot)",
"AND NOT EXISTS (SELECT 1 FROM alg.lane_state)");
assertThat(initialization.indexOf("DELETE FROM alg.dirty_lane_queue"))
.isLessThan(initialization.indexOf("END;"));
assertThat(initialization.substring(initialization.indexOf("END;") + 4))
.doesNotContain("DELETE FROM alg.dirty_lane_queue");
}
private String resource(String name) throws IOException {
try (var stream = getClass().getClassLoader().getResourceAsStream(name)) {
if (stream == null) {
throw new IOException("Missing test resource " + name);
}
return new String(stream.readAllBytes(), StandardCharsets.UTF_8);
}
}
}