Prepared Statements
Ders 7: Prepared Statements
Section titled “Ders 7: Prepared Statements”Öğrenme Hedefleri
Section titled “Öğrenme Hedefleri”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
İçerik İçindekiler
Section titled “İçerik İçindekiler”- Prepared Statements Nedir?
- bind() Metodu
- run() Metodu
- raw() Metodu
- first() Metodu
- Return Objects
- SQL Injection Koruması
Prepared Statements Nedir?
Section titled “Prepared Statements Nedir?”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
Temel Yapı
Section titled “Temel Yapı”// 1. Sorguyu hazırlaconst stmt = db.prepare("SELECT * FROM users WHERE username = ?");
// 2. Parametreleri bağlaconst boundStmt = stmt.bind("ahmet");
// 3. Sorguyu çalıştırconst { results } = await boundStmt.all();Zincirleme
Section titled “Zincirleme”// Kısa ve okunabilir zincirlemeconst { results } = await db .prepare("SELECT * FROM users WHERE username = ?") .bind("ahmet") .all();bind() Metodu
Section titled “bind() Metodu”Temel Kullanım
Section titled “Temel Kullanım”bind() metodu, prepared statement’a parametre bağlar:
// Tek parametreconst stmt = db.prepare("SELECT * FROM users WHERE id = ?");const bound = stmt.bind(1);
// Çoklu parametreconst stmt = db.prepare( "SELECT * FROM users WHERE email = ? AND username = ?");const bound = stmt.bind("user@example.com", "username");
// Run the queryconst { results } = await bound.all();Parametre Türleri
Section titled “Parametre Türleri”// Stringstmt.bind("text value");
// Numberstmt.bind(123);stmt.bind(45.67);
// Boolean (INTEGER'a dönüştürülür)stmt.bind(true); // 1stmt.bind(false); // 0
// nullstmt.bind(null);
// ArrayBuffer / Buffer (BLOB)const buffer = new TextEncoder().encode("binary data");stmt.bind(buffer);Parameter Binding Stratejileri
Section titled “Parameter Binding Stratejileri”Anonim Parametreler (?)
Section titled “Anonim Parametreler (?)”// Sırayla parametre bağlamaconst { results } = await db .prepare("SELECT * FROM users WHERE email = ? AND username = ?") .bind("user@example.com", "username") .all();Sıralı Parametreler (?N)
Section titled “Sıralı Parametreler (?N)”// Belirli sıraya parametre bağlamaconst { results } = await db .prepare("SELECT * FROM users WHERE email = ?2 AND username = ?1") .bind("username", "user@example.com") .all();Çoklu Kez Bind
Section titled “Çoklu Kez Bind”// Aynı statement'ı tekrar kullanconst 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
Section titled “run() Metodu”Temel Kullanım
Section titled “Temel Kullanım”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);D1Result Objesi
Section titled “D1Result Objesi”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}SELECT ile Kullanım
Section titled “SELECT ile Kullanım”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);INSERT ile Kullanım
Section titled “INSERT ile Kullanım”const { meta } = await db .prepare( "INSERT INTO users (email, username, first_name) VALUES (?, ?, ?)" ) .bind("new@example.com", "newuser", "New") .run();
console.log("Last row ID:", meta.last_row_id);console.log("Changes:", meta.changes);UPDATE ile Kullanım
Section titled “UPDATE ile Kullanım”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);DELETE ile Kullanım
Section titled “DELETE ile Kullanım”const { meta } = await db .prepare("DELETE FROM posts WHERE id = ?") .bind(999) .run();
console.log("Deleted rows:", meta.changes);raw() Metodu
Section titled “raw() Metodu”Temel Kullanım
Section titled “Temel Kullanım”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", "ahmet@example.com"],// ["ayse", "ayse@example.com"],// ...// ]Column Names Dahil Etme
Section titled “Column Names Dahil Etme”const results = await db .prepare("SELECT username, email FROM users LIMIT 5") .raw({ columnNames: true });
console.log(results);// Çıktı:// [// ["username", "email"],// ["ahmet", "ahmet@example.com"],// ["ayse", "ayse@example.com"],// ...// ]Performans Avantajı
Section titled “Performans Avantajı”raw() metodu, daha az memory kullanır ve daha hızlıdır:
// ❌ Daha yavaş ve daha fazla memoryconst { results } = await db .prepare("SELECT * FROM large_table") .run();
// ✅ Daha hızlı ve daha az memoryconst results = await db .prepare("SELECT * FROM large_table") .raw();Kullanım Alanları
Section titled “Kullanım Alanları”- ✅ Büyük veri setleri
- ✅ Basit veri okuma işlemleri
- ✅ Memory constrained ortamlar
- ❌ Detaylı meta bilgi gerektiren durumlar
first() Metodu
Section titled “first() Metodu”Temel Kullanım
Section titled “Temel Kullanım”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: "ahmet@example.com",// ...// }Sonuç Bulunamazsa
Section titled “Sonuç Bulunamazsa”const user = await db .prepare("SELECT * FROM users WHERE username = ?") .bind("nonexistent") .first();
console.log(user); // nullBelirli Bir Sütunu Getirme
Section titled “Belirli Bir Sütunu Getirme”// Sadece email sütununu getirconst email = await db .prepare("SELECT email FROM users WHERE username = ?") .bind("ahmet") .first("email");
console.log(email); // "ahmet@example.com"
// Sütun bulunamazsa hatatry { 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);}Pratik Kullanım
Section titled “Pratik Kullanım”// Tek kullanıcı getirmeasync function getUserByUsername(db: D1Database, username: string) { return await db .prepare("SELECT * FROM users WHERE username = ?") .bind(username) .first();}
// Tek değer getirmeasync 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;}Return Objects
Section titled “Return Objects”D1Result
Section titled “D1Result”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)}D1ExecResult
Section titled “D1ExecResult”interface D1ExecResult { count: number; // Çalıştırılan query sayısı duration: number; // Toplam süre (ms)}Meta Bilgi Kullanımı
Section titled “Meta Bilgi Kullanımı”const { results, meta } = await db .prepare("SELECT * FROM posts WHERE user_id = ?") .bind(userId) .run();
// Performans monitoringif (meta.duration > 1000) { console.warn(`Slow query: ${meta.duration}ms`);}
// Row read monitoringif (meta.rows_read > 10000) { console.warn(`High row count: ${meta.rows_read} rows read`);}
// Cost estimationconst estimatedCost = (meta.rows_read * 0.000001) + (meta.rows_written * 0.00001);console.log(`Estimated cost: $${estimatedCost.toFixed(6)}`);SQL Injection Koruması
Section titled “SQL Injection Koruması”SQL Injection Nedir?
Section titled “SQL Injection Nedir?”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!Prepared Statements ile Koruması
Section titled “Prepared Statements ile Koruması”// ✅ GÜVENLİ: Prepared statements kullanınconst 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!)Ek Güvenlik Önlemleri
Section titled “Ek Güvenlik Önlemleri”// 1. Input validationfunction 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ımif (!validateUsername(username)) { return Response.json({ error: "Invalid username" }, { status: 400 });}
const { results } = await db .prepare("SELECT * FROM users WHERE username = ?") .bind(username) .all();İleri Seviye Örnekler
Section titled “İleri Seviye Örnekler”Örnek 1: Pagination
Section titled “Örnek 1: Pagination”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, };}Örnek 2: Search with Filters
Section titled “Örnek 2: Search with Filters”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;}Örnek 3: Transaction-like Operations
Section titled “Örnek 3: Transaction-like Operations”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;}Performans Karşılaştırması
Section titled “Performans Karşılaştırması”run() vs raw()
Section titled “run() vs raw()”// run() - Daha yavaş, daha fazla memoryconsole.time("run");const { results } = await db .prepare("SELECT * FROM large_table") .run();console.timeEnd("run");// ~150ms, 50MB memory
// raw() - Daha hızlı, daha az memoryconsole.time("raw");const results = await db .prepare("SELECT * FROM large_table") .raw();console.timeEnd("raw");// ~100ms, 30MB memoryfirst() vs run()
Section titled “first() vs run()”// 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");// ~75msBu 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
Sonraki Ders
Section titled “Sonraki Ders”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.
Kaynaklar
Section titled “Kaynaklar”Alıştırma Soruları
Section titled “Alıştırma Soruları”- run() ve raw() metotları arasındaki performans farkı nedir?
- SQL injection’den korunmak için hangi yöntemleri kullanmalısınız?
- first() metodu sonuç bulunamazsa ne döner?
- Meta data neden önemlidir ve hangi bilgileri içerir?
- Pagination nasıl implement edilir?
Ders Süresi: 75 dakika Zorluk Seviyesi: Orta Ön Koşullar: Ders 6: D1 Database API