Warning: include_once(/var/www/html/pmwiki-2.2.86/cookbook/soap4pmwiki/soap4pmwiki.php): failed to open stream: No such file or directory in /var/www/html/fields/dbp15/local/config.php on line 4

Warning: include_once(): Failed opening '/var/www/html/pmwiki-2.2.86/cookbook/soap4pmwiki/soap4pmwiki.php' for inclusion (include_path='.:/opt/php/lib/php') in /var/www/html/fields/dbp15/local/config.php on line 4

Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/fields/dbp15/local/config.php:4) in /var/www/html/pmwiki-2.2.86/pmwiki.php on line 1250
Datenbankpraktikum SS 2015 - D - Library

Library

Rust-Library

In Anlehnung an JDBC wurde für die Kommunikation zwischen Client und Server eine Library als Kommunikationsschnittstelle eingerichtet. Dem Client werden Funktionalitäten der Library zur Verfügung gestellt, die den Verbindungsaufbau und den Datentransfer regeln, ohne dass der Client sich um die Kommunikationsprotokolle und Abläufe kümmern muss.

pub fn connect(addr: String, port: u16, usern: String, passw: String) 
    -> Result <Connection, Error>;

Beim Verbindungsaufbau erhält der Client ein Connection-Object, der die eigentliche Verbindung verwaltet und die erforderlichen Protokolle intern ausführt. Ist eine Verbindung aufgebaut und ein valides Connection-Object geliefert worden, können an diesem Object grundlegende Funktionen aufgerufen werden:

  • ping(): Senden einer Ping-Nachricht an den Server, um den Verbindungsaufbau zu testen.
  • quit(): Schließen der Verbindung zu dem Server.
  • execute(String: query): Absenden einer Query-Anfrage an den Server und Erhalt der Ergebnisse dieser Anfrage.
  • Aufruf von Metainformationen der verbundenen Datenbank:
    • get_version(): Abfragen der Versionsnummer des Servers.
    • get_message(): Abfragen der Begrüßungsnachricht des Servers.
  • Client-Informationen, die für den Verbindungsaufbau verwendet wurden:
    • get_ip(): Abfragen der verwendeten IP-Addresse.
    • get_port(): Abfragen der verwendeten Portnummer.
    • get_username(): Abfragen des verwendeten Username.

DataSet

Nach einer erfolgreich bearbeiteten Query sendet der Server die gesammelten Daten in einem ResultSet an den Client. Der Aufbau des ResultSets ist simpel: Vector von Columns und ein Vector von Bytes, der die eigentlich angefragten Daten enthält. Die Library führt ein Preprocessing mit den Daten durch, bevor diese an den Client in Form eines DataSets weitergeben werden.

pub struct ResultSet {
    pub data: Vec<u8>,         // requested query data
    pub columns: Vec<Column>,  // column metadata
}

Das Preprocessing ordnet die Bytes den zugehörigen Spalten zu, und speichert diese in einem Vector von Vectoren zu. Die jeweiligen Größenangaben sind aus den entsprechenden Spalten-Metadaten zu entnehmen. Damit werden die Daten für den Zugriff des Clients schneller zugänglich und können bei jedem Client-Zugriff in eine menschenlesbare Form decodiert werden.

pub struct DataSet {
    data: Vec<Vec<Vec<u8>>>,   // sorted query data
    columns: Vec<Column>,      // column metadata
    current_pos : usize,       // current line position
    line_cnt: usize            // number of lines
}

Erhält der Client das DataSet-Object, ist er in der Lage, über die Daten zu iterieren und die jeweiligen Werte für eine eventuelle clientspezifische Weiterverarbeitung nutzen. Es es möglich vorwärts und rückwärts über die Daten zu iterieren oder auf den Anfang oder das Ende der Daten zu springen. Zugriff auf die einzelnen Spalten ist durch Spaltenindizes oder Spaltennamen möglich. Fehlerhafte Zugriffe liefern Optionen zurück und müssen auf Validität überprüft werden:

pub enum Option<T> {
    None,            // value invalid
    Some(T),         // value valid, e.g. Some(value)
}
DataSet-Methoden

Hier eine kurze Auflistung der implementierten Methoden von DataSet, die dem Client zur Verfügung stehen:

  • get_col_cnt() : Anzahl der erhaltenen Spalten.
  • get_col_idx(name) / get_col_name(index): Abfrage nach dem Index bzw. dem Namen einer Spalte mit einem bestimmten Namen bzw. an einer bestimmten Position.
  • Abfrage der Metainformationen der Spalten:
    • get_col_type_by_name(name) / get_col_type_by_idx(index): Abfrage nach dem Datentyp der Spalte.
    • get_is_primary_by_name(name) / get_is_primary_by_idx(index): Abfrage, ob die Spalte Primärschlüssel der Tabelle enthält.
    • get_allow_null_by_name(name) / get_allow_null_by_idx(index): Abfrage, ob die Spalte Null-Werte enthalten darf.
    • get_description_by_name(name) / get_description_by_idx(index): Abfrage nach der Beschreibung der Spalte.
  • Iteration über die Daten:
    • next() / previous(): Bewegen des Cursors zur nächsten bzw. vorherigen Datenzeile. Aufruf muss vor jedem Zugriff auf eine Datenzeile erfolgen.
    • first() / last(): Bewegen des Cursors vor die erste bzw. hinter die letzte Datenzeile.
    • next_int_by_name(name) / next_int_by_idx(index): Zugriff auf den SqlType::Int einer Spalte.
    • next_bool_by_name(name) / next_bool_by_idx(index): Zugriff auf den SqlType::Bool einer Spalte.
    • next_char_by_name(name) / next_char_by_idx(index): Zugriff auf den SqlType::Char einer Spalte.

Ruby-Library

Neben der Rust-Library entstand auch eine Ruby-Library, die ebenfalls eine Verbindung zu dem uoSQL-Server aufbauen kann. Sie stellt die bereits oben vorgestellten Funktionen zur Verfügung. Die Schwierigkeit bei der Implementierung bestand jedoch darin, dass in die im Server benutzten Encode/Decode-Methoden zum Datentransfer in Rust für die Ruby-Library nicht existieren. Somit musste der ankommende Byte-Stream Byte für Byte encodiert und interpretiert werden.

private
def receive_auth
    """ Receive authentication status """
    pkg = @tcp.recv(4)
 
    arr = pkg.bytes.to_a.pack("C*").unpack("N").first
    if arr == @@pkgtype[:accgranted]
        # do nothing
    elsif arr == @@pkgtype[:accdenied]
        @tcp.close()
        @errormessage = {errorkind: @@pkgtype[:accdenied], message: "Access denied. Close connection"}
        raise "access denied" # throw a runtime exception
    elsif arr == @@pkgtype[:error]
        read_err
        raise "Error package received." # throw a runtime exception
    else
        @errormessage = {errorkind: 1, message: "Unexpected package received."}
        raise "Unexpected package received."
    end
end

Für das Versenden der Pakette an den Serven mussten diese ebenfalls in Byte-Folgen umgewandelt werden. Somit war eine durchgehende Festlegung der Protokollstruktur für die Kommunikation mit dem Server von größter Bedeutung (vgl. Protokolldefinition). Werden die Datentypen der uoSQL-Datenbank oder die Kommunikationspakette zwischen Server und Library in Zukunft erweitert oder verändert, muss die ganze Ruby-Library den Veränderungen angepasst werden. Attach:Ruby_Library

ResultSet

In Ruby wurden die einzelnen uoSQL-Datentypen durch Klassen nachgebaut, damit eine Interpretation der erhaltenen Daten gesichert werden konnte. Nach Erhalt der Daten über das Netzwerk werden diese dem gleichen Preprocessing wie in der Rust-Library unterzogen und sind für den Client schnell zugreifbar. Der Client hat somit die Möglichkeit, über die Daten zu iterieren und auf einzelne Spalten spezifiziert durch Indizes oder Namen zuzugreifen.

ResultSet-Methoden

In Rust mussten sich die Funktionen für den Datenzugriff in ihren Signaturen unterscheiden, da Methodenüberladen in Rust nicht möglich ist. Das Ergebnis ist die Unterscheidung nach den Zugriffen der Spalten über Namen und Indizes. Der Vorteil der Programmiersprache Ruby gegenüber Rust ist der Einsatz der dynamischen Typisierung. Eine Funktion des ResultSets muss nicht während der Compilierzeit durch ihre Signatur festgelegt werden, sondern ist während der Ausführung des Programms interpretierbar. Damit reduziert sich die Menge der Funktionen um die Hälfte und erfordert eine Datentyp-Behandlung nur innerhalb der Funktion selbst.

public
def nextInt column
    """ Return the integer value of the specified column (index or name). 
        Return nil if index is out of bounds or column name is not in the
        ResultSet. """
    if column.class == Fixnum
        if column >= @metadata.get_col_cnt || column < 0
            return nil
        else
            col = @metadata.get_col_type column
            if col.class != Int
                return nil
            else
                data = @data[column][@current_line]
                if data.nil?
                    return nil
                else
                    return data.pack("C*").unpack("N").first
                end
            end
        end
    elsif column.class == String
        idx = get_col_idx column
        if idx.nil?
            return nil
        else
            return nextInt idx
        end
    end
    nil
end

Hier eine kurze Auflistung der implementierten Methoden von ResultSet, die dem Client zur Verfügung stehen:

  • get_col_cnt : Anzahl der erhaltenen Spalten.
  • get_col_idx(name) / get_col_name(index): Abfrage nach dem Index bzw. dem Namen einer Spalte mit einem bestimmten Namen bzw. an einer bestimmten Position.
  • Abfrage der Metainformationen der Spalten:
    • get_col_type(column): Abfrage nach dem Datentyp der Spalte.
    • get_col_is_primary(column): Abfrage, ob die Spalte Primärschlüssel der Tabelle enthält.
    • get_col_allow_null(column): Abfrage, ob die Spalte Null-Werte enthalten darf.
    • get_col_description(column): Abfrage nach der Beschreibung der Spalte.
  • Iteration über die Daten:
    • next() / previous(): Bewegen des Cursors zur nächsten bzw. vorherigen Datenzeile. Aufruf muss vor jedem Zugriff auf eine Datenzeile erfolgen.
    • first() / last(): Bewegen des Cursors vor die erste bzw. hinter die letzte Datenzeile.
    • nextInt(column): Zugriff auf den SqlType::Int einer Spalte.
    • nextBool(column): Zugriff auf den SqlType::Bool einer Spalte.
    • nextChar(column): Zugriff auf den SqlType::Char einer Spalte.

Autor: Elena Resch
Gruppe: Max Doll, Svantje Jung, Elena Resch


Page last modified on September 20, 2015, at 05:54 PM