はじめに
SQLのLinter Toolのsqruff
を試してみました
今回は、誰でも簡単に試せるようにDocdkr上にExpress.jsを起動することで、ブラウザアクセスして、結果がわかるようにしてみました。
構成イメージ
- 画面は最低限のシンプルなHTML、js、CSSを用意
- バックエンドはExpressでシンプル実装
- Expressから
sqruff
はCLIで実行
構成しているファイル
全体構成
├─config
│ └─ .sqruff ※ここは今回は未作成
├─public
│ │ index.html
│ └─ styles.css
│─src
│ └─ server.js
│─ compose.yml
└─ Dockerfile
Docker関連
まずはDockerFileとcomseposeの用意
# ベースイメージとしてNode.js 20を選択
FROM node:20
# sqruffのインストール
RUN apt-get update && \
apt-get install -y curl
RUN curl -fsSL https://raw.githubusercontent.com/quarylabs/sqruff/main/install.sh | bash
# 作業ディレクトリの設定
WORKDIR /app
# expressのインストール
RUN npm install express
# アプリケーションコードをコピー
COPY src ./src
COPY public ./public
# sqruff設定ファイルをコピー
COPY config .
# サーバーを起動
CMD ["node", "src/server.js"]
comseposeは特に何もしていないけど、こっちの方が起動シンプルなので定義してます。
compose.yml
services:
sqruff:
container_name: sqruff
build: .
ports:
- "3000:3000"
サーバーのソース(Expressで実行するファイル)
src/server.js
const express = require('express');
const { spawn } = require('child_process');
const app = express();
const PORT = 3000;
// 静的ファイルを提供するための設定
app.use(express.static('public'));
// JSONデータのパース用(Expressの組み込み機能)
app.use(express.json());
app.post('/lint', (req, res) => {
const { sql, dbEngine } = req.body;
if (!sql || !dbEngine) {
return res.status(400).json({ error: 'SQLとDBエンジンの両方を指定してください' });
}
// sqruffをCLIで実行するプロセスを生成(標準入力にSQLを直接渡す)
const lintProcess = spawn('sqruff', ['lint', '-']);
let output = '';
let errorOutput = '';
// 標準出力からのデータを取得
lintProcess.stdout.on('data', (data) => {
output += data.toString();
});
// 標準エラーからのデータを取得
lintProcess.stderr.on('data', (data) => {
errorOutput += data.toString();
});
// プロセスの終了時の処理
lintProcess.on('close', (code) => {
if (code !== 0) {
console.error(`code: ${code}`);
console.error(`stderr: ${errorOutput}`);
return res.json({ code, result: errorOutput });
}
try {
console.error(`stdout: ${output}`);
res.json({ code: 0, result: output });
} catch (parseError) {
res.status(500).json({ error: 'Lint結果の解析に失敗しました' });
}
});
// 標準入力にSQLを送信(末尾に改行を追加)
lintProcess.stdin.write(sql + '\n');
lintProcess.stdin.end();
});
app.listen(PORT, () => {
console.log(`サーバーがポート ${PORT} で起動しました`);
});
画面のソース
DBドライバーの選択は、よく使いそうなのだけに絞ってます。
public/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SQL Linter</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<header>
<h1>SQL Linter</h1>
<p>Validate your SQL syntax and style effortlessly</p>
</header>
<main>
<form id="sqlForm" class="form">
<div class="form-group">
<label for="dbEngine">DB Engine:</label>
<select id="dbEngine" name="dbEngine" class="input">
<option value="mysql">MySQL</option>
<option value="postgresql">PostgreSQL</option>
<option value="sqlite">SQLite</option>
</select>
</div>
<div class="form-group">
<label for="sql">Enter SQL:</label>
<textarea id="sql" name="sql" class="input textarea" rows="8" placeholder="Write your SQL here..."></textarea>
</div>
<button type="button" onclick="lintSql()" class="btn">Lint SQL</button>
</form>
<div id="result" class="result"></div>
</main>
</div>
<script>
function lintSql() {
const dbEngine = document.getElementById("dbEngine").value;
const sql = document.getElementById("sql").value;
fetch('/lint', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ sql, dbEngine })
})
.then(response => response.json())
.then(data => {
const resultDiv = document.getElementById("result");
resultDiv.innerHTML = `<pre>code: ${data.code}<br><br>${data.result}</pre>`;
})
.catch(error => {
const resultDiv = document.getElementById("result");
resultDiv.innerHTML = `<p class="error">Error: ${error.message}</p>`;
});
}
</script>
</body>
</html>
public/style.css
/* Reset */
body, h1, p, textarea, select, button {
margin: 0;
padding: 0;
font-family: 'Arial', sans-serif;
box-sizing: border-box;
}
body {
background: linear-gradient(135deg, #1e3c72, #2a5298);
color: #ffffff;
line-height: 1.6;
font-size: 16px;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
/* Container */
.container {
width: 90%;
max-width: 800px;
background: #ffffff;
color: #333333;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
overflow: hidden;
padding: 20px;
}
/* Header */
header {
text-align: center;
margin-bottom: 20px;
}
header h1 {
font-size: 2rem;
color: #1e3c72;
}
header p {
color: #666666;
font-size: 1rem;
}
/* Form */
.form {
display: flex;
flex-direction: column;
gap: 15px;
}
.form-group {
display: flex;
flex-direction: column;
}
label {
font-weight: bold;
margin-bottom: 5px;
}
.input {
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
font-size: 1rem;
}
.textarea {
resize: vertical;
min-height: 150px;
}
.btn {
padding: 10px;
background: #1e3c72;
color: #ffffff;
border: none;
border-radius: 5px;
font-size: 1rem;
cursor: pointer;
transition: background 0.3s ease;
}
.btn:hover {
background: #2a5298;
}
/* Result */
.result {
margin-top: 20px;
padding: 10px;
background: #f7f7f7;
border: 1px solid #ddd;
border-radius: 5px;
color: #333;
overflow: auto;
max-height: 200px;
white-space: pre-wrap;
font-family: 'Courier New', Courier, monospace;
}
.error {
color: #e74c3c;
}
実行
以下のコマンドでコンテナを起動します。
docker compose up
compose.ymlを作成していない場合は、以下のようにビルドして起動すれば大丈夫なはず(こっちは試していないので少し心配ですが・・)
# ビルド (少し時間かかる)
docker build -t sqruff-express .
# 起動
docker run -p 3000:3000 -d sqruff-express
実行イメージ
Dockerが起動したらhttp://localhost:3000
でアクセスします。
👇こんな画面が表示されたらSQLを入力して試してみましょう。
構文エラーがある場合は、↓の枠にエラーが表示されました。
- 1つ目:3行目の
As
のキーワードを大文字のAS
に修正すること - 2つ目:4行目のインデントはスペース4つにすること
最後に
今回は、お試しなので構文チェックのルールはデフォルトで設定していますが、設定ファイルを用意することでインデントのサイズなど調整することができそうです。
ちなみに、ソースコードの8割ぐらいChatGPT先生に作成してもらいました。(便利ですねー