İçeriğe geç
Tüm yazılar

Room'da tam metin arama: 2026'da yerel öncelikli bir Android uygulamasına anında arama eklemek

Room'un FTS4 desteğine pratik bir rehber — sanal bir arama tablosu kurmak, onu tetikleyicilerle senkron tutmak ve FTS5'in neden elle bir migration gerektirdiği.

MFKAPPS 4 dk okuma

Arama, kimse fark etmeyene kadar var olmayan bir özelliktir. Üç yüz ürünlük bir kilere “yog” yazın ve liste yazdıkça filtrelensin bekleyin — yanıt 200 milisaniye bile sürse, uygulama bozuk hissettirir. Room tabanlı çoğu uygulama bunu bir LIKE '%sorgu%' cümlesiyle çözer ve küçük tablolarda gayet iyi çalışır. Tablo büyüdüğünde, sorgu birden fazla kelime içerdiğinde ya da yazım hatasına tolerans istediğinizde çalışmayı bırakır. SQLite’ın buna gerçek bir cevabı ezelden beri var: tam metin arama, Room’da @Fts4 ek açıklaması olarak açığa çıkıyor. İşte gerçekte nasıl çalıştığı, nerede kırıldığı ve çoğu eğitimde varsayılan FTS5’i Room’un neden ücretsiz vermediği.

LIKE neden ölçeklenmez

SELECT * FROM pantry_items WHERE name LIKE '%yogurt%' bir index kullanamaz. SQLite her satırı taramak ve her birinde bir alt dize eşleşmesi çalıştırmak zorundadır. Birkaç yüz satırda bu görünmezdir. Barkod tarama ve fiş geçmişiyle bir kiler uygulamasının sandığınızdan daha hızlı ulaştığı birkaç bin satırda, her tuş vuruşunda görünür bir takılmaya dönüşür — özellikle sorgu birden fazla sütunda da (ad, marka, kategori) OR çalıştırmak zorundaysa.

Tam metin arama bunu tersine çevirir. Satırları taramak yerine, SQLite yazma anında ters bir index kurar: her kelime, onu içeren satırlara eşlenir. Bir arama, bir tarama değil, bir index sorgusu haline gelir; bu yüzden tablo büyüdükçe performans sabit kalır.

Room’un gerçekte verdiği: FTS4, FTS5 değil

İnsanları şaşırtan kısım burası, çünkü web’deki çoğu FTS içeriği SQLite’ın daha yeni FTS5 modülünü varsayıyor. Room’un @Fts4 ek açıklaması bir FTS4 sanal tablosu kurar — bir @Fts5 karşılığı yoktur. FTS4 ve FTS5 bunun önemli olacağı kadar farklıdır: FTS5’in daha makul bir sorgu söz dizimi, yerleşik bm25() sıralaması ve önek sorgularının daha iyi ele alınışı vardır; bunların hiçbiri Room’un FTS4 ile otomatik olarak verdiği şeyler değildir.

FTS5’e ihtiyacınız varsa, yine de elde edebilirsiniz — bunu elle yaparsınız: sanal tabloyu, ek açıklamalı bir entity yerine bir Migration içinde ham SQL ile (CREATE VIRTUAL TABLE ... USING fts5(...)) kendiniz oluşturur, okumalar için düz bir @DatabaseView veya ham sorguyla eşlersiniz. Çoğu yerel arama kullanım durumu için — kiler adları, abonelik adları, not başlıkları — FTS4 fazlasıyla yeterlidir ve size bir ek açıklama dışında hiçbir şeye mal olmaz. Oradan başlayın; elle FTS5 yoluna yalnızca gerçekten cümle sorgularına veya yerleşik sıralamaya ihtiyacınız varsa başvurun.

Room’a bir FTS4 tablosu bağlamak

Bir FTS tablosunun bir eşlik eden “içerik” entity’sine ihtiyacı vardır — zaten her şey için sorguladığınız normal tablo — artı aranabilir sütunları indeksleyen bir sanal tablo:

@Entity(tableName = "pantry_items")
data class PantryItem(
    @PrimaryKey(autoGenerate = true) val id: Long = 0,
    val name: String,
    val brand: String,
    val category: String,
)

@Fts4(contentEntity = PantryItem::class)
@Entity(tableName = "pantry_items_fts")
data class PantryItemFts(
    val name: String,
    val brand: String,
    val category: String,
)

contentEntity, Room’a pantry_items’ı gerçeğin kaynağı olarak ele almasını, her satırın bir kopyasını değil yalnızca indeksi FTS tablosunda saklamasını söyler. DAO sorgusu, FTS tablosunun rowid’sine karşı birleştirme yapması dışında, normal bir sorguya neredeyse aynı görünür:

@Query("""
    SELECT pantry_items.* FROM pantry_items
    JOIN pantry_items_fts ON pantry_items.id = pantry_items_fts.rowid
    WHERE pantry_items_fts MATCH :query
""")
fun search(query: String): Flow<List<PantryItem>>

MATCH, FTS operatörüdür — sorguyu bir taramaya değil bir index sorgusuna dönüştüren şey budur. "yog" yerine "yog*" geçmek size önek eşleşmesi verir, yazarken arama için istediğiniz de tam olarak budur.

İndeksi senkron tutmak

contentEntity ile, Room ekleme, güncelleme ve silmede pantry_items_fts’i pantry_items ile senkron tutan tetikleyicileri üretir — bunları elle yazmazsınız. Dikkat edilmesi gereken tek şey: bu senkron yalnızca Room’un üretilmiş insert/update/delete yöntemlerinden geçen yazmaları kapsar. Room’un DAO katmanı dışında çalıştırılan ham bir SQL UPDATE ya da execSQL ile yapılan toplu bir içe aktarma, tetikleyici destekli içerik entity eşlemesini ince biçimlerde yanlış gidebilecek şekilde atlar. Barkod-tarama ekleme yolu dahil kiler değişikliklerini, “hızlı” içe aktarmalar için ayrı bir ham-SQL kısayolu yerine, Room’un şema doğrulamasının zaten hesaba kattığı aynı DAO yöntemlerinden geçirin.

Sıralama: FTS4’ün gerçek sınırı

FTS4 size doğru eşleşmeler verir ama eşleşme sırası dışında bir alaka sıralaması vermez. Biri “süt” arasa ve hem “Tam Yağlı Süt” hem de “Sütlü Çikolata” eşleşse, FTS4 hangisini kastettiğinizi söylemez — satırları tablo sırasına göre alırsınız, alaka sırasına göre değil. Birkaç yüz kiler ürünü için bu pratikte nadiren önemlidir; liste taramak için yeterince kısadır. Önemli olmaya başlarsa — daha büyük bir katalog ya da daha geniş bir alan kümesinde arama — FTS5’e atlamadan çözüm basit bir istemci tarafı yeniden sıralamasıdır: eşleşmeleri çekin, sonra sorgunun adın başlangıcıyla eşleşip eşleşmediğine göre önce sıralayın. Bu birkaç satır Kotlin’dir, bir veritabanı migration’ı değil; ve kullanıcıları gerçekten rahatsız eden durumu düzeltir: tam ve önek eşleşmelerinin ilgisiz kısmi eşleşmelerin altında gömülmesi.

Sonuç

Room’un FTS4 desteği, tek bir ek açıklama ve biraz farklı bir DAO sorgusu karşılığında doğrusal bir taramayı bir index sorgusuna çevirir — sunucu yok, üçüncü taraf arama SDK’sı yok, anında hissetmesi gereken bir şey için ağ üzerinden gidiş-dönüş yok. Herhangi bir yerel öncelikli Android uygulamasında arama için doğru varsayılan budur. Hatırlanmaya değer iki şey: Room FTS4 konuşur, FTS5 değil, bu yüzden yalnızca daha yeni modülde bulunan sıralama özellikleri etrafında tasarım yapmayın; ve aranabilir olması gereken her yazma yolu Room’un DAO katmanından geçmek zorundadır, yoksa index sessizce tanımlaması gereken tablodan senkron dışı kalır. Bu ikisini doğru yapın, arama endişelenmeniz gereken bir özellik olmaktan çıkar.

Bu deseni gönderilmiş bir uygulamada görmek isterseniz, Stocky bunu yüzlerce ürüne büyüyebilen bir kileri aramak için kullanıyor — barkod taramaları, fiş içe aktarmaları ve elle girişlerin hepsi aynı aranabilir tabloya iniyor.