SQLDelight

Gaye Suner
IBTech
Published in
5 min readMar 24, 2022
Photo by Maria Teneva on Unsplash

Başlarken

Merhaba, bu blog’da Cash App tarafından oluşturulan, yazdığımız SQL sorgularından typesafe kotlin API’leri oluşturmaya yardımcı olan bir veri tabanı kütüphanesinden bahsedeceğim: SQLDelight.

SQLDelight, Kotlin multiplatform bir kitaplıktır. Kotlin multiplatform kitaplıklar, kotlin ile yazılmış bir uygulamanın birden çok platformda çalışmasını sağlar. Multiplatform ile ilgili detaylara kotlinin web sitesinden ulaşabilirsiniz.

SQLDelight bir kod üreticisidir. Temel olarak, veri tabanımız için gerçek SQL ifadeleri yazmak yerine SQLDelight onları kotlinden çağırabileceğimiz sınıflar veya yöntemler haline dönüştürür. Ayrıca istediğimiz herhangi bir SQL veri tabanına uyum sağlayabilir. SQLite, MySQL, PostgreSQL vb.

Bir başka deyişle SQLDelight’ta yazdığımız kodlar, uygulamanın alt yapısında kullanılacak veri tabanı sistemleri için abstract kodlardır.

Son olarak SQLDelight plugin’in yararlı özelliklerinden biri de veri tabanı oluşturacak komut dosyaları için de otomatik tamamlama ve syntax önermesi sağlamasıdır. Böylece kod yazımı hızlanır ve zamandan tasarruf sağlanır.

Proje Oluştururken

Konuyu pekiştirmek açısından bir uygulama üzerine deneme yapalım.

  • İlk aşamada SQLDelight IDE eklentisi aşağıdaki adımlar takip edilerek Android Studio’ya eklenmelidir.

File -> Settings -> Plugins -> Marketplace -> SQLDelight ->Install

  • SQLDelight için gereken kitaplık projeye eklenmelidir. Proje seviyesindeki build.gradle dosyasındaki buildscript -> dependencies bloğuna aşağıdaki kod eklenmelidir.
classpath("com.squareup.sqldelight:gradle-plugin:1.5.0")
  • Son olarak, Gradle eklentisi de uygulama modülüne eklenmelidir. Modül seviyesindeki build.gradle dosyasında en üstteki satıra aşağıdaki kod eklenmelidir. Ardından proje sync edilir.
apply plugin: ‘com.squareup.sqldelight:gradle-plugin:1.5.0'

SQLDelight için gradle eklentisi, tüketicilere (domain-specific language) etki alanına özgü bir dil yani DSL sunar. (DSL’in anlamını görmek için tıklayın.)

Modül seviyesindeki build.gradle dosyasının android bloğunun içine aşağıdaki kod eklenmelidir.

sqldelight {

Database {

packageName = "$<apppackagename>.db"

sourceFolders = ["sqldelight"]

schemaOutputDirectory = file("src/main/sqldelight/schemas")

verifyMigrations = true

}

}

packageName: dönüştürülmek istenen database’in oluşacağı paket sourceFolders : sqldelight syntaxı ile yazılmış kodların bulunduğu directory

Not: Yukarıdaki blok içerisine dependencyKey diye bir değişken daha eklenebilir. Dependency key veri tabanının içeriğini birden çok gradle modülü arasında dağıtmayı ve ardından bunları uygulama modülünüzde tekrar bir araya getirmeyi sağlar.

VERİ TABANI İŞLEMLERİ

SQL Delight, tablolar ve sorgular gibi işlevlerini açıklamak için özel komut dosyaları kullanır. Bu dosyalar .sq dosya uzantısını kullanır. Bu .sq uzantılı dosyalar src/main/sqldelight paketi içerisinde oluşturulur.

proje dosyaları görüntüleme seçeneklerinden Android yerine Project’i seçin
klasör yapısını yukarıdaki şekildeki gibi oluşturun

Yukarıdaki adımları tamamladıktan sonra .sq uzantılı dosya artık oluşturulabilir. db klasörüne: sağ tıkla -> new -> sqldelight file/table burada file,table ve migration olmak üzere üç farklı dosya çeşidi eklenebilir.

Bir SQLDelight dosyasında yazılabilecek basit query ifadelerini deneyelim:

  • CREATE TABLE
  • INSERT
  • SELECT
  • DELETE
  • REPLACE

SQLDelight kullanarak veri tabanı oluşturacağımız bir uygulama yapalım. Uygulamayı GitHub hesabında bulabilirsiniz. https://github.com/pawntoqueen/Movies-SQLDelight

CREATE TABLE

SQLDelight kütüphanesinin film ismi, IMDB puanı ve yayınlanma yılı bilgilerini bulunduracak veri tabanını oluşturması için .sq uzantılı dosyaya aşağıdaki gibi create table komutu yazabiliriz.

CREATE TABLE Movies (
movie_name TEXT NOT NULL,
movie_imdb_point INTEGER NOT NULL,
release_year INTEGER NOT NULL
);

INSERT

Uygulamanın alt yapısındaki veri tabanı sistemine (örnekte verdiğim SQLite) veri eklemek için aşağıdaki gibi bir Insert fonksiyonu yazabiliriz. Alınacak parametreler “?” ile temsil edilir.

add_movie:
INSERT INTO Movies
VALUES(?,?,?);

SELECT

SQLite için query üretmesi üzere .sq dosyasına aşağıdaki gibi logic komutları yazılabilir. Örnekte, tüm filmleri seçme veya isme göre seçim yapma fonksiyonları yazılmıştır.

select_all:
SELECT *
FROM Movies
ORDER BY movie_name;
select_by_name:
SELECT *
FROM Movies
WHERE movie_name = ?;

DELETE

Oluşturulacak olan tablodan veri silme temsilini aşağıdaki gibi gösterebiliriz.

delete_by_name:
DELETE
FROM Movies
WHERE movie_name = ?;
empty_table:
DELETE FROM Movies;

TEST EDELİM

Veri tabanı oluşturmak üzere yapılması gereken tüm adımlar bitti. İşlemleri doğru yapıp yapmadığımızı kontrol etmek için bir Unit Test yazalım.

Uygulama bazında kullanılacak veritabanı SQLite olacağından modül seviyesindeki build.gradle dosyasına aşağıdaki bağımlılık eklenmelidir.

testImplementation "com.squareup.sqldelight:sqlite-driver:1.3.0

MoviesDatabaseTest.kt isimli bir test dosyası oluşturalım.

Test aşamasında, veri tabanını oluşturmak veya migrate etmek gibi ara aşamalarla uğraşmamak adına JdbcSqliteDriver sınıfı kullanılması önerilir. Bu unit test yazmak için kullanılan, sadece test esnasında aktif olacak şekilde bellekte oluşturulan bir sınıftır.

private val inMemorySqlDriver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY).apply {
Database.Schema.create(this)
}

Test kodlarının tamamı aşağıdaki gibidir.

package com.pawntoqueen.movieswithsqldelight 
import com.pawntoqueen.sqldelight.models.db.Movies
import com.raywenderlich.android.sqldelight.db.Database
import com.squareup.sqldelight.sqlite.driver.JdbcSqliteDriver
import junit.framework.TestCase.assertEquals
import org.junit.Test
class MovieDatabaseTest { private val inMemorySqlDriver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY).apply {
Database.Schema.create(this)
}
private val queries = Database(inMemorySqlDriver).moviesQueries @Test
fun smokeTest() {
val emptyItems: List = queries.selectAll().executeAsList()
assertEquals(emptyItems.size, 0)

queries.add_movie(
movie_name = "The Batman",
movie_imdb_point = 8,
release_year = 2022
)

val items: List<Movies> =queries.select_all().executeAsList()
assertEquals(items.size, 1)

val movie =queries.select_by_name("The Batman").executeAsOneOrNull()
assertEquals(movie?.movie_imdb_point?.toInt(), 8) assertEquals(movie?.release_year?.toInt(), 2022)
}

UYGULAMA

Uygulama bazında SQLite kullanacığımızdan bahsetmiştik. SQLDelight kütüphanesinin SQLite sürücüsü implemente edilmelidir. Modül seviyesindeki build.gradle dosyasına aşağıdaki AndroidSqliteDriver bağımlılığı eklenmelidir.

Not: Bu projede sadece bir app modülü olduğu için aşağıdaki bağımlılığı eklemek yeterlidir. Ancak çok modüllü bir proje olsaydı yukarıdaki anlattığım database bloğunu oluşturmak daha uygun olurdu.

implementation "com.squareup.sqldelight:android-driver:1.3.0"

Şimdi SQLDelight tarafından üretilmesini beklediğimiz sorguları MainActivity’de çağıralım;

val androidSqlDriver =  AndroidSqliteDriver(            
schema = Database.Schema,
context = applicationContext,
name = "items.db"
)

Test aşamasında kullanılan geçici bellekte veri tabanı oluşturmaya yarayan JdbcSqliteDriver sınıfı yerine verilerin kalıcı olarak bellekte tutulmasını sağlayan SQLite sürücüsü olan AndroidSqliteDriver sınıfından bir obje türetilir.

SQLDelightin ürettiği SQLite veri tabanı sınıfında otomatik olarak <db_name>Queriesisimli bir fonsiyonu oluşur. Bu fonksiyonla Create, Insert, Select, Delete sorgularına ulaşılabilir.

val queries = Database(androidSqlDriver).moviesQueries

Not: Veri tabanına veri ekleme kodları doğrudan MainActivity içinde olamaz. Ancak anlatım kolaylığı açısından şu anda aşağıdaki gibi yeni filmler ekledik.

queries.add_movie("Spiderman", 8, 2002)        queries.add_movie("Ironman",8 , 2008)        queries.add_movie("Captain America", 7, 2011)val itemsAfter: List<Movies> = queries.select_all().executeAsList()       Log.d("ItemDatabase", "Items After: $itemsAfter")

Uygulama Hazır!

VERİ TABANI MIGRATION

.sq dosyası bir veri tabanının en son halinin nasıl oluşturulduğunu açıklar. Veri tabanın şu anda daha eski bir sürümdeyse, .sqm uzantılı migration dosyası oluşturulmalıdır.

Oluşturulan her veritabanının ilk versiyonu 1’dir. Migration dosyalarının isimleri hep <version>.sqm olarak adlandırılır. İlk versiyondan ikinci versiyona geçiş için 1.sqm dosyasının içine şu yazılır:

ALTER TABLE movies ADD COLUMN <DEĞİŞİKLİKLER> INTEGER;

Bu kod parçası Database.Schema.migrate() fonksiyonunu tetikler ve migration işlemi gerçekleştirilmiş olur.

--

--