Skip to content

Commit 03d7a25

Browse files
committed
feat: add postgresql backup and restore commands
1 parent c5a4c28 commit 03d7a25

5 files changed

Lines changed: 232 additions & 2 deletions

File tree

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
Set-StrictMode -Version Latest
2+
$ErrorActionPreference = 'Stop'
3+
4+
<#
5+
.SYNOPSIS
6+
生成 PostgreSQL 备份命令描述。
7+
8+
.DESCRIPTION
9+
将 CLI 参数和已解析的连接上下文翻译为 `pg_dump` 的参数数组,
10+
供 dry-run 和真实执行共用。
11+
12+
.PARAMETER CliOptions
13+
解析后的子命令参数表。
14+
15+
.PARAMETER Context
16+
由 `Resolve-PgContext` 返回的统一连接上下文。
17+
18+
.OUTPUTS
19+
PSCustomObject
20+
返回可交给 `Invoke-PgNativeCommand` 的命令描述对象。
21+
#>
22+
function New-PgBackupCommandSpec {
23+
[CmdletBinding()]
24+
param(
25+
[Parameter(Mandatory)]
26+
[hashtable]$CliOptions,
27+
28+
[Parameter(Mandatory)]
29+
[pscustomobject]$Context
30+
)
31+
32+
$format = if ($CliOptions.ContainsKey('format')) { [string]$CliOptions['format'] } else { 'custom' }
33+
if ($format -ne 'directory' -and $CliOptions.ContainsKey('jobs')) {
34+
throw '只有 directory 格式支持 --jobs。'
35+
}
36+
37+
Assert-PgMutuallyExclusiveOptions `
38+
-Left ($CliOptions.ContainsKey('schema_only')) `
39+
-Right ($CliOptions.ContainsKey('data_only')) `
40+
-LeftName '--schema-only' `
41+
-RightName '--data-only'
42+
43+
$arguments = @(
44+
'-h', $Context.Host,
45+
'-p', [string]$Context.Port,
46+
'-U', $Context.User,
47+
'-d', $Context.Database
48+
)
49+
50+
$arguments += switch ($format) {
51+
'plain' { '-Fp' }
52+
'directory' { '-Fd' }
53+
'tar' { '-Ft' }
54+
default { '-Fc' }
55+
}
56+
57+
if ($CliOptions.ContainsKey('output')) { $arguments += @('-f', [string]$CliOptions['output']) }
58+
if ($CliOptions.ContainsKey('table')) { $arguments += @('-t', [string]$CliOptions['table']) }
59+
if ($CliOptions.ContainsKey('schema')) { $arguments += @('-n', [string]$CliOptions['schema']) }
60+
if ($CliOptions.ContainsKey('exclude_table')) { $arguments += "--exclude-table=$($CliOptions['exclude_table'])" }
61+
if ($CliOptions.ContainsKey('schema_only')) { $arguments += '-s' }
62+
if ($CliOptions.ContainsKey('data_only')) { $arguments += '-a' }
63+
if ($CliOptions.ContainsKey('jobs')) { $arguments += @('-j', [string]$CliOptions['jobs']) }
64+
65+
return New-PgNativeCommandSpec -FilePath 'pg_dump' -ArgumentList $arguments -Environment @{
66+
PGPASSWORD = $Context.Password
67+
}
68+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
Set-StrictMode -Version Latest
2+
$ErrorActionPreference = 'Stop'
3+
4+
<#
5+
.SYNOPSIS
6+
生成 PostgreSQL 恢复命令描述。
7+
8+
.DESCRIPTION
9+
根据输入类型自动在 `psql` 与 `pg_restore` 之间切换,
10+
并把 CLI 参数翻译成对应的参数数组。
11+
12+
.PARAMETER CliOptions
13+
解析后的子命令参数表。
14+
15+
.PARAMETER Context
16+
由 `Resolve-PgContext` 返回的统一连接上下文。
17+
18+
.OUTPUTS
19+
PSCustomObject
20+
返回可交给 `Invoke-PgNativeCommand` 的命令描述对象。
21+
#>
22+
function New-PgRestoreCommandSpec {
23+
[CmdletBinding()]
24+
param(
25+
[Parameter(Mandatory)]
26+
[hashtable]$CliOptions,
27+
28+
[Parameter(Mandatory)]
29+
[pscustomobject]$Context
30+
)
31+
32+
if (-not $CliOptions.ContainsKey('input')) {
33+
throw 'restore 命令缺少 --input。'
34+
}
35+
36+
$inputPath = [string]$CliOptions['input']
37+
$inputKind = Resolve-PgRestoreInputKind -InputPath $inputPath
38+
$targetDatabase = if ($CliOptions.ContainsKey('target_database')) { [string]$CliOptions['target_database'] } else { $Context.Database }
39+
40+
if ($inputKind -eq 'sql') {
41+
$arguments = @(
42+
'-h', $Context.Host,
43+
'-p', [string]$Context.Port,
44+
'-U', $Context.User,
45+
'-d', $targetDatabase,
46+
'-v', 'ON_ERROR_STOP=1',
47+
'-f', $inputPath
48+
)
49+
50+
return New-PgNativeCommandSpec -FilePath 'psql' -ArgumentList $arguments -Environment @{
51+
PGPASSWORD = $Context.Password
52+
}
53+
}
54+
55+
$arguments = @(
56+
'-h', $Context.Host,
57+
'-p', [string]$Context.Port,
58+
'-U', $Context.User,
59+
'-d', $targetDatabase
60+
)
61+
62+
if ($CliOptions.ContainsKey('clean')) { $arguments += '--clean' }
63+
if ($CliOptions.ContainsKey('if_exists')) { $arguments += '--if-exists' }
64+
if ($CliOptions.ContainsKey('no_owner')) { $arguments += '--no-owner' }
65+
if ($CliOptions.ContainsKey('no_privileges')) { $arguments += '--no-privileges' }
66+
if ($CliOptions.ContainsKey('schema_only')) { $arguments += '-s' }
67+
if ($CliOptions.ContainsKey('data_only')) { $arguments += '-a' }
68+
if ($CliOptions.ContainsKey('table')) { $arguments += @('-t', [string]$CliOptions['table']) }
69+
if ($CliOptions.ContainsKey('schema')) { $arguments += @('-n', [string]$CliOptions['schema']) }
70+
if ($CliOptions.ContainsKey('jobs')) { $arguments += @('-j', [string]$CliOptions['jobs']) }
71+
$arguments += $inputPath
72+
73+
return New-PgNativeCommandSpec -FilePath 'pg_restore' -ArgumentList $arguments -Environment @{
74+
PGPASSWORD = $Context.Password
75+
}
76+
}

scripts/pwsh/devops/postgresql/core/arguments.ps1

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@ $ErrorActionPreference = 'Stop'
1919
function ConvertFrom-LongOptionList {
2020
[CmdletBinding()]
2121
param(
22-
[Parameter(Mandatory)]
22+
[AllowEmptyCollection()]
2323
[string[]]$Arguments
2424
)
2525

26+
if ($null -eq $Arguments -or $Arguments.Count -eq 0) {
27+
return @{}
28+
}
29+
2630
$result = @{}
2731
$index = 0
2832

scripts/pwsh/devops/postgresql/main.ps1

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,33 @@ function Invoke-PostgresToolkitCommand {
4848
[string[]]$RawArguments
4949
)
5050

51+
$options = ConvertFrom-LongOptionList -Arguments $RawArguments
52+
$dryRun = $options.ContainsKey('dry_run')
53+
5154
if ([string]::IsNullOrWhiteSpace($CommandName) -or $CommandName -eq 'help') {
5255
return [PSCustomObject]@{
5356
ExitCode = 0
5457
Output = Get-PostgresToolkitHelpText
5558
}
5659
}
5760

58-
throw "未知命令: $CommandName"
61+
$context = Resolve-PgContext -CliOptions $options
62+
switch ($CommandName) {
63+
'backup' {
64+
$spec = New-PgBackupCommandSpec -CliOptions $options -Context $context
65+
return Invoke-PgNativeCommand -Spec $spec -DryRun:$dryRun
66+
}
67+
'restore' {
68+
$spec = New-PgRestoreCommandSpec -CliOptions $options -Context $context
69+
return Invoke-PgNativeCommand -Spec $spec -DryRun:$dryRun
70+
}
71+
default {
72+
return [PSCustomObject]@{
73+
ExitCode = 0
74+
Output = Get-PostgresToolkitHelpText
75+
}
76+
}
77+
}
5978
}
6079

6180
if ($env:PWSH_TEST_SKIP_POSTGRES_TOOLKIT_MAIN -ne '1') {

tests/PostgresToolkit.Commands.Tests.ps1

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ BeforeAll {
1313
'scripts/pwsh/devops/postgresql/core/formats.ps1'
1414
'scripts/pwsh/devops/postgresql/core/validation.ps1'
1515
'scripts/pwsh/devops/postgresql/commands/help.ps1'
16+
'scripts/pwsh/devops/postgresql/commands/backup.ps1'
17+
'scripts/pwsh/devops/postgresql/commands/restore.ps1'
1618
'scripts/pwsh/devops/postgresql/main.ps1'
1719
)) {
1820
. (Join-Path $script:RepoRoot $relativePath)
@@ -39,3 +41,64 @@ Describe 'Invoke-PostgresToolkitCommand' {
3941
$result.Output | Should -Match 'Usage'
4042
}
4143
}
44+
45+
Describe 'New-PgBackupCommandSpec' {
46+
It '默认生成 custom 格式 pg_dump 命令' {
47+
$spec = New-PgBackupCommandSpec -CliOptions @{
48+
database = 'app'
49+
output = './app.dump'
50+
} -Context ([PSCustomObject]@{
51+
Host = '127.0.0.1'
52+
Port = 5432
53+
User = 'postgres'
54+
Password = 'secret'
55+
Database = 'app'
56+
})
57+
58+
$spec.FilePath | Should -Be 'pg_dump'
59+
($spec.ArgumentList -join ' ') | Should -Match '-Fc'
60+
($spec.ArgumentList -join ' ') | Should -Match 'app.dump'
61+
}
62+
}
63+
64+
Describe 'New-PgRestoreCommandSpec' {
65+
It 'sql 文件切换到 psql 恢复路径' {
66+
$inputPath = Join-Path $TestDrive 'sample.sql'
67+
Set-Content -Path $inputPath -Value '-- sql'
68+
69+
$spec = New-PgRestoreCommandSpec -CliOptions @{
70+
input = $inputPath
71+
target_database = 'restore_db'
72+
} -Context ([PSCustomObject]@{
73+
Host = '127.0.0.1'
74+
Port = 5432
75+
User = 'postgres'
76+
Password = 'secret'
77+
Database = 'postgres'
78+
})
79+
80+
$spec.FilePath | Should -Be 'psql'
81+
($spec.ArgumentList -join ' ') | Should -Match 'restore_db'
82+
($spec.ArgumentList -join ' ') | Should -Match '-f'
83+
}
84+
85+
It 'archive 文件走 pg_restore 并支持 --clean' {
86+
$inputPath = Join-Path $TestDrive 'sample.dump'
87+
Set-Content -Path $inputPath -Value 'archive'
88+
89+
$spec = New-PgRestoreCommandSpec -CliOptions @{
90+
input = $inputPath
91+
target_database = 'restore_db'
92+
clean = $true
93+
} -Context ([PSCustomObject]@{
94+
Host = '127.0.0.1'
95+
Port = 5432
96+
User = 'postgres'
97+
Password = 'secret'
98+
Database = 'postgres'
99+
})
100+
101+
$spec.FilePath | Should -Be 'pg_restore'
102+
$spec.ArgumentList | Should -Contain '--clean'
103+
}
104+
}

0 commit comments

Comments
 (0)