Veritabanı fiziksel yapısı (InterBase ve Firebird)
Alexey Kovyazin, Sergey Vostrikov, son güncelleme 05-Haziran-2004
Veritabanı fiziksel yapısı
InterBase veritabanı fiziksel yapısını neden incelemek zorundayız?
Genellikle, InterBase veritabanı fiziksel yapısından bahsettiğimizde, verilerin düşük seviyeli veri organizasyonu açısından - bayt seviyesine kadar - temsil edildiğini kastediyoruz. Yüksek seviyeli dil kullanarak uygulama geliştiren birçok programcı, düşük seviyeli detayları incelemeyi ihmal eder. Ancak, veritabanı içindeki veri organizasyonunun ana prensiplerini bilmek, veritabanı uygulamalarının etkili tasarımının anahtarıdır. Bu nedenle InterBase veritabanı organizasyonunun içine bir gezi yapacağız ve nasıl düzenlendiğini öğreneceğiz.
Peki, veritabanı yönetim sistemi (DBMS) ne için tasarlanmıştır? Açıkçası verileri depolamak ve kontrol etmek için. Bu sıradan görünebilir, ancak üzerinde düşünmeye değer. Bir kullanıcı verileri DBMS’ye koyar, DBMS bu verileri bir şekilde kendisi için anlaşılır olan iç formatlara çevirir. “İç veri formatı” kelimeleri bazı çağrışım zorluklarına neden oluyorsa “0 ve 1” hayal edebilirsiniz. DBMS bu verileri depolar ve ilk talep anında bunları kendi formatından çıkarıp uygun bir görünüme dönüştürüp kullanıcıya vermelidir.
Bu bölümün konusu, DBMS’nin verilerini nasıl depoladığı, hangi görünümde ve en düşük seviyede nasıl organize edildiğidir. Size HDD’de duran bitlerden ve baytlardan nasıl değerli veriler elde ettiğimizi açıklamaya çalışacağız.
InterBase veritabanı dosyaları
Genellikle, bir veritabanından bahsettiğimizde DBMS’nin kendisini, kullanıcı bilgilerini ve hatta verilerle çalışan istemci programlarını kastediyoruz. Bu bölümde, veritabanını veritabanı dosyaları olarak ele alacağız.
InterBase veritabanı, bu veritabanıyla bağlantılı her şey hakkında bilgi içeren bir veya birkaç dosyayı temsil eder. Kullanıcı bilgileri bir istisnadır çünkü kullanıcılar tüm sunucu seviyesinde tanımlanır ve ayrı olarak, güvenlik veritabanı admin.ib içinde saklanır (7 öncesi sürümlerde ISC4.GDB idi).
Tavsiye: InterBase güvenlik prensipleri hakkında daha fazla bilgi edinmek için “Sunucu ve Veritabanı Güvenliği” bölümüne bakın.
Yani, veritabanıyla ilgili tüm bilgiler bu dosyalar içinde saklanır: verilerin kendisi, indeksler, tetikleyiciler, saklı prosedürler vb.
Ortalama bir proje için InterBase veritabanı tek bir dosyayı temsil eder çünkü modern InterBase sürümleri veri dosyasıyla çalışmak için 64bitIO kullanabilir ve bu size 64Gb’ye kadar veri dosyasına sahip olma yeteneği verir. InterBase’in daha eski sürümleri her veritabanı dosyası için 4 gigabayt sınırına sahipti (tüm veritabanı için 64 Tbyte’a kadar). Tahmin edebileceğimiz gibi, 64 gigabayt hemen hemen her veritabanı uygulamasının bilgilerini depolamak için oldukça yeterlidir. Ancak gerekirse, bir veritabanını birkaç dosyaya bölebiliriz. Bu arada, yüzlerce gigabayt boyutunda InterBase veritabanları vardır.
IBSurgeon - InterBase veritabanı için bir rehber
InterBase veritabanı dosyalarının yapısını detaylı olarak bilmek zorundayız. Bu nedenle, InterBase sunucu çekirdeği aracılığıyla değil, doğrudan veritabanı dosyalarıyla çalışmaya izin veren uygun bir araca sahip olmak arzu edilir. En kolay yol, sıradan bir onaltılık görüntüleyici kullanmak ve HEX temsilini dikkate alarak veritabanı dosyalarının yapısını anlamaya çalışmaktır. Bu oldukça yorucu bir iş olurdu.
Ancak neyse ki, InterBase veritabanlarıyla doğrudan çalışmak için bir araç var. IBSurgeon Editor - InterBase veritabanlarıyla doğrudan düşük seviyeli çalışma için bir araçtır; InterBase veritabanlarının iç yapısını incelemek ve geri yüklemek için bozuk veritabanlarını teşhis etmek amacıyla kullanılabilir. Daha fazla ayrıntı için “Yönetici ve InterBase geliştirici araçları” ekine bakın.
IBSurgeon, InterBase/FireBird/Yaffil sunucu çekirdeği tarafından açılamayan, ağır şekilde bozulmuş olanlar da dahil olmak üzere veritabanlarını her durumda açmaya ve incelemeye izin veren kendi alternatif veritabanı erişim mekanizmasını kullanır.
InterBase veritabanının iç yapısını göstermek için IBSurgeon kullanacağız.
İçeriden *.IB/*.FDB dosyaları
IB, InterBase veritabanı dosyaları için önerilen uzantıdır ve FDB Firebird için (önceden GDB idi). IB dosyasının yapısı hakkında söylememiz gereken ilk şey, kesin olarak tanımlanmış boyutta bir dizi sayfayı temsil ettiğidir. Veritabanı dosyasının boyutu, bu veritabanının tüm dosyaları için değişmeyen bir sayfa boyutuna bölünebilir. Farklı InterBase sürümleri, tablo 1’de gösterilen farklı sayfa boyutlarını destekler. Sayfa boyutu veritabanı oluşturulurken ayarlanır ve yaşam döngüsü boyunca değiştirilemez. Başka bir deyişle, sayfa boyutunu yalnızca bir veritabanını yedekten geri yüklerken değiştirebiliriz.
Tablo 1. Farklı InterBase sürümleri tarafından desteklenen sayfa boyutları
| InterBase sürümü | Sayfa boyutu, bayt | ||||
| 1024 | 2048 | 4096 | 8192 | 16384 | |
| InterBase 4.0 | * | * | * | * | |
| InterBase 5.x | * | * | * | * | |
| InterBase 6.x-7.x | * | * | * | * |
Veritabanında veri okuma ve yazma sayfa sayfa yürütülür; veritabanı önbelleğinin boyutu gibi birçok önemli sunucu ve veritabanı özelliği sayfa boyutuna bağlıdır ve “sayfalar” cinsinden sayılır.
Herhangi bir InterBase veritabanını IBSurgeon ile açalım. Veritabanı dosyasına iki kez tıklamak yeterlidir. Resim 1, IBSurgeon veritabanını açtıktan sonra görünen sayfaların bir listesini ifade eder:

Resim 1. Veritabanı sayfalarının listesi
Sayfalar farklı türlerde olabilir ve her biri belirli bir amaca hizmet eder. Farklı türlerin karşılıklı bağımlılıkları resim 2’de koşullu olarak temsil edilir. Resim 2, veritabanı dosyasındaki sayfaların dağılımını şematik olarak gösterir - dosyanın başından itibaren sayılırsa soldan sağa, yukarıdan aşağıya. Aynı türdeki sayfalar kesinlikle birbiri ardına gitmez - sunucu tarafından veritabanlarını genişletirken veya oluştururken oluşturuldukları sırayla bir dosyada kolayca karıştırılabilirler.

Resim 2. InterBase veritabanındaki farklı sayfa türleri arasındaki karşılıklı bağımlılıklar
Bazı sayfa türlerinin diğer sayfa türlerine referansları olmadığını fark etmiş olmalısınız. Ancak, burada bir çelişki yoktur; mesele şu ki, bu sayfa türleri diğer yapısal seviyede bağlantılıdır ve kullanılır. RDB$PAGES tablosuna ve diğer sistem tablolarına bağlanabilirler (bu tabloyu ve diğer sistem nesnelerini aşağıda - “Veritabanı mantıksal yapısı” bölümünde ele alacağız). Resim 2’de yalnızca fiziksel seviyedeki sayfalar arasındaki açık referansları görebiliriz.
InterBase veritabanında hangi sayfa türlerinin bulunduğunu ayrıntılı olarak ele alalım. InterBase birincil kodları setindeki ods.h dosyasında tüm olası sayfa türleri hakkında bilgi vardır. Bu dosyaya yalnızca ODS hakkında değil, aynı zamanda InterBase çekirdeğinin diğer birçok temel şeyi hakkında orijinal kaynakta veri almak için sık sık başvuracağız. Toplamda 11 sayfa türü bildirilmiştir, ancak bunlardan yalnızca 9’u açıklanmaya değerdir (bunu tablo 2’den açıkça görebiliriz). 0 ve 1 tanımlayıcılı sayfa türleri tanımsız veya kullanılmamıştır.
Tablo 3. FB’deki sayfa türleri
| ods.h içindeki tanım | Sayfa türü tanımlayıcısı | Sayfa açıklamaları |
| pag_undefined | 0 | Tanımsız - Bir sayfa bu sayfa türüne sahipse muhtemelen boştur |
| pag_header | 1 | Veritabanı başlık sayfası |
| pag_pages | 2 | Sayfa envanter sayfası (veya Alan envanter sayfası - SIP) |
| pag_transactions | 3 | İşlem envanter sayfası (TIP) |
| pag_pointer | 4 | İşaretçi sayfası |
| pag_data | 5 | Veri sayfası |
| pag_root | 6 | İndeks kök sayfası |
| pag_index | 7 | İndeks (B-ağacı) sayfası |
| pag_blob | 8 | Blob veri sayfası |
| pag_ids | 9 | Gen-id’ler |
| pag_log | 10 | Yazma öncesi günlük bilgisi |
Her sayfanın, sayfa türü ve aynı türdeki bir sonraki sayfanın numarası hakkında bilgi içeren bir başlığı vardır. Her sayfa başlığının içerdiği parametrelerin tam listesini, ods.h tanım dosyasındaki pag yapısını ele alırsak alabiliriz.
/\* Temel sayfa başlığı */
typedef struct pag {
SCHAR pag_type; /*sayfa türü tanımlayıcısı*/
SCHAR pag_flags; /*sayfa bayrakları*/
USHORT pag_checksum; /*sayfa sağlama toplamı: 5.0 sürümünden sonra 12345’e eşittir */
ULONG pag_generation; /*sayfa nesli */
ULONG pag_seqno; /* WAL son güncelleme sıra no - kullanımdan kaldırıldı*/
ULONG pag_offset; /* WAL son güncelleme ofseti - kullanımdan kaldırıldı*/
} *PAG;
Sayfa türleri ve kullanımları
Her sayfa türünü ayrıntılı olarak ele alalım ve işlevleri ile içerdikleri bilgileri öğrenelim. Adım adım başlayacağız - ilk sayfadan.
Veritabanıyla yapılan her işlem, veritabanı başlık sayfasının (veya başlık sayfasının) okunmasıyla başlar. Veritabanı başlık sayfası tüm veritabanı dosyalarında ilk sırada gelir. Buna göre, resim 2’de ilk olarak temsil edilir (resmin veritabanı dosyasının soldan sağa, yukarıdan aşağıya uzantısını temsil ettiğini hayal edersek).
Bir başlık sayfası, veritabanı hakkında genel olarak bilgi içerir. Resim 3’te bir veri sayfası IBSurgeon’un bize gösterdiği şekilde ifade edilir:

Resim 3. Veritabanı başlık sayfası.
Başlık sayfası içeriği hakkında bir fikir edinmek için veritabanı istatistiklerini alabilirsiniz. Bunun için komut satırı yardımcı programını gstat veya “Yönetici ve InterBase geliştirici araçları” uygulamasındaki listeden InterBase yönetimi için daha uygun başka bir araç kullanabilirsiniz. İstatistik alma süreci ve başlık sayfası verilerinin açıklaması hakkında daha fazla ayrıntı için “İstatistikler” bölümüne bakın.
Başlık sayfasının sayfa boyutu, ODS sürüm numarası (hakkında bilgiyi aşağıda bulacaksınız), veritabanı oluşturma verileri, işlemler hakkında bilgi ve bir dizi farklı bilgi gibi önemli bilgiler içerdiği belirtilmelidir. Örneğin, Implementation ID, bu veritabanının hangi işletim sistemi altında oluşturulduğu hakkında bilgi saklar.
Bir veritabanına bağlanırken, InterBase sunucusu dosyanın başlangıcından ilk 1024 bayt bilgiyi okur ve okunan değerlere göre bağlantı satırında belirtilen dosyanın InterBase veritabanı olup olmadığını tanımlar. Ardından sunucu, başlık sayfasından ODS sürüm numarasını ve bu veritabanındaki sayfa boyutunu okur ve ODS sürümü sunucu uygulamasıyla uyumluysa, ilk 1024 bayttan alınan uygun sayfa boyutunu kullanarak tüm başlık sayfasını yeniden okur. Bundan sonra, okuma-yazma modu, veritabanı lehçesi vb. gibi diğer önemli veritabanı parametreleri başlık sayfasından okunur.
Başlık sayfasında, meta verileri içeren veri sayfalarına referanslar saklayan ilk işaretçi sayfalarına bir referans vardır: RDB$Pages tablosu (aşağıda “InterBase veritabanı mantıksal yapısı” bölümüne bakın). Resim 2’de bu referans, “Veritabanındaki 1. işaretçi sayfasının numarası” yazılı bir okla gösterilmiştir. Sunucu, başlık sayfasından 1. işaretçi sayfasının numarasını okur ve ona geçer. İşaretçi sayfası, belirli bir tabloyu oluşturan veri sayfalarının numaralarının iyi düzenlenmiş bir dizisinden oluşur (bir tablo, veritabanı mantıksal yapısı tarafından tanımlanan SQL-nesnesi olarak kabul edilir). Şimdi IBSurgeon’un işaretçi sayfasını nasıl yorumladığını görebilirsiniz (resim 4’e bakın):

Resim 4. InterBase veritabanı işaretçi sayfası
Sayfa, bir veri sayfası vektörü içerir; bu veriler veritabanında belirli bir tabloyu oluşturur. Bu vektör, dosyadaki veri sayfalarının numaralarına karşılık gelen bir işaretçi dizisini temsil eder. Sunucu, veri sayfasının 4 baytlık numarasını okur ve gerekli veri sayfasına geçer. RDB$Pages’in 1. veri sayfasına geçtiğinde, sunucu daha sonra sunucu tarafından veritabanıyla yapılan tüm işlemler için kullanılan iç veritabanı temsilini oluşturmaya başlar. RDB$Pages, yalnızca veritabanı hakkında bilgi içeren veri sayfalarına değil, aynı zamanda veritabanı çalışmasını sağlamada rol oynayan diğer sayfalara da referanslar saklar.
Bu tablodan sık sık bahsediyoruz ve kesin olarak veritabanı mantıksal yapısına aittir. Yine de, her şey birbiriyle bağlantılıdır, bu nedenle başka bir şeye atıfta bulunmadan bir şeyi tanımlayamayız.
Önemli sayfa türlerinden biri işlem envanter sayfasıdır (TIP). Bu sayfalar, diğer tüm sayfalar gibi, bir başlık ve 2 baytlık dizilerden oluşan bir diziyi temsil eden ana bölümden oluşur. Bu diziler, bir veritabanındaki işlemlerin durumunu tanımlar (işlemler hakkında daha fazla ayrıntı için «İşlemler» bölümüne bakın).
Tablo 4. TIP’te olası işlem durumları
| PIP üzerindeki dizi değeri | Anlam |
| 0 | İşlem başlamadı, aktif veya commit veya rollback olmadan kayıp |
| 1 | İşlem Commit gerçekleştirdi |
| 2 | İşlem Rollback gerçekleştirdi |
| 3 | Limbo-işlem (2PC için) |
Her kayıt sürümünün kendi işlem tanımlayıcısı vardır; bu, eşzamanlı olarak yürütülen işlemlerin birbirlerinin durumunu «öğrenmesine» ve çok kullanıcılı çalışma sırasında çakışmaları çözmesine olanak tanır (kayıt sürümleri ve diğer konular hakkında daha fazla bilgi için «InterBase’in çok üretimli mimarisi» bölümüne bakın).
Veritabanı başlık sayfası, işaretçi sayfaları ve TIP, yalnızca sunucu tarafından kullanılan «bakım» sayfa türlerine aittir. InterBase kullanıcıları içerdikleri bilgileri açıkça asla almazlar. Sayfaların tahsisi hakkında bilgi depolayan sayfalar (genellikle Sayfa Envanter Sayfaları (PIP) veya Alan Envanter Sayfaları (SIP) olarak anılır) da bakım sayfa türüne aittir. Bu sayfalar ikinciden başlayarak yer alır; yani ilk PIP, başlık sayfasından hemen sonra gelir ve veritabanında diğer türlerin sabit sayfa aralıklarında görünür. Bu aralıkların boyutu, PIP’in kaç sayfa diğer türde göründüğü anlamına gelir ve bu veritabanı için ayarlanan sayfa boyutuna bağlıdır. Sayfa Envanter Sayfaları, İşaretçi sayfalarında hesaba katılmaz ve RDB$Pages içinde gösterilmez. Bu sayfaların bütünlüğü, tüm veritabanının başarılı çalışması için hayati önem taşır çünkü PIP içeriği, veritabanındaki diğer tüm sayfaların durumunu tanımlar. Her veritabanı sayfasının 3 durumu olabilir: tahsis edilmemiş, boşlukla tahsis edilmiş, tahsis edilmiş ve dolu. Yeni veriler için ek alan gerektiğinde, sunucu tahsis edilmemiş sayfaların olup olmadığını kontrol etmek için PIP’i inceler. Böyle bir sayfa varsa, sunucu durumunu boşlukla tahsis edilmiş olarak değiştirir. Tahsis edilmemiş sayfalar yoksa, veritabanı genişler - yeni bir veri sayfası eklenir.
IBSurgeon’da bir veri sayfası örneği ve içerdiği veriler resim 5’te verilmiştir.

Resim 5. Sayfa Envanter Sayfası
Sayfa tahsis edilir edilmez, InterBase durumunu SIP üzerine yazar ve ardından sayfanın kendisini yazar. Bundan sonra, bu yeni oluşturulan sayfayı çok sayıda sayfaya, örneğin bir tablo için veri sayfalarına eklememiz gerekir. Bunun için, bu yeni sayfaya referansı bu çok sayıdaki sayfanın son sayfasına - örneğin, bir tablonun son veri sayfasına - yazmalıyız. Sunucu, SIP üzerine yazdıktan hemen sonra çalışmasını keserse, ancak yeni tahsis edilen sayfaya referans veren sayfalara referansı yazmazsa, bu sayfa öksüz hale gelir. Öksüz bir sayfa fiziksel olarak oluşturulur, SIP üzerinde ayrılır, ancak diğer sayfalardan ona hiçbir referans yoktur; bu, sunucunun onu bulamayacağı ve diske veri yazamayacağı anlamına gelir. Öksüz sayfa, resim 2’de kırmızı kare ile işaretlenmiştir. Öksüz sayfalar çoğunlukla sunucunun beklenmedik güç kesintisi sonucu ortaya çıkar ve veritabanı onarımı için özel araç gfix (veya FirstAID) (veya IBSurFirstAID) ile «tedavi edilir».
Veri sayfalarını incelemeden önce, önemli sayfa türlerinden bahsetmeliyiz: üreteç ve dizin sayfaları. Üreteç sayfaları, üreteç durumlarını gösteren 4 baytlık sayılar dizisini temsil eder. Aslında, üreteç sıradan bir sayaçtır.
Resim 6’da bir üreteç sayfası görebilirsiniz. IBSurgeon’un üreteç adlarını göstermesine rağmen, bu adların üreteç sayfalarında saklanmadığına dikkat edin. Bu, veritabanını inceleyen kullanıcının rahatlığı için yapılmıştır. Gerçekte, üreteç adları RDB$Generators sistem tablosunda saklanır.

Resim 6. Üreteç sayfası (gen-id’ler)
Bu örnekte gördüğünüz gibi, veritabanı RDB$ önekiyle başlayan sistem üreteçlerini ve kullanıcı tanımlı üreteçleri içerir. InterBase veritabanı uygulamaları geliştirirken üreteçlerin işlevi ve kullanımı hakkında bilgi edinmek istiyorsanız, «Tablolar. Birincil anahtarlar ve üreteçler» bölümüne bakın. Üreteç sayfaları, RDB$Pages tablosundaki diğer sayfalarla birlikte hesaba katılır.
Her tablonun, dizinleri olup olmadığına bakılmaksızın en az bir dizin kök sayfası vardır. Bu sayfa, ilgili tablo için dizin sayfalarına işaretçiler içerir. Dizin kök sayfasının, dizin sayfaları için işaretçi sayfasının veri sayfaları için olduğu kadar önemli olduğunu söyleyebiliriz. Bu nedenle IBSurgeon onu benzer bir şekilde temsil eder. Bir dizin kök sayfası örneği resim 7’de verilmiştir.

Resim 7. Dizin kök sayfası
Dizin kök sayfası, dizin değerlerinin saklandığı sayfaların bir listesini ve ayrıca dizin bilgilerini - dizinin seçiciliği ve çeşitli bayrakları - içerir. InterBase veritabanlarındaki dizinler, rolleri ve kullanımları hakkında daha fazla ayrıntı için «Dizinler» bölümüne bakın.
Dizin sayfaları doğrudan dizin değerlerini veya dizin seviyesi >0 ise, alttaki dizin sayfalarına referansları içerir. İşte bir dizin sayfası örneği (resim 8).

Resim 8. Dizin (B-ağacı) sayfası
Dizin sayfası, dizine alınmış verilerin paketlenmiş değerlerini saklar. Özellikle bileşik dizinler (birden fazla alan içeren) oluştururken oldukça karmaşık bir dizinleme mekanizması kullanılır.
Genel olarak, veri sayfaları ve BLOB değerleri içeren sayfalar kullanıcı bilgilerini saklar. Veri sayfaları, veritabanındaki kullanıcı tablolarındaki kayıtları, kayıt parçalarını, eski sürümleri, sürümler arasındaki farkları, BLOB alanlarını ve benzerlerini içerir. BLOB alanlarına gelince, bunlar veri sayfalarındaki kayıtlarla bağlantılıdır ve veri sayfasında bulunamayacak büyük boyutlu veriler içerir. BLOB değerlerini saklamanın referans türü, büyük verilerin saklanmasına olanak tanır.
IBSurgeon’da bir veri sayfası sunumunun bir örneği resim 9’da verilmiştir:

Resim 9. Veri sayfası
Veri sayfası başlığı, sayfa türünü, sahip tablo tanımlayıcısını (relationID) içerir. Kayıtlar, sayfanın sonundan itibaren veri sayfalarında saklanır ve doldukça sayfanın başlangıcına daha yakın tahsis edilir.
Satır dizinlerine bakarsak bundan emin olabiliriz; bunlar 2 değer içerir - sayfadaki uzaklık ve uzunluğu. Satırın başında, sayfanın sonunda tahsis edilen kayıtlar olduğunu görürsünüz - örneğin, ilk kaydın uzaklığı 8156 bayt ve uzunluğu 34 bayttır - bu nedenle 8156+34=8192 baytta biter - sayfanın tam kenarında (bizim durumumuzda sayfa boyutu 8192 bayttır). Sayfa dolduğunda (üstten verilerle ve alttan kayıt dizinleriyle), sunucu yeni sayfalar için yeni kayıtlar ve eski kayıtların sürümlerini yazmaya başlar. Yukarıda açıklanan sayfa doldurma mekanizmasından, InterBase uzmanlarının neden büyük boyutlu veri sayfaları (minimum 4096 bayt, daha iyisi 8192) kullanmayı şiddetle önerdiğini kolayca söyleyebiliriz. Kayıtlarından biri oldukça büyük boyutta olacak bir tablo oluşturursak (örneğin VARCHAR (255) olan 10 alan), bunlar 2550 bayttan fazla yer kaplar, doldurulur. Bu, böyle bir kaydın küçük boyutlu bir sayfa (1024 veya 2048) için çok büyük olacağı anlamına gelir. Tek bir kaydı okumak için diskten birkaç sayfa yükleme gerekliliğinin veritabanınızla çalışmayı hızlandırmayacağı açıktır. Bu nedenle, bir veritabanı oluştururken veya geri yüklerken veri sayfası boyutunu yeniden tanımlamanız önerilir çünkü varsayılan olarak 1024 bayt boyutu ayarlanır. InterBase veri dosyası sayfalarının ana türlerini ve işlevlerini kısaca inceledik. Şimdi daha yüksek bir yapısal seviyeye geçebiliriz.
ODS
ODS, On-Disk Structure’ın kısaltmasıdır, yani InterBase veritabanının disk üzerindeki veri yapısıdır. ODS, veritabanı dosyaları içindeki verilerin nasıl organize edildiğini tanımlar. On-Disk yapısını uygulamak için ana sabitlerin ve veri yapılarının tanımı, InterBase kaynak kodları setindeki ods.h dosyasındadır. ODS, InterBase geliştirme süreci boyunca değiştirildi ve belirli bir veritabanıyla çalışırken, sunucu neyle uğraştığını bilmek için ODS sürüm numarasını bulur. ods.h dosyası bize On-Disk yapısının aşağıdaki sürümlerini sunar:
-
ODS 5, InterBase 3.3 tarafından kullanıldı ve üzerindeki sürümler tarafından desteklenmiyor
-
ODS 6 ve ODS 7 hiç çıkmadı
-
ODS 8, InterBase 4.0 tarafından kullanılır
-
ODS 9, InterBase 4.5 ve üzeri tarafından kullanılır
-
ODS 10, InterBase 6 ile çıktı
-
ODS 11, InterBase 7.0 ile çıktı
Ana ODS sürümlerinin yanı sıra, bunları oluşturan belirli veritabanı sunucusu sürümüne bağlı olan alt sürümler de vardır. Sürümün ana numaraları, sürümü gösteren sayının tam kısmında, alt numaralar ise kesirli kısımda yazılır. Örneğin, sunucu sürümü 4.0, ODS 8.0’a sahip veritabanları oluşturur ve InterBase 4.2 - 8.2. Alt sürümler arasındaki geçiş aşağıdan yukarıya otomatik olarak gerçekleştirilir. Örneğin, sunucu 4.0 tarafından oluşturulan ODS 8.0’lı bir veritabanını InterBase 5.6 ile açmak yeterlidir ve bu veritabanının ODS’si 8.2 sürümüne sahip olacaktır. Ana veritabanı sürümleri arasındaki geçiş yalnızca, eski bir sürüm kullanarak veritabanı yedekleme ve yeni bir sunucu sürümü kullanarak geri yükleme yoluyla gerçekleştirilir. Sürümler arasındaki geçiş süreci bölüm 1.4 «Göç» içinde ayrıntılı olarak açıklanmıştır.
InterBase sürümleri 4.x ve 5.x için ODS desteğinin uygulanmasındaki önemli nokta, InterBase sunucuları 4.x ve 5.x’in belirli bir sunucunun uygulanmasından bir birim daha düşük sürümle geriye dönük uyumluluğudur. InterBase birkaç olası ODS’yi destekler ve belirli bir veritabanına bağlanırken ODS sürümüne göre, gerekli ODS uygulamasının desteğini seçer. Belirli bir durumda hangi ODS desteği uygulamasının seçileceğine karar verme mekanizmasına Y-Valve ((c) Steve Trenton) denir.
Daha basit söylemek gerekirse, InterBase 4.0’a karşılık gelen ODS 8.x’li bir veritabanı InterBase 5.x’te açılabilir.
Tam ODS uyumluluk tablosu aşağıda gösterilmiştir:
| InterBase sürümü | Ana ODS | Alt ODS |
| 4.0/4.1 | 8.0 | … |
| 4.2 | 8.2 | 8.2 |
| 5.0/5.1 | 9.0 | 8.2 |
| 5.5 | 9.1 | 8.2 |
| 5.6 | 9.1 | 8.2 |
| 6.0 | 10.0 | 9.0/9.1 |
| 7.0 | 11.0 | 10.0 |
| 7.1 | 11.1 | 10.0 |
ODS aşağı yönlü uyumluluğa sahiptir. Başka bir deyişle, daha yüksek sürüme sahip sunucu ve tüm araçları, erken sürümlerde oluşturulmuş bir veritabanıyla çalışmayı başaracaktır, ancak bunun tersi geçerli değildir. 6. InterBase sürümünde oluşturulmuş bir veritabanını InterBase 5.x ile açmayı denerseniz, «Unsupported On-disk structure: Found ODS 10, supported ODS 9» hata mesajını alırsınız.
Sürümler arasında aşağıdan yukarıya ve tersine geçişin açıklaması için «Göç» bölümüne bakın.
ODS, yedekleme ve veritabanı çıkarma ile hasarlı veritabanlarının geri yüklenmesiyle ilgili konular için çok önemlidir. gbak yedekleme ve gfix geri yükleme araçları ODS sürümünü izler ve hizmet etmeleri gereken veritabanının ODS sürümü, içlerinde uygulanan sürümden büyükse çalışmazlar. Bu, 4.x’ten gbak’ın, 5.x sunucusu tarafından oluşturulmuşsa bir veritabanı yedeği oluşturamayacağı anlamına gelir, ancak tersi kolaydır.
Veritabanının fiziksel ve mantıksal yapısı arasında bir köprü
Veritabanı dosyalarının fiziksel yapısını genel bir şekilde inceledik. Şimdi veritabanının mantıksal yapısına geçmemiz gerekiyor. Kavramlarda ayrım ve materyalde boşluk olmaması için veritabanındaki bilgi temsilinin fiziksel ve mantıksal seviyeleri arasında bir köprü kuralım. Farklı veritabanı sayfalarında saklanan her şey, bilgisayar belleğinde bir şekilde organize edilmelidir; veritabanı dosyasındaki veriler, bir dizi sunucu içi nesneye ve değişkene dönüştürülmelidir. Bu sete, Ann Harrison terminolojisine göre dahili veritabanı görüntüsü denir [1.. Bu nedenle, dahili veritabanı görüntüsü oluşturma sürecini incelemeye çalışacağız.
-
Sunucu, dosyanın başlangıcından 1024 bayt okur ve eğer bu gerçekten bir InterBase veritabanı dosyasıysa, bu veritabanının sayfa boyutunu tanımlar ve tüm başlık sayfasını yeniden okur.
-
Sunucu, başlık sayfasından veri sayfalarına referansları saklayan işaretçi sayfasının numarasını çıkarır ve RDB$Pages tablosunu tanımlar.
-
Sunucu bu işaretçi sayfasına gider ve işaret edilen veri sayfalarından bilgi okumaya başlar. İlk RDB$Pages tablosunu verilerle doldurur. Bu tablo, fiziksel nesneler (veritabanı dosyalarının sayfaları) ile mantıksal nesneler (tablolar) arasında bir köprü gibidir. RDB$Pages’in yapısı, diğer sistem tabloları gibi, InterBase’de kesin olarak sabitlenmiştir.
-
Sunucu, sayfaların ilişkiler tarafından tahsisi hakkında veri aldıktan sonra (ilişkiler - aslında sıradan tablolarla aynıdır ve basitleştirme için bu kavramları zihinsel olarak değiştirebiliriz), InterBase veri yapılarını oluşturmaya başlar: önce sistem tabloları, kısıtlamalar ve dizinler, ardından kullanıcı nesneleri.
-
Sistem ve kullanıcı meta verilerinin (tablolar, kısıtlamalar, dizinler ve diğer veritabanı nesneleri) başlatılmasından sonra InterBase, veritabanını açmayı isteyen kullanıcıya bu veritabanının tanıtıcısını döndürür. Ana tanıtıcı, InterBase’e hangi veritabanıyla çalışacağını gösteren bir tanımlayıcıdır çünkü aynı anda birkaç kullanıcı çalışabilir ve bu, birkaç veritabanının açılabileceği anlamına gelir.
-
Bu işlemlerden sonra veritabanı açılmış sayılır ve sunucu, kullanıcının veritabanına yönelik sorgularını yürütmeye hazırdır. Artık veritabanının fiziksel ve mantıksal yapısını bağlayan belirli bir köprü kurulduğuna göre, mantıksal yapının özelliklerini incelemeye başlayabiliriz.
InterBase veritabanının mantıksal yapısı
Mantıksal yapı oldukça belirsiz bir kavramdır, bu nedenle temel fikirleri kademeli olarak öğrenmeye çalışacağız ve umarız ki daha sonra sezgisel olarak netleşeceklerdir. Veritabanının mantıksal yapısıyla ilgili olarak ele alacağımız ilk şey sistem tabloları ve içerikleridir. Sistem tabloları sistemi ve kullanıcı meta verilerini tanımlar. Genel olarak «meta veri» terimi «bir veri kümesini tanımlayan veri» anlamına gelir. «Meta» öneki «bir kümeyi tanımlar» anlamına gelir. Örneğin, meta dil, bir dil kümesini tanımlayan dildir. Meta veriler, kullanıcı verilerini, yani tabloları, tetikleyicileri, görünümleri, saklı prosedürleri ve bu somut veritabanının oluşturulma nedeni olan bilgilerin saklanması ve işlenmesi kurallarını uygulayan her şeyi tanımlar.
İlk başta tüm meta verilerin - kullanıcı tabloları, tetikleyiciler, görünümler ve tüm sistem nesneleri - aynı tablolarda saklandığını ve bu tablolardan sıradan SQL sorgularıyla veri okuyup yazabileceğinizi öğrenmek oldukça ilginçtir. Bu tablolar «görsel olarak» yalnızca adlarının RDB$ ile başlamasıyla farklılık gösterir. Bu 4 sembol, sistem nesnelerinin adları için ayrılmıştır. Hiçbir kullanıcı tablosu, sütunu veya başka bir nesne, adları bu sembollerle başlayan isimlere sahip olma hakkına sahip değildir. Resmi olarak, adı ayrılmış sembollerle başlayan bir tablo oluşturabilirsiniz, ancak InterBase belgeleri bunu yapmayı önermez.
Bir soru ortaya çıkıyor: veritabanı yapısıyla ilgili veriler, kullanıcı verileriyle aynı tablolarda saklanıyorsa, tabloları tanımlayan tablolar hakkındaki bilgiler nerede saklanıyor? «Tavuk ve yumurta» sorununun klasik bir örneği - birbirine bağımlıysalar, biri diğerinden nasıl daha önce ortaya çıkabilir? Cevap şudur: sistem tabloları ilkel durumlarında InterBase’in başlangıç kodlarında sabitlenmiştir ve bir veritabanı oluşturulurken belirli bir sırayla otomatik olarak açılır. Veritabanı dosyalarındaki fiziksel sayfaları bu veritabanının belirli nesneleriyle karşılaştıran RDB$Pages tablosundan daha önce bahsetmiştik. Bu tablonun yapısı aşağıda verilmiştir:
Tablo 5. Sistem tablosu RDB$Pages
| Sütun adı | Veri türü | Açıklama |
| RDB$PAGE_NUMBER | INTEGER | Fiziksel sayfa numarası |
| RDB$RELATION_ID | SMALLINT | Sayfanın tahsis edildiği tablonun tanımlayıcısı |
| RDB$PAGE_SEQUENCE | INTEGER | Bu sayfanın numarası |
| RDB$PAGE_TYPE | SMALLINT | Sayfa türü - bkz. tablo 3 |
Her veri sayfası belirli bir tabloyla ilişkilidir. Bu ilişki, tabloya bir referansın saklandığı RDB$RELATION_ID alanı tarafından desteklenir. Yukarıda açıklandığı gibi, dahili veritabanı görüntüsünü oluşturma sürecinde sunucu bu tabloyu oluşturur ve belirli bir algoritmaya göre verilerle doldurur. Doğru olmak gerekirse, dahili veritabanı görüntüsünü oluşturma anında RDB$Pages bir tablo değil, yalnızca InterBase tarafından bilinen belirli bir formattaki veri dosyasıdır. Sunucu, sabit bir algoritmaya göre bu dosyadan veri okur ve tüm veritabanı için önemli olan RDB$Relations tablosunu oluşturur. Bu tablo, tüm veritabanı tablolarını tanımlar. SQL sorgusu yaparsak:
SELECT * from RDB$Relations
RDB$Relations’in hangi tablolara referanslar içerdiğini öğrenmek için, RDB$Pages’i ve kendisini içerdiğini görürüz. Bu durumda sunucunun biraz kurnazlık yaptığı, bu ve diğer sistem tablolarını RDB$Relations’e geriye dönük olarak ekleyerek onları bu şekilde yasallaştırdığı açıktır. Sunucu bunları, kayıt ekleyebileceği veya silebileceği «normal» tablolar olarak kaydeder. Başka bir deyişle, meta verilerle çalışmak için standart bir SQL arayüzü sağlar.
Ve oldukça makul bir soru ortaya çıkabilir - InterBase geliştiricileri neden sistem verilerini kullanıcı arayüzüne göre ayarlasın ki? Sonuçta, dahili erişim ve okuma işlemleri mekanizmaları daha hızlı olurdu. Elbette, meta verileri tanımlayan tablolarla çalışmak için evrensel bir mekanizma sağlamanın büyük bir anlamı vardır.
Mesele şu ki, veritabanının mantıksal yapısı yalnızca tablolardan değil, aynı zamanda diğer nesnelerden de oluşur. InterBase’de aşağıdaki nesneler vardır:
-
Tablo
-
Görünüm
-
Tetikleyici
-
Hesaplanmış_alan
-
Doğrulama
-
Prosedür
-
İfade_dizini
-
İstisna
-
Kullanıcı
-
Alan
-
Dizin
-
Kullanıcı Tanımlı İşlev (UDF)
Henüz bazı nesnelerin işlevini kesin olarak bilmiyoruz, ancak hepsinin kullanıcı için ve InterBase çekirdeğinden erişim için uygun bir görünümde tanımlanması ve saklanması gerektiğini kesin olarak biliyoruz. En iyisi bu nesneleri sistem tablolarında saklamak olacaktır. Bunların eklenmesi ve değiştirilmesi SQL sorgularıyla yapılır. Akıllıca bir çözüm, değil mi? Sunucu uygulaması, belirli bir veritabanından tamamen ayrılmıştır - tüm bağlantılar SQL ve uzantıları - saklı prosedürler ve tetikleyiciler dili - tarafından tanımlanır.
Yani, tüm sunucu nesneleri tablolarda saklanır. Her nesne türü için, veritabanında tanımlanan tüm örnekleri tanımlayan bir tablo vardır. Örneğin, tetikleyiciler için RDB$Triggers tablosu, saklı prosedürler için RDB$Procedures tablosu vardır, görünümler RDB$Relations tablosunda tanımlanır.
Bir veritabanındaki tüm tabloları ve görünümleri tanımlayan son tablonun yapısını ayrıntılı olarak ele alalım. RDB$RELATIONS tablosunun yapısı, InterBase 6 için Language Reference’dan alınmıştır ve aşağıda tablo 6’da verilmiştir.
Tablo 6. Sistem tablosu RDB$Relations
| Sütun adı | Veri türü | Uzunluk | Açıklama |
| RDB$VIEW_BLR | BLOB | 80 | BLR: görünümler için, InterBase’in görünüme her başvurduğunda yürüttüğü sorgunun BLR’sini (Binary Language Representation) içerir. |
| RDB$VIEW_SOURCE | BLOB | 80 | Metin: görünümler için, bu görünümü uygulayan SQL sorgusunun kodunu içerir. |
| RDB$_DESCRIPTION | BLOB | 80 | Tablo veya görünümün kullanıcı açıklaması |
| RDB$RELATION_ID | SMALLINT | Tablo/görünümün dahili tanımlayıcısını içerir | |
| RDB$SYSTEM_FLAG | SMALLINT | Tablo türünü tanımlar: kullanıcı verileri - 0; Sistem bilgisi > 0. | |
| RDB$DBKEY_LENGTH | SMALLINT | db$key uzunluğu | |
| RDB$FORMAT | SMALLINT | InterBase dahili kullanımı için ayrılmıştır. Verilen tablo için meta veri değişiklik sayacını içerir. | |
| RDB$FIELD_ID | SMALLINT | Tablodaki alan sayısı. | |
| RDB$RELATION_NAME | CHAR | 31 | Benzersiz tablo adı. |
Bu sistem tablosunun açıklamasında BLR kısaltmasını görüyoruz. Bunun ne olduğunu anlamak için SQL’e bir gezi yapacağız. Bilindiği gibi görünümler, tetikleyiciler ve saklı prosedürler, SQL dilinin bir uzantısında yazılmış kodlardır (her DBMS sunucusu için kendi uzantıları vardır). İnsan diline yakındır, bu da içinde kolayca sorgu yapmayı sağlar. Ancak InterBase, bunu açıkça daha «makine» bir şeye çevirir - yani BLR’ye (Binary Language Representation). Herhangi bir sorgu, görünüm, tetikleyici, saklı prosedür her zaman BLR’ye çevrilir ve ardından yürütme için InterBase çekirdeğine iletilir.
BLR
BLR, bir programcının yazdığı SQL kodu ile sunucunun kabul ettiği makine kodu arasında ara bir bağlantı olarak kullanılan özel bir dildir. Kimse doğrudan BLR ile yazmaz - bu oldukça zor olurdu çünkü bu dilde mümkün olan en yüksek çalışma hızı için sözde ters Polonya kaydı kullanılır. İşte küçük bir örnek:
blr_begin,
blr\_assignment,
blr\_field, 0, 7, 'D','A','T','E','I','Z','M',
blr\_variable, 1,0,
blr\_assignment,
blr\_field, 0, 4, 'R','A','T','E',
blr\_variable, 0,0,
blr\_block,
Sorgularınız, prosedürleriniz, tetikleyicileriniz ve diğer tetikleyicileriniz için BLR, sunucu çekirdeğinin bir parçası olan özel bir ön işlemci tarafından oluşturulur. Tablo 7’de gösterildiği gibi, görünümler için metinleri (orijinal) görünüm ve derlenmiş görünüm, yani BLR saklanır. BLR’si olan herhangi bir nesneye başvurulduğunda, sunucu nesnenin ikili kodunu yürütür ve bu nesnelerin orijinal metnini her seferinde yorumlamaz, bu da karmaşık sorguların yürütülmesini hızlandırır.
InterBase’de nesnelerin hiyerarşisi
Veritabanı nesnelerinin neyi temsil ettiğine dair net bir fikre sahip olmak için, «kim neyi içerir» ilkesine göre veritabanı nesnelerinin bir hiyerarşisini oluşturmaya çalışacağız. Veritabanı dosyalarının fiziksel sayfaları, veri organizasyonunun en alt seviyesi olarak hiyerarşimize dahil edilmesi gereken ilk şeydir. Ardından, diğer tüm nesne türlerini tanımlayan temel nesneler olarak tablolar gelir. Tablolar, saklı prosedürleri, tetikleyicileri, hesaplanmış alanları, doğrulamaları, ifade dizinlerini, istisnaları vb. tanımlar. Dikkat edin - yalnızca tanımlar! Tablolar yalnızca bu nesnelerin bildirimlerini ve tanımlarını içerir ve nesneler BLR aracılığıyla uygulanır. Bu nedenle tabloları, diğer tüm veritabanı nesnelerini destekleyen bir çerçeve biçiminde temsil edebiliriz. BLR, çerçevenin altında uygulama katmanı olarak yer alacak, ardından tetikleyiciler, saklı prosedürler, ifade dizinleri ve görünümler gelecektir.
InterBase iç yapısı uzmanlarını sakinleştirmek için, birçok nesnenin (görünümler gibi) BLR’sinin sistem tablolarında saklandığına itiraz edebileceklerini belirterek, bu tutumun bir resimde ifade edilmesinin oldukça zor olduğunu ve basitleştirme için bundan vazgeçtiğimizi yorumlayacağız. Şema, veritabanı nesnelerinin karşılıklı bağımlılıklarını kesin olarak yeniden oluşturmayı amaçlamaz; yalnızca yakın bağlantılarını gösterir.
Bu nesne türlerinin, herhangi bir ara mantık olmadan onları uygulayan BLR ile doğrudan bağlantılı olması onları birleştirir. İstisnalar ayrı olarak tahsis edilmelidir - bunlar kullanıcı tarafından tanımlanan özel hata türlerini temsil eder. İstisnalar InterBase çekirdeği düzeyinde işlenir ve bu nedenle BLR’ye sahip değildir. Kontroller gibi kısıtlama türleri tetikleyicilerin üzerinde tahsis edilir çünkü gerçekte tetikleyiciler kısıtlamaların ve kontrollerin mantığını uygular.
Veritabanının mantıksal ve fiziksel yapısındaki nesnelerin bir hiyerarşisi resim 2’de çizilmiştir.
Resim 10. InterBase veritabanının mantıksal yapısındaki nesneler
Elbette, bu şema veritabanındaki nesnelerin mantıksal yapısını ve bağlantılarını yalnızca yaklaşık olarak tanımlar ve genel bir fikir verir. InterBase veritabanı meta verilerinin yapısını incelemek isteyen herkes, veritabanı sistem tablolarının yeniden mühendisliğini yapabilir ve nesneleri arasındaki tüm bağlantıları göz önünde bulundurabilir, ayrıca belgelere ve InterBase birincil kodlarına başvurabilir. Bu tablo yalnızca ana veritabanı nesnelerini gösterir. Bu nesnelerin veritabanında gerçekleştirdiği ana işlevleri kısaca açıklayalım.
Tablolar - kullanıcı ve sistem verilerini içeren ana nesnedir. Bir tablonun benzersiz bir adı vardır ve adlandırılmış alanlardan oluşan bir küme içerir. Kullanıcı, tablolara veri yerleştirebilir, veri çıkarabilir ve verileri değiştirebilir. Tablonun, elle çizilmiş sıradan kağıt tablolara benzediğini söyleyebiliriz.
Tetikleyiciler - veri işlemleri sırasında ek eylemler uygulamak için kullanılan çalıştırılabilir kod parçalarıdır. Tetikleyiciler, ekleme, değiştirme veya silme işlemlerinden önce veya sonra çalıştırılır ve yeni oluşturulan kayıtlara değerlerin yerleştirilmesi ve daha birçok şeyin gerçekleştirilmesini sağlar.
Saklı yordam, veritabanı düzeyinde iş mantığını uygulamak için güçlü bir araçtır. Sunucu düzeyinde çalıştırıldığı için çok hızlı çalışır ve veri kümeleri üzerinde bir dizi işlemin yürütülmesine olanak tanır. InterBase saklı yordamları, diğer tablolarla birleştirme dahil tüm SQL işlemlerinin uygulanabileceği standart SQL veri kümeleri döndürür.
Görünümler, sunucuda çalıştırılan derlenmiş SQL sorgularıdır. Görünümler, veri kümelerini düzenlemeye ve iş mantığının bir kısmını sunucuya aktarmaya olanak tanır.
Doğrulamalar, tablodaki alanların değerlerine uygulanan kısıtlamalardır. Örneğin, belirli bir alanın yalnızca pozitif değerleri kabul edeceğini belirtebiliriz. Alan değerleri üzerindeki kısıtlamalar tetikleyiciler tarafından uygulanır ve veritabanı düzeyinde referans bütünlüğünü etkili bir şekilde kontrol etmeye olanak tanır. Genellikle kısıtlamalar, bir tabloya yanlış değerlerin yerleştirilmesini önlemek için kullanılır.
Kullanıcılar - InterBase, veritabanıyla çalışmak için birden fazla kullanıcıya sahip olmamıza ve farklı veritabanı nesnelerine erişim haklarını bunlar arasında dağıtmamıza olanak tanır. Böylece, bu veya şu veritabanı işlemlerine yönelik izinleri kontrol edebiliriz.
Kullanıcı Tanımlı İşlevler (UDF) - kullanıcı tarafından tanımlanan işlevlerdir. Bu, standart SQL arayüzünü kendi işlevlerimizle genişletmemize olanak tanıyan InterBase’in en güçlü yeteneklerinden biridir. Örneğin, UPPER (tüm sembolleri büyük harfe çevirme) gibi satırlarla çalışma işlevleri, InterBase setine dahil edilen standart UDF kitaplığında uygulanır. Kendi UDF’lerimizi oluşturma olanağı sayesinde geliştiriciler, InterBase işlevselliğini pratik olarak herhangi bir işlevle genişletebilirler. UDF oluşturmak için dinamik kitaplıklar oluşturmaya olanak tanıyan herhangi bir programlama ortamını (Visual C++, C++ Builder, Delphi vb.) kullanabiliriz.
Sonuç
Bu bölümde, InterBase veritabanı içinde veri depolama ve işleme uygulamasıyla ilgili soruları ilk kez ele aldık. Ne yazık ki, bu konunun kısa bir incelemesini çok sayıda terime ve hatalı analojilere başvurmadan yapamayız. Veritabanının fiziksel ve mantıksal yapısını daha ayrıntılı olarak açıklamış olsaydık, yine de InterBase birincil kodlarına başvurmamız gerekirdi, ancak bu başka bir kitap olurdu.
Yine de, her programcının her gün kullandığı ürünün içeriğini tanımasının faydalı olacağını düşünüyoruz.
Kaynakça
-
«InterBase’in Disk Üzerindeki Yapısı» - Ann.W.Harrison
-
«InterBase’de Alan Yönetimi» - Ann W.Harrison
-
«Veri Sayfasının Yapısı» - Paul Beach (Dave Schnepper ve Deej Bredenberg’e teşekkürlerle)