Current section
Files
Jump to
Current section
Files
src/xqldb_path_table.erl
%% -------------------------------------------------------------------
%%
%% xqerl - XQuery processor
%%
%% Copyright (c) 2018-2020 Zachary N. Dean All Rights Reserved.
%%
%% This file is provided to you under the Apache License,
%% Version 2.0 (the "License"); you may not use this file
%% except in compliance with the License. You may obtain
%% a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing,
%% software distributed under the License is distributed on an
%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
%% KIND, either express or implied. See the License for the
%% specific language governing permissions and limitations
%% under the License.
%%
%% -------------------------------------------------------------------
%% Path to each document.
%% Table record :
%% {Name, {xml, VersionStamp}}
%% {Name, {item, VersionStamp, {Pos, Len}}}
%% {Name, {link, VersionStamp, FileName}}
%% {Name, {res, VersionStamp, {Pos, Len}}}
%% {Name, {json, VersionStamp, {Pos, Len}}}
%% VersionStamp is systemtime in microseconds as retreived by this process.
%% It can be set by insertion or update
%% This process is always the last thing to be updated.
-module(xqldb_path_table).
-behaviour(gen_server).
-include("xqerl_db.hrl").
-export([
init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3
]).
%% ====================================================================
%% API functions
%% ====================================================================
-export([
start_link/4,
stop/1,
uri/1,
insert/2,
lookup/2,
all/1, all/2,
delete/2,
delete_all/1,
maybe_delete_doc_ref/2
]).
% TODO put this value in the config
-define(CLOSE_TIMEOUT, 12000000).
-type state() :: #{
tab => dets:tab_name(),
next => non_neg_integer()
}.
%% Open or create a new string table server.
-spec start_link(
open | new,
DBDirectory :: file:name_all(),
TableName :: string(),
Uri :: binary()
) -> {ok, Pid :: pid()}.
start_link(new, DBDirectory, TableName, Uri) ->
gen_server:start_link(?MODULE, [new, DBDirectory, TableName, Uri], []);
start_link(open, DBDirectory, TableName, Uri) ->
gen_server:start_link(?MODULE, [open, DBDirectory, TableName, Uri], []).
%% Shutdown this server.
-spec stop(db()) -> ok | {ok, _}.
stop(#{paths := Pid}) when is_pid(Pid) ->
gen_server:stop(Pid).
%% Insert new value
-spec uri(db()) -> binary().
uri(#{paths := Pid}) when is_pid(Pid) ->
gen_server:call(Pid, uri).
%% Insert new value
-spec insert(
db(),
{
Name :: binary(),
Type :: res_type(),
{Pos :: non_neg_integer(), Size :: non_neg_integer()} | binary()
}
| {
Name :: binary(),
Type :: res_type(),
{Pos :: non_neg_integer(), Size :: non_neg_integer()},
Stamp :: integer()
}
| {Name :: binary(), xml, Stamp :: integer()}
) -> ok.
insert(#{paths := Pid}, {_, xml, _} = Value) when is_pid(Pid) ->
gen_server:call(Pid, {insert, Value});
insert(#{paths := Pid}, {_, _, _, _} = Value) when is_pid(Pid) ->
gen_server:call(Pid, {insert, Value}).
%% Delete all values with Name
-spec delete(db(), Name :: binary()) -> ok.
delete(#{paths := Pid} = DB, Name) when is_pid(Pid), is_binary(Name) ->
gen_server:call(Pid, {delete, Name, DB}).
%% Delete all values with Name
-spec delete_all(db()) -> ok.
delete_all(#{paths := Pid} = DB) ->
gen_server:call(Pid, {delete_all, DB}, 60000).
%% Returns record for a given name if any.
-spec lookup(db(), Name) ->
{xml, VersionStamp}
| {item, VersionStamp, {Pos, Len}}
| {link, VersionStamp, FileName}
| {text, VersionStamp, {Pos, Len}}
| {raw, VersionStamp, {Pos, Len}}
| []
when
VersionStamp :: integer(),
Name :: binary(),
Pos :: integer(),
Len :: integer(),
FileName :: binary().
lookup(#{paths := Pid}, Req) when is_pid(Pid) ->
gen_server:call(Pid, {lookup, Req}).
%% Returns all names in the table.
-spec all(db()) ->
[
{Name :: binary(), Type :: res_type(), VersionStamp :: integer()}
| {
Name :: binary(),
Type :: res_type(),
VersionStamp :: integer(),
{Pos :: non_neg_integer(), Size :: non_neg_integer()} | binary()
}
].
all(#{paths := Pid}) when is_pid(Pid) ->
gen_server:call(Pid, all).
%% Returns all names of a given type in the table.
-spec all(db(), Type :: res_type()) ->
[
{Name :: binary(), Type :: res_type(), VersionStamp :: integer()}
| {
Name :: binary(),
Type :: res_type(),
VersionStamp :: integer(),
{Pos :: non_neg_integer(), Size :: non_neg_integer()} | binary()
}
].
all(#{paths := Pid}, Type) when is_pid(Pid) ->
gen_server:call(Pid, {all, Type}).
%% ====================================================================
%% Internal functions
%% ====================================================================
%% Creates a new name table in the DBDirectory
%% with the name TableName. Deletes any existing DB file with the same name.
-spec new(
DBDirectory :: file:name_all(),
TableName :: string()
) -> state().
new(DBDirectory, TableName) when is_binary(DBDirectory) ->
new(binary_to_list(DBDirectory), TableName);
new(DBDirectory, TableName) ->
HeapName = filename:absname_join(DBDirectory, TableName ++ ".heap"),
% new heap
{ok, HeapFile} = dets:open_file(HeapName, [{type, set}]),
%{ok,HeapFile} = dets:open_file(HeapName, [{type, bag}]),
#{tab => HeapFile}.
%% Opens an existing name table in the DBDirectory
%% with the name TableName.
-spec open(
DBDirectory :: file:name_all(),
TableName :: string()
) -> state().
open(DBDirectory, TableName) when is_binary(DBDirectory) ->
open(binary_to_list(DBDirectory), TableName);
open(DBDirectory, TableName) ->
HeapName = filename:absname_join(DBDirectory, TableName ++ ".heap"),
{ok, HeapFile} = dets:open_file(HeapName),
#{tab => HeapFile}.
%% ====================================================================
%% Callbacks
%% ====================================================================
init([new, DBDirectory, TableName, Uri]) ->
State = new(DBDirectory, TableName),
{ok, State#{uri => Uri}};
init([open, DBDirectory, TableName, Uri]) ->
State = open(DBDirectory, TableName),
{ok, State#{uri => Uri}}.
terminate(_Reason, #{tab := HeapFile}) ->
ok = dets:close(HeapFile),
ok.
handle_call(uri, _From, #{uri := Uri} = State) ->
{reply, Uri, State, ?CLOSE_TIMEOUT};
handle_call(
{insert, {Name, xml, Timestamp}},
_From,
#{tab := HeapFile} = State
) ->
ok = dets:insert(HeapFile, {Name, {xml, Timestamp}}),
{reply, ok, State, ?CLOSE_TIMEOUT};
handle_call(
{insert, {Name, Type, Pos, Timestamp}},
_From,
#{tab := HeapFile} = State
) ->
ok = dets:insert(HeapFile, {Name, {Type, Timestamp, Pos}}),
{reply, ok, State, ?CLOSE_TIMEOUT};
handle_call(all, _From, #{tab := HeapFile} = State) ->
MatchSpec = [
{{'$1', {'$2', '$3'}}, [], [{{'$1', '$2', '$3'}}]},
{{'$1', {'$2', '$3', '$4'}}, [], [{{'$1', '$2', '$3', '$4'}}]}
],
Got = dets:select(HeapFile, MatchSpec),
{reply, lists:sort(lists:flatten(Got)), State, ?CLOSE_TIMEOUT};
handle_call({all, Type}, _From, #{tab := HeapFile} = State) ->
MatchSpec = [
{{'$1', {Type, '$2'}}, [], [{{'$1', Type, '$2'}}]},
{{'$1', {Type, '$2', '$3'}}, [], [{{'$1', Type, '$2', '$3'}}]}
],
Got = dets:select(HeapFile, MatchSpec),
{reply, lists:sort(lists:flatten(Got)), State, ?CLOSE_TIMEOUT};
handle_call(
{delete, Name, DB},
_From,
#{tab := HeapFile} = State
) ->
case dets:lookup(HeapFile, Name) of
[{Name, {xml, Sp}}] ->
_ = spawn(fun() ->
maybe_delete_doc_ref(DB, {Name, Sp})
end),
dets:delete(HeapFile, Name);
[{Name, {Type, _, PosLen}}] when Type == raw; Type == text; Type == item ->
xqldb_resource_table:delete(DB, PosLen),
dets:delete(HeapFile, Name);
% link
[{Name, _}] ->
dets:delete(HeapFile, Name);
% not found
_ ->
ok
end,
{reply, ok, State, ?CLOSE_TIMEOUT};
handle_call({delete_all, DB}, _From, #{tab := HeapFile} = State) ->
Delete = fun
({Name, {xml, Sp}}) ->
maybe_delete_doc_ref(DB, {Name, Sp}),
dets:delete(HeapFile, Name),
continue;
({Name, {Type, _, PosLen}}) when Type == raw; Type == text; Type == item ->
xqldb_resource_table:delete(DB, PosLen),
dets:delete(HeapFile, Name),
continue;
({Name, _}) ->
dets:delete(HeapFile, Name),
continue
end,
dets:traverse(HeapFile, Delete),
{reply, ok, State, ?CLOSE_TIMEOUT};
handle_call({lookup, Name}, _From, #{tab := HeapFile} = State) ->
Got =
case dets:lookup(HeapFile, Name) of
[{Name, R}] ->
R;
[] ->
[]
end,
{reply, Got, State, ?CLOSE_TIMEOUT}.
handle_cast(_Request, State) -> {noreply, State}.
handle_info(timeout, #{uri := Uri} = State) ->
_ = xqldb_db:close(Uri),
{noreply, State};
handle_info(_Request, State) ->
{noreply, State}.
code_change(_, State, _) -> {ok, State}.
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Delete procedures.
%% Can/Should be moved out into own area that makes sense.
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
maybe_delete_doc_ref(DB, DocId) ->
ok = xqldb_idx_mi:delete(DB, DocId),
_ = xqldb_query_server:delete(DB, DocId),
ok.