PageRenderTime 611ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

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