Featured image of post PolStudio

PolStudio

高效、便捷的数据库查询/脚本管理工具

简介

PolStudio 是一款高效、便捷的数据库查询/脚本管理工具,专注于帮助您轻松保存、查询和执行 SQL 语句或脚本代码。

主要功能特点

  • 语句保存与管理:允许用户保存常用的 SQL 语句或脚本语句,方便随时调用和修改。通过分类和标签功能,用户可以轻松组织和管理代码库或查看历史版本。
  • 元数据支持:允许为语句添加Markdown、Html、备注等信息。
  • 快速查询与执行:内置高性能的查询引擎,PolStudio 能够迅速执行 SQL 查询并展示结果。同时,支持多数据库连接,让用户能够无缝切换不同的数据库环境。
  • 结果处理与导出:执行查询后,PolStudio 提供直观的结果展示,将结果导出为 CSV、Excel 等多种格式,并支持通过 PowerShell 或 Python 脚本对查询结果进行进一步处理。用户可以自定义脚本,对数据进行清洗、分析或可视化。
  • 安全性:重视数据的安全性,数据保存在加密数据库中,确保敏感数据不被非法访问。
  • 集成与扩展:支持与多种主流开发环境和工具集成,如 Visual Studio Code 等。同时,提供了丰富的扩展接口,允许用户根据自己的需求定制功能。

适用场景

  • 数据库管理员:轻松查找和执行已保存的 SQL 查询和脚本。
  • 数据分析师:快速查询和分析数据,通过 PowerShell 或 Python 脚本对数据进行处理。
  • 软件开发者:在开发过程中,保存和执行 SQL 语句,并可以查看历史版本(需添加该自定义功能)。
  • 普通用户:如果您不会编写脚本,您可以在这里找到已经过验证的脚本或其他用户分享的脚本直接运行,实现相应的功能。

下载安装

环境要求

建议根据需要安装下面的扩展程序。(在新建向导中会有相应提示官网下载链接)

  • Windows Terminal (建议安装,Windows11已内置)
  • Powershell Core
  • Python

快速入门

您可以在其他数据库查询工具(对于SQL,可以使用DBeaver;对于Powershell和Python,可以使用visual studio code)中将已验证过的语句运行成功后保存在这里,以备下次快速执行。

主界面

新建语句

向导 第1步:点击【向导-新建项目】,选择代码类型,依次输入 标题副标题 …等元数据。
第2步:将写好的语句粘贴在 代码 中,注意:参数用于在执行代码前进行替换,例如下面的语句:

1
select * from T_Film where director='?dir?' and year='?year?';

参数:?dir?=zhang; ?year?=2024;
⚠注意:多个参数请以分号分隔。
执行时将SQL语句会被替换成如下:

1
select * from T_Film where director='zhang' and year='2024';

第3步:点击最下方的保存按钮。
对于SQL语句,需要稍后设置数据源后方可执行查询语句。

对于Powershell和Python等脚本方法与上述类似,若您的脚本需要管理员权限,请打开向导最上方类型右侧的管理员方式运行开关。

恭喜,到此您将可以在主界面左侧列表中看到刚刚新创建的项目。

管理项目界面

img
在左侧列表中打开项目后App右侧将会显示如上图所示的界面,在这里可以修改语句、执行、导出等所有操作。
注意:对项目所做的修改务必通过按钮1213进行保存,否则更改将会丢失。
1:标题。如需修改可点击按钮19进行修改。
2:副标题。
3:导出sql查询结果数据时的文件名前缀。对于脚本,请将此值设置为类似pwsh|.ps1python|.py的格式。
4:选择执行SQL语句时要连接的数据库。
5:管理数据库连接。
6:编辑代码。
7:使用参数来替换源代码中的占位字符串。
8:复制源代码或替换后的代码。
9:执行SQL查询或运行powershell/python等脚本语句。
10:执行SQL非查询语句或运行powershell/python等脚本语句。
11:停止正在执行的查询任务。
12:将当前项目另存为一份新的项目。
13:保存对当前项目所做的更改。
14:删除当前项目。
15:参数,用以替换源代码中的占位字符串,多个参数请以分号分隔。
16:用于展示附加在项目中的markdown文档,如需修改请点击按钮24
17:用于展示SQL语句的查询结果。
18:用于展示附加在项目中的html网页,如需修改请点击按钮2521展开后的第一个按钮。
19:用于展示和修改项目的更多信息,其中可设置备注等。
20:用于在Data中搜索。
21:Edge枢纽的功能组。
22:将Data中的数据导出为Xlsx/CSV或txt等格式。
23:*插件系统,可以通过编写powershell/python/lua脚本来为Data界面的右键菜单增加扩展功能,此部分功能强大,您可以在本站找到已编写好的脚本代码或他人分享的脚本来为自己的右键菜单增加相应功能。
24:编辑markdown文本。
25:编辑html网站或代码。
26:快速将Data中的数据导出为其他格式并打开。
27:打印Data中的数据。
28:将Data中的数据作为xlsx附件并附加到邮件App。请注意,此功能仅支持旧版的邮件App,如您使用的是新Outlook邮件客户端,附件将不会添加。

管理数据源

若要连接数据库通过SQL语句查询数据,需要设置相应的数据源及连接字符串。

✅支持的数据库

以下数据库经过测试可支持:

  • Sybase: Adaptive Server Enterprise 16.0.03.01
  • Oracle:Oracle Database 19c Enterprise Edition Release 19.0.0.0.0
  • MySQL :8.0
  • SQLite:SQLite3(暂不支持加密数据库)
  • 达梦:DM 8.1.3.12
  • ODBC:以系统为准。除上述已测试数据库外,其他数据库可通过安装相应驱动后使用ODBC来连接。

⛔2024.5.11经测试发现连接Sybase版本15时不成功,此问题可能在将来版本中被修复。

点击【数据源】按钮,将打开一个新窗口,用于添加和修改数据源。

  • 添加数据源

    • 第1步:选择数据库类型后可以点击右侧的【示例】按钮,然后根据需要修改名称友好名称连接字符串备注
      名称:建议使用英文,不要重复。
      友好名称:便于识别的数据库名称,支持emoji。
      连接字符串:请修改示例按钮生成的模板连接字符串,将其中的数据库 IP地址、端口号、用户名、密码等修改为您要连接的数据库。
      备注:用于在选择

    • 第2步:点击【预览添加】按钮,再点击底部的【保存】。

  • 修改数据源
    管理数据源窗口中下部的编辑器中直接修改数据源。若需要暂时隐藏该数据源,可在行号前加上#号。修改完毕后点击【保存】按钮并关闭窗口。

脚本和扩展 / 插件系统

支持的脚本语言

  • powershell
  • python
  • lua

❇️此部分内容需要您对json及powershell/python/lua(三种脚本语言任选其一)有基本的了解。
点击按钮23展开菜单后再点击编辑插件集,即可编写自定义脚本来扩充邮件菜单功能。
示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
[
    {
        "comment":"这里填写的内容将显示为按钮的Tooltip",
        "status":"enable",
        "type": "btn",
        "name": "demo script",
        "loaddata":false,
        "runas":false,
        "icon": "ms-appdata:///local/your icon.png",
        "script": "read-host",
        "scripttype": "pwsh"
    },
    {
        "comment":"分隔符",
        "status":"enable",
        "type": "sep"
    },{...},{...}
]

在script中,可以直接书写简单的代码。若代码量很大,可以新建一个以感叹号!开头的项目,随后将其标题填入script中以引用。
编辑完后点击重载插件集将可以看到更新后的右键菜单。

公共插件

您可以新建一个名为#CommonAddins#的项目,它将在所有的项目中都可用。将在下面的示例中提到。

示例1-脚本调试文件

为方便调试编写脚本,我们可以添加一个功能,用来找到调试文件。

  • 第1步:新建标题为#CommonAddins#的项目,粘贴下面的代码到源代码中并保存,如已存在该项目请修改内容将如下中括号内的数据部分添加进去:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
[
      {
            "comment": "调试",
            "status": "enable",
            "loaddata": "true",
            "type": "btn",
            "name": "Common>Debug",
            "icon": "",
            "script": "#获取当前脚本运行路径\n$x=$MyInvocation.MyCommand.Definition\n$x.Substring($x.Length-8,5)|Write-Host -ForegroundColor Green\n\n\n#复制若干文件到剪切板\nAdd-Type -AssemblyName System.Windows.Forms\n$files = [System.Collections.Specialized.StringCollection]::new()\n$files.Add($x)\n[System.Windows.Forms.Clipboard]::SetFileDropList($files)\n\n\nRead-Host \"ok\""
      }
]

json中的loaddata字段设置为true后生成的脚本中会包含「Data」中的数据,包括完整的数据/鼠标选中的行数据/鼠标选中的单元格 等等。
如您无需在所有项目中可用,也可以不用新建#CommonAddins#项目,直接在项目的编辑插件集中填入该json内容。

  • 第2步:重载插件集。

您将可以看到在【Data】中右键菜单多出一个Common菜单项,其展开后有个名为Debug的子菜单。

  • 第3步:点击Debug,将会复制脚本文件到剪切板。
  • 第4步:在资源管理器任意目录空白处粘贴文件,并使用代码编辑器如visual studio code打开。
    可以看到app生成的脚本代码,您可以在#release code后面编写自己的代码,用于实现自己的自定义功能,功能完成后再将release code部分的代码拷贝出来用作引用项目插件使用。

示例2-版本控制

接下来将演示如何添加版本控制扩展功能到所有项目中。

  • 第1步:安装TortoiseGit。
  • 第2步:新建一个可引用项目。标题名为 !getVersions,请复制下面的代码到!getVersions的源代码中。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
function Encrypt-String {
    param(
        [string]$plaintext,
        [byte[]]$key,
        [byte[]]$iv
    )

    $aes = New-Object System.Security.Cryptography.AesCryptoServiceProvider
    $aes.Key = $key
    $aes.IV = $iv
    $aes.Mode = [System.Security.Cryptography.CipherMode]::CBC
    $aes.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7

    $encryptor = $aes.CreateEncryptor()
    $memoryStream = New-Object System.IO.MemoryStream
    $cryptoStream = New-Object System.Security.Cryptography.CryptoStream $memoryStream, $encryptor, 'Write'

    $writer = New-Object System.IO.StreamWriter $cryptoStream
    $writer.Write($plaintext)
    $writer.Flush()
    $cryptoStream.FlushFinalBlock()
    $memoryStream.Flush()

    $encryptedData = [Convert]::ToBase64String($memoryStream.ToArray())

    $writer.Close()
    $cryptoStream.Close()
    $memoryStream.Close()

    return $encryptedData
}

function Decrypt-String {
    param(
        [string]$encryptedText,
        [byte[]]$key,
        [byte[]]$iv
    )

    $aes = New-Object System.Security.Cryptography.AesCryptoServiceProvider
    $aes.Key = $key
    $aes.IV = $iv
    $aes.Mode = [System.Security.Cryptography.CipherMode]::CBC
    $aes.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7

    $decryptor = $aes.CreateDecryptor()
    $encryptedBytes = [Convert]::FromBase64String($encryptedText)

    $memoryStream = New-Object System.IO.MemoryStream $encryptedBytes, 0, $encryptedBytes.Length
    $cryptoStream = New-Object System.Security.Cryptography.CryptoStream $memoryStream, $decryptor, 'Read'

    $reader = New-Object System.IO.StreamReader $cryptoStream
    $decryptedData = $reader.ReadToEnd()

    $reader.Close()
    $cryptoStream.Close()
    $memoryStream.Close()

    return $decryptedData
}


# 函数:将字符串进行 Base64 编码
function ConvertTo-Base64 {
    param (
        [string]$inputString
    )

    $bytes = [System.Text.Encoding]::UTF8.GetBytes($inputString)
    $base64String = [System.Convert]::ToBase64String($bytes)

    return $base64String
}

# 函数:将 Base64 编码的字符串进行解码
function ConvertFrom-Base64 {
    param (
        [string]$base64String
    )

    $bytes = [System.Convert]::FromBase64String($base64String)
    $decodedString = [System.Text.Encoding]::UTF8.GetString($bytes)

    return $decodedString
}

function ExportObjectPropertiesToTextFile {  
    param (  
        [Parameter(Mandatory = $true)]  
        [object]$Object,  
          
        [Parameter(Mandatory = $true)]  
        [string]$OutputFilePath  
    )  
  
    # 创建一个新的文件流,准备写入  
    $outputStream = New-Object System.IO.StreamWriter($OutputFilePath)  
  
    # # 获取对象的所有属性  
    # $properties = $Object | Get-Member -MemberType Properties |Sort-Object Name | Select-Object -ExpandProperty Name  
  
    # # 遍历每个属性,并将其值写入文件  
    # foreach ($property in $properties) {  
    #     $value = $Object.$property  
    #     $outputStream.WriteLine("--------- $property --------------------------------------------")  
    #     $outputStream.WriteLine("$value")
    #     $outputStream.WriteLine("")
    #     $outputStream.WriteLine("")
    #     $outputStream.WriteLine("")
    # }  
    # 获取对象的所有属性,并直接遍历它们  
    foreach ($property in $Object.PSObject.Properties) { 
        $pn = $property.Name.PadRight(30)
        $pv = $property.Value  
        $outputStream.WriteLine("-------------------------  $pn ----------------------")  
        $outputStream.WriteLine("$pv")
        $outputStream.WriteLine("")
        $outputStream.WriteLine("")
        $outputStream.WriteLine("")  
    } 
  
    # 关闭文件流  
    $outputStream.Close()  
  
    Write-Host "Object properties have been exported to $OutputFilePath"  
}  

function CreateAndClearFolder {  
    param (  
        [Parameter(Mandatory = $true)]  
        [string]$FolderPath  
    )  
      
    # 检查文件夹是否存在  
    if (Test-Path -Path $FolderPath -PathType Container) {  
        # 文件夹存在,清空文件夹内容  
        Write-Host "文件夹已存在,正在清空内容..."  
        Remove-Item -Path $FolderPath\* -Recurse -Force  
    }
    else {  
        # 文件夹不存在,创建文件夹  
        Write-Host "文件夹不存在,正在创建..."  
        New-Item -Path $FolderPath -ItemType Directory  
    }  
      
    # 函数结束,现在 $FolderPath 指向的文件夹是空的或者已经被重新创建了  
} 
  
# 示例用法  
# 创建一个示例对象  
# $exampleObject = New-Object PSObject -Property @{  
#     Name = "John Doe"  
#     Age = 30  
#     Occupation = @"
#     hellow
#     wfas
#     test
# "@
# }  
  
# # 调用函数,将对象属性导出到文本文件  
# ExportObjectPropertiesToTextFile -Object $exampleObject -OutputFilePath "d:\33.txt"

$keyString = ConvertTo-Base64 -inputString "0123456789abcdef0123456789abcdef"
$ivString = ConvertTo-Base64 -inputString "0123456789abcdef"



$key = [Convert]::FromBase64String($keyString)
$iv = [Convert]::FromBase64String($ivString)

# $plaintext = "Hello, World!"
# $encryptedText = Encrypt-String -plaintext $plaintext -key $key -iv $iv
# Write-Output "Encrypted Text: $encryptedText"

# $decryptedText = Decrypt-String -encryptedText $encryptedText -key $key -iv $iv
# Write-Output "Decrypted Text: $decryptedText"
$uid = $pol_ssql.UID;
$vfolder = [System.IO.Path]::Combine($pol_appd, "versions", $uid)
$lst = [System.Collections.ArrayList]::new()
if ([System.IO.Directory]::Exists($vfolder)) {
    $fs = Get-ChildItem $vfolder
    foreach ($f in $fs) {
        $fl = Get-Content $f
        $d = Decrypt-String -encryptedText $fl -key $key -iv $iv
        $d
        $mobj = $d | convertfrom-json
        $lst.Add($mobj)
    }
}
else {
    Write-Host "该项目暂无版本列表"
    Read-Host "press enter to exit"
}
$tmGitRoot = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), "polstugit");
if ($lst.Count -gt 0) {
    $t0 = $lst[0]
    $tmpFd = [System.IO.Path]::Combine($tmGitRoot, $t0.Id);
    $tid = $t0.Id
    CreateAndClearFolder -FolderPath $tmpFd
    $gitFl = [System.IO.Path]::Combine($tmpFd, "src-$tid");
    Set-Location $tmpFd
    git init
    for ($i = 0; $i -lt $lst.Count; $i++) {
        $em = $lst[$i]
        ExportObjectPropertiesToTextFile -Object $em -OutputFilePath $gitFl
        git add .
        $udate = $em.UptTm
        $his = $em.Tag10
        #git commit --date="YYYY-MM-DD HH:MM:SS" -m "Initial commit with custom date"
        git commit --date="$udate" -m "V$i   $his"
        
    }
    #git log
    Set-Location "C:\Program Files\TortoiseGit\bin\"
    ./TortoiseGitProc.exe /command:log /path "$tmpFd"
}

#Read-Host "press enter to exit"
  • 第3步:新建标题为#CommonAddins#的项目,粘贴下面的代码到源代码中并保存,如已存在该项目请修改内容将如下中括号内的数据部分添加进去:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
[
      {
            "comment": "",
            "status": "enable",
            "loaddata": "false",
            "type": "btn",
            "name": "版本控制",
            "icon": "ms-appdata:///local/myfiles/ico/icons8_time_span_16.png",
            "script": "!getVersions"
      }
]

请修改上述代码中的部分字段,如icon,若找不到该图标文件,会显示默认的图标。

  • 第4步:重载插件集或重新打开项目,也可以重启app。

到这里您将可以在每个项目【Data】的右键菜单中看到一个名为版本控制的功能。

示例3-Lua

lua用于调用主程序中公开的API接口。关于更多接口内容,将在后续文档中提供。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[
      {
            "comment": "刷新缓存(Lua)",
            "status": "enable",
            "type": "btn",
            "name": "Common>刷新缓存列表",
            "icon": "ms-appdata:///local/my_tmp_icons/24/shell32_00239.ico",
            "scripttype": "lua",
            "script": "ui:RefreshCacheBtn()"
      },
      {
            "comment": "刷新UI(Lua)",
            "status": "enable",
            "type": "btn",
            "name": "刷新UI",
            "icon": "ms-appdata:///local/my_tmp_icons/24/shell32_00239.ico",
            "scripttype": "lua",
            "script": "ui:RefreshDg() ui:TryRefreshBindings()"
      },
      {
            "comment": "保存修改到缓存(Lua),只保存当前显示出来的数据,如果使用了筛选,其他数据将会丢失!!!",
            "status": "enable",
            "type": "btn",
            "name": "Common>保存修改到缓存⚠",
            "icon": "ms-appdata:///local/my_tmp_icons/i8/icons8_save_16.png",
            "scripttype": "lua",
            "script": "ui:ShowMsg(tostring(ui:SaveDg2Cache()))"
      }
]

左侧边栏

通常我们需要快速的执行某个powershell或python脚本,可以将其添加到左侧边栏中,之后便可以直接点击该按钮以运行。

🚩点击主界面【管理左侧边栏】按钮,若您是初次使用该功能,程序将会为您新建一条示例项目用于演示,您可在稍后修改该内容以自定义边栏;也可在语句列表的搜索栏中搜索#LeftPanel#并点击打开该项目然后点击工具条中的源代码按钮以修改内容。
让我们来看一段示例代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
[
      {
            "comment": "",
            "status": "disable",
            "type": "btn",
            "name": "存储文件到litedb",
            "loaddata": false,
            "icon": "ms-appdata:///local/my_tmp_icons/48/DDORes_00076.ico",
            "script": "!StoreFileToLiteDb"
      },
      {
            "comment": "",
            "status": "enable",
            "type": "sep",
            "name": "",
            "loaddata": false,
            "icon": "",
            "script": ""
      },
      {
            "comment": "数据库管理工具",
            "status": "enable",
            "type": "btn",
            "name": "数据库管理工具-AquaDataStudio",
            "loaddata": false,
            "icon": "ms-appdata:///local/my_tmp_icons/datastudio.ico",
            "script": "Start-Process c:\\apps\\AquaDataStudio\\AquaDataStudio\\datastudio.bat -WorkingDirectory c:\\apps\\AquaDataStudio\\AquaDataStudio\\"
      },
      {
            "comment": "HeidiSQL",
            "status": "enable",
            "type": "btn",
            "name": "打开HeidiSQL",
            "loaddata": false,
            "icon": "ms-appdata:///local/my_tmp_icons/Assets/heidisql_logo.png",
            "script": "C:\\apps_unbak\\HeidiSQL_Portable\\heidisql.exe"
      }
]

上面的代码将会在侧边栏中显示两个按钮和一个分隔空位(第一个按钮不会显示)。
comment:当鼠标悬浮在按钮上时的子提示信息
status:该按钮是否显示。可为enable和disable
type:sep为分隔符,btn为按钮
name:按钮名称
loaddata:false
icon:图标,建议使用32x32或48x48大小的图标
script:最重要的部分。对于简单的脚本语句,可直接在这里书写;对与复杂的脚本,可通过引用已保存的脚本语句(以感叹号!开头的项目)

  • 新增的功能
    scripttype:脚本类型,可以为powershell和python,若不设置此值,将默认为powershell

💚注意:请在修改完毕后点击应用左侧边栏下方的刷新按钮或重启App以生效。

对于按钮图标路径,图片需放置在应用数据目录中,可点击左侧边栏中的第二个按钮(设置⚙️)查看或手动管理。

img