# SQL korumaları

> AST denetleyicisinin her gönderimde uyguladığı kurallar — WHERE zorunluluğu, LIMIT enjeksiyonu, transaction'lar ve yasaklı komutlar.

- Dokümantasyon: Referans
- Güncelleme: 2026-10-13
- Kaynak: https://queryproxy.com/tr/docs/sql-korumalari/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
Her gönderim, onay kuyruğuna girmeden **önce** parse edilip denetlenir. Kural
ihlal eden talep, açık bir mesajla gönderim anında reddedilir — DBA'e bile
ulaşmaz, veritabanına hiç ulaşmaz.

## Kurallar

### UPDATE / DELETE için WHERE zorunlu

```sql
DELETE FROM users;                    -- ✗ reddedilir
DELETE FROM users WHERE id = 42;      -- ✓ kabul (yine de onay gerekir)
```

Bu kural transaction içinde de geçerlidir. Statik olarak her zaman doğru olan
bir `WHERE` eksik sayılır:

```sql
DELETE FROM users WHERE 1 = 1;        -- ✗ reddedilir
DELETE FROM users WHERE true;         -- ✗ reddedilir
DELETE FROM users WHERE id = 1 OR 1 = 1;  -- ✗ reddedilir
```

Denetim; sabit değerleri (sayılar, düz string'ler, `TRUE`, `FALSE`, `NULL`),
karşılaştırmaları, `IS [NOT] NULL|TRUE|FALSE`, `AND`, `OR`, `NOT` ve parantezleri
değerlendirir. Değerlendiremediği bir operand (bir kolon, bir fonksiyon) hiçbir
zaman doğru sayılmaz: tespit statiktir ve en iyi çabayla (best-effort) çalışır.
Değerlendirilemeyecek kadar derin iç içe geçmiş ya da çok büyük bir `WHERE` de
reddedilir (`… with a WHERE clause too complex to verify is not allowed`).
Bu kural geçersiz kılınamaz. Tespit statik olduğu için yaygın hataları durdurur;
bir yazmanın yalnızca birkaç satırı etkilediğini kanıtlamaz.

Kural; `EXPLAIN ANALYZE` ve MariaDB `ANALYZE` içindeki ifade, CTE gövdeleri,
T-SQL batch'leri ve dinamik SQL için de geçerlidir. PostgreSQL `DO` blokları
istisnadır: bu kural için denetlenmezler, yazma olarak sınıflandırılırlar ve her
zaman insan onayından geçerler; incelemeci gövdenin tamamını görür. Bir `DO`
gövdesindeki dinamik SQL (PL/pgSQL `EXECUTE`) ise bu pazarlığın öbür yüzüdür:
çalıştırdığı ifade çalışma anında oluşturulur ve okunamaz, bu yüzden doğrudan
reddedilir (bkz.
[Anahtar kelimeye değil, tehlikeli kısma bakılan kurallar](#anahtar-kelimeye-değil-tehlikeli-kısma-bakılan-kurallar)).

### SELECT'e LIMIT eklenir

- `LIMIT` yok mu? Varsayılan (`1000`) otomatik eklenir — kendi satırında, ki
  sondaki bir satır yorumu onu yutamasın.
- `LIMIT` üst sınırın (`10000`) üzerinde mi? Aşağı çekilir.
- `LIMIT ALL` (PostgreSQL) sınırsız sayılır ve sınırlanır.
- Offset korunur: `LIMIT 10, 20000` → `LIMIT 10, 10000`,
  `LIMIT 99999 OFFSET 4` → `LIMIT 10000 OFFSET 4`.
- `FETCH FIRST n ROWS ONLY` ve T-SQL `TOP n` de aynı şekilde sınırlanır.
- SQL Server hedeflerine `LIMIT` eklenmez (geçerli T-SQL değildir); guard bunun
  yerine `TOP` üzerinde çalışır. Mevcut bir `TOP n` / `TOP (n)` üst sınıra
  (`10000`) çekilir; `TOP` içermeyen bir üst düzey SELECT ise varsayılan değer
  olan `TOP (1000)`'i, varsa `DISTINCT` / `ALL`'dan sonra alır.
  `TOP … PERCENT`, `TOP (@v)`, ifadeler ve ondalıklar **reddedilir**. `WITH`,
  `OFFSET`, `UNION`, `EXCEPT` ve `INTERSECT` için `TOP` eklenmez; bunları işçinin
  satır tavanı (üst sınır) sınırlar ve sonuç "kırpıldı" olarak işaretlenir.
- Guard'ın düz bir satır sayısı olarak anlayamadığı bir `LIMIT` (aritmetik —
  `LIMIT 100*100000` —, ifadeler, yer tutucular) sınırlanmak yerine
  **reddedilir**: doğrulanamayan bir limit uygulanamaz.
- Alt sorgu LIMIT'lerine dokunulmaz; yalnızca dış sorgu denetlenir. Kilitleme
  ifadeleri gözetilir — eklenen LIMIT, `FOR UPDATE`'ten *önce* yerleşir.

Bunun üstüne işçi, sonuçları akıtırken SQL'den bağımsız **mutlak bir satır
tavanı** (üst sınır) uygular — yani cümle düzeyindeki guard'ı atlayan bir sorgu
bile (dialect tuhaflıkları, `SHOW`, `EXPLAIN`) tavandan fazlasını döndüremez;
saklanan sonuç "kırpıldı" olarak işaretlenir.

İki sayı da yapılandırılabilir — bkz. [Yapılandırma](/tr/docs/yapilandirma/).
Hazırlanan SQL (eklenen LIMIT ile) gönderimden önce geliştiriciye, incelemede
DBA'e gösterilir; onayladığınız şey birebir çalışan şeydir.

### Çoklu ifadeler açık transaction ister

```sql
UPDATE a SET x = 1 WHERE id = 1;
UPDATE b SET y = 2 WHERE id = 2;      -- ✗ reddedilir: iki çıplak ifade
```

```sql
BEGIN;
UPDATE a SET x = 1 WHERE id = 1;
UPDATE b SET y = 2 WHERE id = 2;
COMMIT;                               -- ✓ kabul, atomik çalışır
```

İşçi ifadeleri gerçek bir transaction içinde çalıştırır: herhangi biri
başarısız olursa tamamı geri alınır. Gönderimde `ROLLBACK` reddedilir —
başarısızlıkta geri alma otomatiktir, elle yazılacak bir şey değildir. İç içe
transaction desteklenmez.

### Her zaman yasak

Bu komutlar sunucu düzeyinde kod çalıştırma ya da dosya erişimi verir ve bir
sorgu portalı üzerinden gündelik bir karşılıkları yoktur. Rolden bağımsız
olarak, koşulsuz reddedilirler:

- `DROP DATABASE` / `DROP SCHEMA`
- `GRANT`, `REVOKE`, `DENY`
- `CREATE USER` / `ALTER USER` / `DROP USER` (ve `ROLE` / `LOGIN` türevleri)
- `CREATE FUNCTION … SONAME` — yüklenebilir UDF'ler (ve `CREATE AGGREGATE FUNCTION`
  karşılığı)
- `SHUTDOWN`
- Sunucu tarafı dosya / program IO: `SELECT … INTO OUTFILE` / `INTO DUMPFILE`,
  `LOAD DATA`, PostgreSQL `COPY` (`COPY … TO PROGRAM` dahil), `LOAD_FILE()`
  fonksiyonu ve PostgreSQL'in dosya fonksiyonları `pg_read_file()`,
  `pg_read_binary_file()`, `pg_ls_dir()`, `lo_import()` ve `lo_export()` — şema
  nitelikli çağrılar (`pg_catalog.pg_read_file(…)`) dahil. Yalnızca çağrı biçimi
  yakalanır; aynı ada sahip bir kolon ya da takma ad engellenmez.
- Kendilerine string olarak verilen SQL'i çalıştıran ve guard'ın okuyamadığı
  PostgreSQL fonksiyonları: `query_to_xml`, `query_to_xmlschema`,
  `query_to_xml_and_xmlschema`, `xpath_table`, `ts_stat`, `ts_rewrite`,
  `crosstab*` ve `connectby`.

Bu denetimler ifadenin **yorumdan arındırılmış** biçimi üzerinde çalışır; yani
`DROP/**/DATABASE` ya da baştaki bir `/* … */` yorumu gibi hileler yasak bir
ifadeyi denetimden geçiremez.

`DROP TABLE`, `TRUNCATE` ve `ALTER` doğrudan yasak **değildir** — onay
gerektiren yazma olarak çalışırlar, ancak DDL içeren bir talep gönderim anında
işaretlenir: onay kuyruğunda ve talep detay sayfasında **DDL** rozeti görünür,
Slack ve Teams'e giden gönderim bildirimleri de DDL ibaresi taşır (sonraki
onaylandı/reddedildi bildirimleri taşımaz). DDL içermeyen talepte işaret yoktur.

Veritabanı yönetimi altyapı araçlarınızın işidir, bir sorgu portalının değil.

### Anahtar kelimeye değil, tehlikeli kısma bakılan kurallar

Üç ifade yalnızca içlerindekine bakılarak reddedilir; çünkü sözdizimleri,
guard'ın elinden almaması gereken sıradan DBA işini de kapsar.

**`CREATE EXTENSION` — extension adına göre.** Untrusted prosedürel diller
tanımları gereği veritabanı superuser'ı olarak çalışır ve reddedilir:
`plpythonu`, `plpython2u`, `plpython3u`, `plperlu`, `pltclu`, `plsh`, `plr`,
`plluau`, `plrubyu`, `plphpu`, `pljavau` — ve ayrıca PostgreSQL'in untrusted dil
için kullandığı `pl…u` adlandırma konvansiyonuna uyan her ad; yani henüz kimsenin
listelemediği bir bağlama (`plpython4u`) görüldüğü yerde reddedilir. Geri kalan
her şey normal biçimde kurulur: `pg_stat_statements`, `pgcrypto`, `uuid-ossp`,
`postgis`, `plv8`, `plpgsql`. Adı okunamayan bir `CREATE EXTENSION` reddedilir.

**`DO` blokları ve rutin gövdeleri.** Bir PostgreSQL `DO` bloğu,
`CREATE FUNCTION` ya da `CREATE PROCEDURE` başka bir dilde bir gövde taşır; bu
yüzden guard gövdeyi MySQL'in değil PostgreSQL'in kendi sözcüksel kurallarıyla
(yorumlar, `'…'`, `E'…'`, `U&'…'`, tırnaklı tanımlayıcılar ve iç içe
dollar-quoted string'ler) okur ve üst düzey SQL'e uygulanan yasaklı fonksiyon ve
korunan ayar kurallarını aynen uygular. Böylece
`DO $$ BEGIN DROP DATABASE prod; END $$` ve bir ters eğik çizgi ya da yorumun
ardına saklanmış bir `pg_read_file(…)` çağrısı geçemez. Bunun üstüne:

- Yalnızca `plpgsql` ve `sql` dilleri kabul edilir. `LANGUAGE` maddesi
  olmayan bir `DO` bloğu PostgreSQL varsayılanı olan `plpgsql` ile çalışır;
  `CREATE FUNCTION` / `PROCEDURE` ise SQL-standard bir `RETURN` gövdesi yoksa
  açık bir `LANGUAGE plpgsql` ya da `sql` gerektirir. Başka her dil, trusted olsun
  olmasın, reddedilir; guard'ın düz bir ad olarak okuyamadığı dil de öyle.
  Tırnaklı bir ad büyük/küçük harfini korur, dolayısıyla `LANGUAGE "PLPGSQL"`
  reddedilir. `CREATE LANGUAGE` ve `ALTER LANGUAGE … RENAME` de reddedilir.
- Dinamik SQL (`EXECUTE`) kullanan bir gövde, trigger fonksiyonları dahil,
  çalıştırdığı ifade çalışma anında oluşturulduğu için reddedilir. Trigger
  `EXECUTE FUNCTION|PROCEDURE` maddeleri bundan etkilenmez.
- `BEGIN ATOMIC` gövdeleri ve gövdesiz `CREATE FUNCTION` / `CREATE PROCEDURE`
  reddedilir.
- Bir rutin (`CREATE/ALTER FUNCTION|PROCEDURE … SET`), bir veritabanı
  (`ALTER DATABASE … SET`) ya da bir rol (`ALTER ROLE|USER … SET`) üzerindeki
  `SET` maddesi aşağıdaki korunan ayar kuralından geçer. Ayar çözümlenemezse
  ifade reddedilir.

**`SET` — değişken adına göre.** Reddedilenler; bir dosya yolu belirleyen, kodu
sunucu sürecine yükleyen ya da bir korumayı kapatan değişkenlerdir.

- *MySQL ve MariaDB:* `general_log`, `general_log_file`, `slow_query_log`,
  `slow_query_log_file`, `log_error`, `init_file`, `init_connect`, `plugin_dir`,
  `plugin_load`, `plugin_load_add`, `secure_file_priv`, `local_infile`. Yalnızca
  kalıcı scope'lar değerlendirilir — `GLOBAL`, `PERSIST` ve `PERSIST_ONLY`; hem
  anahtar kelimeli (`SET GLOBAL x = …`) hem de değişken yazımında
  (`SET @@GLOBAL.x = …`). Bir `SESSION` yazması hiçbir zaman korunmaz ve `@name`
  bir kullanıcı değişkenidir, asla bir sunucu ayarı değil. `SET STATEMENT … FOR
  <statement>` (MariaDB) iki kez değerlendirilir: atamaları yukarıdaki kuralla,
  `FOR`'dan sonraki ifade ise tek başına gönderilmiş gibi.
- *PostgreSQL:* `session_preload_libraries`, `local_preload_libraries`,
  `dynamic_library_path`, `default_transaction_read_only`,
  `transaction_read_only`, `session_replication_role`,
  `allow_system_table_mods`, `lo_compat_privileges`, `zero_damaged_pages`,
  `ignore_checksum_failure`. Bunlar **her** scope'ta reddedilir — `SET x`,
  `SET LOCAL x`, `SET SESSION x`, `SET x TO …` — çünkü kötüye kullanımları tam da
  oturum düzeyindeki değişikliktir. Hedef veritabanı bilinmediğinde de aynısı
  geçerlidir.
- *Her driver:* `set_config('<name>', …)` listedeki her ad için, üçüncü
  argümanın her iki değerinde de reddedilir.
- *Her driver, her scope:* `sql_mode`. `NO_BACKSLASH_ESCAPES` ve `ANSI_QUOTES`
  ters eğik çizginin ve çift tırnağın ne anlama geldiğini değiştirir; guard bunu
  izleyemez, bu yüzden `SET sql_mode`, `SET SESSION sql_mode`, `SET @@sql_mode`
  ve `set_config('sql_mode', …)` reddedilir. Okumak geçer. Çalıştırma anındaki
  karşılık gelen denetim için bkz.
  [sonraki bölüm](#guardın-sunucudan-farklı-okuduğu-metin).

Eşleme tam ad üzerinden ve büyük/küçük harf duyarsızdır; joker yoktur, bu yüzden
`max_connections`, `wait_timeout`, `time_zone` ve PostgreSQL'de `work_mem` ile
`search_path` geçer.

Bu iki liste kurulumunuz için `QUERYPROXY_EXTRA_UNTRUSTED_LANGUAGES` ve
`QUERYPROXY_EXTRA_DANGEROUS_VARIABLES` ile **genişletilebilir**. Fonksiyon,
prosedür ve SQLite pragma listelerinin kendi `QUERYPROXY_EXTRA_*` ayarları var.
Bkz. [Yapılandırma](/tr/docs/yapilandirma/). Yalnızca ekleme yapabilirler:
yerleşik girdiler tabandır, yapılandırma onları kısaltamaz.

### Guard'ın sunucudan farklı okuduğu metin

Bazı metinleri guard'ın lexer'ı ile veritabanı farklı okur. Tahmin yürütmek
yerine guard bunları ya reddeder ya da her iki okuma altında inceler:

- **Geçerli UTF-8 olmayan SQL** (`SQL must be valid UTF-8.`) ya da lexer'ın
  içinde hiçbir ifade bulamadığı SQL (`QueryProxy could not read this SQL: …`).
- String sabitlerinin, tırnaklı tanımlayıcıların ve yorumların **dışında** kalan
  bir **byte-order mark ya da Unicode boşluk** (U+FEFF, U+0085, U+00A0, U+1680,
  U+180E, U+2000–U+200B, U+2028, U+2029, U+202F, U+205F, U+3000) — her driver'da
  ve dinamik SQL içinde. Lexer böyle bir karakteri komşu sözcüğe yapıştırırken
  sunucu onu ayraç sayabilir.
- **MariaDB çalıştırılabilir yorumları** (`/*M!100000 … */`). `mariadb`
  driver'ında gövde SQL olarak okunur ve 3 seviyeden derin iç içe geçme
  reddedilir. `mysql` driver'ında `/*M!` düz bir yorum olarak kalır; bu yüzden
  bir MariaDB sunucusunu `mariadb` driver'ıyla bağlayın. Hedef veritabanı
  bilinmediğinde `/*M!` doğrudan reddedilir.
- `mysql` ve `mariadb` driver'larında **sürüm koşullu yorumlar**
  (`/*!NNNNN … */`). Böyle bir yorum iki kez denetlenir: bir kez sunucu gövdeyi
  çalıştırıyormuş, bir kez de atlıyormuş gibi; okumalardan biri reddedilirse ya
  da iki okuma ifade sayısı, transaction durumu, tür veya hazırlanan metin
  bakımından farklıysa ifade reddedilir. Şunlar da reddedilir (önceki sürümlere
  göre bir değişiklik): tek talepte farklı sürümler
  (`SELECT /*!40101 1 */, /*!50503 2 */` bir örnektir), 5 haneli olmayan bir
  sürüm (MariaDB'de 5 ya da 6), başka bir yorumun içine yerleşmiş ya da başka bir
  yorum barındıran sürümlü yorum ve gövdesinde bir string ya da satır yorumu
  içinde `*/` geçen yorum.
- **MySQL ve MariaDB'de `sql_mode`.** Ona yazmayı reddetmenin (yukarıda) yanı
  sıra executor, SQL'i çalıştırmadan önce `@@SESSION.sql_mode` değerini okur ve
  `NO_BACKSLASH_ESCAPES` ya da `ANSI_QUOTES` içeriyorsa hiçbir şey çalıştırmadan
  talebi başarısız kılar. QueryProxy açtığı her bağlantıda sunucunun
  varsayılanının yerine geçen katı bir `sql_mode` ayarlar; bu yüzden denetim
  bugün yalnızca bu ayar değişirse ya da aradaki bir şey modu değiştirirse
  devreye girer; bu bayraklardan birine sahip kalan bir bağlantı, bayrak
  kaldırılana dek reddedilir. Diğer driver'larda böyle bir denetim yoktur.
- **PostgreSQL ters eğik çizgileri ve `#`.** Ters eğik çizgiye yalnızca
  yorumlarda, dollar-quoted gövdelerde, `E'…'` string'lerinde ve düzgün biçimli
  `U&` string'lerinde izin verilir; başka her yerde (`'a\'` gibi düz bir string
  dahil) reddedilir, çünkü PostgreSQL ile MySQL bunun ne anlama geldiği
  konusunda anlaşamaz. `#`, sabitlerin, tırnaklı tanımlayıcıların ve yorumların
  dışında reddedilir. Hedef veritabanı bilinmediğinde de aynı kurallar geçerlidir;
  MySQL ve MariaDB etkilenmez.

### Yasak listesi ne değildir

Bu liste **yapısal olarak eksiktir ve öyle kalacaktır.** Bugün, birkaç lehçe
genelinde bilinen tehlikeli sözdizimini sayar; adını koymadığı bir yazım —
bir üretici eklentisi, bir fonksiyon takma adı, sunucunun gelecekteki bir
özelliği — listeden geçer. Yukarıdaki her girdi, birinin daha önceki bir kuralı
aşmanın yolunu bulması yüzünden vardır ve bu süreç bitmez.

Dolayısıyla onu derinlemesine savunmanın bir katmanı sayın, asla yetkilendirme
sınırı değil. **Bir ifadeyi gerçekte yetkilendiren DBA onayıdır**: hazırlanan
SQL'i görüp çalışıp çalışmaması gerektiğine karar verebilen bir insan. Yasak
listesi yalnızca en bariz kötüye kullanım taleplerini o kişiye ulaşmadan eler.
(Yeni bir boşluk yine de geçerli bir güvenlik bildirimidir; bkz.
[güvenlik politikası](https://github.com/QueryProxy/QueryProxy/blob/main/SECURITY.md).)

Bu, `DO` gövdesi taraması için iki kat geçerlidir. Düz metin olarak yazılmış
SQL'i okur; bloğun çalışma anında bir araya getirdiği SQL'i okuyamaz; bir `DO`
ya da rutin gövdesindeki dinamik SQL'in (`EXECUTE`) taranmak yerine
reddedilmesinin nedeni budur. Erişimi dışında kalan, bir bloğun yapabildiği
geri kalan her şeydir: `plpgsql` ile yazılmış bir `DO` bloğu yine de sunucu
tarafında serbest koddur ve gövdesinde düz yazılmış `WHERE`'siz bir `DELETE`
geçer; çünkü bloklar bu kuraldan muaftır ve karar onayda verilir. Taramanın
kazandırdığı şey, doğrudan superuser kod çalıştırma yollarının ve düz yazılmış
yasak ifadenin elenmesidir; verilmemesi gereken bir onayın altındaki son ağdır,
onaylanmış bir bloğun ne yapabileceğine konan bir sınır değil.

## Kapsam

Guard'ın her yapıyla dialect bazında ne yaptığı. **Reddedilir**, talebin
reddedildiği anlamına gelir. **Yazma olarak sınıflandırılır**, reddedilmediği ama
onay gerektiren bir yazma olarak çalıştığı anlamına gelir (bir `DELETE` /
`UPDATE` yine `WHERE` ister). **Okuma** düz bir okumadır. **Kapsanmaz**, guard'ın
bugün bu konuda hiçbir şey yapmadığı anlamına gelir. Davranış, guard'ın birim
testlerine karşı doğrulanır. PostgreSQL için karar ve sunucudaki etkisi ayrıca
canlı bir sunucuya karşı da denetlenir (`live-pgsql` test grubu); MySQL, MariaDB
ve SQL Server canlı sunuculara karşı çalıştırılmamıştır.

### PostgreSQL

| Yapı | Durum |
|---|---|
| `EXPLAIN ANALYZE DELETE FROM t` / `WHERE`'siz `UPDATE` | reddedilir |
| `EXPLAIN (ANALYZE, BUFFERS) UPDATE t SET a=1 WHERE id=1` (ayrıca `ANALYSE`, `ANALYZE true/on/1`) | yazma olarak sınıflandırılır |
| `EXPLAIN ANALYZE SELECT 1` | okuma |
| `EXPLAIN DELETE FROM t WHERE id=1`, `EXPLAIN (ANALYZE false) DELETE FROM t` | okuma |
| `EXPLAIN ANALYZE DROP DATABASE prod` | reddedilir |
| `EXPLAIN (ANALYZE` (kapatılmamış), `ANALYZE maybe`, boş seçenek, tırnaklı ya da bilinmeyen seçenek | reddedilir |
| 3 seviyeden derin iç içe `EXPLAIN ANALYZE` | reddedilir |
| `SELECT * INTO t2 FROM t` (ayrıca `SELECT*INTO`, parantez içinde, bir CTE içinde) | yazma (DDL) olarak sınıflandırılır, `LIMIT` eklenmez |
| `SELECT * INTO TEMP t2 FROM t` | yazma (DDL) olarak sınıflandırılır |
| `nextval`, `setval`, `pg_advisory_lock*`, `pg_advisory_xact_lock*`, `pg_try_advisory_*`, `pg_advisory_unlock*` | yazma olarak sınıflandırılır |
| `pg_sleep`, `pg_sleep_for`, `pg_sleep_until` | yazma olarak sınıflandırılır |
| `pg_terminate_backend`, `pg_cancel_backend`, `pg_reload_conf`, `pg_rotate_logfile`, `pg_promote`, `pg_switch_wal` | reddedilir |
| `dblink*` (önek: `dblink` ile başlayan bir kullanıcı fonksiyonu da reddedilir) | reddedilir |
| `pg_read_file`, `pg_read_binary_file`, `pg_ls_dir`, `lo_import`, `lo_export` (tırnaklı ve `U&` yazımlar dahil) | reddedilir |
| `set_config('literal', …)` | yazma olarak sınıflandırılır |
| `set_config(<not a literal>, …)` (sabit olmayan argüman) | reddedilir |
| `set_config('session_preload_libraries', …)` ve listedeki diğer PostgreSQL ayarları | reddedilir |
| Listedeki bir PostgreSQL ayarının `SET` / `SET LOCAL` / `SET SESSION` ile yazılması | her scope'ta reddedilir |
| `SET sql_mode`, `set_config('sql_mode', …)` | reddedilir |
| `SET work_mem`, `set_config('search_path', …)` ve listede olmayan diğer ayarlar | yazma olarak sınıflandırılır |
| `DELETE FROM t WHERE 1 = 1`, `WHERE true`, `WHERE id = 1 OR 1 = 1` | reddedilir (her zaman doğru `WHERE`) |
| `WHERE`'siz `DELETE FROM t` içeren `DO` bloğu | yazma olarak sınıflandırılır; `WHERE` kuralından muaftır, karar onayda verilir |
| `PREPARE n AS <statement>` | yazma olarak sınıflandırılır; ifade kendi kurallarıyla değerlendirilir |
| `WITH x AS (DELETE FROM t) SELECT …` (`WHERE` yok) | reddedilir |
| `WITH x AS (UPDATE t SET a=1 WHERE id=1 RETURNING …) SELECT …` | yazma olarak sınıflandırılır |
| `WITH … DELETE FROM t` (`WITH`'ten sonraki ifade) | `WHERE` kuralı uygulanır |
| 3 seviyeden derin iç içe ya da okunamayan `WITH` | reddedilir |
| Bir CTE içindeki `INSERT` / `MERGE` gövdesi | yazma olarak sınıflandırılır; gövdeye ayrı bir `WHERE` denetimi yapılmaz (**kapsanmaz**) |
| `EXECUTE` (dinamik SQL) içeren `DO` bloğu, `CREATE FUNCTION` / `CREATE PROCEDURE`, trigger fonksiyonları dahil | reddedilir |
| `plpgsql` ya da `sql` dışında bir dilde `DO` / `CREATE FUNCTION` / `CREATE PROCEDURE`; `BEGIN ATOMIC` gövdesi; gövdesiz `CREATE FUNCTION`; `CREATE LANGUAGE` | reddedilir |
| Yasaklı bir fonksiyonu çağıran ya da korunan bir ayarı yazan `DO` / rutin gövdesi (`PERFORM pg_read_file(…)`) | reddedilir; gövde PostgreSQL'in sözcüksel kurallarıyla okunur |
| Listedeki bir ayarın `CREATE/ALTER FUNCTION … SET`, `ALTER DATABASE … SET`, `ALTER ROLE … SET` ile yazılması | reddedilir |
| `query_to_xml`, `query_to_xmlschema`, `query_to_xml_and_xmlschema`, `xpath_table`, `ts_stat`, `ts_rewrite`, `crosstab*`, `connectby` | reddedilir |
| Bir `DO` / rutin gövdesinde yukarıdakiler dışındaki düz `plpgsql` metni | yazıldığı gibi taranır (bunun ötesi **kapsanmaz**) |
| Sabit, tırnaklı tanımlayıcı ya da yorum dışında `#` | reddedilir |
| Yorum, dollar-quoted gövde, `E'…'` ya da düzgün biçimli `U&` string dışında ters eğik çizgi | reddedilir |
| `SELECT … FOR UPDATE` | okuma (satırları kilitler) |
| `DENY` | reddedilir (`;` ile ayrılmış ya da tek başına biçimleri) |
| Veritabanı tarafında salt okunur transaction | **kapsanmaz** (v0.3.0 için planlandı) |
| Veritabanı tarafında statement timeout | **kapsanmaz** (v0.3.0 için planlandı) |
| TLS: bağlantı `sslmode=prefer` kullanır | **kapsanmaz** (ayar v0.3.0 için planlandı) |

### MySQL / MariaDB

| Yapı | Durum |
|---|---|
| `EXPLAIN ANALYZE UPDATE t SET a=1 WHERE id=1` | yazma olarak sınıflandırılır |
| `EXPLAIN ANALYZE DELETE FROM t` (`WHERE` yok) | reddedilir |
| `ANALYZE`'sız `EXPLAIN` | okuma |
| MariaDB `ANALYZE UPDATE t SET a=1 WHERE id=1` (ayrıca `ANALYZE FORMAT=JSON …`) | yazma olarak sınıflandırılır |
| MariaDB `ANALYZE DELETE FROM t` (`WHERE` yok) | reddedilir |
| MariaDB `ANALYZE SELECT …` | içindeki `SELECT` olarak değerlendirilir |
| MariaDB `ANALYZE DROP TABLE t`, `ANALYZE TRUNCATE …`, `ANALYZE SET …`, `(ANALYZE …)` | reddedilir |
| `ANALYZE TABLE t` (ve `NO_WRITE_TO_BINLOG` / `LOCAL` biçimleri) | yazma olarak sınıflandırılır, değişmedi |
| `/*M!100000 DELETE FROM t */` (`mariadb` driver'ı) | gövde denetlenir; `mysql` driver'ında düz yorum |
| Sürümlü `/*!NNNNN … */` yorumları | gövde her iki okuma altında denetlenir; karışık sürümler ve tuhaf hane sayıları reddedilir (bkz. yukarısı) |
| `SET SESSION sql_mode = …`, `SET GLOBAL sql_mode = …`, `SET @@sql_mode = …`, `SET @@SESSION.sql_mode = …` | her scope'ta reddedilir |
| `SELECT @@sql_mode`, `SHOW VARIABLES LIKE 'sql_mode'` | okuma |
| Çalıştırma anında `NO_BACKSLASH_ESCAPES` ya da `ANSI_QUOTES` içeren oturum `sql_mode`'u | SQL çalışmadan talep başarısız olur |
| MariaDB `SET STATEMENT … FOR <statement>` | `FOR`'dan sonraki ifade kendi kurallarıyla değerlendirilir (`WHERE`, yasak listesi, …); ifade yoksa reddedilir |
| `SET GLOBAL general_log := 1` (`:=` öncesinde boşluk) | reddedilir |
| `SELECT … INTO @var` | okuma |
| `SELECT … INTO OUTFILE` / `INTO DUMPFILE` | reddedilir |
| `get_lock`, `release_lock` | yazma olarak sınıflandırılır |
| `sleep`, `benchmark` | yazma olarak sınıflandırılır |
| `load_file` | reddedilir |
| `PREPARE s FROM '<DML without WHERE>'` (`WHERE`'siz DML) | reddedilir |
| `PREPARE s FROM '<DML with WHERE>'` (`WHERE`'li DML) | yazma olarak sınıflandırılır |
| `PREPARE s FROM @q` (değişken) | reddedilir |
| `WHERE`'siz `WITH x AS (DELETE …)` | reddedilir |
| `WHERE`'siz `WITH … DELETE FROM t` | reddedilir |
| `SELECT 1 DENY …` | okuma (`DENY` bir MySQL ifadesi değildir; `;` ile ayrılmış ve tek başına biçimleri reddedilir) |
| Veritabanı tarafında salt okunur transaction | **kapsanmaz** (v0.3.0 için planlandı) |
| Veritabanı tarafında statement timeout | **kapsanmaz** (v0.3.0 için planlandı) |

### SQL Server

| Yapı | Durum |
|---|---|
| `xp_*`, `sp_configure`, `sp_oa*`, `sp_addlinkedserver`, `sp_addlinkedsrvlogin`, `sp_serveroption`, `sp_addextendedproc`, `sp_execute_external_script` (ifade olarak ya da `EXEC`'ten sonra çağrılan) | reddedilir |
| `OPENROWSET`, `OPENDATASOURCE`, `OPENQUERY` | reddedilir |
| `BULK INSERT` | reddedilir |
| `sp_prepare`, `sp_prepexec`, `sp_prepexecrpc`, `sp_execute`, `sp_cursoropen`, `sp_cursorprepare`, `sp_cursorprepexec`, `sp_msforeachtable`, `sp_msforeachdb`, `sp_msforeach_worker`, `sp_sqlexec`, `sp_send_dbmail`, `sp_add_jobstep`, `sp_update_jobstep` | argümanlar ne olursa olsun reddedilir |
| `WHERE`'siz `EXEC('DELETE FROM t')` / `EXEC(N'…')` | reddedilir |
| `EXEC('DELETE FROM t WHERE id = 1')` | yazma olarak sınıflandırılır |
| `EXEC(@sql)`, `EXEC('a' + 'b')`, `EXEC(…) AT srv`, `EXEC … AS USER` | reddedilir |
| `sp_executesql N'…'` (`EXEC` ile ya da onsuz, ya da `@stmt =` ile) | SQL metni olağan kurallarla değerlendirilir; değişken reddedilir |
| `EXEC('')` | zararsız, geçer |
| `EXEC('EXEC xp_cmdshell ''dir''')` (iç içe sabit) | reddedilir |
| `SELECT 'xp_cmdshell'` (ad bir string sabitinin içinde) | okuma |
| `EXEC dbo.my_proc 1` | yazma olarak sınıflandırılır |
| `EXEC "quoted_name"`, `WHERE exec = 1` | reddedilir (guard katı tarafta yanılır) |
| Dinamik SQL içinde `BEGIN` / `COMMIT` / `ROLLBACK` | reddedilir |
| 3 seviyeden derin iç içe dinamik SQL | reddedilir |
| `SELECT … INTO t2 FROM t` | yazma (DDL) olarak sınıflandırılır, `TOP` eklenmez |
| `SELECT * FROM t` | okuma; `TOP (1000)` alır |
| `SELECT TOP 99999 …` / `TOP (99999)` | okuma, `TOP 10000`'e çekilir |
| `SELECT TOP 10 PERCENT …`, `TOP (@v)`, `TOP (5 + 5)`, `TOP (1.5)` | reddedilir |
| `SELECT * FROM #t WHERE id = 1` (geçici tablolar) | okuma |
| `SELECT 1 DELETE FROM t` (`;`'siz batch) | reddedilir |
| `SELECT 1 DROP DATABASE x` | reddedilir |
| `DENY` | reddedilir |
| `SHUTDOWN` | reddedilir |
| `THROW`, `REVERT`, `CHECKPOINT`, `RECONFIGURE`, `KILL` | yazma olarak sınıflandırılır |
| `EXECUTE AS USER = 'x'` | **kapsanmaz** |
| Veritabanı tarafında salt okunur transaction | **kapsanmaz** (v0.3.0 için planlandı) |
| Veritabanı tarafında statement timeout | **kapsanmaz** (v0.3.0 için planlandı) |

### SQLite

| Yapı | Durum |
|---|---|
| `ATTACH`, `DETACH` | reddedilir |
| `VACUUM … INTO` | reddedilir |
| `VACUUM` (tek başına) | yazma olarak sınıflandırılır |
| `load_extension(` | reddedilir |
| `PRAGMA table_info(t)` ve izin verilen diğer salt okunur pragma'lar (`table_xinfo`, `index_list`, `index_info`, `index_xinfo`, `foreign_key_list`, `database_list`, `table_list`, `collation_list`, `function_list`, `compile_options`) | okuma |
| `PRAGMA x = …`, bir ayar için `PRAGMA x(v)`, listede olmayan her pragma | reddedilir |
| `QUERYPROXY_EXTRA_SQLITE_PRAGMAS` ile eklenen bir pragma | yalnızca çıplak biçimde izinli |
| `EXPLAIN VACUUM INTO '/tmp/x'` | reddedilir |
| `QUERYPROXY_SQLITE_ALLOWED_DIR` dışındaki dosyalar | ayar doluyken reddedilir. Ayar boşken uygulama dizini dışındaki her yol kabul edilir ve bağlantı biçimi uyarı gösterir |
| Uygulama dizini içindeki dosyalar (QueryProxy'nin varsayılan SQLite veritabanı oradadır) | ayardan bağımsız olarak her zaman reddedilir |
| Salt okunur transaction ve statement timeout | **kapsanmaz** (v0.3.0 için planlandı) |

### Tüm dialect'ler

| Yapı | Durum |
|---|---|
| Bir string sabitinin, yorumun ya da dollar-quoted gövdenin içindeki listelenmiş fonksiyon ya da prosedür adı (`SELECT 'pg_sleep(1)'`, `SELECT 1 -- dblink(`) | etkilenmez |
| `DESC analyze` (adı `analyze` olan bir tablo) | reddedilir (aşırı engelleme) |
| Sunucuda tanımlı fonksiyonların ve prosedürlerin içindeki kod | **kapsanmaz** |
| `EXECUTE IMMEDIATE '<DML without WHERE>'` (`WHERE`'siz DML) | reddedilir |
| `EXECUTE IMMEDIATE '<DML with WHERE>'` (`WHERE`'li DML) | yazma olarak sınıflandırılır |
| `EXECUTE IMMEDIATE <variable, expression or concatenation>` (değişken, ifade ya da birleştirme) | reddedilir |
| Bilinmeyen bir anahtar kelimeyle başlayan ifade | yazma olarak sınıflandırılır |

## Lehçeler ve temkinli geri düşüş

Parser önce MySQL lehçesini bilir. Tam parse edemediği ifadeler (PostgreSQL
`::jsonb` cast'leri, sürücüye özgü operatörler) yine **anahtar kelime bazında**
korunur: parse edilemeyen `WHERE`'siz bir `UPDATE` yine reddedilir, parse
edilemeyen bir `SELECT`'e yine LIMIT eklenir — sınıflandırılamayan her şey ise
**yazma** sayılır, yani tam onay incelemesinden geçer. QueryProxy tereddütte
her zaman katı taraftadır.
