Skip to content

Prepared Statements

Bu dersi tamamladıktan sonra:

  • Prepared statements’ın önemini ve kullanımını öğreneceksiniz
  • bind(), run(), raw(), first() metodlarını detaylı şekilde kullanabileceksiniz
  • Return objects ve meta data’yı anlayacaksınız
  • SQL injection’den korunma yöntemlerini kavrayacaksınız
  1. Prepared Statements Nedir?
  2. bind() Metodu
  3. run() Metodu
  4. raw() Metodu
  5. first() Metodu
  6. Return Objects
  7. SQL Injection Koruması

Prepared statements, SQL sorgularının önceden derlenmiş ve parametrelendirilmiş halleridir. Bu sayede:

  • ✅ SQL injection’den korunursunuz
  • ✅ Performans artışı sağlarsınız (query plan caching)
  • ✅ Kod okunabilirliği artar
  • ✅ Tekrar kullanılabilir sorgular oluşturursunuz
// 1. Sorguyu hazırla
const stmt = db.prepare("SELECT * FROM users WHERE username = ?");
// 2. Parametreleri bağla
const boundStmt = stmt.bind("ahmet");
// 3. Sorguyu çalıştır
const { results } = await boundStmt.all();
// Kısa ve okunabilir zincirleme
const { results } = await db
.prepare("SELECT * FROM users WHERE username = ?")
.bind("ahmet")
.all();

bind() metodu, prepared statement’a parametre bağlar:

// Tek parametre
const stmt = db.prepare("SELECT * FROM users WHERE id = ?");
const bound = stmt.bind(1);
// Çoklu parametre
const stmt = db.prepare(
"SELECT * FROM users WHERE email = ? AND username = ?"
);
const bound = stmt.bind("[email protected]", "username");
// Run the query
const { results } = await bound.all();
// String
stmt.bind("text value");
// Number
stmt.bind(123);
stmt.bind(45.67);
// Boolean (INTEGER'a dönüştürülür)
stmt.bind(true); // 1
stmt.bind(false); // 0
// null
stmt.bind(null);
// ArrayBuffer / Buffer (BLOB)
const buffer = new TextEncoder().encode("binary data");
stmt.bind(buffer);
// Sırayla parametre bağlama
const { results } = await db
.prepare("SELECT * FROM users WHERE email = ? AND username = ?")
.bind("[email protected]", "username")
.all();
// Belirli sıraya parametre bağlama
const { results } = await db
.prepare("SELECT * FROM users WHERE email = ?2 AND username = ?1")
.bind("username", "[email protected]")
.all();
// Aynı statement'ı tekrar kullan
const stmt = db.prepare("SELECT * FROM users WHERE id = ?");
const user1 = await stmt.bind(1).first();
const user2 = await stmt.bind(2).first();
const user3 = await stmt.bind(3).first();

run() metodu, sorguyu çalıştırır ve tam sonuç objesi döner:

const result = await db
.prepare("SELECT * FROM users WHERE username = ?")
.bind("ahmet")
.run();
console.log(result);
interface D1Result {
success: boolean;
meta: {
served_by: string; // Sunucu bilgisi
served_by_region: string; // Bölge bilgisi
served_by_primary: boolean; // Primary mi?
duration: number; // SQL süresi (ms)
changes: number; // Değişiklik sayısı
last_row_id: number; // Son eklenen row ID
changed_db: boolean; // DB değişti mi?
size_after: number; // DB boyutu (byte)
rows_read: number; // Okunan row sayısı
rows_written: number; // Yazılan row sayısı
total_attempts: number; // Toplam deneme (retry dahil)
};
results: any[] | null; // Sonuçlar
}
const { results, meta } = await db
.prepare("SELECT * FROM users LIMIT 10")
.run();
console.log("Results:", results);
console.log("Rows read:", meta.rows_read);
console.log("Duration:", meta.duration);
const { meta } = await db
.prepare(
"INSERT INTO users (email, username, first_name) VALUES (?, ?, ?)"
)
.bind("[email protected]", "newuser", "New")
.run();
console.log("Last row ID:", meta.last_row_id);
console.log("Changes:", meta.changes);
const { meta } = await db
.prepare("UPDATE users SET first_name = ? WHERE id = ?")
.bind("Updated", 1)
.run();
console.log("Changed rows:", meta.changes);
console.log("DB changed:", meta.changed_db);
const { meta } = await db
.prepare("DELETE FROM posts WHERE id = ?")
.bind(999)
.run();
console.log("Deleted rows:", meta.changes);

raw() metodu, sonuçları basit array olarak döner (metadata olmadan):

const results = await db
.prepare("SELECT username, email FROM users LIMIT 5")
.raw();
console.log(results);
// Çıktı:
// [
// ["ahmet", "[email protected]"],
// ["ayse", "[email protected]"],
// ...
// ]
const results = await db
.prepare("SELECT username, email FROM users LIMIT 5")
.raw({ columnNames: true });
console.log(results);
// Çıktı:
// [
// ["username", "email"],
// ["ahmet", "[email protected]"],
// ["ayse", "[email protected]"],
// ...
// ]

raw() metodu, daha az memory kullanır ve daha hızlıdır:

// ❌ Daha yavaş ve daha fazla memory
const { results } = await db
.prepare("SELECT * FROM large_table")
.run();
// ✅ Daha hızlı ve daha az memory
const results = await db
.prepare("SELECT * FROM large_table")
.raw();
  • ✅ Büyük veri setleri
  • ✅ Basit veri okuma işlemleri
  • ✅ Memory constrained ortamlar
  • ❌ Detaylı meta bilgi gerektiren durumlar

first() metodu, sadece ilk satırı döner:

const user = await db
.prepare("SELECT * FROM users WHERE username = ?")
.bind("ahmet")
.first();
console.log(user);
// Çıktı:
// {
// id: 1,
// username: "ahmet",
// email: "[email protected]",
// ...
// }
const user = await db
.prepare("SELECT * FROM users WHERE username = ?")
.bind("nonexistent")
.first();
console.log(user); // null
// Sadece email sütununu getir
const email = await db
.prepare("SELECT email FROM users WHERE username = ?")
.bind("ahmet")
.first("email");
console.log(email); // "[email protected]"
// Sütun bulunamazsa hata
try {
const invalid = await db
.prepare("SELECT username FROM users WHERE id = ?")
.bind(1)
.first("email"); // "email" sütunu yok!
} catch (error) {
console.error("Column not found:", error);
}
// Tek kullanıcı getirme
async function getUserByUsername(db: D1Database, username: string) {
return await db
.prepare("SELECT * FROM users WHERE username = ?")
.bind(username)
.first();
}
// Tek değer getirme
async function getUserEmail(db: D1Database, userId: number) {
return await db
.prepare("SELECT email FROM users WHERE id = ?")
.bind(userId)
.first("email");
}
// Exists kontrolü
async function userExists(db: D1Database, username: string): Promise<boolean> {
const user = await db
.prepare("SELECT 1 FROM users WHERE username = ?")
.bind(username)
.first();
return user !== null;
}

interface D1Result {
success: boolean;
meta: D1ResultMeta;
results: any[] | null;
}
interface D1ResultMeta {
// Sunucu bilgisi
served_by: string; // Örn: "miniflare.db", "D1"
served_by_region: string; // Örn: "WEUR", "ENAM"
served_by_primary: boolean; // Primary instance'den mi?
// Performans metrikleri
duration: number; // SQL execution süresi (ms)
sql_duration_ms?: number; // Detaylı SQL süresi
// Değişiklik bilgileri
changes: number; // Etkilenen row sayısı
last_row_id: number; // Son eklenen row ID
changed_db: boolean; // DB değişti mi?
// Storage bilgileri
size_after: number; // DB boyutu (byte)
// Row metrikleri
rows_read: number; // Okunan row sayısı
rows_written: number; // Yazılan row sayısı
// Retry bilgisi
total_attempts: number; // Toplam deneme (retry dahil)
}
interface D1ExecResult {
count: number; // Çalıştırılan query sayısı
duration: number; // Toplam süre (ms)
}
const { results, meta } = await db
.prepare("SELECT * FROM posts WHERE user_id = ?")
.bind(userId)
.run();
// Performans monitoring
if (meta.duration > 1000) {
console.warn(`Slow query: ${meta.duration}ms`);
}
// Row read monitoring
if (meta.rows_read > 10000) {
console.warn(`High row count: ${meta.rows_read} rows read`);
}
// Cost estimation
const estimatedCost = (meta.rows_read * 0.000001) + (meta.rows_written * 0.00001);
console.log(`Estimated cost: $${estimatedCost.toFixed(6)}`);

SQL injection, kötü niyetli kullanıcıların SQL sorgularına müdahale etmesidir:

// ❌ GÜVENLİKSİZ: Asla bunu yapmayın!
const username = req.query.username;
const query = `SELECT * FROM users WHERE username = '${username}'`;
// Kullanıcı "ahmet' OR '1'='1" girerse:
// SELECT * FROM users WHERE username = 'ahmet' OR '1'='1'
// Tüm kullanıcıları döner!
// ✅ GÜVENLİ: Prepared statements kullanın
const username = req.query.username;
const { results } = await db
.prepare("SELECT * FROM users WHERE username = ?")
.bind(username)
.all();
// Kullanıcı "ahmet' OR '1'='1" girerse:
// Aratırılır: "ahmet' OR '1'='1"
// Hiçbir kullanıcı bulunamaz (garanti!)
// 1. Input validation
function validateUsername(username: string): boolean {
// Sadece alfanümerik karakterler
return /^[a-zA-Z0-9_]+$/.test(username);
}
// 2. Length kontrolü
function validateEmail(email: string): boolean {
// Maksimum uzunluk
return email.length <= 255 && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
// 3. Whitelist kullanımı
function validateSortOrder(order: string): boolean {
const allowedOrders = ['ASC', 'DESC'];
return allowedOrders.includes(order.toUpperCase());
}
// Kullanım
if (!validateUsername(username)) {
return Response.json({ error: "Invalid username" }, { status: 400 });
}
const { results } = await db
.prepare("SELECT * FROM users WHERE username = ?")
.bind(username)
.all();

interface PaginationOptions {
page: number;
pageSize: number;
}
interface PaginatedResult<T> {
data: T[];
page: number;
pageSize: number;
totalPages: number;
totalRecords: number;
}
async function paginateUsers(
db: D1Database,
options: PaginationOptions
): Promise<PaginatedResult<any>> {
const { page, pageSize } = options;
const offset = (page - 1) * pageSize;
// Toplam kayıt sayısı
const countResult = await db
.prepare("SELECT COUNT(*) as count FROM users")
.first<{ count: number }>();
const totalRecords = countResult?.count || 0;
const totalPages = Math.ceil(totalRecords / pageSize);
// Sayfa verisi
const { results } = await db
.prepare("SELECT * FROM users ORDER BY created_at DESC LIMIT ? OFFSET ?")
.bind(pageSize, offset)
.run();
return {
data: results,
page,
pageSize,
totalPages,
totalRecords,
};
}
interface UserFilters {
username?: string;
email?: string;
minAge?: number;
maxAge?: number;
sortBy?: string;
sortOrder?: 'ASC' | 'DESC';
}
async function searchUsers(
db: D1Database,
filters: UserFilters
) {
// Base query
let query = "SELECT * FROM users WHERE 1=1";
const params: any[] = [];
// Dinamik filtreler
if (filters.username) {
query += " AND username LIKE ?";
params.push(`%${filters.username}%`);
}
if (filters.email) {
query += " AND email LIKE ?";
params.push(`%${filters.email}%`);
}
if (filters.minAge !== undefined) {
query += " AND age >= ?";
params.push(filters.minAge);
}
if (filters.maxAge !== undefined) {
query += " AND age <= ?";
params.push(filters.maxAge);
}
// Sıralama
const sortBy = filters.sortBy || "created_at";
const sortOrder = filters.sortOrder || "DESC";
query += ` ORDER BY ${sortBy} ${sortOrder}`;
// Limit
query += " LIMIT 100";
// Sorguyu çalıştır
const stmt = db.prepare(query);
const boundStmt = params.reduce((stmt, param) => stmt.bind(param), stmt);
const { results } = await boundStmt.run();
return results;
}
async function createPostWithTags(
db: D1Database,
data: {
userId: number;
title: string;
content: string;
tags: string[];
}
) {
// Batch ile transaction benzeri işlem
const statements = [
// Post'u ekle
db.prepare(
"INSERT INTO posts (user_id, title, content) VALUES (?, ?, ?)"
).bind(data.userId, data.title, data.content),
];
// Her tag için ekle
for (const tag of data.tags) {
statements.push(
db.prepare(
"INSERT INTO post_tags (post_id, tag_name) VALUES (?, ?)"
).bind(null, tag) // post_id bilinmiyor
);
}
// Tümünü çalıştır
const results = await db.batch(statements);
// Post ID'yi al
const postId = results[0].meta.last_row_id;
// Tag'leri güncelle
for (let i = 0; i < data.tags.length; i++) {
await db.prepare(
"UPDATE post_tags SET post_id = ? WHERE rowid = ?"
).bind(postId, /* rowid bilinmiyor */).run();
}
return postId;
}

// run() - Daha yavaş, daha fazla memory
console.time("run");
const { results } = await db
.prepare("SELECT * FROM large_table")
.run();
console.timeEnd("run");
// ~150ms, 50MB memory
// raw() - Daha hızlı, daha az memory
console.time("raw");
const results = await db
.prepare("SELECT * FROM large_table")
.raw();
console.timeEnd("raw");
// ~100ms, 30MB memory
// first() - Optimize edilmiş
console.time("first");
const user = await db
.prepare("SELECT * FROM users WHERE id = ?")
.bind(1)
.first();
console.timeEnd("first");
// ~50ms
// run() - Daha yavaş
console.time("run");
const { results } = await db
.prepare("SELECT * FROM users WHERE id = ?")
.bind(1)
.run();
const user = results[0];
console.timeEnd("run");
// ~75ms

Bu derste aşağıdaki konuları öğrendiniz:

✅ Prepared statements’ın önemi ve kullanımı ✅ bind(), run(), raw(), first() metodları ✅ Return objects ve meta data ✅ SQL injection koruması ✅ Performans optimizasyonu ✅ İleri seviye örnekler

Bir sonraki dersimizde “Return Objects ve Meta Data” başlığı altında:

  • Detaylı meta bilgileri
  • Monitoring ve debugging
  • Performance optimization stratejileri konularını inceleyeceğiz.
  1. run() ve raw() metotları arasındaki performans farkı nedir?
  2. SQL injection’den korunmak için hangi yöntemleri kullanmalısınız?
  3. first() metodu sonuç bulunamazsa ne döner?
  4. Meta data neden önemlidir ve hangi bilgileri içerir?
  5. Pagination nasıl implement edilir?

Ders Süresi: 75 dakika Zorluk Seviyesi: Orta Ön Koşullar: Ders 6: D1 Database API