commit 394edc17092b8eac2b00d54e7daa103a5946e61f Author: Johannes Rest Date: Tue Jan 27 21:30:15 2026 +0100 Initial commit: local SQL expert AI (Ollama + CLI + prompts + systemd timer) diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..423e20f --- /dev/null +++ b/.env.example @@ -0,0 +1,11 @@ +# Base model to pull and use for the expert model +BASE_MODEL=qwen2.5-coder:14b + +# Optional: space-separated list of extra models to pull +EXTRA_MODELS=sqlcoder + +# Name of your custom expert model (built from Modelfile) +EXPERT_MODEL=jr-sql-expert + +# Ollama API endpoint (host network) +OLLAMA_URL=http://127.0.0.1:11434 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b8644a8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +logs/*.log +.env +.DS_Store +*.swp diff --git a/Modelfile b/Modelfile new file mode 100644 index 0000000..ac7131e --- /dev/null +++ b/Modelfile @@ -0,0 +1,31 @@ +# NOTE: scripts will replace ${BASE_MODEL} with your .env BASE_MODEL. +FROM ${BASE_MODEL} + +SYSTEM """ +Du bist ein sehr erfahrener Microsoft SQL Server 2022 Engineer (T-SQL, Query Optimizer, Indexing, Execution Plans, Stored Procedures, Views, Schema-Design, Collations/UTF-8). +Du arbeitest offline und machst KEINE Annahmen über reale Daten. Keine Verbindungen, keine Ausführung – nur Analyse. + +Allgemeine Regeln: +- Antworte präzise, technisch, ohne Marketing. +- Wenn Informationen fehlen: liste exakt auf, was fehlt, und gib trotzdem best-effort Analyse. +- Wenn du Snippets vorschlägst: immer konkret (T-SQL/DDL) und kommentiert. +- Wenn du mehrere Optionen gibst: nenne Vor-/Nachteile und wann welche Option sinnvoll ist. + +Für Query Plans: +- Erkläre Hotspots (Top Operatoren), Kardinalitätsschätzungen, Warnungen, Spills, SARGability, + fehlende Indizes (vorsichtig!), Join-Strategien, Parameter Sniffing, Stats, Memory Grants. + +Für UTF-8 Migration: +- Erkläre Vorgehen über UTF-8-enabled Collations (_UTF8). +- Risiken: Vergleiche/Sortierung, Indexgrößen, Abhängigkeiten (FK/Index/Computed/Triggers), + Teststrategie, Cutover, Rollback. + +Antwort-Struktur (immer): +1) Kurzfazit (3–6 Bulletpoints) +2) Detailanalyse (mit konkreten Snippets) +3) Risiken & Checks (Checkliste) +4) Nächste Schritte +""" + +PARAMETER temperature 0.1 +PARAMETER top_p 0.9 diff --git a/README.md b/README.md new file mode 100644 index 0000000..18d23e9 --- /dev/null +++ b/README.md @@ -0,0 +1,84 @@ +# jr-sql-ai (Terminal-first SQL Server Expert KI, lokal) + +Ziel: Lokale Expert-KI für **SQL Server 2022** (T-SQL, Views, Stored Procedures, Execution Plans, UTF-8 Migration), +aufrufbar **vom Terminal**, ohne direkte DB-Verbindung (nur Copy & Paste / Dateien). + +## Features +- **Einfacher Tech-Stack:** Docker + Ollama + Bash + curl + python +- **Host Networking:** nutzt den Host-Netzwerkstack (Routing/DNS wie Host; ideal wenn nur `br0` zuverlässig ist) +- **Auto-Updates:** Runtime + Models via `systemd --user` Timer +- **Viele Logs:** jede Ausführung schreibt detaillierte Logs unter `./logs/` + +## Voraussetzungen (Arch Linux) +- docker + docker compose +- curl +- python (für JSON-Quoting/Parsing) + +## Quickstart +```bash +cp .env.example .env +./scripts/bootstrap.sh +echo "SELECT 1;" | ./bin/sqlai analyze-tsql +``` + +## Verwendung (Copy & Paste) +### T-SQL +```bash +cat query.sql | ./bin/sqlai analyze-tsql +``` + +### Stored Procedure +```bash +cat dbo.usp_Something.sql | ./bin/sqlai analyze-proc +``` + +### View +```bash +cat dbo.vw_Something.sql | ./bin/sqlai analyze-view +``` + +### Execution Plan +- Unterstützt Showplan XML oder Text. +```bash +./bin/sqlai analyze-plan --file showplan.xml +``` + +### UTF-8 Migration Plan +```bash +cat schema_snippet.sql | ./bin/sqlai utf8-migration +``` + +## Prompt Library (Templates) +Unter `./prompts/` findest du Copy&Paste-Templates: +- `prompts/tsql_review.md` +- `prompts/plan_review.md` +- `prompts/utf8_migration_runbook.md` +- `prompts/indexing_checklist.md` +- `prompts/sniffing_stats_playbook.md` +- `prompts/proc_refactor_template.md` +- `prompts/view_analysis_template.md` + +## Logs +- CLI Logs: `./logs/sqlai-YYYY-MM-DD.log` +- Bootstrap Logs: `./logs/bootstrap-YYYY-MM-DDTHH:MM:SS.log` +- Update Logs: `./logs/update-YYYY-MM-DDTHH:MM:SS.log` + +Die Logs enthalten: +- Input-Bytes, Mode, Model +- Roh-Metriken aus Ollama (Token Counts, Durations) +- Fehler inkl. curl exit codes + +## Auto-Updates aktivieren (systemd --user) +```bash +mkdir -p ~/.config/systemd/user +cp systemd/user/* ~/.config/systemd/user/ +systemctl --user daemon-reload +systemctl --user enable --now jr-sql-ai-update.timer +systemctl --user list-timers | grep jr-sql-ai +``` + +## Troubleshooting +- **API nicht erreichbar**: Prüfe `docker ps` und ob Port 11434 lokal erreichbar ist (Host-Netz). +- **Langsam/OOM**: setze `BASE_MODEL` kleiner (z.B. 7b) und re-run `./scripts/update.sh`. +- **Model fehlt**: `./scripts/update.sh` ausführen und prüfen ob `ollama list` im Container das Model zeigt: + `docker exec -it ollama ollama list` diff --git a/bin/sqlai b/bin/sqlai new file mode 100755 index 0000000..03d2def --- /dev/null +++ b/bin/sqlai @@ -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 < [--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 < +---END INPUT--- diff --git a/prompts/plan_review.md b/prompts/plan_review.md new file mode 100644 index 0000000..cc4a3f5 --- /dev/null +++ b/prompts/plan_review.md @@ -0,0 +1,27 @@ +# Execution Plan Review Template (Showplan XML/Text) + +## Kontext (optional) +- Query text (falls verfügbar) +- Parameterwerte bei Capture +- CPU/Duration/Reads/Rowcount bei Capture +- Server settings: MAXDOP, Cost Threshold for Parallelism (falls bekannt) + +## Aufgabe +1) Kurzfazit +2) Hotspots: + - Top Operatoren nach Kosten + - Spills (Sort/Hash), Warnings + - Memory Grant: zu hoch/zu niedrig + - Parallelism (CXPACKET/CXCONSUMER Kontext) +3) Kardinalität: + - wo weicht Estimated vs Actual ab? (wenn Actual vorhanden) + - Stats/Histogramm Hinweise +4) Fixes: + - Query Rewrite + - Index/Stats Empfehlungen + - Parameter Sniffing Mitigation +5) Checkliste für Validierung + +---BEGIN PLAN--- + +---END PLAN--- diff --git a/prompts/proc_refactor_template.md b/prompts/proc_refactor_template.md new file mode 100644 index 0000000..45e1455 --- /dev/null +++ b/prompts/proc_refactor_template.md @@ -0,0 +1,22 @@ +# Stored Procedure Review / Refactor Template + +## Kontext (optional) +- Aufrufmuster (Parameter, typische Werte): +- Isolation Level / Transactions: +- Deadlocks/Blocking bekannt? (ja/nein + Hinweise) +- Ziel: Latenz, Throughput, Stabilität, Plan-Stabilität + +## Aufgabe +1) Kurzfazit +2) Detailanalyse + - Parameter Sniffing + - Temp tables vs table variables + - Row-by-row (Cursor, WHILE), UDFs, RBAR + - TRY/CATCH + Fehlerbehandlung + - Transaktionsumfang (zu groß?), Lock Escalation, Timeouts +3) Konkrete Refactorings (mit T-SQL) +4) Risiken/Checks/Next steps + +---BEGIN PROC--- + +---END PROC--- diff --git a/prompts/sniffing_stats_playbook.md b/prompts/sniffing_stats_playbook.md new file mode 100644 index 0000000..3684792 --- /dev/null +++ b/prompts/sniffing_stats_playbook.md @@ -0,0 +1,30 @@ +# Parameter Sniffing & Statistics Playbook + +## Symptome +- Laufzeit stark schwankend je nach Parameter +- Plan ist "gut" für einen Wert, "schlecht" für andere +- Große Estimated vs Actual Abweichungen +- Memory grants/spills je nach Parameter + +## Diagnose (ohne DB-Zugriff: theoretisch/plan-basiert) +- Welche Prädikate sind parameterabhängig? +- Gibt es Skew (ein Wert sehr häufig/selten)? +- OR-Logik / optional filters (@p IS NULL OR col=@p)? +- Implizite Konvertierungen? + +## Gegenmaßnahmen (geordnet von "leicht" zu "hart") +1) Query rewrite (SARGability, Splitting optional filters) +2) OPTION(RECOMPILE) selektiv +3) OPTIMIZE FOR (oder OPTIMIZE FOR UNKNOWN) bewusst +4) Zwei Queries / IF branches für stark unterschiedliche Pfade +5) Plan guides / forced plan (nur in Ausnahmefällen) +6) Stats: update + ggf. filtered stats (wenn passend) + +## Checkliste +- Welche Parameter-Sets testen (min/max/typisch) +- Regression: CPU/Reads/Duration +- Concurrency/Blocking beachten + +---BEGIN INPUT--- + +---END INPUT--- diff --git a/prompts/tsql_review.md b/prompts/tsql_review.md new file mode 100644 index 0000000..0fc5f02 --- /dev/null +++ b/prompts/tsql_review.md @@ -0,0 +1,29 @@ +# T-SQL Review Template (Copy & Paste) + +> Zweck: schnelle, präzise Analyse einer Abfrage. +> Eingabe: T-SQL + (optional) Parameterwerte + (optional) Tabelleninfos. + +## Kontext (optional) +- SQL Server Version: 2022 +- DB Kompatibilitätslevel: ? +- Tabellenvolumen grob (Rows): ? +- Häufigkeit/Latency Ziel: ? +- Parameterwerte (typisch & worst-case): ? + +## Aufgabe +Analysiere das T-SQL und liefere: +1) **Kurzfazit** (3–6 Bulletpoints) +2) **Detailanalyse**: + - SARGability (LIKE, Funktionen auf Spalten, CAST/CONVERT, Datentyp-Mismatches) + - Joins/Predicates (Reihenfolge, Join Typen) + - Kardinalität/Stats (wo schätzt der Optimizer falsch) + - Parameter Sniffing Risiko + konkrete Mitigation (z.B. OPTION(RECOMPILE) gezielt, OPTIMIZE FOR, plan guides nur wenn nötig) +3) **Konkrete Verbesserungen**: + - rewrite (mit kommentiertem SQL) + - Index-Vorschläge (nur wenn plausibel; include columns; Filtered Index falls geeignet) +4) **Checks**: + - welche Messwerte/Plan-Indikatoren prüfen + +---BEGIN T-SQL--- + +---END T-SQL--- diff --git a/prompts/utf8_migration_runbook.md b/prompts/utf8_migration_runbook.md new file mode 100644 index 0000000..65edeb7 --- /dev/null +++ b/prompts/utf8_migration_runbook.md @@ -0,0 +1,27 @@ +# UTF-8 Migration Runbook (SQL Server 2022) + +## Ziel +Tabellen/Spalten auf UTF-8 umstellen durch Verwendung von UTF-8-enabled Collations (`*_UTF8`). + +## Input (paste) +- Aktuelles Schema (CREATE TABLE oder relevante Spalten) +- Aktuelle Collations +- Abhängigkeiten (FKs, Indexes, Computed Columns, Triggers) +- App assumptions (Sortierung, Vergleiche, Case sensitivity) + +---BEGIN INPUT--- + +---END INPUT--- + +## Erwartete Ausgabe +1) Kurzfazit +2) Schritt-für-Schritt Plan (staging -> validate -> cutover -> rollback) +3) DDL Snippets: + - neue Collation setzen + - Rebuild Indizes/Constraints +4) Risiken: + - Sort-/Compare-Verhalten kann sich ändern + - mögliche Indexgrößenänderung + - computed columns / persisted computed columns +5) Teststrategie: + - Vergleichssuites, Known tricky strings, roundtrip tests diff --git a/prompts/view_analysis_template.md b/prompts/view_analysis_template.md new file mode 100644 index 0000000..70acb10 --- /dev/null +++ b/prompts/view_analysis_template.md @@ -0,0 +1,23 @@ +# View Analysis Template + +## Kontext (optional) +- Wird die View häufig gejoint/weitergefiltert? +- Erwartete Selectivity / typische Filter? +- Indexed view überhaupt erlaubt/gewünscht? (Schema binding etc.) + +## Aufgabe +1) Kurzfazit +2) Detailanalyse: + - Expand/Inlining Effekte + - SARGability und Pushdown von Predicates + - Aggregationen/Distinct/Union + - Risiken bei Scalar UDFs / Non-deterministic Funktionen +3) Verbesserungen: + - alternative Definition + - Hinweise zu Indizes auf Basistabellen + - falls relevant: indexed view Voraussetzungen (nur als Option) +4) Risiken/Checks + +---BEGIN VIEW--- + +---END VIEW--- diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh new file mode 100755 index 0000000..83c39b3 --- /dev/null +++ b/scripts/bootstrap.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cp -n "${ROOT}/.env.example" "${ROOT}/.env" || true +source "${ROOT}/.env" + +ts(){ date -Is; } +log_dir="${ROOT}/logs" +mkdir -p "$log_dir" +log_file="${log_dir}/bootstrap-$(date -Iseconds).log" +exec > >(tee -a "$log_file") 2>&1 + +echo "[$(ts)] bootstrap: starting (ROOT=$ROOT)" +echo "[$(ts)] bootstrap: docker compose up -d" +docker compose -f "${ROOT}/docker-compose.yml" up -d + +echo "[$(ts)] bootstrap: waiting for Ollama API at ${OLLAMA_URL} ..." +for i in {1..90}; do + if curl -sS "${OLLAMA_URL}/api/tags" >/dev/null 2>&1; then + echo "[$(ts)] bootstrap: Ollama API is up." + break + fi + if [[ $i -eq 90 ]]; then + echo "[$(ts)] bootstrap: ERROR: API did not come up in time." + exit 1 + fi + sleep 1 +done + +echo "[$(ts)] bootstrap: pulling base model: ${BASE_MODEL}" +docker exec -it ollama ollama pull "${BASE_MODEL}" + +if [[ -n "${EXTRA_MODELS:-}" ]]; then + for m in ${EXTRA_MODELS}; do + echo "[$(ts)] bootstrap: pulling extra model: $m" + docker exec -it ollama ollama pull "$m" + done +fi + +echo "[$(ts)] bootstrap: building expert model: ${EXPERT_MODEL}" +tmp="$(mktemp)" +sed "s/\${BASE_MODEL}/${BASE_MODEL}/g" "${ROOT}/Modelfile" > "$tmp" +docker exec -i ollama ollama create "${EXPERT_MODEL}" -f - < "$tmp" +rm -f "$tmp" + +echo "[$(ts)] bootstrap: done" +echo "[$(ts)] bootstrap: test:" +echo " echo "SELECT 1;" | ${ROOT}/bin/sqlai analyze-tsql" diff --git a/scripts/update.sh b/scripts/update.sh new file mode 100755 index 0000000..501ac70 --- /dev/null +++ b/scripts/update.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +source "${ROOT}/.env" + +ts(){ date -Is; } +log_dir="${ROOT}/logs" +mkdir -p "$log_dir" +log_file="${log_dir}/update-$(date -Iseconds).log" +exec > >(tee -a "$log_file") 2>&1 + +echo "[$(ts)] update: starting (ROOT=$ROOT)" +echo "[$(ts)] update: pulling docker image(s)" +docker compose -f "${ROOT}/docker-compose.yml" pull +echo "[$(ts)] update: restarting services" +docker compose -f "${ROOT}/docker-compose.yml" up -d + +echo "[$(ts)] update: waiting for Ollama API at ${OLLAMA_URL} ..." +for i in {1..90}; do + if curl -sS "${OLLAMA_URL}/api/tags" >/dev/null 2>&1; then + echo "[$(ts)] update: Ollama API is up." + break + fi + if [[ $i -eq 90 ]]; then + echo "[$(ts)] update: ERROR: API did not come up in time." + exit 1 + fi + sleep 1 +done + +echo "[$(ts)] update: pulling base model: ${BASE_MODEL}" +docker exec -it ollama ollama pull "${BASE_MODEL}" + +if [[ -n "${EXTRA_MODELS:-}" ]]; then + for m in ${EXTRA_MODELS}; do + echo "[$(ts)] update: pulling extra model: $m" + docker exec -it ollama ollama pull "$m" + done +fi + +echo "[$(ts)] update: rebuilding expert model: ${EXPERT_MODEL}" +tmp="$(mktemp)" +sed "s/\${BASE_MODEL}/${BASE_MODEL}/g" "${ROOT}/Modelfile" > "$tmp" +docker exec -i ollama ollama create "${EXPERT_MODEL}" -f - < "$tmp" +rm -f "$tmp" + +echo "[$(ts)] update: complete" diff --git a/systemd/user/jr-sql-ai-update.service b/systemd/user/jr-sql-ai-update.service new file mode 100644 index 0000000..2fea4a1 --- /dev/null +++ b/systemd/user/jr-sql-ai-update.service @@ -0,0 +1,7 @@ +[Unit] +Description=Update Ollama runtime + models (jr-sql-ai) + +[Service] +Type=oneshot +WorkingDirectory=%h/jr-sql-ai +ExecStart=%h/jr-sql-ai/scripts/update.sh diff --git a/systemd/user/jr-sql-ai-update.timer b/systemd/user/jr-sql-ai-update.timer new file mode 100644 index 0000000..71f30b4 --- /dev/null +++ b/systemd/user/jr-sql-ai-update.timer @@ -0,0 +1,9 @@ +[Unit] +Description=Daily update timer for jr-sql-ai + +[Timer] +OnCalendar=daily +Persistent=true + +[Install] +WantedBy=timers.target