PageRenderTime 68ms CodeModel.GetById 3ms app.highlight 58ms RepoModel.GetById 1ms app.codeStats 1ms

/src/principe.erl

https://github.com/yrashk/medici
Erlang | 851 lines | 450 code | 61 blank | 340 comment | 1 complexity | c1f7b9c55e5487dc86276cc108e5c667 MD5 | raw file
  1%%% The contents of this file are subject to the Erlang Public License,
  2%%% Version 1.1, (the "License"); you may not use this file except in
  3%%% compliance with the License. You should have received a copy of the
  4%%% Erlang Public License along with this software. If not, it can be
  5%%% retrieved via the world wide web at http://www.erlang.org/.
  6%%%
  7%%% Software distributed under the License is distributed on an "AS IS"
  8%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
  9%%% the License for the specific language governing rights and limitations
 10%%% under the License.
 11%%%-------------------------------------------------------------------
 12%%% File:      principe.erl
 13%%% @author    Jim McCoy <mccoy@mad-scientist.com>
 14%%% @copyright Copyright (c) 2009, Jim McCoy.  All Rights Reserved.
 15%%%
 16%%% @doc
 17%%% A thin Erlang wrapper for the Tokyo Tyrant network database protocol.
 18%%% Requires a Tyrant server that uses the 0.91 protocol version (Tyrant
 19%%% servers of version 1.1.23 and beyond.)  
 20%%%
 21%%% Note: The Tyrant protocol is sensitive to endianness. Specifically, while
 22%%% the server will take in data in network-order it will store it internally
 23%%% in big or little endianness depending on the architecture that the Tyrant 
 24%%% server is running on.  For many use-cases this is not a problem, but if you
 25%%% plan on storing an int or float and then operating on it using the addint,
 26%%% adddouble, or script extension functions which may just grab the internal
 27%%% data directly then it is necessary to store numbers in the proper format for
 28%%% the remote database. In these cases use the put* and adddouble functions which
 29%%% take an additional parameter to specify endianness of the remote database.
 30%%% There are also versions of the misc() and misc_no_update() functions that
 31%%% specify remote database byte-order for proper encoding of integers and floats
 32%%% in those functions.
 33%%% @end
 34%%%-------------------------------------------------------------------
 35
 36-module(principe).
 37-export([connect/0, 
 38	 connect/1, 
 39	 put/3, 
 40	 put/4,
 41	 putkeep/3,
 42	 putkeep/4,
 43	 putcat/3,
 44	 putcat/4,
 45	 putshl/4,
 46	 putshl/5,
 47	 putnr/3,
 48	 putnr/4,
 49	 out/2, 
 50	 get/2, 
 51	 mget/2, 
 52	 vsiz/2, 
 53	 iterinit/1, 
 54	 iternext/1, 
 55	 fwmkeys/3,
 56	 addint/3,
 57	 adddouble/3, 
 58	 adddouble/4,
 59	 adddouble/5,
 60	 sync/1,
 61	 optimize/2,
 62	 vanish/1, 
 63	 rnum/1, 
 64	 size/1, 
 65	 stat/1,
 66	 copy/2, 
 67	 restore/3,
 68	 restore_with_check/3,
 69	 setmst/3,
 70	 misc/3,
 71	 misc/4,
 72	 misc_no_update/3,
 73	 misc_no_update/4,
 74	 ext/5]).
 75
 76%% Standard definitions
 77-define(TSERVER, "localhost").
 78-define(TPORT, 1978).
 79-define(TOPTS, [binary, {packet, 0}, {nodelay, true}, {active, true}, {keepalive, true}]).
 80-define(TIMEOUT, 5000).
 81
 82%% Tyrant protocol constants
 83-define(PUT, 16#C810).
 84-define(PUTKEEP, 16#C811).
 85-define(PUTCAT, 16#C812).
 86-define(PUTSHL, 16#C813).
 87-define(PUTNR, 16#C818).
 88-define(OUT, 16#C820).
 89-define(GET, 16#C830).
 90-define(MGET, 16#C831).
 91-define(VSIZ, 16#C838).
 92-define(ITERINIT, 16#C850).
 93-define(ITERNEXT, 16#C851).
 94-define(FWMKEYS, 16#C858).
 95-define(ADDINT, 16#C860).
 96-define(ADDDOUBLE, 16#C861).
 97-define(EXT, 16#C868).
 98-define(SYNC, 16#C870).
 99-define(OPTIMIZE, 16#C871).
100-define(VANISH, 16#C872).
101-define(COPY, 16#C873).
102-define(RESTORE, 16#C874).
103-define(SETMST, 16#C878).
104-define(RNUM, 16#C880).
105-define(SIZE, 16#C881).
106-define(STAT, 16#C888).
107-define(MISC, 16#C890).
108-define(REPL, 16#C8A0).
109
110-define(MONOULOG, 1 bsl 0).
111-define(XOLCKREC, 1 bsl 0).
112-define(XOLCKGLB, 1 bsl 1).
113
114%% Macros for function patterns that are used frequently.
115-define(T0(Code), gen_tcp:send(Socket, [<<Code:16>>])).
116-define(T1(Code), gen_tcp:send(Socket, [<<Code:16>>, <<(iolist_size(Key)):32>>, Key])).
117-define(T2(Code), gen_tcp:send(Socket, [<<Code:16>>, <<(iolist_size(Key)):32>>, <<(iolist_size(Value)):32>>, Key, Value])).
118-define(R_SUCCESS, tyrant_response(Socket, fun recv_success/2)).
119-define(R_INT32, tyrant_response(Socket, fun recv_size/2)).
120-define(R_SIZE_DATA, tyrant_response(Socket, fun recv_size_data/2)).
121-define(R_INT64, tyrant_response(Socket, fun recv_size64/2)).
122
123%%====================================================================
124%% The Tokyo Tyrant access functions
125%%====================================================================
126
127%% @spec connect() -> {ok, port()} | error()
128%%
129%% @doc 
130%% Establish a connection to the tyrant service.
131%% @end
132connect() ->
133    connect([]).
134
135%% @spec connect(ConnectProps::proplist()) -> {ok, port()} | error()
136%%
137%% @doc 
138%% Establish a connection to the tyrant service using properties in the
139%% ConnectProps proplist to determine the hostname, port number and tcp
140%% socket options for the connection.  Any missing parameters are filled
141%% in using the module defaults.
142%% @end
143connect(ConnectProps) ->
144    Hostname = proplists:get_value(hostname, ConnectProps, ?TSERVER),
145    Port = proplists:get_value(port, ConnectProps, ?TPORT),
146    Opts = proplists:get_value(connect_opts, ConnectProps),
147    case Opts of
148	undefined ->
149	    gen_tcp:connect(Hostname, Port, ?TOPTS);
150	_ ->
151	    gen_tcp:connect(Hostname, Port, Opts)
152    end.
153
154%% @spec put(Socket::port(), 
155%%           Key::key(), 
156%%           Value::value_or_num()) -> ok | error()
157%%
158%% @doc
159%% Call the Tyrant server to store a new value for the given key.
160%% @end
161put(Socket, Key, Value) when is_integer(Value), Value < 4294967296 ->
162    put(Socket, Key, <<Value:32>>);
163put(Socket, Key, Value) when is_float(Value) ->
164    put(Socket, Key, <<Value:64/float>>);
165put(Socket, Key, Value) ->
166    ?T2(?PUT),							     
167    ?R_SUCCESS.
168
169%% @spec put(Socket::port(), 
170%%           Key::key(), 
171%%           Value::value_or_num(),
172%%           endian()) -> ok | error()
173%%
174%% @doc
175%% Call the Tyrant server to store a new value for the given key, the
176%% fourth parameter determines how integer and float values are stored
177%% on the remote database.
178%% @end
179put(Socket, Key, Value, big) ->
180    put(Socket, Key, Value);
181put(Socket, Key, Value, little) when is_integer(Value) ->
182    put(Socket, Key, <<Value:32/little>>);
183put(Socket, Key, Value, little) when is_float(Value) ->
184    put(Socket, Key, <<Value:64/little-float>>);
185put(Socket, Key, Value, little) ->
186    put(Socket, Key, Value).
187
188%% @spec putkeep(Socket::port(), 
189%%               Key::key(), 
190%%               Value::value_or_num()) -> ok | error()
191%%
192%% @doc 
193%% Call the Tyrant server to put a new key/value pair into the remote 
194%% database.  Will return an error if there is already a value for the
195%% Key provided.
196%% @end
197putkeep(Socket, Key, Value) when is_integer(Value), Value < 4294967296 ->
198    putkeep(Socket, Key, <<Value:32>>);
199putkeep(Socket, Key, Value) when is_float(Value) ->
200    putkeep(Socket, Key, <<Value:64/float>>);
201putkeep(Socket, Key, Value) ->
202    ?T2(?PUTKEEP),
203    ?R_SUCCESS.
204
205%% @spec putkeep(Socket::port(), 
206%%               Key::key(), 
207%%               Value::value_or_num()
208%%               endian()) -> ok | error()
209%%
210%% @doc 
211%% Call the Tyrant server to put a new key/value pair into the remote 
212%% database.  Will return an error if there is already a value for the
213%% Key provided.  The fourth parameter determines endianness for encoding
214%% numbers on the remote database.
215%% @end
216putkeep(Socket, Key, Value, big) ->
217    putkeep(Socket, Key, Value);
218putkeep(Socket, Key, Value, little) when is_integer(Value), Value < 4294967296 ->
219    putkeep(Socket, Key, <<Value:32/little>>);
220putkeep(Socket, Key, Value, little) when is_float(Value) ->
221    putkeep(Socket, Key, <<Value:64/little-float>>);
222putkeep(Socket, Key, Value, little) ->
223    putkeep(Socket, Key, Value).
224
225%% @spec putcat(Socket::port(), 
226%%              Key::key(), 
227%%              Value::value_or_num()) -> ok | error()
228%%
229%% @doc 
230%% Concatenate a value to the end of the current value for a given key
231%% that is stored in the remote database.  If Key does not already
232%% exist in the database then this call will operate the same as put().
233%% @end
234putcat(Socket, Key, Value) when is_integer(Value), Value < 4294967296 ->
235    putcat(Socket, Key, <<Value:32>>);
236putcat(Socket, Key, Value) when is_float(Value) ->
237    putcat(Socket, Key, <<Value:64/float>>);
238putcat(Socket, Key, Value) ->
239    ?T2(?PUTCAT),
240    ?R_SUCCESS.
241
242%% @spec putcat(Socket::port(), 
243%%              Key::key(), 
244%%              Value::value_or_num(),
245%%              endian()) -> ok | error()
246%%
247%% @doc 
248%% Concatenate a value to the end of the current value for a given key
249%% that is stored in the remote database.  If Key does not already
250%% exist in the database then this call will operate the same as put(). 
251%% The last parameter determines endian encoding on the remote database.
252%% @end
253putcat(Socket, Key, Value, big) ->
254    putcat(Socket, Key, Value);
255putcat(Socket, Key, Value, little) when is_integer(Value), Value < 4294967296 ->
256    putcat(Socket, Key, <<Value:32/little>>);
257putcat(Socket, Key, Value, little) when is_float(Value) ->
258    putcat(Socket, Key, <<Value:64/little-float>>);
259putcat(Socket, Key, Value, little) ->
260    putcat(Socket, Key, Value).
261
262%% @spec putshl(Socket::port(), 
263%%              Key::key(), 
264%%              Value::value_or_num(), 
265%%              Width::integer()) -> ok | error()
266%%
267%% @doc 
268%% Concatenate a value to a given key in the remote database and shift the
269%% resulting value to the left until it is Width bytes long.
270%% @end
271putshl(Socket, Key, Value, Width) when is_integer(Value), Value < 4294967296 ->
272    putshl(Socket, Key, <<Value:32>>, Width);
273putshl(Socket, Key, Value, Width) when is_float(Value) ->
274    putshl(Socket, Key, <<Value:64/float>>, Width);
275putshl(Socket, Key, Value, Width) when is_integer(Width) ->
276    gen_tcp:send(Socket, [<<?PUTSHL:16>>, 
277			  <<(iolist_size(Key)):32>>, 
278			  <<(iolist_size(Value)):32>>, 
279			  <<Width:32>>, Key, Value]),
280    ?R_SUCCESS.
281
282%% @spec putshl(Socket::port(), 
283%%              Key::key(), 
284%%              Value::value_or_num(), 
285%%              Width::integer(),
286%%              endian()) -> ok | error()
287%%
288%% @doc 
289%% Concatenate a value to a given key in the remote database and shift the
290%% resulting value to the left until it is Width bytes long.  The last
291%% parameter determines byte-order encoding for the remote database.
292%% @end
293putshl(Socket, Key, Value, Width, big) ->
294    putshl(Socket, Key, Value, Width);
295putshl(Socket, Key, Value, Width, little) when is_integer(Value), Value < 4294967296 ->
296    putshl(Socket, Key, <<Value:32/little>>, Width);
297putshl(Socket, Key, Value, Width, little) when is_float(Value) ->
298    putshl(Socket, Key, <<Value:64/little-float>>, Width);
299putshl(Socket, Key, Value, Width, little) ->
300    putshl(Socket, Key, Value, Width).
301
302%% @spec putnr(Socket::port(), 
303%%             Key::key(), 
304%%             Value::value_or_num()) -> ok
305%%
306%% @doc 
307%% Put a key/value pair to the remote database and do not wait for a response.
308%% @end
309putnr(Socket, Key, Value) when is_integer(Value), Value < 4294967296 ->
310    putnr(Socket, Key, <<Value:32>>);
311putnr(Socket, Key, Value) when is_float(Value) ->
312    putnr(Socket, Key, <<Value:64/float>>);
313putnr(Socket, Key, Value) ->
314    ?T2(?PUTNR),
315    ok.
316
317%% @spec putnr(Socket::port(), 
318%%             Key::key(), 
319%%             Value::value_or_num(),
320%%             endian()) -> ok
321%%
322%% @doc 
323%% Put a key/value pair to the remote database and do not wait for a response.
324%% The fourth parameter will decide how numbers are encoded for the remote
325%% database.
326%% @end
327putnr(Socket, Key, Value, big) ->
328    putnr(Socket, Key, Value);
329putnr(Socket, Key, Value, little) when is_integer(Value), Value < 4294967296 ->
330    putnr(Socket, Key, <<Value:32/little>>);
331putnr(Socket, Key, Value, little) when is_float(Value) ->
332    putnr(Socket, Key, <<Value:64/little-float>>);
333putnr(Socket, Key, Value, little) ->
334    putnr(Socket, Key, Value).
335
336%% @spec out(Socket::port(), 
337%%           Key::key()) -> ok | error()
338%%
339%% @doc 
340%% Remove a key from the remote database.  Will return an error if Key is
341%% not in the database.
342%% @end
343out(Socket, Key) ->
344    ?T1(?OUT),
345    ?R_SUCCESS.
346
347%% @spec get(Socket::port(), 
348%%           Key::key()) -> binary() | error()
349%%
350%% @doc Get the value for a given key
351get(Socket, Key) ->
352    ?T1(?GET),
353    ?R_SIZE_DATA.
354
355%% @spec mget(Socket::port(),
356%%            KeyList::keylist()) -> [{Key::binary(), Value::binary()}] | error()
357%%
358%% @doc Get the values for a list of keys
359mget(Socket, KeyList) when is_list(KeyList) ->
360    gen_tcp:send(Socket, [<<?MGET:16>>, 
361			  <<(length(KeyList)):32>>, 
362			  [[<<(iolist_size(Key)):32>>, Key] || Key <- KeyList]
363			 ]),
364    tyrant_response(Socket, fun recv_count_4tuple/2).
365
366%% @spec vsiz(Socket::port(),
367%%            Key::key()) -> integer()
368%%
369%% @doc Get the size of the value for a given key.
370vsiz(Socket, Key) ->
371    ?T1(?VSIZ),
372    ?R_INT32.
373
374%% @spec iterinit(Socket::port()) -> ok | error()
375%%
376%% @doc Start iteration protocol.  WARNING: The tyrant iteration protocol has no
377%% concurrency controls whatsoever, so if multiple clients try to do iteration
378%% they will stomp all over each other!
379%% @end
380iterinit(Socket) ->
381    ?T0(?ITERINIT),
382    ?R_SUCCESS.
383
384%% @spec iternext(Socket::port()) -> Key::binary() | error()
385%%
386%% @doc Get the next key/value pair in the iteration protocol
387iternext(Socket) ->
388    ?T0(?ITERNEXT),
389    ?R_SIZE_DATA.
390
391%% @spec fwmkeys(Socket::port(),
392%%               Prefix::iolist(),
393%%               MaxKeys::integer()) -> [binary()]
394%%
395%% @doc Return a number of keys that match a given prefix.
396fwmkeys(Socket, Prefix, MaxKeys) when is_integer(MaxKeys) ->
397    gen_tcp:send(Socket, [<<?FWMKEYS:16>>, 
398			  <<(iolist_size(Prefix)):32>>, 
399			  <<MaxKeys:32>>, Prefix]),
400    tyrant_response(Socket, fun recv_count_2tuple/2).
401
402%% @spec addint(Socket::port(),
403%%              Key::key(),
404%%              Int::integer()) -> integer() | error()
405%%
406%% @doc Add an integer value to the existing value of a key, returns new value
407addint(Socket, Key, Int) when is_integer(Int) ->
408    gen_tcp:send(Socket, [<<?ADDINT:16>>, <<(iolist_size(Key)):32>>, <<Int:32>>, Key]),
409    ?R_INT32.
410
411%% @spec adddouble(Socket::port(),
412%%                 Key::key(),
413%%                 Double::float()) -> {Integral::integer(), Fractional::integer()} | error()
414%%
415%% @doc Add a float to the existing value of a key, returns new value.
416adddouble(Socket, Key, Double) when is_float(Double) ->
417    IntPart = trunc(Double),
418    FracPart = trunc((Double - IntPart) * 1.0e12),
419    gen_tcp:send(Socket, [<<?ADDDOUBLE:16>>, 
420			  <<(iolist_size(Key)):32>>,
421			  <<IntPart:64>>, 
422			  <<FracPart:64>>,
423			  Key]),
424    tyrant_response(Socket, fun recv_size64_size64/2).
425
426%% @spec adddouble(Socket::port(),
427%%                 Key::key(),
428%%                 Double::float() | IntPart::integer(),
429%%                 endian() | FracPart::integer()) -> {Integral::integer(), Fractional::integer()} | error()
430%%
431%% @doc 
432%% Add a float to the existing value of a key, returns new value. The byte-order
433%% of the remote database is specified by the last parameter.
434%% @end
435adddouble(Socket, Key, Double, big) ->
436    adddouble(Socket, Key, Double);
437adddouble(Socket, Key, Double, little) when is_float(Double) ->
438    IntPart = trunc(Double),
439    FracPart = trunc((Double - IntPart) * 1.0e12),
440    gen_tcp:send(Socket, [<<?ADDDOUBLE:16>>, 
441			  <<(iolist_size(Key)):32>>,
442			  <<IntPart:64/little>>, 
443			  <<FracPart:64/little>>,
444			  Key]),
445    tyrant_response(Socket, fun recv_size64_size64/2);
446%% Need to stuff this one in here because the arity is 4
447adddouble(Socket, Key, IntPart, FracPart) when is_integer(IntPart), is_integer(FracPart) ->
448    gen_tcp:send(Socket, [<<?ADDDOUBLE:16>>, 
449			  <<(iolist_size(Key)):32>>,
450			  <<IntPart:64>>, 
451			  <<FracPart:64>>,
452			  Key]),
453    tyrant_response(Socket, fun recv_size64_size64/2).
454
455%% @spec adddouble(Socket::port(),
456%%                 Key::key(),
457%%                 Integral::integer(),
458%%                 Fractional::integer(),
459%%                 endian()) -> {Integral::integer(), Fractional::integer()} | error()
460%%
461%% @doc 
462%% The raw adddouble function for those who need a bit more control on float adds
463%% The last parameter determines remote database byte-order.
464%% @end
465adddouble(Socket, Key, IntPart, FracPart, little) when is_integer(IntPart), is_integer(FracPart) ->
466    gen_tcp:send(Socket, [<<?ADDDOUBLE:16>>, 
467			  <<(iolist_size(Key)):32>>, 
468			  <<IntPart:64/little>>, 
469			  <<FracPart:64/little>>,
470			  Key]),
471    tyrant_response(Socket, fun recv_size64_size64/2);
472adddouble(Socket, Key, IntPart, FracPart, big) when is_integer(IntPart), is_integer(FracPart) ->
473    gen_tcp:send(Socket, [<<?ADDDOUBLE:16>>, 
474			  <<(iolist_size(Key)):32>>,
475			  <<IntPart:64>>, 
476			  <<FracPart:64>>,
477			  Key]),
478    tyrant_response(Socket, fun recv_size64_size64/2).
479
480%% @spec sync(Socket::port()) -> ok | error()
481%%
482%% @doc Call sync() on the remote database
483sync(Socket) ->
484    ?T0(?SYNC),
485    ?R_SUCCESS.
486
487%% @spec vanish(Socket::port()) -> ok | error()
488%%
489%% @doc Remove all records from the remote database.
490vanish(Socket) ->
491    ?T0(?VANISH),
492    ?R_SUCCESS.
493
494%% @spec optimize(Socket::port(),
495%%                iolist()) -> ok | error()
496%%
497%% @doc Change the remote database tuning parameters.  The second parameter
498%%      should be a list of the database tuning parameters that will be applied
499%%      at the remote end (e.g. "#bnum=1000000#opts=ld").
500optimize(Socket, Key) ->
501    ?T1(?OPTIMIZE), % Using 'Key' so that the macro binds properly...
502    ?R_SUCCESS.
503
504%% @spec rnum(Socket::port()) -> integer() | error()
505%%
506%% @doc Get the number of records in the remote database.
507rnum(Socket) ->
508    ?T0(?RNUM),
509    ?R_INT64.
510
511%% @spec size(Socket::port()) -> integer() | error()
512%%
513%% @doc Get the size in bytes of the remote database.
514size(Socket) ->
515    ?T0(?SIZE),
516    ?R_INT64.
517
518%% @spec stat(Socket::port()) -> proplist() | error()
519%%
520%% @doc Get the status string of a remote database.
521stat(Socket) ->
522    ?T0(?STAT),
523    StatString = ?R_SIZE_DATA,
524    case StatString of
525	{error, Reason} ->
526	    {error, Reason};
527	GoodStat ->
528	    stat_to_proplist(GoodStat)
529    end.
530
531stat_to_proplist(StatBin) ->
532    stat_to_proplist(string:tokens(binary_to_list(StatBin), "\n\t"), []).
533
534stat_to_proplist([], Acc) ->
535    Acc;
536stat_to_proplist([H1, H2 | T], Acc) ->
537    stat_to_proplist(T, [{list_to_atom(H1), H2} | Acc]).
538
539%% @spec copy(Socket::port(), 
540%%            iolist()) -> ok | error()
541%%
542%% @doc Make a copy of the database file of the remote database.
543copy(Socket, Key) when is_binary(Key) ->
544    ?T1(?COPY), % Using 'Key' so that the macro binds properly...
545    ?R_SUCCESS.
546
547%% @spec restore(Socket::port(), 
548%%               PathName::iolist(), 
549%%               TimeStamp::integer,
550%%               Options::) -> ok | error()
551%%
552%% @doc Restore the database to a particular point in time from the update log.
553restore(Socket, PathName, TimeStamp) ->
554    gen_tcp:send(Socket, [<<?RESTORE:16>>, 
555			  <<(iolist_size(PathName)):32>>,
556			  <<TimeStamp:64>>,
557			  <<0:32>>,
558			  PathName]),
559    ?R_SUCCESS.
560
561%% @spec restore_with_check(Socket::port(), 
562%%                          PathName::iolist(), 
563%%                          TimeStamp::integer) -> ok | error()
564%%
565%% @doc Restore the database to a particular point in time from the update log and
566%%      perform a consistency check
567%% @end
568restore_with_check(Socket, PathName, TimeStamp) ->
569    gen_tcp:send(Socket, [<<?RESTORE:16>>, 
570			  <<(iolist_size(PathName)):32>>,
571			  <<TimeStamp:64>>,
572			  <<1:32>>,
573			  PathName]),
574    ?R_SUCCESS.    
575
576%% @spec setmst(Socket::port(), 
577%%              HostName::iolist(), 
578%%              Port::integer) -> ok | error()
579%%
580%% @doc Set the replication master of a remote database server.
581setmst(Socket, HostName, Port) when is_integer(Port) ->
582    gen_tcp:send(Socket, [<<?SETMST:16>>, 
583			  <<(iolist_size(HostName)):32>>, 
584			  <<Port:32>>, HostName]),
585    ?R_SUCCESS.
586
587%% @spec repl(Socket::port(),
588%%            TimeStamp::integer(),
589%%            Sid::integer())
590%%
591%% @doc Initiate master->slave replication to the server id provided starting at a
592%%      given timestamp.
593%% repl(Socket, TimeStamp, Sid) ->
594%%     gen_tcp:send(Socket, [<<?SETMST:16>>, 
595%% 			  <<TimeStamp:64>>, 
596%% 			  <<Sid:32>>]),
597%%     ?R_SUCCESS.
598
599%% @spec misc(Socket::port(),
600%%            Func::iolist(),
601%%            Args::arglist()) -> [binary()] | error()
602%% @type arglist() = [iolist()]
603%%
604%% @doc
605%% Tyrant misc() call that writes to the update logs
606%% All database types support putlist, outlist, and getlist.
607%%    putlist -> store records, Args is list of sequential keys and values, returns []
608%%    outlist -> remove records, Args is list of keys, returns []
609%%    getlist -> retrieve records, args is list of keys, returns list of values
610%% Table database supports setindex, search, and genuid.
611%%    setindex -> set the column index, Arg is name of col and type of col data, returns success val
612%%    search -> run a search on the columns, returns list of values
613%%    genuid -> generate unique ID number, returns integer
614%% @end
615misc(Socket, Func, Args) when length(Args) > 0 ->
616    gen_tcp:send(Socket, [<<?MISC:16>>, 
617			  <<(iolist_size(Func)):32>>, <<0:32>>, 
618			  <<(length(Args)):32>>, 
619			  Func,
620			  misc_arg_encode(big, Args)
621			 ]),
622    tyrant_response(Socket, fun recv_count_2tuple/2);
623misc(Socket, Func, _Args) ->
624    gen_tcp:send(Socket, [<<?MISC:16>>, 
625			  <<(iolist_size(Func)):32>>, <<0:32>>, 
626			  <<0:32>>, 
627			  Func]),
628    tyrant_response(Socket, fun recv_count_2tuple/2).
629
630%% @spec misc(Socket::port(),
631%%            Func::iolist(),
632%%            Args::arglist(),
633%%            endian()) -> [binary()] | error()
634%% @type arglist() = [iolist()]
635%%
636%% @doc
637%% Tyrant misc() call that writes to the update logs with a specific
638%% byte-order for encoding
639%% All database types support putlist, outlist, and getlist.
640%%    putlist -> store records, Args is list of sequential keys and values, returns []
641%%    outlist -> remove records, Args is list of keys, returns []
642%%    getlist -> retrieve records, args is list of keys, returns list of values
643%% Table database supports setindex, search, and genuid.
644%%    setindex -> set the column index, Arg is name of col and type of col data, returns success val
645%%    search -> run a search on the columns, returns list of values
646%%    genuid -> generate unique ID number, returns integer
647%% @end
648misc(Socket, Func, Args, big) ->
649    misc(Socket, Func, Args);
650misc(Socket, Func, Args, little) when length(Args) > 0 ->
651    gen_tcp:send(Socket, [<<?MISC:16>>, 
652			  <<(iolist_size(Func)):32>>, <<0:32>>, 
653			  <<(length(Args)):32>>, 
654			  Func,
655			  misc_arg_encode(little, Args)
656			 ]),
657    tyrant_response(Socket, fun recv_count_2tuple/2);
658misc(Socket, Func, Args, _Endian) ->
659    misc(Socket, Func, Args).
660
661%% @spec misc_no_update(Socket::port(),
662%%                      Func::iolist(),
663%%                      Args::arglist()) -> [binary()] | error()
664%% @type arglist() = [iolist()]
665%%
666%% @doc Tyrant misc() call that does not write to the update logs
667misc_no_update(Socket, Func, Args) when length(Args) > 0 ->
668    gen_tcp:send(Socket, [<<?MISC:16>>, 
669			  <<(iolist_size(Func)):32>>, <<1:32>>, 
670			  <<(length(Args)):32>>,
671			  Func,
672			  misc_arg_encode(big, Args)
673			 ]),
674    tyrant_response(Socket, fun recv_count_2tuple/2);
675misc_no_update(Socket, Func, _Args) ->
676    gen_tcp:send(Socket, [<<?MISC:16>>, 
677			  <<(iolist_size(Func)):32>>, <<1:32>>, 
678			  <<0:32>>, 
679			  Func]),
680    tyrant_response(Socket, fun recv_count_2tuple/2).
681
682%% @spec misc_no_update(Socket::port(),
683%%                      Func::iolist(),
684%%                      Args::arglist(),
685%%                      endian()) -> [binary()] | error()
686%% @type arglist() = [iolist()]
687%%
688%% @doc Tyrant misc() call that does not write to the update logs.
689misc_no_update(Socket, Func, Args, big) ->
690    misc_no_update(Socket, Func, Args);
691misc_no_update(Socket, Func, Args, little) when length(Args) > 0 ->
692    gen_tcp:send(Socket, [<<?MISC:16>>, 
693			  <<(iolist_size(Func)):32>>, <<1:32>>, 
694			  <<(length(Args)):32>>,
695			  Func,
696			  misc_arg_encode(little, Args)
697			 ]),
698    tyrant_response(Socket, fun recv_count_2tuple/2);
699misc_no_update(Socket, Func, Args, little) ->
700    misc_no_update(Socket, Func, Args).
701
702%% Encoding helper for misc() that tries to keep integers in the
703%% proper form for the remote database.
704misc_arg_encode(Endian, ArgList) ->
705    misc_arg_encode(Endian, ArgList, []).
706
707misc_arg_encode(_Endian, [], ArgList) ->
708    lists:reverse(ArgList);
709misc_arg_encode(big, [Arg | Tail], ArgList) when is_integer(Arg), Arg < 4294967296 ->
710    misc_arg_encode(big, Tail, [[<<4:32>>, <<Arg:32>>] | ArgList]);
711misc_arg_encode(little, [Arg | Tail], ArgList) when is_integer(Arg), Arg < 4294967296 ->
712    misc_arg_encode(little, Tail, [[<<4:32>>, <<Arg:32/little>>] | ArgList]);
713misc_arg_encode(big, [Arg | Tail], ArgList) when is_float(Arg) ->
714    misc_arg_encode(big, Tail, [[<<8:32>>, <<Arg:64/float>>] | ArgList]);
715misc_arg_encode(little, [Arg | Tail], ArgList) when is_float(Arg) ->
716    misc_arg_encode(little, Tail, [[<<8:32>>, <<Arg:64/float-little>>] | ArgList]);
717misc_arg_encode(Endian, [Arg | Tail], ArgList) ->
718    misc_arg_encode(Endian, Tail, [[<<(iolist_size(Arg)):32>>, Arg] | ArgList]).
719
720%% @spec ext(Socket::port(),
721%%            Func::iolist(),
722%%            Opts::proplist(),
723%%            Key::iolist(),
724%%            Value::iolist()) -> ok | error()
725%%
726%% @doc Call a function defined by the Tyrant script language extensions.
727ext(Socket, Func, Opts, Key, Value) ->
728    %% TODO: Opts needs to be parsed.  Probably as a proplist [record_lock, global_lock, neither...]
729    gen_tcp:send(Socket, [<<?EXT:16>>, <<(iolist_size(Func)):32>>, <<Opts:32>>, 
730			  <<(iolist_size(Key)):32>>, <<(iolist_size(Value)):32>>, 
731			  Func, Key, Value]),
732    ?R_SUCCESS.
733
734%%====================================================================
735%% Handle response from the server
736%%====================================================================
737
738tyrant_response(Socket, ResponseHandler) ->
739    receive
740	{tcp, Socket, <<1:8, _Rest/binary>>} ->
741	    {error, invalid_operation};
742	{tcp, Socket, <<2:8, _Rest/binary>>} ->
743	    {error, no_host_found};
744	{tcp, Socket, <<3:8, _Rest/binary>>} ->
745	    {error, connection_refused};
746	{tcp, Socket, <<4:8, _Rest/binary>>} ->
747	    {error, send_error};
748	{tcp, Socket, <<5:8, _Rest/binary>>} ->
749	    {error, recv_error};
750	{tcp, Socket, <<6:8, _Rest/binary>>} ->
751	    {error, existing_record};
752	{tcp, Socket, <<7:8, _Rest/binary>>} ->
753	    {error, no_such_record};
754        {tcp, Socket, <<ErrorCode:8, _Rest/binary>>} when ErrorCode =/= 0 ->
755	    {error, ErrorCode};
756        {tcp_closed, Socket} -> 
757	    {error, conn_closed};
758        {tcp_error, Socket, _Reason} -> 
759	    {error, conn_error};
760        Data -> 
761	    ResponseHandler(Socket, Data)
762    after ?TIMEOUT -> 
763	    {error, timeout}
764    end.
765
766%% receive 8-bit success flag
767recv_success(_Socket, {tcp, _, <<0:8>>}) -> 
768    ok.
769 
770%% receive 8-bit success flag + 32-bit int (endianness determined by remote database)
771recv_size(_Socket, {tcp, _, <<0:8, ValSize:32>>}) ->
772    ValSize.
773 
774%% receive 8-bit success flag + 64-bit int
775recv_size64(_Socket, {tcp, _, <<0:8, ValSize:64>>}) -> 
776    ValSize.
777 
778%% receive 8-bit success flag + 64-bit int + 64-bit int
779recv_size64_size64(_Socket, {tcp, _, <<0:8, V1:64, V2:64>>}) -> 
780    {V1, V2}.
781 
782%% receive 8-bit success flag + length1 + data1
783recv_size_data(Socket, Data) ->
784    case Data of
785        {tcp, _, <<0:8, Length:32, Rest/binary>>} ->
786            {Value, <<>>} = recv_until(Socket, Rest, Length),
787            Value
788    end.
789
790%% receive 8-bit success flag + count + (length1, length2, data1, data2)*count
791recv_count_4tuple(Socket, Data) ->
792    case Data of
793        {tcp, _, <<0:8, 0:32, _Rest/binary>>} ->
794            [];
795        {tcp, _, <<0:8, RecCnt:32, Rest/binary>>} ->
796            {KVS, _} = lists:mapfoldl(
797                            fun(_N, Acc) ->
798                                <<KeySize:32, ValSize:32, Bin/binary>> = Acc,
799                                {Key, Rest1} = recv_until(Socket, Bin, KeySize),
800                                {Value, Rest2} = recv_until(Socket, Rest1, ValSize),
801                                {{Key, Value}, Rest2}
802                            end, 
803                            Rest, lists:seq(1, RecCnt)
804                        ),
805            KVS
806    end.
807
808%% receive 8-bit success flag + count + (length1, data1)*count
809recv_count_2tuple(Socket, Data) ->
810    case Data of
811        {tcp, _, <<0:8, 0:32, _Rest/binary>>} ->
812	    [];
813        {tcp, _, <<0:8, Cnt:32, Rest/binary>>} ->
814            {Keys, _} = lists:mapfoldl(
815                            fun(_N, Acc) ->
816                                <<KeySize:32, Bin/binary>> = Acc,
817                                recv_until(Socket, Bin, KeySize)
818                            end,
819                            Rest, lists:seq(1, Cnt)
820                        ),
821            Keys
822    end.
823 
824%% receive length-delimited data that may require multiple pulls from the socket
825recv_until(Socket, Bin, ReqLength) when byte_size(Bin) < ReqLength ->
826    receive
827        {tcp, Socket, Data} ->
828            Combined = <<Bin/binary, Data/binary>>,
829            recv_until(Socket, Combined, ReqLength);
830        {tcp_closed, Socket} -> 
831	    {error, conn_closed};
832	{error, closed} ->
833	    {error, conn_closed}
834    after ?TIMEOUT -> 
835	    {error, timeout}
836    end;    
837recv_until(_Socket, Bin, ReqLength) when byte_size(Bin) =:= ReqLength ->
838    {Bin, <<>>};
839recv_until(_Socket, Bin, ReqLength) when byte_size(Bin) > ReqLength ->
840    <<Required:ReqLength/binary, Rest/binary>> = Bin,
841    {Required, Rest}.
842
843
844%% Some standard types for edoc
845%%
846%% @type key() = iolist()
847%% @type value() = iolist()
848%% @type value_or_num() = iolist() | integer() | float()
849%% @type keylist() = [key()]
850%% @type error() = {error, term()}
851%% @type endian() = little | big