IOS SQLite3 Library
Swift SQLite3
Dosya tabanlı bir veritabanıdır.SQLite kullanımı yapılan projeye göre tercih edilebilir.Veriler disk de depolanmaktadır.Doğrudan okuma ve yazma sağlar.MySQL veya PostgereSQL gibi bir sunucuda tutulmamaktadır.Ancak API ile erişilmek istenildiğinde nasıl yapılabiliri aşağıda anlatıyor olacağım.
SQLite’ı 3 farklı dizinde tutabiliriz.
- Document Dizinine Depolama : Kullanıcının iTunes veya Finder aracılığıyla erişebileceği ve yedekleyebileceği dosyaları içerir.
let documentsDirectory = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let databaseURL = documentsDirectory.appendingPathComponent("myDatabase.sqlite")s
2.Library Dizini Depolama : Kullanıcı bu dizine doğrudan erişemez , ancak verileri İCloud ile yedeklenebilir.
let libraryDirectory = try! FileManager.default.url(for: .libraryDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let databaseURL = libraryDirectory.appendingPathComponent("myDatabase.sqlite")
3.Caches Dizini Depolama : Geçici verileri depolamak için kullanılır.Bu dizindeki veriler gerektiğinde yeniden oluşturulabilir ve bu nedenle iCloud yedeklemerine dahil edilmez.
let cachesDirectory = try! FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let databaseURL = cachesDirectory.appendingPathComponent("myDatabase.sqlite")
SQLite Sorguları:
CREATE
let createTableString = "CREATE TABLE IF NOT EXISTS person(Id Integer PRIMARY KEY,name TEXT,age INTEGER);"
SELECT
let queryStatementString = "SELECT * FROM tableName;"
or
let queryStatementString = "SELECT * FROM person WHERE person id = 1;"
INSERT
let insertStatementString = "INSERT INTO person(id,name,age) VALUES(?,?,?);"
UPDATE
let updateStatementString = "UPDATE User SET name=?, address=? WHERE email=?;"
DELETE
let deleteStatementString = "DELETE FROM person WHERE id = ?;"
SQLite Fonksiyonları
NOT:Veritabanında işlem yapılmadan önce bir bağlantı açılır.
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
Database’in Oluşturulması
FileManager : Dosyalar üzerinden verilerin kayıt edilmesini , çekilmesini sağlar yani dosya yönetimi için kullanırız.
func openDatabase() -> OpaquePointer?{
let filePath = try! FileManager.default.url(for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil, create: false)
.appendingPathComponent(dbPath)
var db:OpaquePointer? = nil
if sqlite3_open(filePath.path, &db) != SQLITE_OK{
print("can not open DB")
return nil
}else {
print("created connection to db at : \(dbPath)")
return db
}
}
Kayıt edilecek yer belirtilir.
“documentDirectory” : kullanıcının belge dizinini belirtir.
“userDomainMask” : kullanıcının ana dizinini ( home dizini) belirtir.Bu alanda çalıştırılır.
“appropriateFor” : Bu parametre , belirli bir dosya veya dizin için URL döndürmek istenildiğinde kullanılır. ”nil” girilerek yalnızca temel belge dizinini almak istenildiğini belirtir.
“create” : Belirtilen dizin mevcut değilse oluşturulup oluşturulmayacağını belirtir. Burada “false"
verilmiş, yani dizin mevcut değilse oluşturulmaz.
“appendingPathComponent”. : Tam dosya yolunu oluşturur.
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — -
Database’in Kayıt Edileceği Yerin Belirtilmesi:
.OpaquePointer ( ) ->Bir yapı dır.Memoryde adres ataması ve kullanılmasında kullanılır.SQL bunlar kullanılarak yapılır.Her şey bir adres dir ve her şey adres üzerinden yapılmaktadır.
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — -
Tablonun Oluşturulması :
Oluşturulmak istenen tablo için ilk önce create sorgusu yapılır.
func createTable() {
let createTableString = "CREATE TABLE IF NOT EXISTS person(Id Integer PRIMARY KEY,name TEXT,age INTEGER);"
var createTableStatement:OpaquePointer? = nil
if( sqlite3_prepare_v2(db, createTableString, -1, &createTableStatement, nil) == SQLITE_OK){
if sqlite3_step(createTableStatement) == SQLITE_DONE{
print("person table created")
}else{
print("person table could'nt be created")
}
}else{
print("CREATE TABLE statement could not be prepared")
}
sqlite3_finalize(createTableStatement)
}
- Oluşturulacak tablonun ismi “person” burada istenilen ismi girebilirsiniz.tabloda bulunacak özellikler parantez içerisinde ismi ve türü belirtilir.Her zaman “Id” özelliğinin type’ı PRIMARY KEY yapılır.Tek ve otomatik artmasını sağlar.
- “OpaquePointer” SQLite veritabanı işlemlerini işlemek için kullanılan bir göstericiyi temsil eder.Bir işaretçidir.
- SQL Sorgusunu hazırlama adımı : “sqlite3_prepare_v2” fonksiyonu ,SQL sorgusunu derleyip çalıştırmak üzere bir sorgu işaretçisi oluşturur.UTF-8 ‘i encoding yapar.
- SQL sorgusunu yürütme : “sqlite3_step” hazırlanan sorguyu yürütür.
- SQL sorgusunda Kaynakları Serbest Bırakma: “sqlite3_finalize”Her sorgu hazırlamasından(prepare) sonra finalize kullanılmalıdır.
NOT: Her tablo için 1 tane sınıf oluşturulması gerekiyor.
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — -
Select İşlemi
Burada kolaylık oluşması adına eklenecek veriye ait bir class(sınıf) oluşturuyoruz.Oluşturduğumuz class ismi “Employe” dir.
class Employee {
var name : String = ""
var age:Int = 0
var id:Int = 0
init(name: String, age: Int, id: Int) {
self.name = name
self.age = age
self.id = id
}
}
Select, belirtilen tabloda tüm verileri çekmek için “ * ” kullanırız.Ancak spesifik bir veriyide çekebiliyoruz.
func read() -> [Employee] {
let queryStatementString = "SELECT * FROM person "
var queryStatement:OpaquePointer? = nil
var employesArray:[Employee] = []
if sqlite3_prepare_v2(db, queryStatementString, -1, &queryStatement, nil) == SQLITE_OK {
while sqlite3_step(queryStatement) == SQLITE_ROW {
let id = sqlite3_column_int(queryStatement, 0)
let name = String(describing: String(cString: sqlite3_column_text(queryStatement, 1)))
let year = sqlite3_column_int(queryStatement, 2)
employesArray.append(Employee(name: name, age: Int(year), id: Int(id)))
print("Query Result:")
print("\(id) \(name) \(year)")
}
}else{
print("SELECT statement could not be prepared")
}
sqlite3_finalize(queryStatement)
return employesArray
}
İlk başta prepare yapılır daha sonrasında step kontrolü yapıldıktan sonra gelen verinin özelliklerini alarak bir değişkene atamamız gerekiyor.Nedeni ise burada “employesArray:Array” ‘i print yaptırmak istediğim için ,en son olarak finalize çağırılıp veri ekleme işlemi sonlandırılır.
Burada “sqlite3_column_int(queryStatement, 0)” 2.parametrenin amacı queryStatement yoksa bu değeri kullan demek için.
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — -
Insert İşlemi
Veri eklemek için kullanırız.
func insert(id:Int,name:String,age:Int){
let person = read() //db'den verileri almak için oluşturulan method.
for p in person {
if p.id == id {
return
}
}
let insertStatementString = "INSERT INTO person(id,name,age) VALUES(?,?,?);"
var insertStatement:OpaquePointer? = nil
if sqlite3_prepare_v2(db, insertStatementString, -1, &insertStatement, nil) == SQLITE_OK {
sqlite3_bind_int(insertStatement, 1, Int32(id))
sqlite3_bind_text(insertStatement, 2, (name as NSString).utf8String,-1,nil)
sqlite3_bind_int(insertStatement, 3, Int32(age))
if sqlite3_step(insertStatement) == SQLITE_DONE {
print("Successfully insterted")
}else {
print("could not instert row")
}
}else{
print("INSERT statemnt could not be prepared")
}
sqlite3_finalize(insertStatement)
}
Burada database’e eklenecek verinin bilgileri için bind kullanırız.
ilk başta prepare yapılır daha sonrasında step kontrolü yapıldıktan sonra en son olarak finalize çağırılıp veri ekleme işlemi sonlandırılır.
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — -
Delete İşlemi
Burada belirtilen veriyi nasıl siliyoruz onu göreceğiz.
Belirtilen id’deki veriyi sil demiş olacağız.
func deleteById(id:Int) {
let deleteStatementString = "DELETE FROM person WHERE id = ?;"
var deleteStatement:OpaquePointer? = nil
if sqlite3_prepare_v2(db, deleteStatementString, -1, &deleteStatement, nil) == SQLITE_OK {
sqlite3_bind_int(deleteStatement, 1, Int32(id))
if sqlite3_step(deleteStatement) == SQLITE_DONE {
print("Successfuly deleted row")
}else{
print("Coult not deleted row")
}
}else {
print("DELETE statement could not be prepared")
}
sqlite3_finalize(deleteStatement)
}
ilk başta prepare yapılır daha sonrasında silincek veriyi bind yaparız.Daha sonrasında step kontrolü yapıldıktan sonra en son olarak finalize çağırılıp veri ekleme işlemi sonlandırılır.
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — -
Update İşlemi
Buradaki işlemde aynı insert işlemi gibidir.Burada “WHERE age” diyerek age için verilen değere eşit olan tüm verilerin id ve name değerini girilen input ile güncelle demek oluyor.Güncellemede buna benzer sorgu yapısını değiştirebiliriz.
func updateUser(name: String, age: String, id: Int){
let updateStatementString = "UPDATE person SET id=?, name=? WHERE age=?;"
var updateStatement: OpaquePointer? = nil
if sqlite3_prepare_v2(db, updateStatementString, -1, &updateStatement, nil) == SQLITE_OK {
sqlite3_bind_int(updateStatement, 1, Int32(exactly: id)!)
sqlite3_bind_text(updateStatement, 2, (name as NSString).utf8String,-1,nil) /
sqlite3_bind_int(updateStatement, 3, Int32(age)!)
if sqlite3_step(updateStatement) == SQLITE_DONE {
print("User updated successfully.")
sqlite3_finalize(updateStatement)
} else {
print("Could not update.")
}
} else {
print("UPDATE statement is failed.")
}
}
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — -
FINISH
AKLIMIZA GELEN BİR KAÇ SORUYA CEVAP VE ÖNERİLER
Sınırlı veri okuyabiliyor muyuz ?
- Evet
Veri tabanı üzerinde search yaptırabiliyor muyuz ?
- Evet
İstenilen tek bir tane veriyi getirebiliyor muyuz ?
- Evet
Database nerede depolanır ?
Veriler Diskde depolanır.Doğrudan okuma ve yazma sağlar.MySQL ve PostgreSQL gibi değildir bir sunucuda tutulmaz ve bir istemci ile ulaşılmaz.Ancak
DB bir sunucudan ulaşılacaksa her bir sorgu için ayrı bir API endpoint’i oluşturulması gerekmektedir.Aşağıda Connection başlığında açıklıyoruz.
Peki telefonda nerelerde nasıl depolayabiliriz ?
- Document Dizinine Depolama : Kullanıcının iTunes veya Finder aracılığıyla erişebileceği ve yedekleyebileceği dosyaları içerir.
let documentsDirectory = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let databaseURL = documentsDirectory.appendingPathComponent("myDatabase.sqlite")
2.Library Dizini Depolama : Kullanıcı bu dizine doğrudan erişemez , ancak verileri İCloud ile yedeklenebilir.
let libraryDirectory = try! FileManager.default.url(for: .libraryDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let databaseURL = libraryDirectory.appendingPathComponent("myDatabase.sqlite")
3.Caches Dizini Depolama : Geçici verileri depolamak için kullanılır.Bu dizindeki veriler gerektiğinde yeniden oluşturulabilir ve bu nedenle iCloud yedeklemerine dahil edilmez.
let cachesDirectory = try! FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
let databaseURL = cachesDirectory.appendingPathComponent("myDatabase.sqlite")
JSON yapısında veri tutabilir miyiz ?
Evet Tutulabilir. TEXT türünde tutulabiliyor.Nasıl mı şu şekilde
let json: [String: Any] = [“name”: “John Doe”, “age”: 30, “city”: “New York”]
Böyle bir json’ımız mevcut diyelim.Bunu String’e dönüştürerek tutabiliyoruz.Aslında normal veriyi nasıl kayıt edebiliyorsak o şekilde düşünebiliriz.
ConnectionString ( SQL’e Api ile erişim) mümkün mü?
HAYIR : Direkt olarak API den ulaşılamıyor.SQLITE Dosya tabanlı olduğu için.
ANCAK alternatif olarak şu şekilde yapılabilir.
- Sunucu oluşturup SQLITE veri tabanına erişim sağlayan bir API oluşturabiliriz.
- veritabanı işlemleri (okuma,yazma,silme vb.). işlemler için ayrı ayrı API ler oluşturulabilir.
- Http istekleri göndererek API endpoint’lerine erişir ve veritabanı işlemlerini gerçekleştirir.
Transection Nedir, Nasıl ,Ne için kullanılır ?
Transaction’ların Kullanım Durumları
- Birden Fazla İşlemin Tek Bir Birim Olarak Ele Alınması:
- Örneğin, bir bankacılık uygulamasında, bir hesaptan para çekip başka bir hesaba yatırma işlemi iki ayrı işlemden oluşur. Bu işlemlerden herhangi biri başarısız olursa, diğerinin de geri alınması gerekir.
Veri Tutarlılığını Sağlama:
- Bir transaction, tüm işlemler tamamlanana kadar veri tutarlılığını korur. Örneğin, stok güncellemeleri yaparken, bir ürün stoğunun güncellenmesi ve satış kaydının eklenmesi gibi işlemler bir transaction içinde yapılmalıdır.
Hata Yönetimi ve Geri Alma (Rollback):
- Bir işlem sırasında hata oluşursa, transaction’ı geri alarak (rollback) veritabanını önceki durumuna döndürebilirsiniz. Bu, kısmi işlemler nedeniyle veritabanının tutarsız bir durumda kalmasını önler.
INJECTION Nedir ? Ne için Kullanılır ?
Bir uygulamanın kullanıcı girdilerini uygun şekilde denetlemediği durumlarda, kötü niyetli kullanıcıların veri tabanına zararlı SQL sorguları enjekte etmesine olanak tanıyan bir güvenlik açığıdır. Bu, özellikle web uygulamaları için ciddi bir güvenlik riski oluşturur ve SQLite gibi veri tabanı yönetim sistemlerinde de meydana gelebilir.
Bir örnekle açıklayalım:
Bir web uygulamasında, kullanıcı adı ve şifre ile giriş yapılan bir form olduğunu varsayalım. Uygulama, bu bilgileri şu şekilde bir SQL sorgusuyla kontrol ediyor olabilir:
SELECT * FROM users WHERE username = 'kullanici_adi' AND password = 'sifre';
Kötü niyetli bir kullanıcı, bu sorguya zararlı kod enjekte edebilir. Örneğin, kullanıcı adını şu şekilde girdiğinde:
' OR '1'='1
Sorgu artık değişti ve şu hale geldi
SELECT * FROM users WHERE username = '' OR '1'='1';
Bu sorgu, 1=1
her zaman doğru olduğu için, veri tabanındaki tüm kullanıcı kayıtlarını döndürecektir. Bu da saldırganın uygulamaya giriş yapmasına olanak tanır.
SQLite’da SQL Injection’ı Önlemek
- Hazır Değişkenler (Prepared Statements) Kullanmak: Parametreli sorgular kullanarak kullanıcı girdilerinin doğrudan SQL sorgularına dahil edilmesini engellemek.
import sqlite3
connection = sqlite3.connect('database.db')
cursor = connection.cursor()
# Kullanıcı girdilerini parametrik olarak sorguya dahil etmek
cursor.execute("SELECT * FROM users WHERE username = ? AND password = ?", (username, password))
Injection ‘ı önlemek için bazı kütüphaneler mevcut.En popüler olanı ilk ikisidir.
Öneri
Eğer kolay kullanım ve hızlı başlangıç yapmak istiyorsanız, SQLite.swift sizin için iyi bir seçenek olacaktır. Daha karmaşık ihtiyaçlarınız varsa veya performans ve esneklik sizin için önemliyse, GRDB’yi tercih edebilirsiniz.
- SQLite.swift
- GRDB
- FMDB
Aşağıdaki Bağlatıda SQLite.swift kullanımını anlattığım diğer gönderim :