123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333 |
- <html>
- <head>
- <meta charset="utf-8" />
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <meta name="description" content="" />
- <meta name="author" content="Maxim Sokhatsky" />
- <title>KVS</title>
- <link rel="stylesheet" href="https://n2o.space/n2o.css" />
- </head>
- <body>
- <header>
- <a href="../index.html"><img src="https://n2o.space/img/Synrc Neo.svg"></a>
- <h1>STREAM</h1>
- </header>
- <main>
- <section>
- <h3>INTRO</h3>
- <p>The <b>kvs_stream</b> is intended to store and retrieve doubly-linked lists
- using simple key-value access to different KV databases through its backends:
- redis, mongo, mnesia, riak, kai, fs.
- The descriptor of the cursor consist mainly of two parts (reader and writer).
- Writer holds the cached value of the top or bottom of the stream.
- Reader holds the cached value of current cursor position.
- Descriptor also tracks moving direction and distances to both ends,
- which are updated during reader cursor repositioning.
- Cursor should be stored in databse.
- If there is no cursor for some data then this data is not alive yet.
- The data could be added only to list ends using writer cursor.
- The data in list could be removed only by record id.
- The list could not contain duplicates or even records with the same id.
- When you consume the stream, the data is not deleted.</p>
- <p>This module could be used to manage different kinds of lists
- including doubly-linked lists on top of any KV storage.
- The possible applications are: public and private feeds, FIFO queues,
- unread messages, chat applications, blockchain applications, etc.</p>
- <p>You can download <a style="margin-bottom:30px;" href="https://raw.githubusercontent.com/synrc/kvs/master/src/kvs_stream.erl">kvs_stream</a> as it's a self-containing module.
- <br><br>
- <img src="https://n2o.space/img/Erlang.png" width=50>
- </p>
- </section>
- <section>
- <h3>RECORDS</h3>
- <figure><figcaption>KVS CORE</figcaption><code>
- #ok { data= [] :: term() }.
- #error { data= [] :: term() }.
- #cur { id= [] :: term(),
- top= [] :: [] | integer(),
- bot= [] :: [] | integer(),
- dir= 0 :: 0 | 1,
- reader= [] :: [] | tuple(),
- writer= [] :: [] | tuple() }.
- #iter { id= [] :: [] | integer(),
- prev= [] :: [] | integer(),
- next= [] :: [] | integer()).
- </code></figure>
- <p><ul>
- <li>id — Unique key of the cursor.</li>
- <li>writer — Append writes to one of the two ends of the list.</li>
- <li>reader — Reader cursor tracks distances to edges.</li>
- <li>dir=0 — Adding to top, reading up from bottom (default).</li>
- <li>dir=1 — Adding to bottom, reading down from top.</li>
- <li>top — The top of the list.</li>
- <li>bot — The bottom of the list.</li>
- </ul></p>
- <p><ul>
- <li>id — Unique key of the record in the list.</li>
- <li>next — The next element of the list.</li>
- <li>prev — The prev element of the list.</li>
- </ul></p>
- </section>
- <section>
- <h3>EXAMPLE</h3>
- <figure><code>
- -module(test).
- -include_lib("kvs/include/kvs.hrl").
- -include_lib("kvs/include/user.hrl").
- -compile(export_all).
- check() ->
- Cur = kvs_stream:new(),
- [A,B,C,D] = [ kvs:next_id(person,1)
- || _ <- lists:seq(1,4) ],
- R = kvs_stream:save(
- kvs_stream:add(#person{id=A},
- kvs_stream:add(#person{id=B},
- kvs_stream:add(#person{id=C},
- kvs_stream:add(#person{id=D}, Cur ))))),
- X = kvs_stream:take(-1,
- kvs_stream:down(
- kvs_stream:top(R))),
- Y = kvs_stream:take(-1,
- kvs_stream:up(
- kvs_stream:bot(R))),
- X = lists:reverse(Y),
- L = length(X).
- > test:check().
- 4
- > kvs:all(cur).
- [{cur,1,{user2,1,3,[],[],[],[],[],[]},0,1,2}]
- > kvs_stream:take(-1,
- kvs_stream:top(kvs:get(cur,1))).
- [{user2,2,[],4,[],[],[],[],[]},
- {user2,4,2,3,[],[],[],[],[]},
- {user2,3,4,1,[],[],[],[],[]},
- {user2,1,3,[],[],[],[],[],[]}]
- </code></figure>
- </section>
- <section>
- <h3>API</h3>
- <p>We prepared for you sequential steps manual.
- Just enter one by one the erlang commands
- in shell in right order and check the results.</p>
- <h4>new() -> #cur{}.</h4>
- <p>Creates a KVS cursor.</p>
- <figure><code>
- > kvs_stream:new().
- {cur,1,[],0,[],[]}
- > kvs:get(id_seq,"cur").
- {ok,{id_seq,"cur",1}}
- > kvs_stream:new().
- {cur,2,[],0,[],[]}
- > kvs:get(id_seq,"cur").
- {ok,{id_seq,"cur",2}}
- </code></figure>
- <h4>save(#cur{}) -> #cur{}.</h4>
- <p>Saves the cursor to database.<?p>
- <figure><code>
- > kvs:all(cur).
- []
- > kvs_stream:save(kvs_stream:new()).
- {cur,3,[],0,[],[]}
- > kvs:all(cur).
- [{cur,3,[],0,[],[]}]
- </code></figure>
- <h4>load(Id) -> #ok{data::#cur{}} | #error{}.</h4>
- <p>Gets a cursor from database.</p>
- <figure><code>
- > S = 3, kvs_stream:load(S).
- {cur,3,[],[],0,[]}
- </code></figure>
- <h4>add(Object::tuple(),#cur{}) -> #cur{}.</h4>
- <p>Adds any object to datatabase and update writer cursor.
- Object is linked on next prev fields with existed data under cursor.
- If cursor doesn't contain top or bottom value the additional
- seek to the end is performed according to cursor direction.</p>
- <figure><code>
- > rr(kvs_stream).
- [block,container,cur,id_seq,iter,iterator,kvs,log,operation]
- > P = {user2,[],[],[],[],[],[],[],[]},
- kvs_stream:save(kvs_stream:add(P,kvs_stream:load(S))).
- #cur{id = 3,top = 1,bot = 1,dir = 0,
- reader = {user2,1,[],[],[],[],[],[],[]},
- writer = {user2,1,[],[],[],[],[],[],[]},
- left = 0,right = 0}
- </code></figure>
- <h4>prev(#cur{}) -> #cur{} | #error{}.</h4>
- <p>Moves cursor to prev. Consume data up from bottom.
- Reutrn error if lists is empty, otherwise next element or last.</p>
- <figure><code>
- > kvs_stream:prev(kvs_stream:load(S)).
- {error,not_found}
- </code></figure>
- <h4>next(#cur{}) -> #cur{} | #error{}.</h4>
- <p>Moves cursor to next. Consume data down from top.
- Reutrn error if lists is empty, otherwise next element or last.</p>
- <figure><code>
- > kvs_stream:next(kvs_stream:top(
- kvs_stream:save(kvs_stream:add(P,kvs_stream:load(S))))).
- #cur{id = 3,top = 2,bot = 1,dir = 1,
- reader = {user2,1,[],2,[],[],[],[],[]},
- writer = {user2,2,1,[],[],[],[],[],[]},
- left = 0,right = 1}
- </code></figure>
- <h4>seek(#cur{}) -> #cur{} | #error{}.</h4>
- <p>Moves cursor to begin or end of the list depending of cursor order.
- If cursor has no cached value then function returns error.</p>
- <figure><code>
- > Stream = kvs_stream:save(kvs_stream:add(P,kvs_stream:load(S))),
- kvs_stream:seek(kvs_stream:up(Stream)).
- #cur{id = 3,top = 3,bot = 1,dir = 0,
- reader = {user2,1,[],2,[],[],[],[],[]},
- writer = {user2,3,2,[],[],[],[],[],[]},
- left = 2,right = 0}
- </code></figure>
- <h4>top(#cur{}) -> #cur{} | #error{}.</h4>
- <p>Moves cursor to top of the list.</p>
- <figure><code>
- > kvs_stream:top(kvs_stream:load(S)).
- #cur{id = 3,top = 3,bot = 1,dir = 1,
- reader = {user2,3,2,[],[],[],[],[],[]},
- writer = {user2,3,2,[],[],[],[],[],[]},
- left = 2,right = 0}
- </code></figure>
- <h4>bot(#cur{}) -> #cur{} | #error{}.</h4>
- <p>Moves cursor to bottom of the list.</p>
- <figure><code>
- > kvs_stream:bot(kvs_stream:load(S)).
- #cur{id = 3,top = 3,bot = 1,dir = 0,
- reader = {user2,1,[],2,[],[],[],[],[]},
- writer = {user2,3,2,[],[],[],[],[],[]},
- left = 2,right = 0}
- </code></figure>
- <h4>take(N,#cur{}) -> list().</h4>
- <p>Trying to consume N records from stream using its current value and direction.
- Returns consumed data. Usually you seek to some position and then consume some data.</p>
- <figure><code>
- > kvs_stream:take(-1,kvs_stream:load(S)).
- [{user2,3,2,[],[],[],[],[],[]},
- {user2,2,1,3,[],[],[],[],[]},
- {user2,1,[],2,[],[],[],[],[]}]
- </code></figure>
- <h4>down(#cur{}) -> #cur{}.</h4>
- <p>Changes the cursor direction.</p>
- <h4>up(#cur{}) -> #cur{}.</h4>
- <p>Changes the cursor direction (default).</p>
- <h4>remove(Id,#cur{}) -> #cur{} | #error{}.</h4>
- <p>Removes record by id from database and unlink it from list.
- If cursor has no cached value then function returns error.
- Please do not use remove, keep your data immutable :-)</p>
- <figure><code>
- > kvs_stream:take(-1,kvs_stream:top(kvs_stream:save(
- kvs_stream:remove(Stream#cur.top-1,kvs_stream:load(S))))).
- [{user2,1,[],3,[],[],[],[],[]},
- {user2,3,1,[],[],[],[],[],[]}]
- </code></figure>
- </section>
- <section>
- <h3>CONFIG</h3>
- <p>In sys.config you should specify kvs backend and list of modules
- containing <b>metainfo/0</b> exported function.</p>
- <figure><code>
- [{kvs, [{dba, store_mnesia},
- {schema, [kvs]} ]}].
- </code></figure>
- </section>
- <section>
- <p>This module may refer to:
- <a href="http://erlang.org/doc/man/mnesia.html">mnesia</a></b>,
- <a href="kvs.htm"><b>kvs</b></a>.
- </p>
- </section>
- </main>
- <footer>
- 2005—2017 © Synrc Research Center
- </footer>
- </body>
- </html>
|