我先前寫過兩篇文章,一篇是 使用 Docker 執行 SQL Server on Linux 容器之常用工具與命令 分享各種常見的執行命令,另一篇則是 深入剖析 docker run 與 docker exec 的 -i 與 -t 技術細節 幫助我更加理解 Docker 處理 STDIN 與 Virtual Terminal (VT) 之間的關係。這讓我連結到一個長久以來希望被微軟實現的需求,如果我想直接透過 STDIN 將 T-SQL 傳入 SQL Server on Linux 容器執行,不想先把本機的 T-SQL 檔案複製進去,那該如何處理?想不到還真的給我研究出方法了!
我當時在 使用 Docker 執行 SQL Server on Linux 容器之常用工具與命令 文章中,所寫的範例都以 SQL Server 2019 為主,這篇文章我打算改用 SQL Server 2022 來做示範,但基本上是一樣的。
以下就是完整的實作過程:
-
建立 SQL Server 2022 on Linux 容器
docker run --rm -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=Ver7CompleXPW" -p 1433:1433 --name sql1 -d mcr.microsoft.com/mssql/server:2022-latest
-
查看 SQL Server 容器是否順利啟動
docker logs sql1
-
查看 SQL Server 容器版本資訊
docker exec -it sql1 /opt/mssql-tools/bin/sqlcmd -S . -U sa -P Ver7CompleXPW -Q "SELECT @@VERSION"
-
下載 ContosoUniversity
範例資料庫的 T-SQL 檔案
curl -sSLO https://gist.github.com/doggy8088/2a2f7075d49b3814d19513426ede3549/raw/ba8c4e7d46597188a17a39de7906d358f18d834d/ContosoUniversity.sql
-
匯入 ContosoUniversity
範例資料庫到 SQL Server 容器中
這裡就是本篇文章的精華所在了,因為我不想透過 docker cp
把 ContosoUniversity.sql
檔案複製到容器中,再用 sqlcmd
的 -i
參數傳入檔案路徑,所以想方設法看能不能透過 STDIN
傳入 T-SQL 執行,直到我找到 sqlcmd does not accept queries from standard input #2 這篇討論,但微軟在留言中回覆支援 STDIN
不在計畫內,就把 Issue 給關閉了,殘酷的拒絕了這個想法!🔥
其實透過 STDIN
傳入應用程式在 Linux 世界是非常常見的用法,還好有善心人在這則 Issue 的下方提供了 Workarround (應變措施),想不到還真的有效,實在是太棒了!😍
在 Linux 作業系統有個特殊的 /dev/stdin
檔案,嚴格說起來它並不是一個「檔案」,而是一個「虛擬裝置」(pseudo-devices),只是你可以把他當成「檔案」來操作。這個 /dev/stdin
檔案的內容,恰巧就是虛擬終端機(VT)中「標準輸入」的內容,而這正好可以拿來當成 sqlcmd
工具的 -i inputfile
參數值!👍
Windows PowerShell / Command Prompt
type ContosoUniversity.sql | docker exec -i sql1 /opt/mssql-tools/bin/sqlcmd -S . -U sa -P Ver7CompleXPW -i /dev/stdin
Bash
cat ContosoUniversity.sql | docker exec -i sql1 /opt/mssql-tools/bin/sqlcmd -S . -U sa -P Ver7CompleXPW -i /dev/stdin
改寫原本文章的語法
如果要執行 SELECT sys.databases
命令,以前我會用 -Q
參數,直接在參數傳入執行,但現在我會改成這樣寫了:
echo "SELECT name FROM sys.databases" | docker exec -i sql1 /opt/mssql-tools/bin/sqlcmd -S . -U sa -P Ver7CompleXPW -i /dev/stdin
或是將欄位調整短一點,讓命令列模式下輸出結果比較好看:
echo "SELECT CAST(name as varchar(30)) as name FROM sys.databases" | docker exec -i sql1 /opt/mssql-tools/bin/sqlcmd -S . -U sa -P Ver7CompleXPW -i /dev/stdin
如果要列出 ContosoUniversity
資料庫中有多少表格,我就會這樣寫:
echo 'SELECT CAST(name as varchar(30)) as name FROM sys.tables' | docker exec -i sql1 /opt/mssql-tools/bin/sqlcmd -S . -U sa -P Ver7CompleXPW -d 'ContosoUniversity' -i /dev/stdin
如果要列出 ContosoUniversity
資料庫中``Course` 表格的內容,我就會這樣寫:
echo 'SELECT * FROM dbo.Course' | docker exec -i sql1 /opt/mssql-tools/bin/sqlcmd -S . -U sa -P Ver7CompleXPW -d 'ContosoUniversity' -i /dev/stdin
基本上顯示中文也不會有問題:
總結
學會了這個技巧,我在 CI 與 CD 的 Pipelines 就可以寫的更漂亮些,而且也不會佔用容器中的空間,自動化建置 Container Image 的過程也會更加方便,實在是太棒了!👍
相關連結