FreeTDS ile SqlServer Bağlantısı
Linux tabanlı çözüm kümelerinde her ne kadar pek kullanım alanı bulmasa da, ticari dünyada zaman zaman Microsoft SQL Server veritabanı sunucusuna bağlanmanız ve üzerinde işlem yapmanız gerekebilmektedir.
Java gibi yüksek seviyeli dillerde ODBC sürücüleri üzerinden çeşitli çözümler olmakla beraber bu bölümde biz C üzerinden en alt seviyede native protokolü kullanarak SqlServer ile haberleşme konusu üzerinde duracağız.
FreeTDS
FreeTDS, TDS (Tabular Data Stream) protokolünün LGPL lisanslı özgür bir gerçekleştirimidir.
Tabular Data Stream (TDS), bir veritabanı sunucusu ile ona bağlı istemciler arasındaki iletişim ve veri transferini modelleyen, TCP/IP tabanlı bir protokoldür.
Protokol Sybase Inc. tarafından geliştirilmiş ve ilk olarak 1984 yılında Sybase SQL Server ürününde kullanılmıştır.
1990 yılında Sybase ve Microsoft firmalarının aralarında yapmış oldukları teknoloji işbirliği anlaşmasını takiben, Microsoft firması da Sybase SQL Server kodunu temel alarak kendi veritabanı sunucusu olan SQL Server ürününü geliştirdi. Bu nedenle Microsoft SQL Server ürününde de TDS protokolü kullanılmaktadır.
Linux platformlarında TDS protokolünün FreeTDS gerçekleştirimi oldukça kararlı durumda olup, C dilinin yanı sıra Php, Ruby, C++ vb. dillerde de alt katmanda FreeTDS kullanan farklı kütüphane alternatifleri mevcuttur.
TDS protokolünün 5.0 versiyonu Sybase tarafından dokümante edilmiş olmakla birlikte, diğer versiyonlarına dair bilgiler genel kullanıma açılmamıştı. 2008 yılında Microsoft, daha önce hayata geçirdiği Open Specification Promise doğrultusunda TDS protokol detaylarını genel kullanıma açtı ve bu tarihten sonra kütüphaneler daha güvenilir hale geldi.
Not: TDS 5.0 versiyonu ile Sybase sunuculara bağlanılabiliyor olmasına karşın bu versiyon Microsoft tarafından desteklenmemektedir. Microsoft SQL Server bağlantıları için protokolün 7.X versiyonları kullanılmalıdır.
Konsol İstemcisi - Sqsh
Linux sistemlerde kullanılmak üzere, Sybase tarafından geliştirilen isql konsol arayüzündeki temel fonksiyonaliye ve ek olarak kullanım kolaylığı açısından bazı yeni fonksiyonlara sahip sqsh uygulaması geliştirilmiştir. Uygulamayı paket yöneticinizle aşağıdaki gibi sisteminize kurabilirsiniz:
Sqsh ile bir sunucuya bağlanırken temel olarak aşağıdaki parametreler kullanılır:
Parametre
Açıklama
-S
Sunucu adresi
-U
Kullanıcı Adı
-P
Parola (parametre olarak girilmez ise konsolda tekrar sorulacaktır)
-D
Veritabanı Adı
Örnek olarak 172.16.2.139 ip adresindeki example_db veritabanına testuser kullanıcı adı ve tstpwd123 parolasıyla bağlanalım ve bolgeler tablosundaki kayıtları görelim:
Yukarıdaki sonuç kümesine baktığımızda bazı karakterlerin düzgün görüntülenmediğini, bazılarının ise değiştirildiğini görmekteyiz (ğ -> g vb.)
Sorunun çözümü için sunucuya bağlantı kurarken kullanılacak karakter seti kümesi olarak UTF-8'i belirtmemiz gereklidir. Her ne kadar sqsh uygulamasının yardım sayfasında -J UTF-8 gibi bir parametre geçirmek suretiye bu işlemin yapılabildiği yazsa da kullandığımız versiyonda (2.1.7) bu şekilde çözüm üretemedik. Karakter problemini, sunucu bazlı genel ayarlamaların yapılmasına imkan veren freetds.conf
dosyası üzerinden yapacağımız tanımlamalarla çözeceğiz.
freetds.conf
FreeTDS kütüphanesi ile çalışırken öntanımlı olarak /etc/freetds/freetds.conf
dosyası okunmaktadır.
Bu dosyada genel olarak kütüphanenin davranışını değiştirebilecek tanımlamalar bulunmaktadır. Ayrıca belirli bir SQL sunucu için özel ayarların da buradan yapılmasına imkan verilmektedir.
Dosyanın genel içeriği ve örnek sunucu bazlı tanımlamalar aşağıdaki gibidir:
Yukarıda anlaşılabileceği üzere, tüm sunucuları etkileyecek ayarlar [global]
bölümü altında yer almakta, aynı zamanda [mssql]
örneğindeki gibi belirli bir sunucua isim verilerek (DNS ismi olması gerekmiyor), sunucu bazlı ek ayarlamalar yapma şansı da bulunmaktadır.
Örneğimizde mssql adında bir sunucu ismi tanımladık ve client charset değerini UTF-8 olacak şekilde değiştirdik.
Bu tanım sonrasında hem sqsh uygulamasından hem de freetds kullanan diğer uygulamalarda, sunucu isim/ip parametresinde mssql ismini kullanabilir ve konfigürasyon dosyasında bu bölümde belirtilmiş ayarların aktif olmasını sağlayabiliriz. Bir önceki select örneğimizi tekrar edelim:
.sqshrc
Sqsh ile çalışırken kullanım ortamınızı daha konforlu hale getirmek için ek ayarlamaları ev dizininiz altındaki .sqshrc
dosyası üzerinden tanımlayabilirsiniz (henüz hiç ayar yapılmadı ise dosyanın oluşturulması gerekecektir)
Örneğin yukarıdaki çıktı formatı yerine öntanımlı MySQL konsol arayüzündekine benzer bir fomrmat kullanılmasını istiyorsanız, go komutunu -m pretty parametresi ile çalıştırmalısınız. Bu komutu her çalıştırdığımızda parametresini girmek zorunda kalmamak için bir alias tanımlayabiliriz.
Aşağıdaki satırı ~/.sqshrc
dosyanıza girin:
Şimdi tekrar bölge listesini sorgulayalım:
Diğer bazı kullanışlı örnekler için http://www.sypron.nl/sqsh.html adresine bakabilirsiniz.
Kütüphane Kullanımı
FreeTDS kütüphanesini C uygulamalarında kullanabilmek için aşağıdaki komutla geliştirme paketini sisteminize yükleyebilirsiniz:
Aşağıdaki örnek uygulamayı mssql_connect.c adıyla kaydedip şu şekilde derleyebilirsiniz:
Örnek kodumuzu listeleyip önemli yerlerini detaylandırmaya çalışalım:
Kütüphanenin İlklendirilmesi: dbinit
dbinit
FreeTDS kütüphanesinin kullanıldığı uygulamalarda, kütüphane içerisinden herhangi bir fonksiyon çağrılmadan önce, dbinit()
fonksiyonunun çağrılmış olması şarttır.
dbinit()
dahili bazı veri yapılarının doldurulmasını ve yerel spesifik tarih vb. format bilgilerini okumak için freetds içerisinden çıkan -varsa- /etc/freetds/locales.conf
dosyasını okur.
locales.conf
dosyasının bu şekilde okunması deprecated bir özellik olmuştur. Güncel kütüphane versiyonları sistemin yerel (locale) ayarlarından bu bilgileri temin etmektedir. Ancak gene delocales.conf
dosyası bulunursa işlenmektedir.
Bu işlemin uygulamanın main fonksiyonu içerisinde yapılmasında fayda vardır. Ancak herhangi bir sebeple dbinit işleminin bir fonksiyon içerisinden koşullu olarak sonradan yapılması gerekiyorsa, mutlaka statik bir değişkenle kütüphanenin ilklendirme işleminin daha önce yapılıp yapılmadığını tutmanız zorunludur. İlklendirme işleminin tekrar edilmesi, takibi zor hatalara yol açabilmektedir.
Hata İşleme: dberrhandle
dberrhandle
Kütüphanenin hata ve uyarı durumlarında çağıracağı calback fonksiyonunu, dberrhandle()
fonksiyonu ile belirtilmelidir.
Bu fonksiyonun prototipi aşağıdaki gibidir:
Fonksiyon çağrıldığında dberr
parametresi 0'dan farklı ise, kritik bir veritabanı hatası olduğu anlaşılır.
Bağlantı Kurma ve Veritabanı Seçimi
Bağlantı kurmak için öncelikle kullanıcı adı ve parola bilgileri LOGINREC
veriyapısı içerisine doldurulmalıdır.
Bunun için öncelikle LOGINREC
tipinde bir değişken, dblogin()
fonksiyonu ile ilklendirilir, ardındandbsetluser ve dbsetlpwd fonksiyonları ile ilgili parametreleri ayarlanır.
Sonraki adımda hazırlanan LOGINREC
veri yapısı ve sunucu bilgisini (burada IP adresi, DNS üzerinden çözülebilen bir hostname veya freetds.conf
içerisinde tanımlanmış bir sunucu adı kullanılabilir) parametre olarak alıp, geriye sürecin ilerleyen aşamalarında sürekli kullanılacak olan DBPROCESS
handle döndürecek olan dbopen()
fonksiyonu çağrılır.
Herhangi bir sebeple hata alınırsa, ilgili hata mesajının detayı hata işlemeleri için önceden belirlenmiş olan callback fonksiyonundan alınabilir.
Bağlantı zaman aşımı süresini kontrol altına almak isterseniz, dbopen()
fonksiyonu çağırmadan önce dbsetlogintime(int seconds)
prototipindeki fonksiyonu kullanarak saniye cinsinden bir limit de tanımlayabilirsiniz.
Bağlantı gerçekleştikten sonra dbuse()
fonksiyonu ile üzerinde çalışılacak olan veritabanı seçimi işlemi yapılmaktadır.
Sorgu Çalıştırma ve Yanıt İşleme
Veritabanı üzerinde çalıştırılacak olan sorgu, öncelikle dbfcmd()
fonksiyonu ile hazırlanır. Ardından dbsqlexec()
fonksiyonu ile çalıştırılır.
Sorgu bu şekilde işletildikten sonra geriye dönen değerlerin saklanacağı uygun veri yapıları oluşturulmalıdır. Bunun için uygulama kaynak kodumuzun ilk bölümünde, struct mssql_column
şeklinde bir yapı tanımladık. Bu yapıyı ihtiyaçlarınız doğrultusunda genişletebilirsiniz.
Tanımladığımız yapıyı, işletmiş olduğumuz sorgunun yanıt kümesindeki her bir sütun ile ilgili veri tipi, uzunluk ve sütun ismi bilgilerini işlemek için kullanacağız.
Örneğimizi geri dönen sütun sayısını önceden bilemeyeceğimiz, her türlü sorgu için çalışacak şekilde hazırladık. Dolayısıyla öncelikle göndermiş olduğumuz sorgu yanıtının kaç sütundan oluştuğunu öğrenmemiz gerekiyor. Bu işlem için dbnumcols()
fonksiyonunu kullanıyoruz.
Sütun sayısını öğrendikten sonra ilgili bilgileri hazırlamış olduğumuz struct mssql_column
veri yapısında saklamak üzere bellekte yer ayırıyoruz.
Ardından sorgu yanıtındaki satırları işlemeye geçmeden hemen önce, sütunlarla ilgili sütun ismi, tipi ve veri uzunluğu bilgilerini sırasıyla dbcolname()
, dbcoltype()
ve dbcollen()
fonksiyonlarıyla elde ediyoruz.
Sütun ile ilgili bilgileri bu şekilde öğrendikten sonra, hazırlamış olduğumuz veri yapısında yanıtları saklayacağımız yerleri hazırlıyoruz. Bu noktada kodumuzu kısa tutmak adına, metin dışındaki tiplerin, dbwillconvert()
fonksiyonuyla metin tabanlı bir formata dönüştürüldüğünde gereken uzunluğu hesaplatıp, metne dönüştüğü zamanki uzunluğu için yetecek kadar bellekte alan açıyoruz. Örnek olarak 4 byte'lık INT
tipindeki bir sütun için dbwillconvert()
sonrası sütun boyutunun 11 olarak geleceğini göreceğiz zira 4 byte'lık bir işaretli INT değerini metne dönüştürüp saklayabilmek için 11 byte uzunluğunda bir alan gereklidir.
Gerçek ortamda gönderdiğiniz sorgularla ilgili bilgi sahibi olacağınızdan, tüm sütunları metin tabanlı dönüşüme zorlamak yerine, struct mssql_column
veri yapısı içerisindeki genel amaçlı buffer
değişkenini bir union
yapısı ile değiştirip, sütun tipine göre union
içerisinde INT, FLOAT vb. veri tipleri kullanabilir ve metin dönüşümü yapmadan doğrudan bu alanların içerisine yazılmasını sağlayabilirsiniz.
Bellekteki alanlar hazır edildikten sonra her bir sütunu dbbind()
fonksiyonu ile yanıt setine nasıl bağladığımızı belirtmemiz gerekiyor. Örneğimizde bind tipi olarak hep NTBSTRINGBIND
değerini kullandık. Yukarıdaki ek notumuz doğrultusunda eğer metin dönüşümü uygulamayacaksanız bunun yerine INTBIND
, REALBIND
, BIGINTBIND
vb. diğer veri tipleri için uygun binding değerlerini de kullanabilirsiniz.
Her bir sütun için gerekli bind işleminin yanı sıra NULL değerler için de dbnullbind()
fonksiyonuyla bir adet binding işleminin daha yapılması gereklidir.
Not: Sütun ve binding tipleri için sabitler, kütüphane içerisinden çıkan
sybdb.h
dosyası içerisinde yer almaktadır.
Şimdi artık sıra satırları işlemeye geldi. Bunun için dbnextrows()
fonksiyonu NO_MORE_ROWS
değeri döndürmediği müddetçe iterasyonla tüm bilgileri alabiliriz.
Örnek uyguladığımızda her bir satırda aldığımız değerleri, sütun ismi ile birlikte ekrana bastırdık.
Yanıt satırlarının işlenmesi tamamlandıktan sonra sistem kaynaklarını serbest bırakıyoruz.
Bağlantının Sonlandırılması
Veritabanı ile ilgili işlemlerimiz tamamlandıysa açık olan bağlantımızı dbclose()
fonksiyonuyla kapatmamız gerekir.
Son olarak kullandığımız DBPROCESS
ve LOGINREC
değişkenlerini de dbfreebuf()
ve dbloginfree()
fonksiyonlarıyla da geride artık kalmayacak şekilde temizliyoruz.
Örnek Kullanım
Hazırlamış olduğumuz uygulama ile bir miktar veri içeren bolgeler
ve iller
adında 2 tablo üzerinde INNER JOIN sorgusu çalıştıralım:
Not:
debug.h
dosyasını Kaynak Dosyalar bölümünden edinebilirsiniz.
Last updated
Was this helpful?