PageRenderTime 53ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/src/medici/principe.erl

http://github.com/evanmiller/ChicagoBoss
Erlang | 1054 lines | 609 code | 80 blank | 365 comment | 1 complexity | bfb5407c9b43381018df31cd99f8352c 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. -include("principe.hrl").
  75. %%====================================================================
  76. %% The Tokyo Tyrant access functions
  77. %%====================================================================
  78. %% @spec connect() -> {ok, port()} | error()
  79. %%
  80. %% @doc
  81. %% Establish a connection to the tyrant service.
  82. %% @end
  83. connect() ->
  84. connect([]).
  85. %% @spec connect(ConnectProps::proplist()) -> {ok, port()} | error()
  86. %%
  87. %% @doc
  88. %% Establish a connection to the tyrant service using properties in the
  89. %% ConnectProps proplist to determine the hostname, port number and tcp
  90. %% socket options for the connection. Any missing parameters are filled
  91. %% in using the module defaults.
  92. %% @end
  93. connect(ConnectProps) ->
  94. Hostname = proplists:get_value(hostname, ConnectProps, ?TSERVER),
  95. Port = proplists:get_value(port, ConnectProps, ?TPORT),
  96. Opts = proplists:get_value(connect_opts, ConnectProps),
  97. case Opts of
  98. undefined ->
  99. gen_tcp:connect(Hostname, Port, ?TOPTS);
  100. _ ->
  101. gen_tcp:connect(Hostname, Port, Opts)
  102. end.
  103. %% @spec put(Socket::port(),
  104. %% Key::key(),
  105. %% Value::value_or_num()) -> ok | error()
  106. %%
  107. %% @doc
  108. %% Call the Tyrant server to store a new value for the given key.
  109. %% @end
  110. put(Socket, Key, Value) when is_integer(Value), Value < 4294967296 ->
  111. put(Socket, Key, <<Value:32>>);
  112. put(Socket, Key, Value) when is_float(Value) ->
  113. put(Socket, Key, <<Value:64/float>>);
  114. put(Socket, Key, Value) ->
  115. ?T2(?PUT),
  116. ?R_SUCCESS.
  117. %% @spec put(Socket::port(),
  118. %% Key::key(),
  119. %% Value::value_or_num(),
  120. %% endian()) -> ok | error()
  121. %%
  122. %% @doc
  123. %% Call the Tyrant server to store a new value for the given key, the
  124. %% fourth parameter determines how integer and float values are stored
  125. %% on the remote database.
  126. %% @end
  127. put(Socket, Key, Value, big) ->
  128. put(Socket, Key, Value);
  129. put(Socket, Key, Value, little) when is_integer(Value) ->
  130. put(Socket, Key, <<Value:32/little>>);
  131. put(Socket, Key, Value, little) when is_float(Value) ->
  132. put(Socket, Key, <<Value:64/little-float>>);
  133. put(Socket, Key, Value, little) ->
  134. put(Socket, Key, Value).
  135. %% @spec putkeep(Socket::port(),
  136. %% Key::key(),
  137. %% Value::value_or_num()) -> ok | error()
  138. %%
  139. %% @doc
  140. %% Call the Tyrant server to put a new key/value pair into the remote
  141. %% database. Will return an error if there is already a value for the
  142. %% Key provided.
  143. %% @end
  144. putkeep(Socket, Key, Value) when is_integer(Value), Value < 4294967296 ->
  145. putkeep(Socket, Key, <<Value:32>>);
  146. putkeep(Socket, Key, Value) when is_float(Value) ->
  147. putkeep(Socket, Key, <<Value:64/float>>);
  148. putkeep(Socket, Key, Value) ->
  149. ?T2(?PUTKEEP),
  150. ?R_SUCCESS.
  151. %% @spec putkeep(Socket::port(),
  152. %% Key::key(),
  153. %% Value::value_or_num()
  154. %% endian()) -> ok | error()
  155. %%
  156. %% @doc
  157. %% Call the Tyrant server to put a new key/value pair into the remote
  158. %% database. Will return an error if there is already a value for the
  159. %% Key provided. The fourth parameter determines endianness for encoding
  160. %% numbers on the remote database.
  161. %% @end
  162. putkeep(Socket, Key, Value, big) ->
  163. putkeep(Socket, Key, Value);
  164. putkeep(Socket, Key, Value, little) when is_integer(Value), Value < 4294967296 ->
  165. putkeep(Socket, Key, <<Value:32/little>>);
  166. putkeep(Socket, Key, Value, little) when is_float(Value) ->
  167. putkeep(Socket, Key, <<Value:64/little-float>>);
  168. putkeep(Socket, Key, Value, little) ->
  169. putkeep(Socket, Key, Value).
  170. %% @spec putcat(Socket::port(),
  171. %% Key::key(),
  172. %% Value::value_or_num()) -> ok | error()
  173. %%
  174. %% @doc
  175. %% Concatenate a value to the end of the current value for a given key
  176. %% that is stored in the remote database. If Key does not already
  177. %% exist in the database then this call will operate the same as put().
  178. %% @end
  179. putcat(Socket, Key, Value) when is_integer(Value), Value < 4294967296 ->
  180. putcat(Socket, Key, <<Value:32>>);
  181. putcat(Socket, Key, Value) when is_float(Value) ->
  182. putcat(Socket, Key, <<Value:64/float>>);
  183. putcat(Socket, Key, Value) ->
  184. ?T2(?PUTCAT),
  185. ?R_SUCCESS.
  186. %% @spec putcat(Socket::port(),
  187. %% Key::key(),
  188. %% Value::value_or_num(),
  189. %% endian()) -> ok | error()
  190. %%
  191. %% @doc
  192. %% Concatenate a value to the end of the current value for a given key
  193. %% that is stored in the remote database. If Key does not already
  194. %% exist in the database then this call will operate the same as put().
  195. %% The last parameter determines endian encoding on the remote database.
  196. %% @end
  197. putcat(Socket, Key, Value, big) ->
  198. putcat(Socket, Key, Value);
  199. putcat(Socket, Key, Value, little) when is_integer(Value), Value < 4294967296 ->
  200. putcat(Socket, Key, <<Value:32/little>>);
  201. putcat(Socket, Key, Value, little) when is_float(Value) ->
  202. putcat(Socket, Key, <<Value:64/little-float>>);
  203. putcat(Socket, Key, Value, little) ->
  204. putcat(Socket, Key, Value).
  205. %% @spec putshl(Socket::port(),
  206. %% Key::key(),
  207. %% Value::value_or_num(),
  208. %% Width::integer()) -> ok | error()
  209. %%
  210. %% @doc
  211. %% Concatenate a value to a given key in the remote database and shift the
  212. %% resulting value to the left until it is Width bytes long.
  213. %% @end
  214. putshl(Socket, Key, Value, Width) when is_integer(Value), Value < 4294967296 ->
  215. putshl(Socket, Key, <<Value:32>>, Width);
  216. putshl(Socket, Key, Value, Width) when is_float(Value) ->
  217. putshl(Socket, Key, <<Value:64/float>>, Width);
  218. putshl(Socket, Key, Value, Width) when is_integer(Width) ->
  219. gen_tcp:send(Socket, [<<?PUTSHL:16>>,
  220. <<(iolist_size(Key)):32>>,
  221. <<(iolist_size(Value)):32>>,
  222. <<Width:32>>, Key, Value]),
  223. ?R_SUCCESS.
  224. %% @spec putshl(Socket::port(),
  225. %% Key::key(),
  226. %% Value::value_or_num(),
  227. %% Width::integer(),
  228. %% endian()) -> ok | error()
  229. %%
  230. %% @doc
  231. %% Concatenate a value to a given key in the remote database and shift the
  232. %% resulting value to the left until it is Width bytes long. The last
  233. %% parameter determines byte-order encoding for the remote database.
  234. %% @end
  235. putshl(Socket, Key, Value, Width, big) ->
  236. putshl(Socket, Key, Value, Width);
  237. putshl(Socket, Key, Value, Width, little) when is_integer(Value), Value < 4294967296 ->
  238. putshl(Socket, Key, <<Value:32/little>>, Width);
  239. putshl(Socket, Key, Value, Width, little) when is_float(Value) ->
  240. putshl(Socket, Key, <<Value:64/little-float>>, Width);
  241. putshl(Socket, Key, Value, Width, little) ->
  242. putshl(Socket, Key, Value, Width).
  243. %% @spec putnr(Socket::port(),
  244. %% Key::key(),
  245. %% Value::value_or_num()) -> ok
  246. %%
  247. %% @doc
  248. %% Put a key/value pair to the remote database and do not wait for a response.
  249. %% @end
  250. putnr(Socket, Key, Value) when is_integer(Value), Value < 4294967296 ->
  251. putnr(Socket, Key, <<Value:32>>);
  252. putnr(Socket, Key, Value) when is_float(Value) ->
  253. putnr(Socket, Key, <<Value:64/float>>);
  254. putnr(Socket, Key, Value) ->
  255. ?T2(?PUTNR),
  256. ok.
  257. %% @spec putnr(Socket::port(),
  258. %% Key::key(),
  259. %% Value::value_or_num(),
  260. %% endian()) -> ok
  261. %%
  262. %% @doc
  263. %% Put a key/value pair to the remote database and do not wait for a response.
  264. %% The fourth parameter will decide how numbers are encoded for the remote
  265. %% database.
  266. %% @end
  267. putnr(Socket, Key, Value, big) ->
  268. putnr(Socket, Key, Value);
  269. putnr(Socket, Key, Value, little) when is_integer(Value), Value < 4294967296 ->
  270. putnr(Socket, Key, <<Value:32/little>>);
  271. putnr(Socket, Key, Value, little) when is_float(Value) ->
  272. putnr(Socket, Key, <<Value:64/little-float>>);
  273. putnr(Socket, Key, Value, little) ->
  274. putnr(Socket, Key, Value).
  275. %% @spec out(Socket::port(),
  276. %% Key::key()) -> ok | error()
  277. %%
  278. %% @doc
  279. %% Remove a key from the remote database. Will return an error if Key is
  280. %% not in the database.
  281. %% @end
  282. out(Socket, Key) ->
  283. ?T1(?OUT),
  284. ?R_SUCCESS.
  285. %% @spec get(Socket::port(),
  286. %% Key::key()) -> binary() | error()
  287. %%
  288. %% @doc Get the value for a given key
  289. get(Socket, Key) ->
  290. ?T1(?GET),
  291. ?R_SIZE_DATA.
  292. %% @spec mget(Socket::port(),
  293. %% KeyList::keylist()) -> [{Key::binary(), Value::binary()}] | error()
  294. %%
  295. %% @doc Get the values for a list of keys
  296. mget(Socket, KeyList) when is_list(KeyList) ->
  297. gen_tcp:send(Socket, [<<?MGET:16>>,
  298. <<(length(KeyList)):32>>,
  299. [[<<(iolist_size(Key)):32>>, Key] || Key <- KeyList]
  300. ]),
  301. ?R_4TUPLE.
  302. %% @spec vsiz(Socket::port(),
  303. %% Key::key()) -> integer()
  304. %%
  305. %% @doc Get the size of the value for a given key.
  306. vsiz(Socket, Key) ->
  307. ?T1(?VSIZ),
  308. ?R_INT32.
  309. %% @spec iterinit(Socket::port()) -> ok | error()
  310. %%
  311. %% @doc Start iteration protocol. WARNING: The tyrant iteration protocol has no
  312. %% concurrency controls whatsoever, so if multiple clients try to do iteration
  313. %% they will stomp all over each other!
  314. %% @end
  315. iterinit(Socket) ->
  316. ?T0(?ITERINIT),
  317. ?R_SUCCESS.
  318. %% @spec iternext(Socket::port()) -> Key::binary() | error()
  319. %%
  320. %% @doc Get the next key/value pair in the iteration protocol
  321. iternext(Socket) ->
  322. ?T0(?ITERNEXT),
  323. ?R_SIZE_DATA.
  324. %% @spec fwmkeys(Socket::port(),
  325. %% Prefix::iolist(),
  326. %% MaxKeys::integer()) -> [binary()]
  327. %%
  328. %% @doc Return a number of keys that match a given prefix.
  329. fwmkeys(Socket, Prefix, MaxKeys) when is_integer(MaxKeys) ->
  330. gen_tcp:send(Socket, [<<?FWMKEYS:16>>,
  331. <<(iolist_size(Prefix)):32>>,
  332. <<MaxKeys:32>>, Prefix]),
  333. ?R_2TUPLE.
  334. %% @spec addint(Socket::port(),
  335. %% Key::key(),
  336. %% Int::integer()) -> integer() | error()
  337. %%
  338. %% @doc Add an integer value to the existing value of a key, returns new value
  339. addint(Socket, Key, Int) when is_integer(Int) ->
  340. gen_tcp:send(Socket, [<<?ADDINT:16>>, <<(iolist_size(Key)):32>>, <<Int:32>>, Key]),
  341. ?R_INT32.
  342. %% @spec adddouble(Socket::port(),
  343. %% Key::key(),
  344. %% Double::float()) -> {Integral::integer(), Fractional::integer()} | error()
  345. %%
  346. %% @doc Add a float to the existing value of a key, returns new value.
  347. adddouble(Socket, Key, Double) when is_float(Double) ->
  348. IntPart = trunc(Double),
  349. FracPart = trunc((Double - IntPart) * 1.0e12),
  350. gen_tcp:send(Socket, [<<?ADDDOUBLE:16>>,
  351. <<(iolist_size(Key)):32>>,
  352. <<IntPart:64>>,
  353. <<FracPart:64>>,
  354. Key]),
  355. ?R_SIZE64_SIZE64.
  356. %% @spec adddouble(Socket::port(),
  357. %% Key::key(),
  358. %% Double::float() | IntPart::integer(),
  359. %% endian() | FracPart::integer()) -> {Integral::integer(), Fractional::integer()} | error()
  360. %%
  361. %% @doc
  362. %% Add a float to the existing value of a key, returns new value. The byte-order
  363. %% of the remote database is specified by the last parameter.
  364. %% @end
  365. adddouble(Socket, Key, Double, big) ->
  366. adddouble(Socket, Key, Double);
  367. adddouble(Socket, Key, Double, little) when is_float(Double) ->
  368. IntPart = trunc(Double),
  369. FracPart = trunc((Double - IntPart) * 1.0e12),
  370. gen_tcp:send(Socket, [<<?ADDDOUBLE:16>>,
  371. <<(iolist_size(Key)):32>>,
  372. <<IntPart:64/little>>,
  373. <<FracPart:64/little>>,
  374. Key]),
  375. ?R_SIZE64_SIZE64;
  376. %% Need to stuff this one in here because the arity is 4
  377. adddouble(Socket, Key, IntPart, FracPart) when is_integer(IntPart), is_integer(FracPart) ->
  378. gen_tcp:send(Socket, [<<?ADDDOUBLE:16>>,
  379. <<(iolist_size(Key)):32>>,
  380. <<IntPart:64>>,
  381. <<FracPart:64>>,
  382. Key]),
  383. ?R_SIZE64_SIZE64.
  384. %% @spec adddouble(Socket::port(),
  385. %% Key::key(),
  386. %% Integral::integer(),
  387. %% Fractional::integer(),
  388. %% endian()) -> {Integral::integer(), Fractional::integer()} | error()
  389. %%
  390. %% @doc
  391. %% The raw adddouble function for those who need a bit more control on float adds
  392. %% The last parameter determines remote database byte-order.
  393. %% @end
  394. adddouble(Socket, Key, IntPart, FracPart, little) when is_integer(IntPart), is_integer(FracPart) ->
  395. gen_tcp:send(Socket, [<<?ADDDOUBLE:16>>,
  396. <<(iolist_size(Key)):32>>,
  397. <<IntPart:64/little>>,
  398. <<FracPart:64/little>>,
  399. Key]),
  400. ?R_SIZE64_SIZE64;
  401. adddouble(Socket, Key, IntPart, FracPart, big) when is_integer(IntPart), is_integer(FracPart) ->
  402. gen_tcp:send(Socket, [<<?ADDDOUBLE:16>>,
  403. <<(iolist_size(Key)):32>>,
  404. <<IntPart:64>>,
  405. <<FracPart:64>>,
  406. Key]),
  407. ?R_SIZE64_SIZE64.
  408. %% @spec sync(Socket::port()) -> ok | error()
  409. %%
  410. %% @doc Call sync() on the remote database
  411. sync(Socket) ->
  412. ?T0(?SYNC),
  413. ?R_SUCCESS.
  414. %% @spec vanish(Socket::port()) -> ok | error()
  415. %%
  416. %% @doc Remove all records from the remote database.
  417. vanish(Socket) ->
  418. ?T0(?VANISH),
  419. ?R_SUCCESS.
  420. %% @spec optimize(Socket::port(),
  421. %% iolist()) -> ok | error()
  422. %%
  423. %% @doc Change the remote database tuning parameters. The second parameter
  424. %% should be a list of the database tuning parameters that will be applied
  425. %% at the remote end (e.g. "#bnum=1000000#opts=ld").
  426. %% @end
  427. optimize(Socket, Key) ->
  428. ?T1(?OPTIMIZE), % Using 'Key' so that the macro binds properly...
  429. ?R_SUCCESS.
  430. %% @spec rnum(Socket::port()) -> integer() | error()
  431. %%
  432. %% @doc Get the number of records in the remote database.
  433. rnum(Socket) ->
  434. ?T0(?RNUM),
  435. ?R_INT64.
  436. %% @spec size(Socket::port()) -> integer() | error()
  437. %%
  438. %% @doc Get the size in bytes of the remote database.
  439. size(Socket) ->
  440. ?T0(?SIZE),
  441. ?R_INT64.
  442. %% @spec stat(Socket::port()) -> proplist() | error()
  443. %%
  444. %% @doc Get the status string of a remote database.
  445. stat(Socket) ->
  446. ?T0(?STAT),
  447. StatString = ?R_SIZE_DATA,
  448. case StatString of
  449. {error, Reason} ->
  450. {error, Reason};
  451. GoodStat ->
  452. stat_to_proplist(GoodStat)
  453. end.
  454. stat_to_proplist(StatBin) ->
  455. stat_to_proplist(string:tokens(binary_to_list(StatBin), "\n\t"), []).
  456. stat_to_proplist([], Acc) ->
  457. Acc;
  458. stat_to_proplist([H1, H2 | T], Acc) ->
  459. stat_to_proplist(T, [{list_to_atom(H1), H2} | Acc]).
  460. %% @spec copy(Socket::port(),
  461. %% iolist()) -> ok | error()
  462. %%
  463. %% @doc Make a copy of the database file of the remote database.
  464. copy(Socket, Key) when is_binary(Key) ->
  465. ?T1(?COPY), % Using 'Key' so that the macro binds properly...
  466. ?R_SUCCESS.
  467. %% @spec restore(Socket::port(),
  468. %% PathName::iolist(),
  469. %% TimeStamp::integer,
  470. %% Options::) -> ok | error()
  471. %%
  472. %% @doc Restore the database to a particular point in time from the update log.
  473. restore(Socket, PathName, TimeStamp) ->
  474. gen_tcp:send(Socket, [<<?RESTORE:16>>,
  475. <<(iolist_size(PathName)):32>>,
  476. <<TimeStamp:64>>,
  477. <<0:32>>,
  478. PathName]),
  479. ?R_SUCCESS.
  480. %% @spec restore_with_check(Socket::port(),
  481. %% PathName::iolist(),
  482. %% TimeStamp::integer) -> ok | error()
  483. %%
  484. %% @doc Restore the database to a particular point in time from the update log and
  485. %% perform a consistency check
  486. %% @end
  487. restore_with_check(Socket, PathName, TimeStamp) ->
  488. gen_tcp:send(Socket, [<<?RESTORE:16>>,
  489. <<(iolist_size(PathName)):32>>,
  490. <<TimeStamp:64>>,
  491. <<1:32>>,
  492. PathName]),
  493. ?R_SUCCESS.
  494. %% @spec setmst(Socket::port(),
  495. %% HostName::iolist(),
  496. %% Port::integer) -> ok | error()
  497. %%
  498. %% @doc Set the replication master of a remote database server.
  499. setmst(Socket, HostName, Port) when is_integer(Port) ->
  500. gen_tcp:send(Socket, [<<?SETMST:16>>,
  501. <<(iolist_size(HostName)):32>>,
  502. <<Port:32>>, HostName]),
  503. ?R_SUCCESS.
  504. %% @spec repl(Socket::port(),
  505. %% TimeStamp::integer(),
  506. %% Sid::integer())
  507. %%
  508. %% @doc Initiate master->slave replication to the server id provided starting at a
  509. %% given timestamp.
  510. %% repl(Socket, TimeStamp, Sid) ->
  511. %% gen_tcp:send(Socket, [<<?SETMST:16>>,
  512. %% <<TimeStamp:64>>,
  513. %% <<Sid:32>>]),
  514. %% ?R_SUCCESS.
  515. %% @spec misc(Socket::port(),
  516. %% Func::iolist(),
  517. %% Args::arglist()) -> [binary()] | error()
  518. %% @type arglist() = [iolist()]
  519. %%
  520. %% @doc
  521. %% Tyrant misc() call that writes to the update logs
  522. %% All database types support putlist, outlist, and getlist.
  523. %% putlist -> store records, Args is list of sequential keys and values, returns []
  524. %% outlist -> remove records, Args is list of keys, returns []
  525. %% getlist -> retrieve records, args is list of keys, returns list of values
  526. %% Table database supports setindex, search, and genuid.
  527. %% setindex -> set the column index, Arg is name of col and type of col data, returns success val
  528. %% search -> run a search on the columns, returns list of values
  529. %% genuid -> generate unique ID number, returns integer
  530. %% @end
  531. misc(Socket, Func, Args) when length(Args) > 0 ->
  532. gen_tcp:send(Socket, [<<?MISC:16>>,
  533. <<(iolist_size(Func)):32>>, <<0:32>>,
  534. <<(length(Args)):32>>,
  535. Func,
  536. misc_arg_encode(big, Args)
  537. ]),
  538. ?R_2TUPLE;
  539. misc(Socket, Func, _Args) ->
  540. gen_tcp:send(Socket, [<<?MISC:16>>,
  541. <<(iolist_size(Func)):32>>, <<0:32>>,
  542. <<0:32>>,
  543. Func]),
  544. ?R_2TUPLE.
  545. %% @spec misc(Socket::port(),
  546. %% Func::iolist(),
  547. %% Args::arglist(),
  548. %% endian()) -> [binary()] | error()
  549. %% @type arglist() = [iolist()]
  550. %%
  551. %% @doc
  552. %% Tyrant misc() call that writes to the update logs with a specific
  553. %% byte-order for encoding
  554. %% All database types support putlist, outlist, and getlist.
  555. %% putlist -> store records, Args is list of sequential keys and values, returns []
  556. %% outlist -> remove records, Args is list of keys, returns []
  557. %% getlist -> retrieve records, args is list of keys, returns list of values
  558. %% Table database supports setindex, search, and genuid.
  559. %% setindex -> set the column index, Arg is name of col and type of col data, returns success val
  560. %% search -> run a search on the columns, returns list of values
  561. %% genuid -> generate unique ID number, returns integer
  562. %% @end
  563. misc(Socket, Func, Args, big) ->
  564. misc(Socket, Func, Args);
  565. misc(Socket, Func, Args, little) when length(Args) > 0 ->
  566. gen_tcp:send(Socket, [<<?MISC:16>>,
  567. <<(iolist_size(Func)):32>>, <<0:32>>,
  568. <<(length(Args)):32>>,
  569. Func,
  570. misc_arg_encode(little, Args)
  571. ]),
  572. ?R_2TUPLE;
  573. misc(Socket, Func, Args, _Endian) ->
  574. misc(Socket, Func, Args).
  575. %% @spec misc_no_update(Socket::port(),
  576. %% Func::iolist(),
  577. %% Args::arglist()) -> [binary()] | error()
  578. %% @type arglist() = [iolist()]
  579. %%
  580. %% @doc Tyrant misc() call that does not write to the update logs
  581. misc_no_update(Socket, Func, Args) when length(Args) > 0 ->
  582. gen_tcp:send(Socket, [<<?MISC:16>>,
  583. <<(iolist_size(Func)):32>>, <<1:32>>,
  584. <<(length(Args)):32>>,
  585. Func,
  586. misc_arg_encode(big, Args)
  587. ]),
  588. ?R_2TUPLE;
  589. misc_no_update(Socket, Func, _Args) ->
  590. gen_tcp:send(Socket, [<<?MISC:16>>,
  591. <<(iolist_size(Func)):32>>, <<1:32>>,
  592. <<0:32>>,
  593. Func]),
  594. ?R_2TUPLE.
  595. %% @spec misc_no_update(Socket::port(),
  596. %% Func::iolist(),
  597. %% Args::arglist(),
  598. %% endian()) -> [binary()] | error()
  599. %% @type arglist() = [iolist()]
  600. %%
  601. %% @doc Tyrant misc() call that does not write to the update logs.
  602. misc_no_update(Socket, Func, Args, big) ->
  603. misc_no_update(Socket, Func, Args);
  604. misc_no_update(Socket, Func, Args, little) when length(Args) > 0 ->
  605. gen_tcp:send(Socket, [<<?MISC:16>>,
  606. <<(iolist_size(Func)):32>>, <<1:32>>,
  607. <<(length(Args)):32>>,
  608. Func,
  609. misc_arg_encode(little, Args)
  610. ]),
  611. ?R_2TUPLE;
  612. misc_no_update(Socket, Func, Args, little) ->
  613. misc_no_update(Socket, Func, Args).
  614. %% Encoding helper for misc() that tries to keep integers in the
  615. %% proper form for the remote database.
  616. misc_arg_encode(Endian, ArgList) ->
  617. misc_arg_encode(Endian, ArgList, []).
  618. misc_arg_encode(_Endian, [], ArgList) ->
  619. lists:reverse(ArgList);
  620. misc_arg_encode(big, [Arg | Tail], ArgList) when is_integer(Arg), Arg < 4294967296 ->
  621. misc_arg_encode(big, Tail, [[<<4:32>>, <<Arg:32>>] | ArgList]);
  622. misc_arg_encode(little, [Arg | Tail], ArgList) when is_integer(Arg), Arg < 4294967296 ->
  623. misc_arg_encode(little, Tail, [[<<4:32>>, <<Arg:32/little>>] | ArgList]);
  624. misc_arg_encode(big, [Arg | Tail], ArgList) when is_float(Arg) ->
  625. misc_arg_encode(big, Tail, [[<<8:32>>, <<Arg:64/float>>] | ArgList]);
  626. misc_arg_encode(little, [Arg | Tail], ArgList) when is_float(Arg) ->
  627. misc_arg_encode(little, Tail, [[<<8:32>>, <<Arg:64/float-little>>] | ArgList]);
  628. misc_arg_encode(Endian, [Arg | Tail], ArgList) ->
  629. misc_arg_encode(Endian, Tail, [[<<(iolist_size(Arg)):32>>, Arg] | ArgList]).
  630. %% @spec ext(Socket::port(),
  631. %% Func::iolist(),
  632. %% Opts::proplist(),
  633. %% Key::iolist(),
  634. %% Value::iolist()) -> ok | error()
  635. %%
  636. %% @doc Call a function defined by the Tyrant script language extensions.
  637. ext(Socket, Func, Opts, Key, Value) ->
  638. %% TODO: Opts needs to be parsed. Probably as a proplist [record_lock, global_lock, neither...]
  639. gen_tcp:send(Socket, [<<?EXT:16>>, <<(iolist_size(Func)):32>>, <<Opts:32>>,
  640. <<(iolist_size(Key)):32>>, <<(iolist_size(Value)):32>>,
  641. Func, Key, Value]),
  642. ?R_SIZE_DATA.
  643. %%====================================================================
  644. %% Handle response from the server
  645. %%====================================================================
  646. %% @spec (ResponseHandler::function()) -> ok | error() | term()
  647. %%
  648. %% @private Get the response from a Tyrant request, parse it, and return the
  649. %% data or error code.
  650. %% @end
  651. tyrant_response(ResponseHandler) ->
  652. receive
  653. {tcp, _, <<1:8, _Rest/binary>>} ->
  654. {error, invalid_operation};
  655. {tcp, _, <<2:8, _Rest/binary>>} ->
  656. {error, no_host_found};
  657. {tcp, _, <<3:8, _Rest/binary>>} ->
  658. {error, connection_refused};
  659. {tcp, _, <<4:8, _Rest/binary>>} ->
  660. {error, send_error};
  661. {tcp, _, <<5:8, _Rest/binary>>} ->
  662. {error, recv_error};
  663. {tcp, _, <<6:8, _Rest/binary>>} ->
  664. {error, existing_record};
  665. {tcp, _, <<7:8, _Rest/binary>>} ->
  666. {error, no_such_record};
  667. {tcp, _, <<ErrorCode:8, _Rest/binary>>} when ErrorCode =/= 0 ->
  668. {error, ErrorCode};
  669. {tcp_closed, _} ->
  670. {error, conn_closed};
  671. {tcp_error, _, _} ->
  672. {error, conn_error};
  673. Data ->
  674. ResponseHandler(Data)
  675. after ?TIMEOUT ->
  676. {error, timeout}
  677. end.
  678. %% receive 8-bit success flag
  679. recv_success({tcp, _, <<0:8>>}) ->
  680. ok;
  681. %% TODO: find out why principe_table:search enters this clause
  682. %% as table becomes large. {Update from Jim to Jim: this was probably
  683. %% due to data chunks that was recently fixed due to the bug Bhasker
  684. %% spotted -- try yanking this and checking again...}
  685. recv_success({tcp, _, _})->
  686. ok.
  687. %% receive 8-bit success flag + 32-bit int (endianness determined by remote database)
  688. recv_size({tcp, _, <<0:8, ValSize:32>>}) ->
  689. ValSize;
  690. recv_size({tcp, _, <<0:8, SmallBin/binary>>}) ->
  691. {ValSize, _Rest} = recv_until(SmallBin, 4),
  692. ValSize.
  693. %% receive 8-bit success flag + 64-bit int
  694. recv_size64({tcp, _, <<0:8, ValSize:64>>}) ->
  695. ValSize;
  696. recv_size64({tcp, _, <<0:8, SmallBin/binary>>}) ->
  697. {ValSize, _Rest} = recv_until(SmallBin, 8),
  698. ValSize.
  699. %% receive 8-bit success flag + 64-bit int + 64-bit int
  700. recv_size64_size64({tcp, _, <<0:8, V1:64, V2:64>>}) ->
  701. {V1, V2};
  702. recv_size64_size64({tcp, _, <<0:8, SmallBin/binary>>}) ->
  703. %% Did not get a full chunk of data, so get more.
  704. {V1, V2Bin} = recv_until(SmallBin, 8),
  705. {V2, _Rest} = recv_until(V2Bin, 8),
  706. {V1, V2}.
  707. %% receive 8-bit success flag + length1 + data1
  708. recv_size_data({tcp, _, <<0:8, Size:32, Data/binary>>}) when byte_size(Data) >= Size ->
  709. <<Value:Size/binary, _Rest/binary>> = Data,
  710. Value;
  711. recv_size_data({tcp, _, <<0:8, Size:32, Data/binary>>}) ->
  712. %% Have at least the size, need to pull more for the data payload.
  713. {Value, _Rest} = recv_until(Data, Size),
  714. Value;
  715. recv_size_data({tcp, _, <<0:8, SmallBin/binary>>}) ->
  716. %% Did not even get the size, pull size, then pull data.
  717. {<<Size:32>>, TailBin} = recv_until(SmallBin, 4),
  718. {Value, _Rest} = recv_until(TailBin, Size),
  719. Value.
  720. %% receive 8-bit success flag + count + (length1, length2, data1, data2)*count
  721. recv_count_4tuple({tcp, _, <<0:8, 0:32, _Rest/binary>>}) ->
  722. [];
  723. recv_count_4tuple({tcp, _, <<0:8, Cnt:32, Rest/binary>>}) ->
  724. {KeyVals, _} = lists:foldl(
  725. %% This fold should grab/process one value per iteration.
  726. fun(_IterCount, {Vals, <<KeySize:32, ValSize:32, Bin/binary>>}) ->
  727. %% We have at least the key and value sizes, so make recv_until's
  728. %% job easier and just ask it to split/pull the data elements.
  729. {Key, ValBin} = recv_until(Bin, KeySize),
  730. {Value, RestBin} = recv_until(ValBin, ValSize),
  731. {[{Key, Value}] ++ Vals, RestBin};
  732. (_IterCount, {Vals, <<SmallBin/binary>>}) ->
  733. %% Not enough in SmallBin to even get the sizes, read the key and
  734. %% value sizes then read enough to get the new data elements.
  735. {<<KeySize:32>>, ValSizeAndDataBin} = recv_until(SmallBin, 4),
  736. {<<ValSize:32>>, DataBin} = recv_until(ValSizeAndDataBin, 4),
  737. {Key, ValBin} = recv_until(DataBin, KeySize),
  738. {Value, RestBin} = recv_until(ValBin, ValSize),
  739. {[{Key, Value}] ++ Vals, RestBin}
  740. end,
  741. {[], Rest}, lists:seq(1, Cnt)
  742. ),
  743. lists:reverse(KeyVals).
  744. %% receive 8-bit success flag + count + (length1, data1)*count
  745. recv_count_2tuple({tcp, _, <<0:8, 0:32, _Rest/binary>>}) ->
  746. [];
  747. recv_count_2tuple({tcp, _, <<0:8, Cnt:32, Rest/binary>>}) ->
  748. {Data, _} = lists:foldl(
  749. %% This fold should grab/process one value per iteration.
  750. fun(_IterCount, {Vals, <<Size:32, Bin/binary>>}) ->
  751. %% We have at least the key sizes, so make recv_until's job
  752. %% easier and just ask it to split/pull the data element.
  753. {NewVal, RestBin} = recv_until(Bin, Size),
  754. {[NewVal] ++ Vals, RestBin};
  755. (_IterCount, {Vals, <<SmallBin/binary>>}) ->
  756. %% Not enough in SmallBin to even get the size, read the size then read
  757. %% enough to get the new data element.
  758. {<<Size:32>>, RestBin} = recv_until(SmallBin, 4),
  759. {NewVal, SecondRestBin} = recv_until(RestBin, Size),
  760. {[NewVal] ++ Vals, SecondRestBin}
  761. end,
  762. {[], Rest}, lists:seq(1, Cnt)
  763. ),
  764. lists:reverse(Data).
  765. %% receive length-delimited data that may require multiple pulls from the socket
  766. recv_until(Bin, ReqLength) when byte_size(Bin) < ReqLength ->
  767. receive
  768. {tcp, _, Data} ->
  769. Combined = <<Bin/binary, Data/binary>>,
  770. recv_until(Combined, ReqLength);
  771. {tcp_closed, _} ->
  772. {error, conn_closed};
  773. {error, closed} ->
  774. {error, conn_closed}
  775. after ?TIMEOUT ->
  776. {error, timeout}
  777. end;
  778. recv_until(Bin, ReqLength) ->
  779. <<Required:ReqLength/binary, Rest/binary>> = Bin,
  780. {Required, Rest}.
  781. %% Some standard types for edoc
  782. %%
  783. %% @type key() = iolist()
  784. %% @type value() = iolist()
  785. %% @type value_or_num() = iolist() | integer() | float()
  786. %% @type keylist() = [key()]
  787. %% @type error() = {error, term()}
  788. %% @type endian() = little | big
  789. %% EUnit tests
  790. %%
  791. -ifdef(EUNIT).
  792. test_setup() ->
  793. {ok, Socket} = ?MODULE:connect(),
  794. ok = ?MODULE:vanish(Socket),
  795. Socket.
  796. get_random_count() ->
  797. get_random_count(1000).
  798. get_random_count(Max) ->
  799. crypto:start(),
  800. {A1,A2,A3} = now(),
  801. random:seed(A1, A2, A3),
  802. round(Max * random:uniform()).
  803. put_get_test() ->
  804. Socket = test_setup(),
  805. ?assert(?MODULE:put(Socket, "put_get1", "testval") =:= ok),
  806. ?assert(?MODULE:put(Socket, <<"put_get2">>, <<32,145,56,0,14>>) =:= ok),
  807. ?assert(?MODULE:get(Socket, <<"put_get1">>) =:= <<"testval">>),
  808. ?assert(?MODULE:get(Socket, "put_get2") =:= <<32, 145, 56, 0, 14>>),
  809. case proplists:get_value(bigend, ?MODULE:stat(Socket)) of
  810. "0" ->
  811. ?assert(?MODULE:put(Socket, <<"put_get3">>, 42, little) =:= ok),
  812. ?assert(?MODULE:get(Socket, <<"put_get3">>) =:= <<42:32/little>>);
  813. "1" ->
  814. ?assert(?MODULE:put(Socket, <<"put_get3">>, 42, big) =:= ok),
  815. ?assert(?MODULE:get(Socket, <<"put_get3">>) =:= <<42:32>>)
  816. end.
  817. put_get_random_test() ->
  818. Socket = test_setup(),
  819. ElementCount = get_random_count(),
  820. PutVals = lists:foldl(fun(_Seq, Acc) ->
  821. KeySize = random:uniform(1024),
  822. Key = crypto:rand_bytes(KeySize),
  823. ValSize = random:uniform(65536),
  824. Val = crypto:rand_bytes(ValSize),
  825. ok = ?MODULE:put(Socket, Key, Val),
  826. [{Key, Val} | Acc]
  827. end, [], lists:seq(1, ElementCount)),
  828. lists:foreach(fun({K, V}) ->
  829. ?assert(?MODULE:get(Socket, K) =:= V)
  830. end, PutVals),
  831. ok.
  832. putkeep_test() ->
  833. Socket = test_setup(),
  834. ok = ?MODULE:put(Socket, <<"test">>, <<"foo">>),
  835. ?assert(?MODULE:get(Socket, <<"test">>) =:= <<"foo">>),
  836. ?assertMatch({error, _}, ?MODULE:putkeep(Socket, <<"test">>, <<"bar">>)),
  837. ?assert(?MODULE:get(Socket, <<"test">>) =:= <<"foo">>), % no effect if key already exists before putkeep
  838. ok = ?MODULE:putkeep(Socket, <<"another">>, <<"baz">>),
  839. ?assert(?MODULE:get(Socket, <<"another">>) =:= <<"baz">>), % puts the key if key does not exist already
  840. ok.
  841. putcat_test() ->
  842. Socket = test_setup(),
  843. ok = ?MODULE:put(Socket, <<"putcat1">>, <<"foo">>),
  844. % append "bar" to the end
  845. ok = ?MODULE:putcat(Socket, <<"putcat1">>, <<"bar">>),
  846. ?assert(?MODULE:get(Socket, <<"putcat1">>) =:= <<"foobar">>),
  847. ok.
  848. putshl_test() ->
  849. Socket = test_setup(),
  850. ok = ?MODULE:put(Socket, <<"putshl">>, <<"foo">>),
  851. % append "bar" to the end and shift to the left to retain the width of "4"
  852. ok = ?MODULE:putshl(Socket, <<"putshl">>, <<"bar">>, 4),
  853. ?assert(?MODULE:get(Socket, <<"putshl">>) =:= <<"obar">>),
  854. ok.
  855. putnr_test() ->
  856. Socket = test_setup(),
  857. ?MODULE:putnr(Socket, <<"putnr1">>, <<"no reply">>),
  858. ?assert(?MODULE:get(Socket, <<"putnr1">>) =:= <<"no reply">>),
  859. ok.
  860. out_test() ->
  861. Socket = test_setup(),
  862. ok = ?MODULE:put(Socket, <<"out1">>, <<"to remove">>),
  863. ?assert(?MODULE:get(Socket, <<"out1">>) =:= <<"to remove">>),
  864. ok = ?MODULE:out(Socket, <<"out1">>),
  865. ?assertMatch({error, _}, ?MODULE:get(Socket, <<"out1">>)),
  866. ok.
  867. mget_test() ->
  868. Socket = test_setup(),
  869. ok = ?MODULE:put(Socket, <<"mget1">>, <<"alice">>),
  870. ok = ?MODULE:put(Socket, <<"mget2">>, <<"bob">>),
  871. ok = ?MODULE:put(Socket, <<"mget3">>, <<"carol">>),
  872. ok = ?MODULE:put(Socket, <<"mget4">>, <<"trent">>),
  873. ?assert(?MODULE:mget(Socket, [<<"mget1">>, <<"mget2">>,
  874. <<"mget3">>, <<"mget4">>]) =:=
  875. [{<<"mget1">>, <<"alice">>},
  876. {<<"mget2">>, <<"bob">>},
  877. {<<"mget3">>, <<"carol">>},
  878. {<<"mget4">>, <<"trent">>}]),
  879. ok.
  880. vsiz_test() ->
  881. Socket = test_setup(),
  882. ok = ?MODULE:put(Socket, <<"vsiz1">>, <<"vsiz test">>),
  883. ?assert(?MODULE:vsiz(Socket, <<"vsiz1">>) =:= 9),
  884. ok.
  885. vanish_test() ->
  886. Socket = test_setup(),
  887. ok = ?MODULE:put(Socket, <<"vanish1">>, <<"going away">>),
  888. ok = ?MODULE:vanish(Socket),
  889. ?assertMatch({error, _}, ?MODULE:get(Socket, <<"vanish1">>)),
  890. ok.
  891. iter_test() ->
  892. Socket = test_setup(),
  893. ok = ?MODULE:put(Socket, <<"a">>, <<"first">>),
  894. ok = ?MODULE:iterinit(Socket),
  895. <<"a">> = ?MODULE:iternext(Socket), % "a" should be the first key
  896. % Now to test a bit of real iteration
  897. ok = ?MODULE:put(Socket, <<"b">>, <<"second">>),
  898. ok = ?MODULE:put(Socket, <<"c">>, <<"third">>),
  899. ok = ?MODULE:iterinit(Socket),
  900. One = ?MODULE:iternext(Socket),
  901. Two = ?MODULE:iternext(Socket),
  902. Three = ?MODULE:iternext(Socket),
  903. ?assertMatch({error, _}, ?MODULE:iternext(Socket)),
  904. ?assertMatch([<<"a">>, <<"b">>, <<"c">>], lists:sort([One, Two, Three])),
  905. ok.
  906. fwmkeys_test() ->
  907. Socket = test_setup(),
  908. ok = ?MODULE:put(Socket, <<"fwmkeys1">>, <<"1">>),
  909. ok = ?MODULE:put(Socket, <<"fwmkeys2">>, <<"2">>),
  910. ok = ?MODULE:put(Socket, <<"fwmkeys3">>, <<"3">>),
  911. ok = ?MODULE:put(Socket, <<"fwmkeys4">>, <<"4">>),
  912. Keys1 = ?MODULE:fwmkeys(Socket, <<"fwmkeys">>, 4),
  913. ?assert(length(Keys1) =:= 4),
  914. ?assert(lists:member(<<"fwmkeys1">>, Keys1)),
  915. ?assert(lists:member(<<"fwmkeys2">>, Keys1)),
  916. ?assert(lists:member(<<"fwmkeys3">>, Keys1)),
  917. ?assert(lists:member(<<"fwmkeys4">>, Keys1)),
  918. Keys2 = ?MODULE:fwmkeys(Socket, <<"fwmkeys">>, 2),
  919. ?assert(length(Keys2) =:= 2),
  920. ok.
  921. addint_test() ->
  922. Socket = test_setup(),
  923. case proplists:get_value(bigend, ?MODULE:stat(Socket)) of
  924. "0" ->
  925. ?MODULE:put(Socket, <<"addint1">>, 100, little);
  926. "1" ->
  927. ?MODULE:put(Socket, <<"addint1">>, 100)
  928. end,
  929. ?assert(?MODULE:addint(Socket, <<"addint1">>, 20) =:= 120),
  930. ok.
  931. sync_test() ->
  932. Socket = test_setup(),
  933. ok = ?MODULE:sync(Socket),
  934. ok.
  935. rnum_test() ->
  936. Socket = test_setup(),
  937. ok = ?MODULE:put(Socket, <<"rnum1">>, <<"foo">>),
  938. ok = ?MODULE:put(Socket, <<"rnum2">>, <<"foo">>),
  939. ?assert(?MODULE:rnum(Socket) =:= 2),
  940. ok = ?MODULE:vanish(Socket),
  941. ?assert(?MODULE:rnum(Socket) =:= 0),
  942. ok.
  943. size_test() ->
  944. Socket = test_setup(),
  945. OldSize = ?MODULE:size(Socket),
  946. ok = ?MODULE:put(Socket, <<"size">>, <<"foo">>),
  947. NewSize = ?MODULE:size(Socket),
  948. ?assert(NewSize > OldSize),
  949. ok.
  950. stat_test() ->
  951. Socket = test_setup(),
  952. ?MODULE:stat(Socket).
  953. optimize_test() ->
  954. Socket = test_setup(),
  955. ok = ?MODULE:optimize(Socket, "#bnum=1000000#opts=ld").
  956. misc_test() ->
  957. Socket = test_setup(),
  958. [] = ?MODULE:misc(Socket, "putlist",
  959. ["key1", "value1",
  960. "key2", "value2",
  961. "key3", "value3",
  962. "key4", "value4"]),
  963. ?assert(?MODULE:rnum(Socket) =:= 4),
  964. ?assert(?MODULE:get(Socket, "key1") =:= <<"value1">>),
  965. [] = ?MODULE:misc(Socket, "outlist",
  966. ["key1", "key2", "key3"]),
  967. ?assert(?MODULE:rnum(Socket) =:= 1),
  968. ?MODULE:put(Socket, "key5", "value5"),
  969. GetlistOut = ?MODULE:misc(Socket, "getlist", ["key4", "key5"]),
  970. ?assert(lists:all(fun (K) -> lists:member(K, GetlistOut) end,
  971. [<<"key4">>, <<"value4">>,
  972. <<"key5">>, <<"value5">>])),
  973. ok.
  974. -endif.