Skip to content

Commit 0ae70a7

Browse files
committed
feat: add postgresql csv import and install commands
1 parent 03d7a25 commit 0ae70a7

7 files changed

Lines changed: 363 additions & 0 deletions

File tree

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
Set-StrictMode -Version Latest
2+
$ErrorActionPreference = 'Stop'
3+
4+
<#
5+
.SYNOPSIS
6+
生成 PostgreSQL CSV 导入命令描述。
7+
8+
.DESCRIPTION
9+
使用 `psql -c "\copy ..."` 方式构建本地 CSV 导入命令,
10+
第一版只覆盖“导入到已存在表”的场景。
11+
12+
.PARAMETER CliOptions
13+
解析后的子命令参数表。
14+
15+
.PARAMETER Context
16+
由 `Resolve-PgContext` 返回的统一连接上下文。
17+
18+
.OUTPUTS
19+
PSCustomObject
20+
返回可交给 `Invoke-PgNativeCommand` 的命令描述对象。
21+
#>
22+
function New-PgImportCsvCommandSpec {
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 'import-csv 命令缺少 --input。'
34+
}
35+
36+
if (-not $CliOptions.ContainsKey('table')) {
37+
throw 'import-csv 命令缺少 --table。'
38+
}
39+
40+
$schema = if ($CliOptions.ContainsKey('schema')) { [string]$CliOptions['schema'] } else { 'public' }
41+
$delimiter = if ($CliOptions.ContainsKey('delimiter')) { [string]$CliOptions['delimiter'] } else { ',' }
42+
$header = if ($CliOptions.ContainsKey('header')) { 'true' } else { 'false' }
43+
$columns = if ($CliOptions.ContainsKey('columns')) { "($($CliOptions['columns']))" } else { '' }
44+
$nullString = if ($CliOptions.ContainsKey('null_string')) { ", NULL '$($CliOptions['null_string'])'" } else { '' }
45+
$truncateSql = if ($CliOptions.ContainsKey('truncate_first')) { "TRUNCATE TABLE $schema.$($CliOptions['table']); " } else { '' }
46+
$copySql = "$truncateSql\copy $schema.$($CliOptions['table'])$columns FROM '$($CliOptions['input'])' WITH (FORMAT csv, HEADER $header, DELIMITER '$delimiter'$nullString);"
47+
48+
$arguments = @(
49+
'-h', $Context.Host,
50+
'-p', [string]$Context.Port,
51+
'-U', $Context.User,
52+
'-d', $Context.Database,
53+
'-v', 'ON_ERROR_STOP=1',
54+
'-c', $copySql
55+
)
56+
57+
return New-PgNativeCommandSpec -FilePath 'psql' -ArgumentList $arguments -Environment @{
58+
PGPASSWORD = $Context.Password
59+
}
60+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
Set-StrictMode -Version Latest
2+
$ErrorActionPreference = 'Stop'
3+
4+
<#
5+
.SYNOPSIS
6+
检测缺失的 PostgreSQL CLI 工具。
7+
8+
.DESCRIPTION
9+
默认检查 `psql`、`pg_dump`、`pg_restore`、`pg_dumpall`,
10+
为 `install-tools` 决定是否需要输出或执行安装计划。
11+
12+
.PARAMETER Tools
13+
要检查的工具名称列表。
14+
15+
.OUTPUTS
16+
string[]
17+
返回当前环境中缺失的工具名数组。
18+
#>
19+
function Get-MissingPgTools {
20+
[CmdletBinding()]
21+
param(
22+
[string[]]$Tools = @('psql', 'pg_dump', 'pg_restore', 'pg_dumpall')
23+
)
24+
25+
return @(
26+
foreach ($tool in $Tools) {
27+
if (-not (Get-Command $tool -ErrorAction SilentlyContinue)) {
28+
$tool
29+
}
30+
}
31+
)
32+
}
33+
34+
<#
35+
.SYNOPSIS
36+
生成 PostgreSQL CLI 安装计划。
37+
38+
.DESCRIPTION
39+
根据平台与包管理器策略选择对应的安装命令列表。
40+
41+
.PARAMETER Platform
42+
目标平台,只支持 `windows`、`macos`、`linux`。
43+
44+
.PARAMETER PackageManager
45+
指定包管理器名称;`auto` 会自动选择默认策略。
46+
47+
.PARAMETER Tools
48+
需要安装或检测的 PostgreSQL CLI 工具名列表。
49+
50+
.OUTPUTS
51+
PSCustomObject
52+
返回包管理器名称和待执行命令列表。
53+
#>
54+
function Get-PgInstallPlan {
55+
[CmdletBinding()]
56+
param(
57+
[Parameter(Mandatory)]
58+
[ValidateSet('windows', 'macos', 'linux')]
59+
[string]$Platform,
60+
61+
[string]$PackageManager = 'auto',
62+
63+
[string[]]$Tools = @('psql', 'pg_dump', 'pg_restore', 'pg_dumpall')
64+
)
65+
66+
switch ($Platform) {
67+
'windows' { return (Get-PgWindowsInstallPlan -PackageManager $PackageManager) }
68+
'macos' { return (Get-PgMacOSInstallPlan -PackageManager $PackageManager) }
69+
'linux' { return (Get-PgLinuxInstallPlan -PackageManager $PackageManager) }
70+
}
71+
}
72+
73+
<#
74+
.SYNOPSIS
75+
输出或执行 PostgreSQL CLI 安装计划。
76+
77+
.DESCRIPTION
78+
默认仅返回安装命令文本;传入 `-Apply` 时按平台选择 shell 执行命令。
79+
80+
.PARAMETER Plan
81+
由 `Get-PgInstallPlan` 返回的安装计划对象。
82+
83+
.PARAMETER Apply
84+
是否执行安装命令。
85+
86+
.OUTPUTS
87+
PSCustomObject
88+
返回包含 `ExitCode` 与 `Output` 的标准结果对象。
89+
#>
90+
function Invoke-PgInstallPlan {
91+
[CmdletBinding()]
92+
param(
93+
[Parameter(Mandatory)]
94+
[pscustomobject]$Plan,
95+
96+
[switch]$Apply
97+
)
98+
99+
if (-not $Apply) {
100+
return [PSCustomObject]@{
101+
ExitCode = 0
102+
Output = ($Plan.Commands -join [Environment]::NewLine)
103+
}
104+
}
105+
106+
$runner = if ($IsWindows) {
107+
@{
108+
FilePath = 'pwsh'
109+
ArgumentList = @('-NoProfile', '-Command')
110+
}
111+
}
112+
else {
113+
@{
114+
FilePath = '/bin/sh'
115+
ArgumentList = @('-lc')
116+
}
117+
}
118+
119+
foreach ($commandText in $Plan.Commands) {
120+
Write-PostgresToolkitMessage -Level info -Message ("执行安装命令: {0}" -f $commandText)
121+
$null = & $runner.FilePath @($runner.ArgumentList + $commandText)
122+
if ($LASTEXITCODE -ne 0) {
123+
throw "安装命令执行失败: $commandText"
124+
}
125+
}
126+
127+
return [PSCustomObject]@{
128+
ExitCode = 0
129+
Output = ($Plan.Commands -join [Environment]::NewLine)
130+
}
131+
}

scripts/pwsh/devops/postgresql/main.ps1

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,30 @@ function Invoke-PostgresToolkitCommand {
6868
$spec = New-PgRestoreCommandSpec -CliOptions $options -Context $context
6969
return Invoke-PgNativeCommand -Spec $spec -DryRun:$dryRun
7070
}
71+
'import-csv' {
72+
$spec = New-PgImportCsvCommandSpec -CliOptions $options -Context $context
73+
return Invoke-PgNativeCommand -Spec $spec -DryRun:$dryRun
74+
}
75+
'install-tools' {
76+
$platform = if ($IsWindows) { 'windows' } elseif ($IsMacOS) { 'macos' } else { 'linux' }
77+
$requestedTools = if ($options.ContainsKey('tool')) {
78+
@([string]$options['tool'] -split ',')
79+
}
80+
else {
81+
@('psql', 'pg_dump', 'pg_restore', 'pg_dumpall')
82+
}
83+
$missingTools = Get-MissingPgTools -Tools $requestedTools
84+
if ($missingTools.Count -eq 0) {
85+
return [PSCustomObject]@{
86+
ExitCode = 0
87+
Output = '所有 PostgreSQL CLI 工具已可用。'
88+
}
89+
}
90+
91+
$packageManager = if ($options.ContainsKey('package_manager')) { [string]$options['package_manager'] } else { 'auto' }
92+
$plan = Get-PgInstallPlan -Platform $platform -PackageManager $packageManager -Tools $missingTools
93+
return Invoke-PgInstallPlan -Plan $plan -Apply:$($options.ContainsKey('apply'))
94+
}
7195
default {
7296
return [PSCustomObject]@{
7397
ExitCode = 0
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
Set-StrictMode -Version Latest
2+
$ErrorActionPreference = 'Stop'
3+
4+
<#
5+
.SYNOPSIS
6+
生成 Linux 下的 PostgreSQL CLI 安装方案。
7+
8+
.DESCRIPTION
9+
当前支持 `apt`、`dnf`、`yum`、`apk`,`auto` 默认选择 `apt`。
10+
11+
.PARAMETER PackageManager
12+
指定包管理器名称;`auto` 会自动选择默认策略。
13+
14+
.OUTPUTS
15+
PSCustomObject
16+
返回包管理器名称和待执行命令列表。
17+
#>
18+
function Get-PgLinuxInstallPlan {
19+
[CmdletBinding()]
20+
param(
21+
[string]$PackageManager = 'auto'
22+
)
23+
24+
$manager = if ($PackageManager -eq 'auto') { 'apt' } else { $PackageManager }
25+
$command = switch ($manager) {
26+
'apt' { 'sudo apt-get update && sudo apt-get install -y postgresql-client' }
27+
'dnf' { 'sudo dnf install -y postgresql' }
28+
'yum' { 'sudo yum install -y postgresql' }
29+
'apk' { 'sudo apk add postgresql-client' }
30+
default { throw "Linux 不支持的包管理器: $manager" }
31+
}
32+
33+
return [PSCustomObject]@{
34+
PackageManager = $manager
35+
Commands = @($command)
36+
}
37+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
Set-StrictMode -Version Latest
2+
$ErrorActionPreference = 'Stop'
3+
4+
<#
5+
.SYNOPSIS
6+
生成 macOS 下的 PostgreSQL CLI 安装方案。
7+
8+
.DESCRIPTION
9+
当前只支持 Homebrew 路径,`auto` 会默认选择 `brew`。
10+
11+
.PARAMETER PackageManager
12+
指定包管理器名称;`auto` 会自动选择默认策略。
13+
14+
.OUTPUTS
15+
PSCustomObject
16+
返回包管理器名称和待执行命令列表。
17+
#>
18+
function Get-PgMacOSInstallPlan {
19+
[CmdletBinding()]
20+
param(
21+
[string]$PackageManager = 'auto'
22+
)
23+
24+
$manager = if ($PackageManager -eq 'auto') { 'brew' } else { $PackageManager }
25+
if ($manager -ne 'brew') {
26+
throw "macOS 不支持的包管理器: $manager"
27+
}
28+
29+
return [PSCustomObject]@{
30+
PackageManager = $manager
31+
Commands = @('brew install libpq', 'brew link --force libpq')
32+
}
33+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
Set-StrictMode -Version Latest
2+
$ErrorActionPreference = 'Stop'
3+
4+
<#
5+
.SYNOPSIS
6+
生成 Windows 下的 PostgreSQL CLI 安装方案。
7+
8+
.DESCRIPTION
9+
当前默认优先使用 `winget`,并支持显式切换到 `choco`。
10+
11+
.PARAMETER PackageManager
12+
指定包管理器名称;`auto` 会自动选择默认策略。
13+
14+
.OUTPUTS
15+
PSCustomObject
16+
返回包管理器名称和待执行命令列表。
17+
#>
18+
function Get-PgWindowsInstallPlan {
19+
[CmdletBinding()]
20+
param(
21+
[string]$PackageManager = 'auto'
22+
)
23+
24+
$manager = if ($PackageManager -eq 'auto') { 'winget' } else { $PackageManager }
25+
$command = switch ($manager) {
26+
'winget' { 'winget install --id PostgreSQL.PostgreSQL --source winget' }
27+
'choco' { 'choco install postgresql --yes' }
28+
default { throw "Windows 不支持的包管理器: $manager" }
29+
}
30+
31+
return [PSCustomObject]@{
32+
PackageManager = $manager
33+
Commands = @($command)
34+
}
35+
}

tests/PostgresToolkit.Commands.Tests.ps1

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ BeforeAll {
1515
'scripts/pwsh/devops/postgresql/commands/help.ps1'
1616
'scripts/pwsh/devops/postgresql/commands/backup.ps1'
1717
'scripts/pwsh/devops/postgresql/commands/restore.ps1'
18+
'scripts/pwsh/devops/postgresql/commands/import-csv.ps1'
19+
'scripts/pwsh/devops/postgresql/commands/install-tools.ps1'
20+
'scripts/pwsh/devops/postgresql/platforms/windows.ps1'
21+
'scripts/pwsh/devops/postgresql/platforms/macos.ps1'
22+
'scripts/pwsh/devops/postgresql/platforms/linux.ps1'
1823
'scripts/pwsh/devops/postgresql/main.ps1'
1924
)) {
2025
. (Join-Path $script:RepoRoot $relativePath)
@@ -102,3 +107,41 @@ Describe 'New-PgRestoreCommandSpec' {
102107
$spec.ArgumentList | Should -Contain '--clean'
103108
}
104109
}
110+
111+
Describe 'New-PgImportCsvCommandSpec' {
112+
It '生成带 header 的 \copy 语句' {
113+
$csvPath = Join-Path $TestDrive 'users.csv'
114+
Set-Content -Path $csvPath -Value "id,name`n1,Alice"
115+
116+
$spec = New-PgImportCsvCommandSpec -CliOptions @{
117+
input = $csvPath
118+
table = 'users'
119+
header = $true
120+
} -Context ([PSCustomObject]@{
121+
Host = '127.0.0.1'
122+
Port = 5432
123+
User = 'postgres'
124+
Password = 'secret'
125+
Database = 'app'
126+
})
127+
128+
$spec.FilePath | Should -Be 'psql'
129+
($spec.ArgumentList -join ' ') | Should -Match '\\copy public.users'
130+
($spec.ArgumentList -join ' ') | Should -Match 'HEADER true'
131+
}
132+
}
133+
134+
Describe 'Get-PgInstallPlan' {
135+
It 'Windows auto 策略优先返回 winget 命令' {
136+
$plan = Get-PgInstallPlan -Platform 'windows' -PackageManager 'auto' -Tools @('psql', 'pg_dump')
137+
138+
$plan.PackageManager | Should -Be 'winget'
139+
$plan.Commands[0] | Should -Match 'winget'
140+
}
141+
142+
It 'Linux apt 策略返回 apt install 命令' {
143+
$plan = Get-PgInstallPlan -Platform 'linux' -PackageManager 'apt' -Tools @('psql')
144+
145+
$plan.Commands[0] | Should -Match 'apt-get install'
146+
}
147+
}

0 commit comments

Comments
 (0)