1 puan yazan GN⁺ 3 시간 전 | 1 yorum | WhatsApp'ta paylaş
  • 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

 
GN⁺ 3 시간 전
Hacker News görüşleri
  • SQLite’ta bir tabloyu ALTER ile 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 eklendi
    CLI’da uvx sqlite-utils transform data.db mytable --strict, Python’da ise db.table("mytable").transform(strict=True) ile kullanılabiliyor
    Sü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.creditScore alanı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 olabilir
    Katı 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

    • Güvenilir bir veritabanı yapılandırmasının sağladığı güvenlik ve güvenilirliğin veri hatalarını düzeltmeyi aksine zorlaştırdığı bakış açısını anlamak güç
      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
    • MongoDB’de de her şeyi saklayabilme konusunda benzer bir mantık vardı, ancak çoğu kişi gerçek kullanım sonrasında bunun genellikle kötü bir özellik olduğunu fark etti
      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

    • Yabancı anahtarlar bile varsayılan olarak etkin değil; PRAGMA foreign_keys = ON; kullanmak gerekiyor
      Daha büyük sorun, katı tablolar için eşdeğer bir pragma olmaması; her CREATE TABLE ifadesine standart dışı STRICT eklemek gerekiyor
      Genel 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
    • SQLite geriye dönük uyumluluğa önem verdiği için varsayılanları neredeyse hiç değiştirmiyor
      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ı önlemek
    • Yabancı anahtar kısıtlamalarının da uyumluluk gerekçesiyle varsayılan olarak devre dışı olması gibi benzer bir sorun var
      Bir dönem SQLite’ın yabancı anahtar söz dizimini destekleyip gerçek işlevini uygulamamış olup olmadığını merak ettiriyor
    • Basit bir string yerine düzgün tarih·zaman damgası tipleri de gerekiyor
    • Veritabanının yanlışlıkla hatalı tip girilmesine izin vermesi için hiçbir neden yok; SQLite’ın tip güvenliği felsefesi hayal kırıklığı yaratıyor
      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ğildi
      Zaman damgası tipi de olmadığı için metin sütununda saklamak gerekiyor; standart tarih-saat fonksiyonları bile ISO biçimi olan yyyy-mm-ddTHH:MM:SSZ yerine UTC olduğu örtük olarak varsayılması gereken yyyy-mm-dd HH:MM:SS üretiyor
      SQLite 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.

    • Kendiniz uyguladığınızda TCP’den farklı bir şekilde tasarlayabilir ve bazen büyük fayda elde edebilirsiniz; QUIC ve HTTP/3 bunun örnekleri.
      Ancak katı olmayan SQLite’ın üzerinde tip denetimini kendiniz uygulamak, benzer bir üstünlük sağlayacak gibi görünmüyor.
    • Yabancı anahtarlar 2009’dan önce hiç desteklenmiyordu ve bugün bile varsayılan olarak etkin değil: https://sqlite.org/foreignkeys.html
    • Bu yaklaşımı gerçek bir işe dönüştürüp başarılı olanlar da var: https://aeron.io/
    • Tip denetimi tamamen desteklenmiyor olsaydı SQLite’ı ciddiye almama tavrını anlayabilirdim; ancak çoğu veritabanı da kusursuz varsayılan ayarlar sunmuyor.
      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.
    • Jeep veya Bronco ile ilgileniyorsanız genel otomobil eleştirmenlerine bakmazsınız.
      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.

    • SQLite’ta yalnızca INTEGER, TEXT, BLOB, REAL, NUMERIC olmak üzere 5 veri tipi vardır: https://sqlite.org/datatype3.html
    • SQLite’ta tarih veri tipi yoktur; ayrıca rastgele bir SELECT sorgusuna EXPLAIN çağırarak biçimsel olarak belirtilen tip adını elde etmenin bir yolu da yoktur.
      Bu nedenle DATE veya DATETIME gibi adlar kullansanız bile bunlar çıkarılamaz.
    • Date gerç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.

    • O belgedeki artılar ve eksiler bölümünde temel argüman eksik.
      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.
    • Çoğu durumda kodun tip yapısına uyup uymadığı derleme zamanında kontrol edilir; birlikte geliştirilen SQLite şeması da statik analiz için gerekli tip bilgisini sağlar.
      Ç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çin NONSTRICT gibi bir anahtar sözcük ya da ANY tipini 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.

    • Bunun nedeni, SQLite’ın tipsiz bir veritabanını hedeflerken söz dizimini standart SQL’e benzetmeye çalışması olabilir.
      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 STRICT uygulayıp tüm tabloyu yeniden oluşturarak çözdüm.

    • Bu SQLite değil, dil sürücüsünün aşırı yardımcı olmasının sonucuydu.
      SQLite’ta sekizlik sayı tipi yoktur.
  • Varsayılan olarak CREATE TABLE ... STRICT WITHOUT ROWID kullanıyorum; açıkçası farklı oluşturmak için özel bir neden göremiyorum.