- Tablo tanımının sonuna
STRICT eklemek, tamsayı sütununa rastgele metin girilmesi gibi tip hatalarını erken aşamada engelleyerek veri bütünlüğünü artırabilir
- Ekleme ve güncelleme sırasında tipler denetlenirken,
'123' gibi kayıpsız dönüştürülebilen değerler kabul edilir; sütun tipleri ise INT, INTEGER, REAL, TEXT, BLOB, ANY ile sınırlıdır
- Birden fazla tip barındırması gereken sütunlar için
ANY belirtilebilir; böylece sıkı doğrulama ile esnek depolama aynı tablo içinde birlikte kullanılabilir
- Mevcut tablolar doğrudan STRICT'e çevrilemez; bunun yerine yeni tablo oluşturma ve veriyi kopyalama gerekir, ayrıca hatalı mevcut veriler temizlenmeli veya tip dönüşümünden geçirilmelidir
- STRICT tablolar yalnızca SQLite 3.37.0 ve sonrası sürümlerde desteklenir; kuramsal bir denetim maliyeti olsa da, gayriresmî deneylerde belirgin bir performans ya da dosya boyutu farkı görülmemiştir
STRICT tablo oluşturma
- SQLite'ın STRICT tabloları, diğer SQL motorlarına benzer şekilde daha sıkı tip denetimi uygular
CREATE TABLE tanımının sonuna STRICT eklemek yeterlidir
CREATE TABLE people (name TEXT) STRICT;
Ekleme ve güncellemede tip denetimi
- Normal SQLite tablolarında
INTEGER sütununa 'garbage' gibi bir metin kaydedilebilir; ancak STRICT tablolar tip uyuşmazlığını hata olarak ele alır
CREATE TABLE people_nonstrict (age INTEGER);
INSERT INTO people_nonstrict (age) VALUES ('garbage');
-- başarıyla işlendi
CREATE TABLE people_strict (age INTEGER) STRICT;
INSERT INTO people_strict (age) VALUES ('garbage');
-- hata: cannot store TEXT value in INTEGER column
- Aynı doğrulama
UPDATE için de geçerlidir; yani kayıt sonrasında değer değiştirilirken de yanlış tipler engellenir
- Ancak bir değer kayıpsız biçimde dönüştürülebiliyorsa, STRICT tabloda buna izin verilir
'123' dizgesi, 123 tamsayısına eksiksiz dönüştürülebildiği için aşağıdaki iki ekleme aynı şekilde işlenir
INSERT INTO people_strict (age) VALUES ('123');
INSERT INTO people_strict (age) VALUES (123);
Tablo tanımı aşamasında tip kısıtlamaları
- Normal tablolarda, SQLite'ın desteklemediği tip adları da sütun bildiriminde kullanılabilir
GARBAGE, DATETIME, JSON, UUID, BLOBB kabul edilir
- Bu tür bildirimler, yazım hatalarından veya SQLite'ın desteklediği veri tipleri hakkındaki yanlış anlamalardan kaynaklanabilir
- STRICT tablolar, desteklenmeyen bir tip adı kullanıldığında oluşturma aşamasında hata verir
CREATE TABLE tbl (name GARBAGE) STRICT;
CREATE TABLE tbl (name DATETIME) STRICT;
CREATE TABLE tbl (name JSON) STRICT;
CREATE TABLE tbl (name UUID) STRICT;
CREATE TABLE tbl (name BLOBB) STRICT;
- İzin verilen sütun tipleri yalnızca
INT, INTEGER, REAL, TEXT, BLOB, ANY'dir
- Tüm sütunlara bir tip verilmelidir; bu nedenle
CREATE TABLE tbl (name) gibi tip atlaması yapılamaz
ANY ile esnekliği korumak
- Tipi sabit olmayan verileri depolamanız gerekiyorsa
ANY sütunu kullanabilirsiniz
- STRICT tablo içinde bile
ANY sütununa tamsayı, metin, kayan noktalı sayı, BLOB gibi her tip kaydedilebilir
CREATE TABLE tbl (value ANY) STRICT;
INSERT INTO tbl (value) VALUES (123);
INSERT INTO tbl (value) VALUES ('text');
INSERT INTO tbl (value) VALUES (12.34);
INSERT INTO tbl (value) VALUES (X'8647');
Mevcut tabloları taşıma
- Mevcut gevşek tipli bir tabloyu
ALTER ile doğrudan STRICT tabloya çevirmenin yolu yoktur; bu yüzden baştan STRICT oluşturmak daha basittir
- Mevcut bir tabloyu dönüştürmek için yeni STRICT tablo oluşturma → veriyi kopyalama → eski tabloyu değiştirme adımları gerekir
CREATE TABLE new_people (name TEXT) STRICT;
INSERT INTO new_people SELECT * FROM people;
DROP TABLE people;
ALTER TABLE new_people RENAME TO people;
- Mevcut veride, örneğin tamsayı sütununda metin bulunması gibi tip hataları varsa kopyalama sırasında hata oluşabilir
- Taşıma öncesinde veriyi temizlemek veya
CAST ile tip dönüşümü yapmak gerekebilir
- Yalnızca yeni tablolara STRICT uygulama politikası da mümkündür; ancak tablolar arasında doğrulama sıkılığı farklı olacağından, tüm tabloların gevşek olduğu duruma göre davranışı öngörmek daha zor olabilir
Esnek tiplerin uygun olduğu durumlar
- SQLite geliştiricileri, esnek tiplerin avantajlarını ayrıca açıklayarak varsayılan davranışın yararlı olduğu örnekler sunar
- Gevşek tipli tabloların makul olabileceği kullanım alanları şunlardır
- Birden çok tipi olduğu gibi tutan saf anahtar-değer deposu
- Tipleri farklı çeşitli özellikleri saklama alanı
- Kirli CSV verilerini doğrudan içe aktarırken geçersiz değerleri de kaybetmeden korumak gereken durumlar
- Ancak beklenmeyen tipler ince hatalara yol açabilir; bu nedenle genel amaçlı tablolarda bunları sessizce kabul etmektense hemen hata vermek daha avantajlıdır
- SQLite kaynak kodunda, gevşek tipli tabloları
legacy olarak adlandıran yorumlar da vardır; ancak bunu resmi belgeler kadar güvenilir bir dayanak saymak zordur
Sürüm uyumluluğu
- STRICT tablolar, Kasım 2021'de yayımlanan SQLite 3.37.0 ile tanıtıldı
- Daha eski SQLite sürümlerinde STRICT tablolar kullanılamaz
- Eski sürümler, STRICT tablo içeren veritabanlarını da okuyamaz
- Güncel SQLite ile STRICT tablo oluşturduktan sonra aynı veritabanını SQLite 3.36.0 ile açarsanız hata alırsınız
Performans ve depolama alanı
- STRICT tablolar, ekleme ve güncelleme sırasında veri tiplerini ek olarak denetlediği için kuramsal olarak daha yavaş olabilir
- 100 sütunlu bir tabloya milyonlarca satır eklenen gayriresmî deneylerde, farklı bilgisayarlarda belirgin bir performans farkı görülmemiştir
- Diskteki veritabanı dosya boyutu da aynıydı; ancak bu kapsamlı bir benchmark olmadığından gözden kaçan farklar olabilir
- Sütun eğilimine (column affinity) uymayan değerlerin yanlışlıkla kaydedilmesini engelleyerek performansı hatta iyileştirme ihtimali de vardır, ancak bu ayrıca doğrulanmış değildir
Uygularken değerlendirilecek noktalar
- STRICT tablolar tüm veri sorunlarını çözmez, ancak tiple ilgili hataları azaltır ve veri bütünlüğünü güçlendirir
- Çoğu durumda tablo tanımına yalnızca
STRICT eklemek yeterlidir; bu da uygulamayı kolaylaştırır
- Genel amaçlı tablolarda, sıkı tip denetiminin sağladığı fayda; taşıma yükü, eski sürüm uyumsuzluğu ve sınırlı esnekliğe kıyasla daha büyüktür
1 yorum
Hacker News görüşleri
SQLite’ta bir tabloyu
ALTERile katı moda çevirme özelliği olmadığı ve katı olmayan tablodaki verilerin katı tabloya kopyalanması gerektiği fikrinden yola çıkarak sqlite-utils 4.1’e iki yönlü dönüştürme özelliği eklendiCLI’da
uvx sqlite-utils transform data.db mytable --strict, Python’da isedb.table("mytable").transform(strict=True)ile kullanılabiliyorSürüm notlarına https://sqlite-utils.datasette.io/en/stable/changelog.html#v..., Python API belgelerine https://sqlite-utils.datasette.io/en/stable/python-api.html#..., CLI belgelerine ise https://sqlite-utils.datasette.io/en/stable/cli.html#transfo... adresinden bakılabilir
https://sqlite.org/flextypegood.html, esnek tiplendirmenin neden varsayılan olduğunu ve bunun gelecekte de muhtemelen değişmeyeceğini açıklıyor, ancak gerçek deneyimlerle örtüşmüyor
Bir müşteri adını tamsayı türündeki
Customer.creditScorealanına koysanız bile bunun kolayca bulunup düzeltilebileceği açıklamasının aksine, bozulmuş satırları kurtarmak zordur ve veri tamamen kaybolmuş da olabilirKatı tip denetiminin yalnızca bulunması kolay hataları engelleyerek hata tespitini ve düzeltmeyi aksine zorlaştırdığı mantığına katılmak da hiç kolay değil
Postgres’te mümkün olduğunca çok denetim ve güvenlik önlemi ekleme eğilimi vardır; böylece en başta hiç oluşmaması gereken hataların peşine sonradan düşmek gerekmez
Bu, katı mühendislik ilkelerinden ziyade SQLite’ın ortaya çıktığı dönemden ve yazarının güçlü kanaatlerinden kaynaklanmış bir sonuç gibi görünüyor; çok eleştiri aldıktan sonra da hangi gerekçeyle olursa olsun pozisyonunu korumaya çalıştığı izlenimini veriyor
Postgres’teki JSON veya HSTORE gibi tiplerle istenen esneklik sağlanabilir, ancak sınırsız tiplendirmeyi varsayılan olarak dayatmak yerine gerektiğinde seçilebilir yapmak neredeyse her zaman daha iyidir
STRICT’in varsayılan olmasını isterdim
Bu nokta dışında SQLite geliştiricisi harika bir araç yapmış müthiş biri
PRAGMA foreign_keys = ON;kullanmak gerekiyorDaha büyük sorun, katı tablolar için eşdeğer bir pragma olmaması; her
CREATE TABLEifadesine standart dışıSTRICTeklemek gerekiyorGenel bir STRICT pragma’sı da değerlendirilmişti ama uygulanmadı; ilgili kaynaklara https://sqlite.org/foreignkeys.html ve https://sqlite.org/forum/forumpost/1b9d073a37ca5998 adreslerinden bakılabilir
Amaç, SQLite 3.53 için yazılmış bir yazılım 3.54’e yükseltildikten sonra
CREATE TABLE’ın birden katı tablo oluşturup hata vermesi ve her şeyin bozulması gibi durumları önlemekBir dönem SQLite’ın yabancı anahtar söz dizimini destekleyip gerçek işlevini uygulamamış olup olmadığını merak ettiriyor
Binlerce cihaza dağıtılmış kod, boolean sütunlarında
'1've'0'string’lerini saklamıştı ve bunları temizlemek zorunda kalmak hiç keyifli değildiZaman damgası tipi de olmadığı için metin sütununda saklamak gerekiyor; standart tarih-saat fonksiyonları bile ISO biçimi olan
yyyy-mm-ddTHH:MM:SSZyerine UTC olduğu örtük olarak varsayılması gerekenyyyy-mm-dd HH:MM:SSüretiyorSQLite gerçekten harika bir proje, ancak bazı tasarım kararları şaşırtıcı
Kurumsal SQL ortamından gelen biri olarak, alan tiplerinin varsayılan olarak zorlanmaması nedeniyle SQLite’ı ciddiye almıyordum; akıllı telefon uygulaması metadata’sının temeli hâline geldiğinde de şaşırmıştım.
Bu, düşük gecikme ve basitlik nedeniyle UDP’yi seçip sonra TCP’nin güvenilirlik özelliklerinin çoğunu uygulama içinde baştan yazmaya benzeyen eski ağ hikâyesini hatırlatıyor.
Ancak katı olmayan SQLite’ın üzerinde tip denetimini kendiniz uygulamak, benzer bir üstünlük sağlayacak gibi görünmüyor.
PostgreSQL’in varsayılanları da düşük özellikli sistemleri hedeflediği için en iyi performans için ayar yapmak gerekir.
Ayrıca TCP akış tabanlı olduğundan birçok uygulamada HOL blocking sorunu vardır; UDP üzerinde kendi güvenilirlik katmanınızı kurmuş olsaydınız, çok işten sonra bile TCP’den daha iyi sonuç alabilirdiniz.
Onlar bu araçları gürültülü ve yol tutuşu kötü diye değerlendirir; çünkü baştan farklı bir kullanım için tasarlanmış ürünlere, yalnızca kendi kullanım senaryoları önemliymiş gibi yaklaşırlar.
Katı tabloların Date gibi bazı tipleri kullanamama dezavantajı var, ama yine de varsayılan olmalılar.
Birden fazla uygulama veritabanını paylaşıyorsa, bildirilen tipe güvenebilmelisiniz; içlerinden biri sayısal bir sütuna string kaydederse geri kalan her şey bozulur.
Buna karşılık SQLite’ın başlıca kullanım alanı olan gömülü veritabanlarında genellikle yalnızca tek bir uygulama vardır; bu da yeni bir veritabanı oluşturup kopyalamak yerine şemayı evrimleştirebilme avantajı sağlar.
Bu durumda uygulama kodu, karma tipler dâhil her sütunda ne beklemesi gerektiğini bilir.
INTEGER,TEXT,BLOB,REAL,NUMERIColmak üzere 5 veri tipi vardır: https://sqlite.org/datatype3.htmlSELECTsorgusunaEXPLAINçağırarak biçimsel olarak belirtilen tip adını elde etmenin bir yolu da yoktur.Bu nedenle
DATEveyaDATETIMEgibi adlar kullansanız bile bunlar çıkarılamaz.Dategerçek bir tip değildir; yalnızca sayısal yakınlığa sahip bir sütun oluşturulur.Katı mod, sütun tipi gösterimini sınırlayarak daha anlamlı adlar kullanmayı engeller ve veritabanı tipleriyle uygulama tiplerini eşlerken kodun bu adlardan yararlanmasını önler; bu yüzden uygulama katmanındaki daha katı tip sistemini aksine sekteye uğratır.
Ayrıntılar https://hn.algolia.com/?query=chrismorgan+strict+sqlite&type... adresinde; Rust’ın sqlx gibi crate’leriyle veritabanı kullanıyorsanız katı moddan kaçınmanın daha iyi olduğunu düşünüyorum.
Veritabanları ve RPC için katı tipler isterim; ancak SQLite’ın kullanım amacı biraz farklı, doğrudan kullanınca https://sqlite.org/flextypegood.html sayfasını daha iyi anlayabilirsiniz.
Örneğin SQLite için yazılmamış rastgele script’lerin de tesadüfen çalışmasını sağlama hedefi, diğer DBMS’lerde genellikle dikkate alınmayan bir özellik.
Esnek tiplendirmenin asıl avantajı şemayı kolayca evrimleştirebilmektir.
Gömülü bir veritabanının gereksinimleri her değiştiğinde yeni bir veritabanı oluşturup verileri taşımak yerine şemayı yerinde değiştirebilirsiniz; tek bir uygulama okuyup yazıyorsa, tamsayı sütununa string eklenmesi şaşırtıcı olmaz.
Buna karşılık birden fazla uygulama kendi takvimlerine göre güncellenip aynı veritabanını paylaşıyorsa şema bir sözleşmedir; tek bir yanlış tip başka bir uygulamayı bozabilir.
Bir uygulamanın oluşturduğu tabloyu başka bir uygulama kullanıyorsa, üzerinde anlaşılmış veri tiplerine sıkı şekilde uyulmalıdır.
Çalışma zamanında tekrar kontrol etmek pratik bir fayda sağlamaz; statik analizde string koyduğu doğrulanan kod birden mutasyona uğrayıp integer koymaz.
SQLite, birden fazla uygulamanın veri paylaştığı ve üçüncü taraflara güvenilmesi gereken Postgres’ten farklıdır.
Postgres gibi ortamlarda çalışma zamanı doğrulaması zorunludur; ancak SQLite temelde tek bir uygulama ve tek bir veritabanı için tasarlandığından, derleme zamanında değerlendirilebilen kendi kodunuza güvenmeniz yeterlidir.
Birden fazla uygulamanın tek bir dosyayı paylaştığı alışılmadık durumlarda katı tablolar etkinleştirilebilir.
Dinamik veri tipleri basit anahtar-değer deposu gibi kullanımlara uygun olabilir; ancak
INTEGERın hata vermeden'hello world'kabul etmesi ile böyle değerler koyabilmek içinNONSTRICTgibi bir anahtar sözcük ya daANYtipini açıkça belirtmenin gerekmesi arasında hangisinin en az şaşırtma ilkesine uyduğunu sormak gerekir.SQLite kullanıcılarının büyük çoğunluğu ilkinin mümkün olacağını beklemez gibi görünüyor.
Bu, başka DBMS’ler için yazılmış kodun SQLite’ta da tesadüfen çalışmasını istedikleri açıklamasıyla da uyumlu.
Yine de kullanıcı tipi atlamamışken,
INTEGERı açıkça belirtip metin koyabilmek çok şaşırtıcı.08123…gibi bir değerle başlayan UUID’nin sekizlik sayı olarak parse edilmesi nedeniyle bazı kısımların yanlışlıkla sayıya dönüştürüldüğü olmuştu.Kafa karıştırıcı ve can sıkıcıydı; ama
STRICTuygulayıp tüm tabloyu yeniden oluşturarak çözdüm.SQLite’ta sekizlik sayı tipi yoktur.
Varsayılan olarak
CREATE TABLE ... STRICT WITHOUT ROWIDkullanıyorum; açıkçası farklı oluşturmak için özel bir neden göremiyorum.