Initial commit: local SQL expert AI (Ollama + CLI + prompts + systemd timer)

This commit is contained in:
2026-01-27 21:30:15 +01:00
commit 394edc1709
18 changed files with 574 additions and 0 deletions

130
bin/sqlai Executable file
View File

@@ -0,0 +1,130 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ENV_FILE="${ROOT}/.env"
[[ -f "$ENV_FILE" ]] && source "$ENV_FILE"
: "${OLLAMA_URL:=http://127.0.0.1:11434}"
: "${EXPERT_MODEL:=jr-sql-expert}"
ts() { date -Is; }
log_dir="${ROOT}/logs"
mkdir -p "$log_dir"
log_file="${log_dir}/sqlai-$(date -I).log"
# log everything (stdout+stderr)
exec > >(tee -a "$log_file") 2>&1
usage() {
cat <<EOF
Usage:
sqlai <mode> [--file path]
Modes:
analyze-tsql
analyze-proc
analyze-view
analyze-plan
utf8-migration
Examples:
cat query.sql | sqlai analyze-tsql
sqlai analyze-plan --file showplan.xml
EOF
}
mode="${1:-}"
[[ -z "$mode" ]] && { usage; exit 1; }
shift || true
file=""
while [[ $# -gt 0 ]]; do
case "$1" in
--file) file="$2"; shift 2;;
*) echo "[$(ts)] ERROR: Unknown arg: $1"; usage; exit 2;;
esac
done
input=""
if [[ -n "$file" ]]; then
if [[ ! -f "$file" ]]; then
echo "[$(ts)] ERROR: file not found: $file"
exit 3
fi
input="$(cat "$file")"
else
input="$(cat)"
fi
if [[ -z "${input//[[:space:]]/}" ]]; then
echo "[$(ts)] ERROR: empty input"
exit 4
fi
case "$mode" in
analyze-tsql)
instruction="Analysiere das folgende T-SQL (SQL Server 2022). Finde Performance-Probleme, SARGability, Indizes, Statistiken, Parameter Sniffing Risiken und gib konkrete Verbesserungen."
;;
analyze-proc)
instruction="Analysiere die folgende Stored Procedure (SQL Server 2022). Finde Performance-/Correctness-Risiken, Transaktions-/Locking-Themen, Parameter Sniffing, fehlende Indizes. Gib konkrete Refactorings."
;;
analyze-view)
instruction="Analysiere die folgende View (SQL Server 2022). Prüfe SARGability, Expand/Inlining-Effekte, mögliche Indexing-Optionen (z.B. indexed view falls sinnvoll) und Plan-Auswirkungen."
;;
analyze-plan)
instruction="Analysiere den folgenden SQL Server Execution Plan (XML Showplan oder Text). Identifiziere teure Operatoren, Spills, Warnungen, Kardinalitätsfehler, fehlende Indizes und gib konkrete Fixes."
;;
utf8-migration)
instruction="Erstelle einen Migrationsplan, um Tabellen/Spalten auf UTF-8 umzustellen (UTF-8 enabled collations mit _UTF8). Berücksichtige Abhängigkeiten (FK/PK/Indexes/Computed/Triggers), Risiken und gib eine Schritt-für-Schritt Checkliste."
;;
*)
echo "[$(ts)] ERROR: unknown mode: $mode"
usage
exit 5
;;
esac
prompt=$(cat <<EOF
${instruction}
---BEGIN INPUT---
${input}
---END INPUT---
EOF
)
echo "[$(ts)] sqlai: MODE=$mode MODEL=$EXPERT_MODEL OLLAMA_URL=$OLLAMA_URL"
echo "[$(ts)] sqlai: INPUT_BYTES=$(printf "%s" "$input" | wc -c)"
payload="$(python - <<'PY'
import json, os, sys
model=os.environ.get("EXPERT_MODEL","jr-sql-expert")
prompt=sys.stdin.read()
print(json.dumps({"model": model, "prompt": prompt, "stream": False}, ensure_ascii=False))
PY
<<<"$prompt")"
echo "[$(ts)] sqlai: sending request..."
resp="$(curl -sS -X POST "${OLLAMA_URL}/api/generate" -H 'Content-Type: application/json' --data-binary "$payload")" || {
rc=$?
echo "[$(ts)] sqlai: ERROR: curl failed rc=$rc"
exit 10
}
python - <<'PY'
import json,sys
obj=json.loads(sys.stdin.read())
print("\n" + (obj.get("response","").rstrip()) + "\n")
md = {
"total_duration": obj.get("total_duration"),
"load_duration": obj.get("load_duration"),
"prompt_eval_count": obj.get("prompt_eval_count"),
"prompt_eval_duration": obj.get("prompt_eval_duration"),
"eval_count": obj.get("eval_count"),
"eval_duration": obj.get("eval_duration"),
}
print("METRICS=" + json.dumps(md, ensure_ascii=False))
PY <<<"$resp"
echo "[$(ts)] sqlai: done"