czwartek, 28 marca 2013

[HTML|JS|CSS] HTML5: WebSQL

WebSQL jest jedną z tych funkcjonalności, które pomimo iż kojarzone są ze standardem HTML5, nie znajdą się w tym standardzie. Mimo wszystko wiele przeglądarek wspiera możliwość składowania danych w relacyjnej bazie po stronie klienta. API dostępne z JavaScript pozwala na pisanie zapytań w języku SQL, a dane składowane są w pliku (podobnie jak w SQLite). Jeżeli chodzi o kompatybilność z nowoczesnymi przeglądarkami, to problem pojawia się w przypadku Firefoxa i Explorera, co wyklucza sporą część użytkowników (link).

Aby połączyć się z bazą wykorzystujemy funkcję openDatabase:

var db = openDatabase('mydb', '1.0', 'my first database', 2 * 1024 * 1024);

Jeżeli baza nie istnieje to zostaje automatycznie utworzona. Instrukcje SQL wykonywane są w funkcjach zapewniających tranzakcyjność (jeżeli którekolwiek z zapytań się nie powiedzie, cała operacja zostanie cofnięta).

$("#create").click(function(){
 db.transaction(function (tx) {
  tx.executeSql('CREATE TABLE messages (id unique, text)');
 });
});

Tranzakcje można zagnieżdżać, gdyż w callbackach odczytujemy wyniki zapytań.

$("#store").click(function(){
 var value = $("#msg").val();
 db.transaction(function (tx) {
  tx.executeSql('SELECT * FROM "messages"', [], function(tx,results){
   var i = results.rows.length + 1;
   tx.executeSql('INSERT INTO messages (id, text) VALUES (?,?)',
    [i, value]);
  });
 });
 $("#msg").val("");
});

Dane, które chcemy wyświetlić, można filtrować zgodnie ze składnią SQL.

$("#restore2").click(function(){
 var items = $("#items");
 items.html("");
 db.transaction(function (tx) {
  tx.executeSql('SELECT * FROM "messages" WHERE LENGTH(text) > 5', 
   [], function(tx,results){
   var l = results.rows.length;
   for (var i = 0; i < l; i++) {
    var item = results.rows.item(i);
    var li = $("<li>");
    $(li).html(item.id + ", " + item.text);
    items.append(li);
   };
  });
 });
});

Usuwanie z tabeli wykonuje się oczywiście przy użyciu instrukcji DELETE.

$("#removeLast").click(function(){
 var value = $("#msg").val();
 db.transaction(function (tx) {
  tx.executeSql('SELECT * FROM "messages"', [], function(tx,results){
   var i = results.rows.length;
   var sql = 'DELETE FROM "messages" WHERE "id" = ' + i;
   tx.executeSql(sql);
  });
 });
});

Na koniec warto pamiętać, że podobnie jak w przypadku IndexedDB, możemy łatwo oglądać stan naszych tabel w przeglądarce Google Chrome.

niedziela, 24 marca 2013

[NoSQL] MongoDB: Queries

Bazy NoSQL składują dane w postaci dokumentów JSON. Dokumenty takie odpytuje się przy użyciu zapytań specyficznych dla danej bazy danych. MongoDB udostępnia całkiem bogate API do efektywnego przeszukiwania dokumentów składowanych w kolekcjach. Zależnie od języka programowania z którego łączymy się z bazą, zapytania mogą być budowane w nieco odmienny sposób, ale koncepcja pozostaje ta sama. Poniżej kilka użytecznych przykładów na to jak pisać zapytania. Przykłady te nadają się do urchomienia z shella MongoDB (program mongo.exe), jednak na przykład w C# składnia jest bardzo podobna. Tym co różni connectory jest sposób rzutowania danych do danego języka programowania.

Podstawowymi funkcjami służącymi do budowania zapytań są find i findOne. Pierwsza zwraca wszystkie dokumenty spełniające dany warunek, natomiast findOne zwraca tylko pierwszy dokument spełniający ten warunek. Jako argument przekazuje się obiekt JavaScript zgodny z językiem zapytań. Dokumentacja MongoDB mówi, że find jest odpowiednikiem instrukcji SELECT z SQL, natomiast obiekt przekazywany jako parametr odpowiada instrukcji WHERE.

Wybieranie bezwarunkowe:

Nie podając warunku jako argument dla obu funkcji wybieramy wszystkie dokumenty, bądź pierwszy zaindeksowany.

db.users.find();
db.users.findOne();

Przeszukiwanie warunkowe:

Tutaj mamy sporo możliwości. Możemy żądać by dane property miało dokładnie jakąś wartość:

db.users.find({lastname: "Doe"});

Przeszukiwanie warunkowe wykonuje się za pomocą operatorów $lt i $gt (względnie $lte czy $gte).

var query = {};
query.lastname = "Doe";
query.age = { $gt :20};
db.users.find(query);

Podanie dwóch warunków powoduje, że oba muszą być spełnione, aby dokument został zwrócony.

Dokumenty zagnieżdżone:

Przy przeszukiwaniu zagnieżdżonych dokumentów, stosuje się notację z kropkami, tak jakbyśmy odczytywali zagnieżdżone property w JavaScripcie:

var query = {};
query["address.country"] = "USA";
db.users.find(query);

Projekcje:

Drugim, opcjonalnym argumentem dla zapytań jest obiekt, w którym możemy zadecydować, które pola nas interesują. Domyślnie zwrócone zostaną wszystkie pola dokumentu. Jeżeli chcemy któreś konkretne pola, pomijając inne, oznaczamy te pożądane wartością 1. Przy oznaczeniu pierwszego pola przez 1 zostanie zwrócone ono, oraz pola _id nadawane przez bazę. Aby je wyłączyć, możemy ustawić tą wartość na 0. Zatem aby zwrócić dokumenty z samymi imionami wystarczy:

db.users.find(query, {firstname:1, _id: 0});

Paginacja:

Stronnicowanie może być wykonywane bezpośrednio na bazie danych przy użyciu operatorów limit i take. 

db.users.find().limit(1).skip(1);

Sortowanie:
.
Podobnie jak paginacja wykonywane na wynikach jako kolejna funkcja w łańcuchu wywołań

db.users.find().sort({age: 1});

Wartość 1 oznacza sortowanie rosnąco, wartość -1 sortowanie malejąco.

środa, 20 marca 2013

[NoSQL] MongoDB: klient C#

MongoDB, jako jedna z najpopularniejszych, jeżeli nie najpopularniejsza baza NoSQL, dostarcza drivery dla większości najpopularniejszych współcześnie języków programowania. Firma 10gen udostępnia oficjalny sterownik dla języka C#, który można zainstalować w projekcie przy użyciu menadżera pakietów NuGet.


API jest bardzo proste, poniżej pokazane proste CRUDowe operacje:

Dodawanie do kolekcji:

public static void Insert()
{
    var settings = new MongoServerSettings();
    settings.Server = new MongoServerAddress("127.0.0.1", 27017);
    var server = new MongoServer(settings);

    var collection = 
        server.GetDatabase("mongotest").GetCollection("users");

    var doc = new BsonDocument();
    doc["firsname"] = "John";
    doc["lastname"] = "Doe";
    doc["age"] = 17;
    collection.Insert(doc);
}

Najprostszym sposobem dodawania dokumentów do kolekcji jest klasa BsonDocument. Klasa ta jest odpowiednikiem JSONa. BsonDocument może składać się z par klucz - wartość, gdzie wartością może być kolejny BsonDocument, tablica (BsonArray) czy zwykły obiekt typu prostego. Na powyższym przykładzie widać też, jak niewiele kodu potrzeba by składować dane, serwer MongoDB na początku nie zna ani bazy mongotest ani też tabeli users - zostaną one utworzone.

Odczyt i aktualizację danych można wykonać w następujący sposób:

public static void Modify()
{
    var settings = new MongoServerSettings();
    settings.Server = new MongoServerAddress("127.0.0.1", 27017);
    var server = new MongoServer(settings);

    var collection =
        server.GetDatabase("mongotest").GetCollection("users");

    BsonDocument jdoe = 
        collection.FindOne(Query.GT("age", BsonValue.Create(15)));

    jdoe["age"] = 19;
    collection.Save(jdoe);
}

Metoda FindOne gwarantuje zwrócenie pierwszego obiektu spełniającego dany warunek, natomiast klasa Query udostępnia szereg statycznych metod, umożliwiających budowanie zapytań MongoDB podobnych do tych z shella.

Do usuwania danych można użyć funkcji Remove do której podajemy jako parametr obiekty typu BsonDocument, lub FindAndRemove, która usuwa wszystkie dokumenty spełniające dany warunek.

public static void Delete()
{
    var settings = new MongoServerSettings();
    settings.Server = new MongoServerAddress("127.0.0.1", 27017);
    var server = new MongoServer(settings);

    var collection =
        server.GetDatabase("mongotest").GetCollection("users");

    collection.FindAndRemove(Query.GT("age", BsonValue.Create(15)), 
        SortBy.Null);
}

niedziela, 17 marca 2013

[NoSQL] MongoDB + Node.js: mongoose

Interesującą konfiguracją dla aplikacji webowych jest Node.js jako serwer http udostępniający RESTowe API oraz MongoDB jako baza danych. Za takim pomysłem przemawia przede wszystkim format danych: klient przysyła JSONa, Node.js automatycznie parsuje go do obiektu JavaScript na którym można wykonać pewne operacje a później zapisać w bazie jako JSON znowu.

Klientem MongoDB w Node.js jest mongoose. Można go pobrać z menadżera pakietów poleceniem

npm install mongoose

Po dodaniu pakietu do serwera Node.js przy użyciu konstrukcji require, można rozpocząć pracę z MongoDB. Pakiet mongoose do CRUDowych operacji wykorzystuje obiekty typu Schema. Definiując taki obiekt podajemy jakiego typu pola znajdować się będą w naszych dokumentach. Dzięki temu, wstawiając dokument do bazy mamy pewność, że wstawione zostaną jedynie te pola, które zdefiniowaliśmy w schemacie. Mechanizm ten jest konfigurowalny poprzez opcję strict. Jeżeli ustawimy ją na false, to do bazy trafiać będą całe dokumenty. Jeden schemat mapowany będzie na jedną kolekcję w bazie przy zachowaniu zasad języka angielskiego. Na przykład schemat Car zmapowany zostanie do kolekcji cars. Poniżej prosta konfiguracja:
var mongoose = require('mongoose');
mongoose.connect('localhost','mongoosetest');

var schema = mongoose.Schema({ 
    make: 'string',
    enginePower : 'number'
 }, {strict: false});

var Car = mongoose.model('Car', schema);

Dodawanie dokumentów do bazy przy odbieraniu wiadomości typu POST:
app.post('/store/', function(req, resp) {
    var nm = req.body.name;
    var pw = req.body.enginePower;
    var car = new Car({ make: nm, enginePower: pw, day: "17-03" });
    car.save(function(err){
        if(err){
            resp.send({
                ok: 'sth went wrong'
            });
        }
        else{
            resp.send({
                ok: 'everything is fine'
            });
        }       
    });    
});

Analogicznie pobieranie danych wykonujemy także na obiekcie modelu;
app.get('/store/:key', function(req, resp) {
    Car.find({make: req.params.key}, function(err, docs){
        if(err)
            resp.send(501);
        else
            resp.send(docs);
    })
});

Funkcja find może przekazywać bardziej skomplikowane zapytania MongoDB, te same, które dostępne są w shellu bazy (program mongo.exe).

Z kolei aby usunąć obiekt, używamy funkcji remove wołanej również na modelu. Jako pierwszy parametr podaje się warunek.
app.del('/store/:key', function(req, resp) {
    Car.remove({make: req.params.key}, function(err, docs){
        if(err)
            resp.send(500);
        else
            resp.send(200);
    });
});

czwartek, 14 marca 2013

[NoSQL] MongoDB: Wprowadzenie

Jedną z najpopularniejszych baz NoSQL jest MongoDB. Podobnie jak RavenDB opisany w poprzednich postach jest to baza dokumentowa. Z poziomu API dane są składowane jako dokumenty JSONowe, jednak fizycznie na dysku składowanie odbywa się w postaci binarnych JSONów nazywanych BSON. Każdy dokument otrzymuje od bazy własne unikatowe id.

Kilka zalet i ciekawych funkcjonalności:
  • możliwość tworzenia indeksów na dowolnych polach dokumentów
  • mechanizm automatycznej replikacji danych
  • sharding - balansowanie obciążenia na wiele komputerów
  • bogaty język zapytań
  • oficjalne connectory dla wielu popularnych języków programowania 
Aby rozpocząć pracę z MongoDB należy pobrać pliki ze strony producenta odpowiednią wersję. W folderze należy utworzyć folder, w którym przechowywane będą dane, np. mydata. Serwis mongodb uruchamiany jest z command line, po wejściu do folderu z pobranymi plikami, poleceniem:
mongod --rest --port 27017 --dbpath mydata

Konfiguracja serwera bazy odbywa się również przez command line. Należy wykorzystać polecenie mongo podając numer portu, np.
mongo  --port 27017

Z poziomu shella można wykonywać proste komendy w języku JavaScript, np. zapytania.

poniedziałek, 11 marca 2013

[NoSQL] RavenDB: Queries

Provider LINQ dla RavenDb implementuje interfejs IQueryable<T>, co oznacza między innymi, że zbudowanie zapytania nie jest tożsame z zapytaniem do bazy. Dane pobierane są w momencie materializacji, czyli np. gdy wywołamy na obiekcie IQueryable funkcje ToArray, ToList, First, Last, Take itd.
using (var session = store.OpenSession())

{

    var audi = from car in session.Query<Car>()

               where car.Make.StartsWith("Audi")

               select car;

    MessageBox.Show(String.Format("Audi count: {0}", audi.Count()));

}

W zapytaniach można także używać agregatów, np do policzenia średniej:
using (var session = store.OpenSession())

{

    var avg = session.Query<Car>().ToArray().Average(c => c.Price);

    MessageBox.Show(String.Format("Average Price: {0}",avg));

}

Nic nie stoi także na przeszkodzie, by używać projekcji - mechanizmu, w którym pobieramy tylko fragment dokumentu z bazy o takich polach jakie sobie wybierzemy w typie anonimowym. Należy pamiętać, że zmiany na takich obiektach nie będą śledzone przez klienta i nie zostaną uwzględnione przy operacji SaveChanges().
using (var session = store.OpenSession())

{

    var bestengines = (from car in session.Query<Car>()

                       orderby car.EnginePower descending

                       select new {car.Make, car.EnginePower}).Take(5);

    var s = Enumerable.Aggregate(bestengines, "",

        (current, bestengine) => current + (bestengine.Make + ", " + bestengine.EnginePower + "\n"));

    MessageBox.Show(String.Format("Best engines: {0}", s));

}

Klient Ravena wspiera także paginację, a więc możemy pobierać dane porcjami z bazy za pomocą operatorów Skip i Take
var bestengines = (from car in session.Query<Car>()
orderby car.EnginePower descending
select new { car.Make, car.EnginePower }).Skip(5).Take(5);

wtorek, 5 marca 2013

[NoSQL] RavenDB: CRUD

W tym poście przedstawione zostaną podstawowe operacje dodawania, odczytu, usuwania i modyfikowania dokumentów składowanych w bazie RavenDB.

Dostęp do API RavenDB uzyskujemy instalując w naszym projekcie klienckie dll-ki Ravena. Najprościej zrobić to przy użyciu managera pakietów NuGet.


Podczas CRUDowych operacji korzysta się z wzorca Unit of Work, to znaczy operacje wykonywane są na obiekcie sesji, a wszystkie zmiany wędrują do serwera przy wywołaniu instrukcji SaveChanges. 

Każdą sesję otwiera obiekt typu IDocumentStore, którego inicjalizację najlepiej wykonać raz, na początku pracy programu, na przykład korzystając ze wzorca singletonu.

public class CarsStore
{
    public static DocumentStore Instance
    {
        get { return NestedClass.instance; }
    }

    public class NestedClass
    {
        static NestedClass()
        {
            var store = new DocumentStore()
                {
                    Url = "http://localhost:8080", 
                    DefaultDatabase = "RavenTest"
                };
            store.Initialize();
            instance = store;
        }

        internal static readonly DocumentStore instance;
    }
}

Dodanie pojedynczego obiektu jest bardzo proste:

var store = CarsStore.Instance;
using (IDocumentSession session = store.OpenSession())
{
    session.Store(new Car()
    {
        EnginePower = powerVal,
        Make = make.Text,
        Price = priceVal,
        Rating = ratingVal
    });
    session.SaveChanges();
}

gdzie Car jest obiektem POCO (zawierającym jedynie propercje, serializowane następnie do JSONa).
Wyniki operacji można oglądać w Management Studio RavenDB, dostępnego w przeglądarce.


Odczyt danych, odbywa się za pomocą zapytań LINQ. Do usuwania z kolecji służy specjalna metoda Delete  z obiektu sesji. Update' ować wystarczy obiekty POCO, ponieważ są one śledzone przez sesję i zmiany lądują w bazie w momencie wywołania SaveChanges.

var store = CarsStore.Instance;
using (var session = store.OpenSession())
{
    var cars = session.Query<Car>().ToArray();

    foreach (var car in cars.Where(c => c.Rating < thresholdVal))
    {
        session.Delete(car);
    }

    foreach (var car in cars.Where(c => c.Rating >= thresholdVal))
    {
        car.Price += 1000;
    }
    session.SaveChanges();
}

niedziela, 3 marca 2013

[NoSQL] RavenDB : Wprowadzenie

Bazy NoSQL, a więc takie, w których przeważnie nie ma tabel i zapytań SQL-owych, stają się w ostatnim czasie coraz częściej używane. Jedną z takich baz, napisaną w .NET jest RavenDB.  Baza ta składuje dane w postaci dokumentów zapisywanych w formacie JSON. Każdy dokument posiada unikalny identyfikator, będący jednocześnie jego adresem URL. Do dokumentów można załączać dodatkowo pliki binarne, takie jak na przykład zdjęcia.

Kilka zalet przemawiających za skorzystaniem z RavenDB, to na pewno mniejszy nakład pracy na operacje CRUD w porównaniu z bazami relacyjnymi, bardzo dobra skalowalność, szybkość dostępu do danych i możliwość tworzenia baz rozproszonych dzięki technologii shardingu. Brak schematu wprowadza sporą elastyczność. Ponadto, aby pobierać dane, nie potrzebujemy żadnych narzędzi ORM, zapytania pisane są w specjalnej wersji LINQ.

Po stronie serwerowej szybki dostęp do danych uzyskiwany jest dzięki mechanizmowi indeksów. Indeksy w RavenDB tworzone są w tle, nie wpływają więc na szybkość pojedynczej informacji.

Aby rozpocząć pracę z RavenDB, wystarczy pobrać pliki z tej strony, Po rozpakowaniu folderu, do uruchomienia serwera RavenDB, wystarczy uruchomić plik Start.cmd. Jeżeli wszystko pójdzie po naszej myśli, to serwer zostanie uruchomiony na porcie 8080. Oczywiście pod warunkiem, że port ten jest zwolniony w chwili uruchomienia serwera RavenDB.

Wpisując w przeglądarce nazwę komputera, na którym pracuje serwer RavenDB i odpowiedni port, uzyskujemy dostęp do narzędzi służących do zarządzania bazą, gdzie mamy możliwość modyfikacji danych, oglądania statystyk, przeglądania logów itd.