Warning: strpos() [function.strpos]: Empty needle in /home/dealer/kasat/pkg/cahir/web/html/lib/plugins/translation2/action.php on line 53

Warning: Cannot modify header information - headers already sent by (output started at /home/dealer/kasat/pkg/cahir/web/html/lib/plugins/translation2/action.php:53) in /home/dealer/kasat/pkg/cahir/web/html/inc/actions.php on line 163
erlang:database:qlc [Krystian Bacławski Wiki]
 

QLC - Query List Comprehensions

Zapytania QLC

Wprowadzenie

QLC jest językiem zapytań bazującym na możliwościach konstruktorów list. Oczywiście QLC może używać tabel w generatorach, a dodatkowo wpływać na prezentację i optymalizację wyników (sortowanie, unikalność, pamięć podręczna zapytań).

Moduł qlc dostarcza następujących narzędzi:

  • tworzenie tabel, które można używać w zapytaniach QLC,
  • konstrukcja uchwytów do zapytań,
  • wykonywanie zapytań,
  • kursory.

Ponieważ konstruktory list bazują na teorii zbiorów, w swej konstrukcji przypominają mocno język SQL.

Uchwyty zapytań

Uchwyty zapytań są pewnego rodzaju iteratorami po zbiorach. Wszystkie erlangowe bazy danych dostarczają takie uchwyty przy pomocy funkcji table/1 lub table/2.

ets:table(Tab, [Option]) -> QueryHandle
dets:table(Name [Option]) -> QueryHandle
 
Option = {n_objects, Num} | {traverse, first_next | last_prev | select | {select, MatchSpec}}

Opcja:

  • n_objects – ile obiektów na raz będzie zwracało wywołanie ets:select/3 i ets:select/1 (domyślnie 100),
  • traverse – sposób przeglądania tabeli:
    • first_next – trawersowanie od pierwszego,
    • last_prev – trawersowanie od ostatniego,
    • select – jeśli to możliwe QLC próbuje skonstruować samo zapytanie dopasowujące i nakarmić nim ets:select/3,
    • {select, MatchSpec} – podajemy sami zapytanie dopasowujące użyte do nakarmienia etc:select/3.

W przypadku konstrukcji uchwytów do baz DETS jest tak samo, poza tym, że nie ma trawersowania last_prev.

Uchwyt bez opcji

> qlc:e( ets:table(shop_tab) ).
 
[#shop{item = potato,quantity = 2456,cost = 1.2},
 #shop{item = orange,quantity = 100,cost = 3.8},
 #shop{item = apple,quantity = 20,cost = 2.3},
 #shop{item = pear,quantity = 200,cost = 3.6},
 #shop{item = banana,quantity = 420,cost = 4.5}]

Uchwyt z trawersowaniem w obu kierunkach

> qlc:e( ets:table(shop_tab, [ {traverse, first_next} ])).
 
[#shop{item = banana,quantity = 420,cost = 4.5},
 #shop{item = pear,quantity = 200,cost = 3.6},
 #shop{item = apple,quantity = 20,cost = 2.3},
 #shop{item = orange,quantity = 100,cost = 3.8},
 #shop{item = potato,quantity = 2456,cost = 1.2}]
 
> qlc:e( ets:table(shop_tab, [ {traverse, last_prev} ])).
[#shop{item = banana,quantity = 420,cost = 4.5},
 #shop{item = pear,quantity = 200,cost = 3.6},
 #shop{item = apple,quantity = 20,cost = 2.3},
 #shop{item = orange,quantity = 100,cost = 3.8},
 #shop{item = potato,quantity = 2456,cost = 1.2}]

Uchwyt z zapytaniem dopasowującym

> MS = ets:fun2ms( fun({shop,X,_,Y}) when Y > 3.0 -> {X,Y} end ).
[{#shop{item = '$1',quantity = '_',cost = '$2'},
  [{'>','$2',3.0}],
  [{{'$1','$2'}}]}]
 
> qlc:e( ets:table(shop_tab, [ {traverse, {select, MS}} ])).
[{orange,3.8},{pear,3.6},{banana,4.5}]

Omówienie uchwytów do tabel Mnesia będzie później!

Złączenia tabel

Zostały zaimplementowane dwie metody szybkiego złączania dwóch tabel:

  • lookup_join – generuje wszystkie wyniki z pierwszego uchwytu T1, a następnie wyszukuje w drugim uchwycie T2 rekordów tak by T1.key == T2.key,
  • merge_join – sortuje obydwa uchwyty i odsiewa te rekordy T1 i T2, które mają różne klucze, następnie dokonuje złączenia.

Opcje zapytań QLC

  • {max_lookup, MaxLookup | false} – jeśli podana liczba wyszukiwań zostanie przekroczona, cała tabela zostanie przejrzana (i potencjalnie sprowadzona do pamięci podręcznej),
  • {cache, list | ets} lub cache – przechowuje wyniki pośrednie w pamięci podręcznej (list lub tabela ETS),
  • {join, lookup | merge | nested_loops | any } – wybiera metodę złączania tabel,
  • {lookup, false } – wyłącza możliwość wyszukiwania w tabeli i wymusza trawersowanie,
  • {unique, true} lub unique – usuwa duplikaty z odpowiedzi.

Ewaluacja

Ewaluacja zapytania przebiega następująco:

  1. odczytywanie opcji zapytania,
  2. zbieranie informacji na temat tabel,
  3. optymalizacja zapytania (unikalność, sortowanie, trzymanie wyników pośrednich w pamięci podręcznej),
  4. obliczenie wartości (listy) wszystkich generatorów (jeśli użyto kursora, obliczenia odbywają się na koszt procesu kursora),
  5. sprawdzane są warunki, jeśli wszystkie przeszły wynik jest włączany do listy wyjściowej.

Należy uważać na skutki uboczne, ponieważ kolejność obliczania wyrażeń w zapytaniu QLC jest niezdefiniowana :!:

qlc:e(QueryHandleOrList, [Option]) -> Answers | {error, Module, Reason}
 
Option = {cache_all, list | ets | no} | cache_all | {unique_all, Bool} | unique_all |
         {max_list_size, MaxListSize} |
         {tmpdir_usage, allowed | not_allowed | info_msg | warning_msg | error_msg} |
         {tmpdir, TempDirectory}

Opcje:

  • {cache_all, list | ets | no} – powoduje, że wynik każdego generatora jest przechowywany odpowiednio w liście lub bazie ETS (o ile generator nie jest listą lub tabelą), domyślnie wyłączone,
  • unique_allFIXME
  • max_list_size – maksymalny rozmiar wyniku zapytania (w bajtach), po przekroczeniu cały wynik będzie przechowywana w pliku tymczasowym,
  • tmpdir_usage – określa zachowanie przy przekroczeniu rozmiaru zapytania.

Podobnie jak w przypadku zapytań dopasowujących QLC korzysta z pseudo-funkcji przyjmujących de-facto ciąg znakowy zamiast konstruktora listy. Żeby ta sztuczka z parserem działała na poziomie skompilowanego modułu należy dołączyć odpowiedni plik:

-include_lib("stdlib/include/qlc.hrl").

% SELECT * FROM shop;
 
> qlc:e( qlc:q([X || X <- ets:table(shop_tab)]) ).
 
[{shop,potato,2456,1.2},
 {shop,orange,100,3.8},
 {shop,apple,20,2.3},
 {shop,pear,200,3.6},
 {shop,banana,420,4.5}]
% SELECT item, quantity FROM shop;
 
> qlc:e( qlc:q([{X#shop.item, X#shop.quantity} || X <- ets:table(shop_tab)]) ).
 
[{potato,2456},
 {orange,100},
 {apple,20},
 {pear,200},
 {banana,420}]
% SELECT shop.item FROM shop
%  WHERE shop.quantity < 250;
 
> qlc:e( qlc:q( [X#shop.item || X <- ets:table(shop_tab), X#shop.quantity < 250]) ).
 
[orange,apple,pear]
% SELECT shop.item, shop.quantity, cost.price
%  FROM shop, cost
%  WHERE shop.item = cost.name
%   AND cost.price < 2.5
%   AND shop.quantity < 250
 
> qlc:e( qlc:q([{X#shop.item, X#shop.quantity, Y#cost.price} ||
                X <- ets:table(shop_tab), X#shop.quantity < 250,
                Y <- ets:table(cost_tab), X#shop.item =:= Y#cost.name, Y#cost.price < 2.25] )).
 
[{apple,20,1.5},{pear,200,2.2}]

Kursory

Kursory służą do częściowego odczytywania wyników jednego zapytania. Kursory QLC są zaimplementowane jako procesy.

qlc:cursor(QueryHandleOrList, [Option]]) -> QueryCursor
qlc:next_answers(QueryCursor, NumberOfAnswers) -> Answers | Error
qlc:delete_cursor(QueryCursor) -> ok

Parametry Option są te same jak w przypadku ewaluacji zapytań.

> self().
<0.156.0>
 
> QC = qlc:cursor( ets:table(shop_tab) ).
{qlc_cursor,{<0.161.0>,<0.156.0>}}
 
72> erlang:monitor(process, pid(0,161,0)).
#Ref<0.0.0.3781>
 
> qlc:next_answers(QC, 2).
[#shop{item = potato,quantity = 2456,cost = 1.2},
 #shop{item = orange,quantity = 100,cost = 3.8}]
 
> qlc:next_answers(QC, 2).
 
[#shop{item = apple,quantity = 20,cost = 2.3},
 #shop{item = pear,quantity = 200,cost = 3.6}]
 
> qlc:next_answers(QC, 2).
[#shop{item = banana,quantity = 420,cost = 4.5}]
 
> qlc:delete_cursor(QC).
ok
 
> flush().
Shell got {'DOWN',#Ref<0.0.0.3781>,process,<0.161.0>,normal}
ok

Operacje na uchwytach QLC

Scalanie

Funkcja qlc:append/1 skleja nam kilka uchwytów do tabel i pozwala nam je traktować jak jedną.

qlc:append(QHL) -> QH

Trawersowanie po scalonych tabelach, będzie trawersowało je w porządku przekazanym do funkcji qlc:append/1:

> qlc:e( qlc:q( [X || X <- qlc:append([ets:table(shop_tab), ets:table(cost_tab)]) ] )). [#shop{item = potato,quantity = 2456,cost = 1.2},
 #shop{item = orange,quantity = 100,cost = 3.8},
 #shop{item = apple,quantity = 20,cost = 2.3},
 #shop{item = pear,quantity = 200,cost = 3.6},
 #shop{item = banana,quantity = 420,cost = 4.5},
 #cost{name = potato,price = 0.6},
 #cost{name = orange,price = 2.4},
 #cost{name = apple,price = 1.5},
 #cost{name = pear,price = 2.2},
 #cost{name = banana,price = 1.5}]

Sortowanie

qlc:keysort(KeyPos, QH1 [, SortOptions]) -> QH2
qlc:sort(QH1 [, SortOptions]) ->  QH2
qlc:e( qlc:q([{X#shop.item, X#shop.quantity * (X#shop.cost - Y#cost.price)} ||
              X <- ets:table(shop_tab), Y <- ets:table(cost_tab), X#shop.item =:= Y#cost.name]) ).
 
[{potato,1473.6},
 {orange,140.0},
 {apple,16.0},
 {pear,280.0},
 {banana,1260.0}]
 
> qlc:e( qlc:sort( qlc:q([{X#shop.item, X#shop.quantity * (X#shop.cost - Y#cost.price)} ||
                          X <- ets:table(shop_tab), Y <- ets:table(cost_tab), X#shop.item =:= Y#cost.name]) ) ).
[{apple,16.0},
 {banana,1260.0},
 {orange,140.0},
 {pear,280.0},
 {potato,1473.6}]
 
> qlc:e( qlc:keysort( 2, qlc:q([{X#shop.item, X#shop.quantity * (X#shop.cost - Y#cost.price)} ||
                                X <- ets:table(shop_tab), Y <- ets:table(cost_tab), X#shop.item =:= Y#cost.name]) ) ).
[{apple,16.0},
 {orange,140.0},
 {pear,280.0},
 {banana,1260.0},
 {potato,1473.6}]

Informacje

Przy pomocy poniżej funkcji możemy zobaczyć jak jest zrealizowane zapytanie QLC od środka:

qlc:info(QueryHandleOrList, Options) -> Info

Opcje takie same jak w przypadku qlc:e/2 plus:

  • {format, abstract_code} – kod zapytania zostaje zwrócony w postaci drzewa rozbioru (domyślnie w postaci tekstu),
  • {depth, Depth}FIXME (nie działa ?)
  • {flat, true | false} – j.w.
  • {n_elements, NElements} – j.w.
> QH = qlc:q( [X#shop.item || X <- ets:table(shop_tab), X#shop.quantity < 250]).
{qlc_handle, ... }
 
> io:format("~s~n", [qlc:info(QH)] ).
qlc:q([
       case X of
           {shop,rec0,_,_} ->
               rec0;
           _ ->
               erlang:error({badrecord,shop})
       end ||
           X <- ets:table(shop_tab),
           (is_record(X, shop, 4)
            orelse
            fail)
           and
           (element(3, X) < 250)
      ])
ok

Konwersja

Możemy zapytanie w postaci tekstowej kazać sparsować Erlangowi.

qlc:string_to_handle(QueryString, Options, Bindings) ->  QueryHandle | Error

FIXME parametr Options.

Parametr Bindings to wiązania wolnych nazw występujących w zapytaniu z wartościami.

> L = [1,2,3].
> Bs = erl_eval:add_binding('L', L, erl_eval:new_bindings()).
> qlc:eval( qlc:string_to_handle("[X+1 || X <- L].", [], Bs) ).
[2,3,4]

Tabele QLC

Wprowadzenie

Tabele QLC są tak na prawdę adapterami, które prezentują dowolne kontenery w sposób taki, by dało się na nich operować jak na listach i używać w konstruktorach list.

Poniższa funkcją zwraca się pierwotny uchwyt na tabelę QLC:

qlc:table(TraverseFun, Options) -> QueryHandle

Opcje skrótowo omówimy sobie poniżej…

Trawersowanie

TraverseFun = TraverseFun0 | TraverseFun1
TraverseFun0 = fun() -> TraverseResult
TraverseFun1 = fun(MatchExpression) -> TraverseResult

Funkcje pomocnicze

  • format_fun – zwraca reprezentację zapytania (w postaci drzewa rozbioru wyrażenia lub ciągu znaków),
  • key_equality – jeden z dwóch operatorów =:=/2 lub ==/2
  • info_fun
  • lookup_fun
  • parent_fun
  • post_fun
  • pre_fun
fun(InfoTag) -> InfoValue
  • indices – lista wszystkich pozycji w tabeli (lista indeksów elementów),
  • is_unique_objects – true jeśli funkcja trawersująca zwraca nie powtarzające się obiekty,
  • keypos – pozycja klucza w krotce,
  • is_sorted_key – true jeśli obiekty w tabeli są posortowane po kluczu,
  • num_of_objects – ilość obiektów w tabeli.

Tabela bazująca na gb_trees

Przykład konstrukcji widoku tabeli na strukturę drzewa zbalansowanego

-module(gb_table).
-export([table/1]).
 
table(T) ->
    TF = fun() -> qlc_next(gb_trees:next(gb_trees:iterator(T))) end,
    InfoFun = fun(num_of_objects) -> gb_trees:size(T);
                 (keypos) -> 1;
                 (is_sorted_key) -> true;
                 (is_unique_objects) -> true;
                 (_) -> undefined
              end,
    LookupFun =
        fun(1, Ks) ->
                lists:flatmap(fun(K) ->
                                      case gb_trees:lookup(K, T) of
                                          {value, V} -> [{K,V}];
                                          none -> []
                                      end
                              end, Ks)
        end,
    FormatFun =
        fun({all, NElements, ElementFun}) ->
                ValsS = io_lib:format("gb_trees:from_orddict(~w)",
                                      [gb_nodes(T, NElements, ElementFun)]),
                io_lib:format("gb_table:table(~s)", [ValsS]);
           ({lookup, 1, KeyValues, _NElements, ElementFun}) ->
                ValsS = io_lib:format("gb_trees:from_orddict(~w)",
                                      [gb_nodes(T, infinity, ElementFun)]),
                io_lib:format("lists:flatmap(fun(K) -> "
                              "case gb_trees:lookup(K, ~s) of "
                              "{value, V} -> [{K,V}];none -> [] end "
                              "end, ~w)",
                              [ValsS, [ElementFun(KV) || KV <- KeyValues]])
        end,
    qlc:table(TF, [{info_fun, InfoFun}, {format_fun, FormatFun},
                   {lookup_fun, LookupFun},{key_equality,'=='}]).
 
qlc_next({X, V, S}) ->
    [{X,V} | fun() -> qlc_next(gb_trees:next(S)) end];
qlc_next(none) ->
    [].
 
gb_nodes(T, infinity, ElementFun) ->
    gb_nodes(T, -1, ElementFun);
gb_nodes(T, NElements, ElementFun) ->
    gb_iter(gb_trees:iterator(T), NElements, ElementFun).
 
gb_iter(_I, 0, _EFun) ->
    '...';
gb_iter(I0, N, EFun) ->
    case gb_trees:next(I0) of
        {X, V, I} ->
            [EFun({X,V}) | gb_iter(I, N-1, EFun)];
        none ->
            []
    end.

Źródła

Moduły:

  • qlc - język zapytań bazujących na konstruktorach list
 
erlang/database/qlc.txt · Last modified: 2010/06/01 16:03 by Krystian Bacławski
 
Except where otherwise noted, content on this wiki is licensed under the following license:CC Attribution-Noncommercial-No Derivative Works 3.0 Unported
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki