93 lines
7.2 KiB
PowerShell
93 lines
7.2 KiB
PowerShell
param(
|
|
[Parameter(Mandatory=$true)][string]$Server,
|
|
[Parameter(Mandatory=$true)][string]$Database,
|
|
[switch]$TestDatabase
|
|
)
|
|
$ErrorActionPreference='Stop'
|
|
[Console]::OutputEncoding=[Text.UTF8Encoding]::new($false)
|
|
if($TestDatabase -and $Database -notmatch '^FT546WMS_MixedPilot_Test_[a-f0-9]{32}$'){throw 'Invalid isolated test database name'}
|
|
if(-not $TestDatabase -and $Database -ine 'FT546-wms'){throw 'Only FT546-wms is supported'}
|
|
$builder=[Data.SqlClient.SqlConnectionStringBuilder]::new()
|
|
$builder['Data Source']=$Server; $builder['Initial Catalog']=$Database; $builder['Integrated Security']=$true
|
|
$builder['Connect Timeout']=8; $builder['Application Name']='FT546MixedPilotInstaller'
|
|
$cn=[Data.SqlClient.SqlConnection]::new($builder.ConnectionString);$cn.Open();$tx=$null
|
|
function Cmd([string]$sql) {
|
|
$c=$cn.CreateCommand();$c.CommandText=$sql;$c.CommandTimeout=15
|
|
if($tx){$c.Transaction=$tx};return $c
|
|
}
|
|
function Exec([string]$sql) {$c=Cmd $sql;try{[void]$c.ExecuteNonQuery()}finally{$c.Dispose()}}
|
|
function Scalar([string]$sql) {$c=Cmd $sql;try{$c.ExecuteScalar()}finally{$c.Dispose()}}
|
|
function Hash([string]$s) {
|
|
$sha=[Security.Cryptography.SHA256]::Create()
|
|
try{([BitConverter]::ToString($sha.ComputeHash([Text.Encoding]::Unicode.GetBytes($s)))).Replace('-','')}finally{$sha.Dispose()}
|
|
}
|
|
function RenameProc([string]$sql,[string]$name) {
|
|
$regex=[regex]::new('(?i)\bCREATE\s+PROCEDURE\s+(?:\[?dbo\]?\.)?\[?proc_[a-z_]+\]?')
|
|
if($regex.Matches($sql).Count -ne 1){throw 'Unexpected procedure header'}
|
|
$regex.Replace($sql,('CREATE PROCEDURE [mixed_pilot].['+$name+']'),1)
|
|
}
|
|
try {
|
|
Exec 'SET LOCK_TIMEOUT 2000; SET DEADLOCK_PRIORITY LOW;'
|
|
$tx=$cn.BeginTransaction()
|
|
Exec "DECLARE @r int; EXEC @r=sys.sp_getapplock @Resource='FT546.mixed_pilot.install',@LockMode='Exclusive',@LockOwner='Transaction',@LockTimeout=0; IF @r<0 THROW 51140,'Another pilot installation is in progress',1;"
|
|
if((Scalar "SELECT OBJECT_ID('mixed_pilot.control','U')") -isnot [DBNull]){
|
|
if((Scalar 'SELECT installed FROM mixed_pilot.control WHERE id=1') -ne 1){throw 'Previously uninstalled package: audit data retained; prepare a new reviewed installation rather than overwriting it'}
|
|
if((Scalar 'SELECT version FROM mixed_pilot.control WHERE id=1') -ne '20260910-1'){throw 'Unsupported existing mixed-pilot version'}
|
|
if((Scalar "SELECT COUNT(*) FROM mixed_pilot.module_backup WHERE installed_hash<>HASHBYTES('SHA2_256',OBJECT_DEFINITION(OBJECT_ID(object_name))) OR OBJECT_DEFINITION(OBJECT_ID(object_name)) IS NULL") -ne 0){throw 'Installed procedure drift detected; refusing overwrite'}
|
|
if((Scalar "SELECT OBJECT_ID('mixed_pilot.installed_module','U')") -is [DBNull]){throw 'Missing installed-module manifest'}
|
|
if((Scalar "SELECT COUNT(*) FROM mixed_pilot.installed_module WHERE definition_hash<>HASHBYTES('SHA2_256',OBJECT_DEFINITION(OBJECT_ID(object_name))) OR OBJECT_DEFINITION(OBJECT_ID(object_name)) IS NULL") -ne 0){throw 'Pilot helper/trigger drift detected; refusing to report success'}
|
|
foreach($table in @('lane','admission','move_audit','dispatch_hold','module_backup')){
|
|
if((Scalar "SELECT OBJECT_ID('mixed_pilot.$table','U')") -is [DBNull]){throw "Missing pilot table: $table"}
|
|
}
|
|
$tx.Commit();$tx.Dispose();$tx=$null;Write-Output 'ALREADY_INSTALLED: switches and data preserved';return
|
|
}
|
|
$expected=[ordered]@{
|
|
proc_allot_space='DFF37262FD49271E9E6E657F01697731F1FF73DCD6DECA1F340107D560E26850'
|
|
proc_yk_job_add='72708181352148DAF9490DD59FCBD0E3AC7A8C849EE8FC4AFCBFA0B1AF79AB5B'
|
|
proc_yk_complete='C80CD445262BB483E7A7BDCF7DB97A44AD07A09D491869A60C094C5037621131'
|
|
}
|
|
$original=@{}
|
|
foreach($name in $expected.Keys){
|
|
$definition=Scalar "SELECT OBJECT_DEFINITION(OBJECT_ID('dbo.$name'))"
|
|
if($definition -is [DBNull] -or (Hash $definition) -ne $expected[$name]){throw "Site procedure differs from reviewed baseline: $name. Nothing installed."}
|
|
$original[$name]=$definition
|
|
}
|
|
# No stock/tasks/slot metadata are changed by installation. Admission remains OFF.
|
|
Exec "IF SCHEMA_ID('mixed_pilot') IS NOT NULL THROW 51141,'Unexpected pre-existing mixed_pilot schema',1;"
|
|
Exec 'CREATE SCHEMA mixed_pilot AUTHORIZATION dbo'
|
|
foreach($batch in ([IO.File]::ReadAllText("$PSScriptRoot/objects.sql") -split '(?im)^\s*GO\s*\r?$')){if($batch.Trim()){Exec $batch}}
|
|
foreach($name in $expected.Keys){
|
|
$c=Cmd "INSERT mixed_pilot.module_backup(object_name,original_definition,original_hash,ansi_nulls,quoted_identifier) SELECT @name,definition,HASHBYTES('SHA2_256',definition),uses_ansi_nulls,uses_quoted_identifier FROM sys.sql_modules WHERE object_id=OBJECT_ID(@name)"
|
|
[void]$c.Parameters.AddWithValue('@name',"dbo.$name");try{[void]$c.ExecuteNonQuery()}finally{$c.Dispose()}
|
|
$ansi=Scalar "SELECT ansi_nulls FROM mixed_pilot.module_backup WHERE object_name='dbo.$name'"
|
|
$quoted=Scalar "SELECT quoted_identifier FROM mixed_pilot.module_backup WHERE object_name='dbo.$name'"
|
|
Exec (('SET ANSI_NULLS '+$(if($ansi){'ON'}else{'OFF'}))+'; SET QUOTED_IDENTIFIER '+$(if($quoted){'ON'}else{'OFF'})+';')
|
|
Exec (RenameProc $original[$name] ('original_'+$name.Substring(5)))
|
|
}
|
|
Exec 'SET ANSI_NULLS ON; SET QUOTED_IDENTIFIER ON;'
|
|
$inbound=RenameProc $original['proc_allot_space'] 'allot_space_with_fallback'
|
|
$noSlot=[regex]::new("(?is)set\s+@v_result\s*=\s*2\s+set\s+@v_resultmsg\s*=\s*'[^']+'\s+RETURN")
|
|
if($noSlot.Matches($inbound).Count -ne 2){throw 'Expected two Legacy no-position exits'}
|
|
$inbound=$noSlot.Replace($inbound,[Text.RegularExpressions.MatchEvaluator]{param($match)
|
|
'EXEC mixed_pilot.try_fallback @v_job_id,@v_cargo_space_id OUTPUT;'+"`r`n"+
|
|
'IF @v_cargo_space_id IS NULL BEGIN '+$match.Value+' END;'+"`r`n"+
|
|
'GOTO mixed_pilot_destination;'
|
|
})
|
|
$destination=[regex]::new('(?i)SELECT\s+@v_job_id\s*=\s*job_id\s+from\s+mydo_job\s+where\s+tray_code\s*=\s*@v_tray_code')
|
|
if($destination.Matches($inbound).Count -ne 1){throw 'Expected one common Legacy destination block'}
|
|
$inbound=$destination.Replace($inbound,@'
|
|
mixed_pilot_destination:
|
|
EXEC mixed_pilot.check_inbound @v_job_id,@v_cargo_space_id;
|
|
SELECT @v_job_id = job_id from mydo_job where tray_code = @v_tray_code
|
|
'@)
|
|
Exec $inbound
|
|
foreach($batch in ([IO.File]::ReadAllText("$PSScriptRoot/wrappers.sql") -split '(?im)^\s*GO\s*\r?$')){if($batch.Trim()){Exec $batch}}
|
|
Exec "UPDATE mixed_pilot.module_backup SET installed_hash=HASHBYTES('SHA2_256',OBJECT_DEFINITION(OBJECT_ID(object_name)))"
|
|
Exec "INSERT mixed_pilot.installed_module(object_name,definition_hash) SELECT QUOTENAME(OBJECT_SCHEMA_NAME(object_id))+'.'+QUOTENAME(OBJECT_NAME(object_id)),HASHBYTES('SHA2_256',definition) FROM sys.sql_modules WHERE OBJECT_SCHEMA_NAME(object_id)='mixed_pilot' OR OBJECT_NAME(object_id) IN ('tr_mixed_pilot_job_guard','tr_mixed_pilot_slot_release','tr_mixed_pilot_stock_release')"
|
|
$tx.Commit();$tx.Dispose();$tx=$null
|
|
Write-Output 'INSTALL_OK: pilot OFF; three C lanes configured but not enabled; Legacy definitions backed up in mixed_pilot.module_backup'
|
|
} catch {
|
|
if($tx){try{$tx.Rollback()}catch{};$tx.Dispose();$tx=$null}
|
|
throw
|
|
} finally {$cn.Dispose()}
|