SQL Sorguları Oluşturma ve Doğrulama
Güvenli SQL için istem kalıpları: yalnızca SELECT modu ve parametreli sorgular.
SQL Sorguları Oluşturma ve Doğrulama, CoddyKit'te ücretsiz bir AI Agents dersidir. Bu, 4 dersinin 3. dersidir. Aşağıdan dersin tamamını ücretsiz okuyabilir, sonra tarayıcıda yerleşik kod editörü ve 7/24 yapay zeka koçu ile uygulamalı olarak pratik yapabilirsin. Bu, AI Agents öğrenme yolunun bir parçasıdır ve ilerlemeniz web ve CoddyKit uygulaması arasında senkronize olur. AI Agents kursu toplamda 4 dersten oluşur.
SQL Oluşturma Hedefi
Bir SQL sorgusu oluşturmak işin yalnızca yarısıdır. Gerçek bir veritabanında yürütmeden önce sorgunun güvenli ve söz dizimi açısından doğru olduğunu, ayrıca kullanıcının tam olarak niyet ettiği işlemi yaptığını doğrulamanız gerekir.
Bu derste yalnızca SELECT kullanımını zorlama, ayrıştırma, güvenli yürütme ve açıklama planı doğrulaması ele alınmaktadır.
Yalnızca SELECT Modunu Zorlama
Bir NL-to-SQL aracısının yapabileceği en tehlikeli şey, yıkıcı bir ifadeyi yürütmektir. LLM ne döndürürse döndürsün, her zaman yalnızca SELECT modunu zorlayın.
Basit bir dize denetimi yeterli değildir — uygun bir SQL ayrıştırıcısı kullanın.
import sqlparse
def is_select_only(sql):
parsed = sqlparse.parse(sql)
if not parsed:
return False
for statement in parsed:
stmt_type = statement.get_type()
if stmt_type != 'SELECT':
print(f'Blocked statement type: {stmt_type}')
return False
return True
# Test
print(is_select_only('SELECT * FROM users')) # True
print(is_select_only('DROP TABLE users')) # False — BlockedDerinlemesine Savunma için Anahtar Kelime Engelleme Listesi
sqlparse kullanıyor olsanız bile ikincil bir savunma olarak anahtar kelime engelleme listesi ekleyin. Bazı SQL enjeksiyonları ayrıştırıcıları yanıltabilir. Yürütmeden önce tehlikeli anahtar kelimeleri denetlemek ek bir güvenlik katmanı sağlar.
DANGEROUS_KEYWORDS = [
'INSERT', 'UPDATE', 'DELETE', 'DROP', 'CREATE',
'ALTER', 'TRUNCATE', 'GRANT', 'REVOKE', 'EXEC',
'EXECUTE', 'CALL', 'MERGE'
]
def passes_blocklist(sql):
sql_upper = sql.upper()
for keyword in DANGEROUS_KEYWORDS:
# Check as whole word to avoid false positives like 'CREATED_AT'
import re
if re.search(r'\b' + keyword + r'\b', sql_upper):
raise ValueError(f'Blocked keyword detected: {keyword}')
return True
def validate_sql(sql):
if not is_select_only(sql):
raise ValueError('Only SELECT statements are allowed')
passes_blocklist(sql)
return Truesqlparse ile SQL Ayrıştırma
sqlparse, SQL dizelerini yürütmeden belirteçlere ayırır ve ayrıştırır. Sorgu yapısını inceleyebilir, tablo adlarını çıkarabilir ve söz dizimi sorunlarını denetleyebilirsiniz.
pip install sqlparse komutuyla yükleyin.
import sqlparse
from sqlparse.sql import IdentifierList, Identifier
from sqlparse.tokens import Keyword, DML
def extract_table_names(sql):
parsed = sqlparse.parse(sql)[0]
tables = []
from_seen = False
for token in parsed.tokens:
if token.ttype is DML and token.value.upper() == 'SELECT':
continue
if token.ttype is Keyword and token.value.upper() in ('FROM', 'JOIN'):
from_seen = True
continue
if from_seen:
if isinstance(token, Identifier):
tables.append(token.get_name())
elif isinstance(token, IdentifierList):
for item in token.get_identifiers():
tables.append(item.get_name())
from_seen = False
return tables
print(extract_table_names('SELECT u.name FROM users u JOIN orders o ON u.id = o.user_id'))
# ['users', 'orders']Tabloların Şemada Var Olduğunu Doğrulama
Oluşturulan SQL'den tablo adlarını çıkardıktan sonra bunları bildiğiniz şemayla karşılaştırın. LLM bir tablo adını uydurduysa, anlaşılması güç bir veritabanı hatası almak yerine sorguyu yürütmeden önce reddedin.
def validate_tables_exist(sql, known_tables):
used_tables = extract_table_names(sql)
invalid = [t for t in used_tables if t and t not in known_tables]
if invalid:
raise ValueError(
f'Query references non-existent tables: {invalid}. '
f'Available tables: {list(known_tables)[:10]}...'
)
return True
# Usage
known = set(build_schema_dict(conn).keys())
try:
validate_tables_exist(generated_sql, known)
except ValueError as e:
# Send error back to LLM for correction
corrected_sql = llm_fix_sql(generated_sql, str(e))
print('Corrected SQL:', corrected_sql)Parametreli Yürütme
Kullanıcı tarafından sağlanan değerleri SQL'e eklemek için hiçbir zaman dize biçimlendirmesi kullanmayın. Sorguyu LLM oluştursa da kullanıcı tarafından sağlanan tüm filtre değerleri, SQL enjeksiyonunu önlemek için parametre olarak aktarılmalıdır.
import sqlite3
conn = sqlite3.connect(':memory:')
conn.execute('CREATE TABLE orders (status TEXT, user_id INTEGER)')
conn.execute("INSERT INTO orders VALUES ('pending', 42)")
def safe_execute(conn, sql_template, params=()):
"""Execute with parameterized values."""
cur = conn.cursor()
cur.execute(sql_template, params) # driver handles escaping
columns = [d[0] for d in cur.description]
rows = cur.fetchmany(200)
return {'columns': columns, 'rows': rows}
sql = 'SELECT * FROM orders WHERE status = ? AND user_id = ?'
result = safe_execute(conn, sql, params=('pending', 42))
print(result)Yürütme Öncesi EXPLAIN Planı
Büyük tablolara yönelik pahalı sorgular için asıl sorgudan önce EXPLAIN çalıştırın. Planlayıcı, milyonlarca satır içeren bir tabloda tam tablo taraması gösteriyorsa kullanıcıyı uyarın veya sorguyu reddedin.
def check_explain_plan(conn, sql):
explain_sql = f'EXPLAIN {sql}'
with conn.cursor() as cur:
cur.execute(explain_sql)
plan = '\n'.join(row[0] for row in cur.fetchall())
# Check for sequential scans on large tables
if 'Seq Scan' in plan:
print('WARNING: Query involves a sequential scan')
print(plan)
return {'safe': False, 'plan': plan, 'warning': 'Sequential scan detected'}
return {'safe': True, 'plan': plan}
# Use before executing
plan_result = check_explain_plan(conn, generated_sql)
if not plan_result['safe']:
print(f'Optimization hint: {plan_result["warning"]}')Satır Sınırını Zorlama
Bir LLM, LIMIT olmadan SELECT * FROM logs oluşturabilir ve bu da potansiyel olarak milyonlarca satır döndürebilir. Sorguya LIMIT ekleyerek veya sınırlı bir sonuç kümesi getirerek her zaman bir üst satır sınırı uygulayın.
import re
MAX_ROWS = 500
def enforce_row_limit(sql, max_rows=MAX_ROWS):
sql_upper = sql.upper().rstrip().rstrip(';')
# Check if LIMIT already present
if re.search(r'\bLIMIT\b', sql_upper):
# Extract current limit and enforce maximum
match = re.search(r'LIMIT\s+(\d+)', sql_upper)
if match:
current = int(match.group(1))
if current > max_rows:
sql = re.sub(r'LIMIT\s+\d+', f'LIMIT {max_rows}', sql, flags=re.IGNORECASE)
else:
sql = sql.rstrip(';') + f' LIMIT {max_rows}'
return sql
print(enforce_row_limit('SELECT * FROM users'))
# SELECT * FROM users LIMIT 500LLM Çıktısından Temiz SQL Çıkarma
LLM'ler SQL'i genellikle Markdown kod blokları (```sql ... ```) içine sarılmış veya açıklayıcı metinle birlikte döndürür. Ayrıştırmadan ya da yürütmeden önce ham SQL'i çıkarmanız gerekir.
import re
CODE_FENCE = chr(96) * 3 # three backticks, built at runtime to avoid template issues
def extract_sql(llm_response):
# Remove markdown code blocks ('''sql ... ''' or ''' ... ''')
pattern = CODE_FENCE + r'(?:sql)?\s*([\s\S]+?)' + CODE_FENCE
match = re.search(pattern, llm_response, re.IGNORECASE)
if match:
return match.group(1).strip()
# If no code block, look for SELECT statement
match = re.search(r'(SELECT\s+[\s\S]+?;)', llm_response, re.IGNORECASE)
if match:
return match.group(1).strip()
# Fallback: strip common preamble phrases
cleaned = re.sub(r'^(Here is|The SQL query is|Query:)[^\n]*\n', '',
llm_response, flags=re.IGNORECASE).strip()
return cleaned
if __name__ == '__main__':
demo_response = 'Here is the SQL query:\n' + CODE_FENCE + 'sql\nSELECT * FROM users;\n' + CODE_FENCE
print(extract_sql(demo_response))
Eksiksiz Doğrulama Akışı
Tüm doğrulama adımlarını, ham LLM çıktısını alan ve güvenli, yürütülebilir bir SQL dizesi döndüren ya da kurtarma amacıyla açıklayıcı bir iletiyle hata oluşturan tek bir işlevde birleştirin.
def validate_and_prepare_sql(llm_output, known_tables, max_rows=500):
# Step 1: extract raw SQL
sql = extract_sql(llm_output)
if not sql:
raise ValueError('No SQL found in LLM response')
# Step 2: type check
if not is_select_only(sql):
raise ValueError('Only SELECT queries allowed')
# Step 3: keyword blocklist
passes_blocklist(sql)
# Step 4: table existence check
validate_tables_exist(sql, known_tables)
# Step 5: row limit
sql = enforce_row_limit(sql, max_rows)
return sql
# Full flow
try:
safe_sql = validate_and_prepare_sql(llm_output, known_tables)
result = safe_execute(conn, safe_sql)
except ValueError as e:
corrected = llm_fix_sql(llm_output, str(e))
safe_sql = validate_and_prepare_sql(corrected, known_tables)
result = safe_execute(conn, safe_sql)Salt Okunur Veritabanı Kullanıcısı
Kod düzeyindeki doğrulama önemlidir ancak yeterli değildir. Son bir savunma katmanı olarak veritabanına yalnızca SELECT ayrıcalıklarına sahip salt okunur bir kullanıcı hesabı kullanarak bağlanın. Kötü amaçlı bir sorgu tüm denetimleri atlasa bile veritabanı sorguyu reddeder.
# Create read-only user in PostgreSQL:
# CREATE USER nl_to_sql_reader WITH PASSWORD 'secure_password';
# GRANT CONNECT ON DATABASE yourdb TO nl_to_sql_reader;
# GRANT USAGE ON SCHEMA public TO nl_to_sql_reader;
# GRANT SELECT ON ALL TABLES IN SCHEMA public TO nl_to_sql_reader;
import os
import psycopg2
def get_readonly_connection():
return psycopg2.connect(
host=os.getenv('DB_HOST'),
database=os.getenv('DB_NAME'),
user='nl_to_sql_reader', # read-only account
password=os.getenv('DB_READER_PASS')
)Bilgi Kontrolü
Bir NL-to-SQL aracısında SQL doğrulaması için derinlemesine savunmaya dayalı doğru yaklaşım nedir?
Özet: SQL Oluşturma ve Doğrulama
Güvenli SQL oluşturma, eksiksiz bir doğrulama akışı gerektirir: LLM çıktısından temiz SQL'i çıkarın, sqlparse kullanarak yalnızca SELECT kullanımını zorlayın, bir anahtar kelime engelleme listesi uygulayın, tablo adlarını gerçek şemaya göre doğrulayın, satır sınırlarını zorlayın ve son güvenlik önlemi olarak salt okunur bir veritabanı kullanıcısı kullanın.
Parametreli sorgular, kullanıcı tarafından sağlanan değerler söz konusu olduğunda enjeksiyona karşı koruma sağlar. EXPLAIN planı denetimleri, beklenmedik derecede pahalı sorguların üretim verileri üzerinde çalıştırılmasını önler.
Sıkça Sorulan Sorular
“SQL Sorguları Oluşturma ve Doğrulama” dersi ücretsiz mi?
Evet — “SQL Sorguları Oluşturma ve Doğrulama” dersin tüm metni burada web'de ücretsiz olarak okunabilir. Etkileşimli olarak pratik yapmak (yerleşik kod editörü ve 7/24 yapay zeka koçu) ve AI Agents kursunun geri kalanını açmak için CoddyKit PRO'ya yükselt. AI Agents kursu toplamda 4 dersten oluşur.
“SQL Sorguları Oluşturma ve Doğrulama” dersinde ne öğreneceğim?
Güvenli SQL için istem kalıpları: yalnızca SELECT modu ve parametreli sorgular. AI Agents ile uygulamalı kodu tarayıcıda doğrudan çalıştırarak pratik yaparsın ve 7/24 yapay zeka koçu dersi çalışırken sorularını yanıtlar.
AI Agents öğrenmeye başlamak için deneyim gerekli mi?
Önceden deneyim gerekmez. CoddyKit'te AI Agents, başlangıçtan ileri seviyeye kadar yapılandırıldığı için buradan başlayabilir veya başından başlayıp kendi hızında ilerleme yapabilirsin. Bu, 4 dersinin 3. dersidir.
“SQL Sorguları Oluşturma ve Doğrulama” dersi ne kadar sürer?
Çoğu CoddyKit dersi yaklaşık 5–10 dakika sürer. Her biri kısa ve etkileşimli olduğu için sabit ilerleme yaparsın ve web ile uygulama arasında tam olarak bıraktığın yerden devam edebilirsin.
Bu AI Agents dersinde kod yazıp çalıştırabilir miyim?
Evet. Her AI Agents dersi yerleşik bir kod editörü içerir, bu sayede tarayıcıda gerçek kod yazıp çalıştırabilir ve anlık yapay zeka geri bildirimi alırsın — yerel kurulum gerekli değildir.
Bu kursun tüm dersleri
- NL'den SQL'e Aracılar Nasıl Çalışır
- Şema Anlama ve Enjeksiyonu
- SQL Sorguları Oluşturma ve Doğrulama
- Belirsiz Veritabanı Sorularını Ele Alma