/home/runner/work/klsn/klsn/_build/test/cover/eunit/klsn_db.html

1 -module(klsn_db).
2
3 -export([
4 create_db/1
5 , create_db/2
6 , create_doc/2
7 , create_doc/3
8 , bulk_create_doc/2
9 , bulk_create_doc/3
10 , exists/2
11 , exists/3
12 , get/2
13 , get/3
14 , lookup/2
15 , lookup/3
16 , bulk_lookup/2
17 , bulk_lookup/3
18 , mango_find/2
19 , mango_find/3
20 , mango_index/2
21 , mango_index/3
22 , mango_explain/2
23 , mango_explain/3
24 , update/3
25 , update/4
26 , upsert/3
27 , upsert/4
28 , bulk_upsert/3
29 , bulk_upsert/4
30 , time_now/0
31 , new_id/0
32 , db_info/0
33 ]).
34 -export_type([
35 info/0
36 , db/0
37 , key/0
38 , payload/0
39 , value/0
40 , id/0
41 , rev/0
42 , update_function/0
43 , upsert_function/0
44 ]).
45
46 %% ------------------------------------------------------------------
47 %% Exported types
48 %% ------------------------------------------------------------------
49
50 %% Connection information used by the helper functions when talking to a
51 %% CouchDB-compatible server. Currently only the base URL is recorded.
52 -type info() :: #{
53 url := unicode:unicode_binary()
54 }.
55
56 %% Name of the database (will be url-encoded when used in a request).
57 -type db() :: unicode:unicode_binary().
58
59 %% Document key (i.e. the _id field) inside the database.
60 -type key() :: unicode:unicode_binary().
61
62 %% Document identifier returned by CouchDB after a create / update.
63 -type id() :: unicode:unicode_binary().
64
65 %% Revision string returned by CouchDB (the _rev field).
66 -type rev() :: unicode:unicode_binary().
67
68 %% JSON-serialisable map that becomes the body of a CouchDB document.
69 -type payload() :: maps:map(atom() | unicode:unicode_binary(), value()).
70
71 %% Allowed JSON values used inside a payload().
72 -type value() :: atom()
73 | unicode:unicode_binary()
74 | lists:list(value())
75 | maps:map(atom() | unicode:unicode_binary(), value())
76 .
77
78 %% Callback used by update/3,4. Receives the existing payload() and
79 %% must return the updated one.
80 -type update_function() :: fun((payload())->payload()).
81
82 %% Callback used by upsert/3,4. Receives none when the document is
83 %% missing, or {value, Payload} when it exists, and must return the new
84 %% version that will be stored.
85 -type upsert_function() :: fun((klsn:'maybe'(payload()))->payload()).
86
87 %% @doc
88 %% Create a new database named *Db* on the configured CouchDB server. If
89 %% the database already exists the call is idempotent and still returns
90 %% ok.
91 -spec create_db(db()) -> ok.
92 create_db(Db) ->
93
:-(
create_db(Db, db_info()).
94
95 %% @doc
96 %% Same as create_db/1 but allows passing a custom connection Info
97 %% record (usually produced by db_info/0).
98 -spec create_db(db(), info()) -> ok.
99 create_db(Db, Info) when is_atom(Db) ->
100
:-(
create_db(atom_to_binary(Db), Info);
101 create_db(Db0, #{url:=Url0}) ->
102
:-(
Db1 = klsn_binstr:urlencode(Db0),
103
:-(
Db = <<"/", Db1/binary>>,
104
:-(
Url = <<Url0/binary, Db/binary>>,
105
:-(
Res = httpc:request(put, {Url, []}, [], [{body_format, binary}]),
106
:-(
case Res of
107 {ok, {{_, Stat, _}, _, _}} when 200=<Stat,Stat=<299 ->
108
:-(
ok;
109 {ok, {{_, 412, _}, _, _}} ->
110
:-(
error(exists)
111 end.
112
113
114 %% @doc
115 %% Run a Mango query against Db using the provided Body.
116 %%
117 %% Body must be a JSON-serialisable map compatible with CouchDB's
118 %% /_find endpoint. Returns the list of matching documents from the
119 %% "docs" field.
120 -spec mango_find(db(), map()) -> [payload()].
121 mango_find(Db, Body) ->
122
:-(
mango_find(Db, Body, db_info()).
123
124 -spec mango_find(db(), map(), info()) -> [payload()].
125 mango_find(Db, Body, Info) when is_atom(Db) ->
126
:-(
mango_find(atom_to_binary(Db), Body, Info);
127 mango_find(Db0, Body0, #{url := Url0}) ->
128
:-(
Db1 = klsn_binstr:urlencode(Db0),
129
:-(
Path = <<"/", Db1/binary, "/_find">>,
130
:-(
Url = <<Url0/binary, Path/binary>>,
131
:-(
Payload = jsone:encode(Body0),
132
:-(
Res = httpc:request(post, {Url, [], "application/json", Payload}, [], [{body_format, binary}]),
133
:-(
case Res of
134 {ok, {{_, Stat, _}, _, Data}} when 200 =< Stat, Stat =< 299 ->
135
:-(
#{<<"docs">> := Docs} = jsone:decode(Data),
136
:-(
Docs;
137 {ok, {{_, 404, _}, _, _}} ->
138
:-(
error(not_found)
139 end.
140
141
142 %% @doc
143 %% Create a Mango index on Db using the provided Body.
144 %%
145 %% Body must be a JSON-serialisable map for the /_index endpoint.
146 %% Returns the decoded response (e.g. #{"result" := "created"|"exists", ...}).
147 -spec mango_index(db(), map()) -> map().
148 mango_index(Db, Body) ->
149
:-(
mango_index(Db, Body, db_info()).
150
151 -spec mango_index(db(), map(), info()) -> map().
152 mango_index(Db, Body, Info) when is_atom(Db) ->
153
:-(
mango_index(atom_to_binary(Db), Body, Info);
154 mango_index(Db0, Body0, #{url := Url0}) ->
155
:-(
Db1 = klsn_binstr:urlencode(Db0),
156
:-(
Path = <<"/", Db1/binary, "/_index">>,
157
:-(
Url = <<Url0/binary, Path/binary>>,
158
:-(
Payload = jsone:encode(Body0),
159
:-(
Res = httpc:request(post, {Url, [], "application/json", Payload}, [], [{body_format, binary}]),
160
:-(
case Res of
161 {ok, {{_, Stat, _}, _, Data}} when 200 =< Stat, Stat =< 299 ->
162
:-(
jsone:decode(Data);
163 {ok, {{_, 404, _}, _, _}} ->
164
:-(
error(not_found)
165 end.
166
167
168 %% @doc
169 %% Explain a Mango query plan for Db using the provided Body.
170 %%
171 %% Body matches the /_find request body. Returns the decoded explanation map.
172 -spec mango_explain(db(), map()) -> map().
173 mango_explain(Db, Body) ->
174
:-(
mango_explain(Db, Body, db_info()).
175
176 -spec mango_explain(db(), map(), info()) -> map().
177 mango_explain(Db, Body, Info) when is_atom(Db) ->
178
:-(
mango_explain(atom_to_binary(Db), Body, Info);
179 mango_explain(Db0, Body0, #{url := Url0}) ->
180
:-(
Db1 = klsn_binstr:urlencode(Db0),
181
:-(
Path = <<"/", Db1/binary, "/_explain">>,
182
:-(
Url = <<Url0/binary, Path/binary>>,
183
:-(
Payload = jsone:encode(Body0),
184
:-(
Res = httpc:request(post, {Url, [], "application/json", Payload}, [], [{body_format, binary}]),
185
:-(
case Res of
186 {ok, {{_, Stat, _}, _, Data}} when 200 =< Stat, Stat =< 299 ->
187
:-(
jsone:decode(Data);
188 {ok, {{_, 404, _}, _, _}} ->
189
:-(
error(not_found)
190 end.
191
192
193 %% @doc
194 %% Insert a new document Data into Db and return the {Id, Rev} pair
195 %% assigned by the server. Convenience wrapper that uses default *Info*.
196 -spec create_doc(db(), payload()) -> {id(), rev()}.
197 create_doc(Db, Data0) ->
198
:-(
create_doc(Db, Data0, db_info()).
199
200 %% @doc
201 %% Same as create_doc/2 but with explicit Info.
202 -spec create_doc(db(), payload(), info()) -> {id(), rev()}.
203 create_doc(Db, Data0, Info) ->
204
:-(
Data2 = remove_keys(['_rev', 'C', 'U'], Data0),
205
:-(
TimeNow = time_now(),
206
:-(
Data = Data2#{<<"U">>=>TimeNow, <<"C">>=>TimeNow},
207
:-(
post(Db, Data, Info).
208
209 -spec bulk_create_doc(db(), [payload()]) -> [klsn:'maybe'({id(), rev()})].
210 bulk_create_doc(Db, Docs) ->
211
:-(
bulk_create_doc(Db, Docs, db_info()).
212 -spec bulk_create_doc(db(), [payload()], info()) -> [klsn:'maybe'({id(), rev()})].
213
:-(
bulk_create_doc(_Db, [], _Info) -> [];
214 bulk_create_doc(Db, Docs0, #{url := Url0}) when is_list(Docs0) ->
215
:-(
TimeNow = time_now(),
216
:-(
Docs1 = lists:map(
217 fun(D0) ->
218
:-(
D1 = remove_keys(['_rev', 'C', 'U'], D0),
219
:-(
D1#{<<"U">> => TimeNow, <<"C">> => TimeNow}
220 end,
221 Docs0),
222
:-(
DbBin = klsn_binstr:urlencode(klsn_binstr:from_any(Db)),
223
:-(
Path = <<"/", DbBin/binary, "/_bulk_docs">>,
224
:-(
Url = <<Url0/binary, Path/binary>>,
225
:-(
Body = jsone:encode(#{<<"docs">> => Docs1}),
226
:-(
Res = httpc:request(post, {Url, [], "application/json", Body}, [], [{body_format, binary}]),
227
:-(
case Res of
228 {ok, {{_, Stat, _}, _, Data}} when 200 =< Stat, Stat =< 299 ->
229
:-(
Results = jsone:decode(Data),
230
:-(
lists:map(
231
:-(
fun(#{<<"ok">> := true, <<"id">> := Id, <<"rev">> := Rev}) -> {value, {Id, Rev}};
232
:-(
(_) -> none
233 end,
234 Results);
235 _ ->
236
:-(
lists:duplicate(length(Docs0), none)
237 end.
238
239 %% @doc
240 %% Fetch the document identified by Key from Db or raise error:not_found.
241 -spec get(db(), key()) -> payload().
242 get(Db, Key) ->
243
:-(
get(Db, Key, db_info()).
244
245 %% @doc
246 %% Return true when the document identified by Key exists in Db, false otherwise.
247 %% Uses HTTP HEAD against CouchDB to avoid transferring the document body.
248 -spec exists(db(), key()) -> boolean().
249 exists(Db, Key) ->
250
:-(
exists(Db, Key, db_info()).
251
252 %% @doc
253 %% Same as exists/2 but with explicit Info.
254 -spec exists(db(), key(), info()) -> boolean().
255 exists(Db, Key, Info) when is_atom(Db) ->
256
:-(
exists(atom_to_binary(Db), Key, Info);
257 exists(_, <<>>, _) ->
258
:-(
false;
259 exists(Db0, {raw, Key0}, #{url := Url0}) ->
260
:-(
Db1 = klsn_binstr:urlencode(Db0),
261
:-(
Db = <<"/", Db1/binary>>,
262
:-(
Key = <<"/", Key0/binary>>,
263
:-(
Url = <<Url0/binary, Db/binary, Key/binary>>,
264
:-(
Res = httpc:request(head, {Url, []}, [], [{body_format, binary}]),
265
:-(
case Res of
266 {ok, {{_, Stat, _}, _, _}} when 200 =< Stat, Stat =< 299 ->
267
:-(
true;
268 {ok, {{_, 404, _}, _, _}} ->
269
:-(
false;
270 {ok, {{_, Stat, _}, _, _}} ->
271
:-(
error({http_error, Stat})
272 end;
273 exists(Db0, Key0, #{url := Url0}) ->
274
:-(
Db1 = klsn_binstr:urlencode(Db0),
275
:-(
Db = <<"/", Db1/binary>>,
276
:-(
Key1 = klsn_binstr:urlencode(Key0),
277
:-(
Key = <<"/", Key1/binary>>,
278
:-(
Url = <<Url0/binary, Db/binary, Key/binary>>,
279
:-(
Res = httpc:request(head, {Url, []}, [], [{body_format, binary}]),
280
:-(
case Res of
281 {ok, {{_, Stat, _}, _, _}} when 200 =< Stat, Stat =< 299 ->
282
:-(
true;
283 {ok, {{_, 404, _}, _, _}} ->
284
:-(
false;
285 {ok, {{_, Stat, _}, _, _}} ->
286
:-(
error({http_error, Stat})
287 end.
288
289 %% @doc
290 %% Same as get/2 but with explicit Info.
291 -spec get(db(), key(), info()) -> payload().
292 get(Db, Key, Info) ->
293
:-(
case lookup(Db, Key, Info) of
294
:-(
{value, Value} -> Value;
295
:-(
none -> error(not_found)
296 end.
297
298 %% @doc
299 %% Safe variant of get/2. Returns {value, Payload} when the document
300 %% exists or none when it is missing.
301 -spec lookup(db(), key()) -> klsn:'maybe'(payload()).
302 lookup(Db, Key) ->
303
:-(
lookup(Db, Key, db_info()).
304
305 %% @doc
306 %% Same as lookup/2 but with explicit Info.
307 -spec lookup(db(), key(), info()) -> klsn:'maybe'(payload()).
308 lookup(Db, Key, Info) when is_atom(Db) ->
309
:-(
lookup(atom_to_binary(Db), Key, Info);
310 lookup(_, <<>>, _) ->
311
:-(
none;
312 lookup(Db0, {raw, Key0}, #{url:=Url0}) -> % for _design view
313
:-(
Db1 = klsn_binstr:urlencode(Db0),
314
:-(
Db = <<"/", Db1/binary>>,
315
:-(
Key = <<"/", Key0/binary>>,
316
:-(
Url = <<Url0/binary, Db/binary, Key/binary>>,
317
:-(
Res = httpc:request(get, {Url, []}, [], [{body_format, binary}]),
318
:-(
case Res of
319 {ok, {{_, Stat, _}, _, Data}} when 200=<Stat,Stat=<299->
320
:-(
{value, jsone:decode(Data)};
321 {ok, {{_, 404, _}, _, _}} ->
322
:-(
none
323 end;
324 lookup(Db0, Key0, #{url:=Url0}) ->
325
:-(
Db1 = klsn_binstr:urlencode(Db0),
326
:-(
Db = <<"/", Db1/binary>>,
327
:-(
Key1 = klsn_binstr:urlencode(Key0),
328
:-(
Key = <<"/", Key1/binary>>,
329
:-(
Url = <<Url0/binary, Db/binary, Key/binary>>,
330
:-(
Res = httpc:request(get, {Url, []}, [], [{body_format, binary}]),
331
:-(
case Res of
332 {ok, {{_, Stat, _}, _, Data}} when 200=<Stat,Stat=<299->
333
:-(
{value, jsone:decode(Data)};
334 {ok, {{_, 404, _}, _, _}} ->
335
:-(
none
336 end.
337
338
339 -spec bulk_lookup(db(), [key()]) -> [klsn:'maybe'(payload())].
340 bulk_lookup(Db, Keys) ->
341
:-(
bulk_lookup(Db, Keys, db_info()).
342 -spec bulk_lookup(db(), [key()], info()) -> [klsn:'maybe'(payload())].
343
:-(
bulk_lookup(_Db, [], _Info) -> [];
344 bulk_lookup(Db, Keys0, #{url := Url0}) when is_list(Keys0) ->
345
:-(
Keys = lists:map(fun klsn_binstr:from_any/1, Keys0),
346
:-(
DbBin = klsn_binstr:urlencode(klsn_binstr:from_any(Db)),
347
:-(
Path = <<"/", DbBin/binary, "/_all_docs?include_docs=true">>,
348
:-(
Url = <<Url0/binary, Path/binary>>,
349
:-(
Body = jsone:encode(#{<<"keys">> => Keys}),
350
:-(
Res = httpc:request(post, {Url, [], "application/json", Body}, [], [{body_format, binary}]),
351
:-(
case Res of
352 {ok, {{_, Stat, _}, _, Data}} when 200 =< Stat, Stat =< 299 ->
353
:-(
#{<<"rows">> := Rows} = jsone:decode(Data),
354
:-(
lists:map(
355 fun(Row) ->
356
:-(
case Row of
357
:-(
#{<<"error">> := _} -> none;
358
:-(
#{<<"doc">> := Doc} -> {value, Doc}
359 end
360 end,
361 Rows);
362 _ ->
363
:-(
lists:duplicate(length(Keys0), none)
364 end.
365 -spec bulk_upsert(db(), [key()], upsert_function()) -> [payload()].
366 bulk_upsert(Db, Keys, Fun) ->
367
:-(
bulk_upsert(Db, Keys, Fun, db_info()).
368 -spec bulk_upsert(db(), [key()], upsert_function(), info()) -> [payload()].
369
:-(
bulk_upsert(_Db, [], _Fun, _Info) -> [];
370 bulk_upsert(Db, Keys0, Fun, #{url := Url0} = Info) when is_list(Keys0) ->
371 %% Fetch current documents once
372
:-(
MaybeDocs = bulk_lookup(Db, Keys0, Info),
373
374 %% Prepare updated/new documents using the callback
375
:-(
TimeNow = time_now(),
376
:-(
DocsPrepared = lists:map(
377 fun({Key, MaybeDoc}) ->
378
:-(
New0 = Fun(MaybeDoc),
379
:-(
New1 = remove_keys(['_id', 'C', 'U'], New0),
380
:-(
New2 = New1#{<<"_id">> => klsn_binstr:from_any(Key)},
381
:-(
New3 = New2#{<<"U">> => TimeNow},
382
:-(
case MaybeDoc of
383
:-(
{value, #{<<"C">> := C}} -> New3#{<<"C">> => C};
384
:-(
_ -> New3#{<<"C">> => TimeNow}
385 end
386 end,
387 lists:zip(Keys0, MaybeDocs)
388 ),
389
390 %% Submit via _bulk_docs
391
:-(
DbBin = klsn_binstr:urlencode(klsn_binstr:from_any(Db)),
392
:-(
Path = <<"/", DbBin/binary, "/_bulk_docs">>,
393
:-(
Url = <<Url0/binary, Path/binary>>,
394
:-(
Body = jsone:encode(#{<<"docs">> => DocsPrepared}),
395
:-(
Res = httpc:request(post, {Url, [], "application/json", Body}, [], [{body_format, binary}]),
396
397
:-(
case Res of
398 {ok, {{_, Stat, _}, _, Data}} when 200 =< Stat, Stat =< 299 ->
399
:-(
Results = jsone:decode(Data),
400
:-(
lists:map(
401 fun({Doc0, ResRow}) ->
402
:-(
Doc = jsone:decode(jsone:encode(Doc0)),
403
:-(
case ResRow of
404 #{<<"ok">> := true, <<"id">> := Id, <<"rev">> := Rev} ->
405
:-(
Doc#{<<"_id">> => Id, <<"_rev">> => Rev};
406 #{<<"error">> := _} ->
407 %% Retry single-document upsert that already
408 %% has conflict–handling logic.
409
:-(
KeyBin = maps:get(<<"_id">>, Doc),
410
:-(
upsert(Db, KeyBin, Fun, Info)
411 end
412 end,
413 lists:zip(DocsPrepared, Results)
414 )
415 end.
416
417
418 -spec post(db(), payload(), info()) -> {id(), rev()}.
419 post(Db, Payload, Info) when is_atom(Db) ->
420
:-(
post(atom_to_binary(Db), Payload, Info);
421 post(Db0, Payload0, #{url:=Url0}) ->
422
:-(
Db1 = klsn_binstr:urlencode(Db0),
423
:-(
Db = <<"/", Db1/binary>>,
424
:-(
Payload = jsone:encode(Payload0),
425
:-(
Url = <<Url0/binary, Db/binary>>,
426
:-(
Res = httpc:request(post, {Url, [], "application/json", Payload}, [], [{body_format, binary}]),
427
:-(
case Res of
428 {ok, {{_, Stat, _}, _, Data}} when 200=<Stat,Stat=<299 ->
429
:-(
#{<<"ok">>:=true,<<"id">>:=Id,<<"rev">>:=Rev} = jsone:decode(Data),
430
:-(
{Id, Rev};
431 {ok, {{_, 404, _}, _, _}} ->
432
:-(
error(not_found);
433 {ok, {{_, 409, _}, _, _}} ->
434
:-(
error(conflict)
435 end.
436
437 %% @doc
438 %% Read-modify-write helper. Applies *Fun* to the current document and
439 %% stores the result. Fails with error:not_found when the key is absent.
440 -spec update(db(), key(), update_function()) -> payload().
441 update(Db, Key, Fun) ->
442
:-(
update(Db, Key, Fun, db_info()).
443
444 %% @doc
445 %% Same as update/3 but with explicit Info.
446 -spec update(db(), key(), update_function(), info()) -> payload().
447 update(Db, Key, Fun0, Info) ->
448
:-(
Fun = fun
449 (none) ->
450
:-(
error(not_found);
451 ({value, Data}) ->
452
:-(
Fun0(Data)
453 end,
454
:-(
upsert_(Db, Key, Fun, Info, 1).
455
456 %% @doc
457 %% Insert or update the document located at Key using Fun. Fun will
458 %% receive none on insert or {value, Old} on update and must return
459 %% the new payload.
460 -spec upsert(db(), key(), upsert_function()) -> payload().
461 upsert(Db, Key, Fun) ->
462
:-(
upsert(Db, Key, Fun, db_info()).
463
464 %% @doc
465 %% Same as upsert/3 but with explicit Info.
466 -spec upsert(db(), key(), upsert_function(), info()
467 ) -> payload().
468 upsert(_, <<>>, Fun, _) ->
469
:-(
Fun(none);
470 upsert(Db, Key, Fun, Info) ->
471
:-(
upsert_(Db, Key, Fun, Info, 1).
472
473 upsert_(_Db, _Key, _Fun, _Info, ReTry) when ReTry >= 10 ->
474
:-(
error(too_many_retry);
475 upsert_(Db, {raw, Key}, Fun, Info, Retry) ->
476
:-(
upsert_(Db, Key, Fun, Info, Retry);
477 upsert_(Db, Key, Fun, Info, Retry) ->
478
:-(
MaybeData = lookup(Db, Key, Info),
479
:-(
Data0 = Fun(MaybeData),
480
:-(
Data1 = remove_keys(['_id', 'C', 'U'], Data0),
481
:-(
Data2 = Data1#{<<"_id">>=>Key},
482
:-(
TimeNow = time_now(),
483
:-(
Data3 = Data2#{<<"U">>=>TimeNow},
484
:-(
Data = case MaybeData of
485
:-(
{value, #{<<"C">>:=C}} -> Data3#{<<"C">>=>C};
486
:-(
_ -> Data3#{<<"C">>=>TimeNow}
487 end,
488
:-(
try
489
:-(
post(Db, Data, Info)
490 of
491 {Id, Rev} ->
492
:-(
Data#{
493 <<"_id">> => Id
494 , <<"_rev">> => Rev
495 }
496 catch
497 error:conflict ->
498
:-(
sleep(Retry),
499
:-(
upsert_(Db, Key, Fun, Info, Retry+1);
500 error:not_found ->
501
:-(
error(not_found);
502 throw:Error ->
503
:-(
throw(Error);
504 Class:Error:Stack ->
505
:-(
spawn(fun()-> erlang:raise(Class,Error,Stack) end),
506
:-(
sleep(Retry),
507
:-(
upsert_(Db, Key, Fun, Info, Retry+5)
508 end.
509
510
511 %% @doc
512 %% ISO-8601/RFC-3339 timestamp with millisecond precision and a fixed
513 %% +09:00 offset. Used in audit fields C (created) and U (updated).
514 -spec time_now() -> unicode:unicode_binary().
515 time_now() ->
516
:-(
list_to_binary(calendar:system_time_to_rfc3339(erlang:system_time(millisecond), [{unit, millisecond}, {offset, "+09:00"}])).
517
518 -spec remove_keys([atom()], map()) -> map().
519 remove_keys(Keys, Map) when is_list(Keys), is_map(Map) ->
520
:-(
lists:foldl(fun(Key, Data0) ->
521
:-(
Data1 = maps:remove(Key, Data0),
522
:-(
maps:remove(atom_to_binary(Key), Data1)
523 end, Map, Keys).
524
525
526 %% @doc
527 %% Generate a monotonic-ish unique identifier suitable for use as a CouchDB
528 %% _id. Combines the current Unix time (seconds) with parts of an Erlang
529 %% reference encoded in base-36 so that the resulting IDs sort roughly in
530 %% creation order and stay URL-safe.
531
532 new_id() ->
533
:-(
Ref = make_ref(),
534
:-(
Time = erlang:system_time(second),
535
:-(
Str0 = ref_to_list(Ref),
536
:-(
[_|Str1] = lists:reverse(Str0),
537
:-(
Str2 = lists:reverse(Str1),
538
:-(
[_,A1,A2,A3] = string:split(Str2, ".", all),
539
:-(
N1 = list_to_integer(A1),
540
:-(
N2 = list_to_integer(A2),
541
:-(
N3 = list_to_integer(A3),
542
:-(
List = lists:flatten([
543 string:casefold(integer_to_list(Time, 36)),
544 "-",
545 string:casefold(integer_to_list(N1, 36)),
546 "-",
547 string:casefold(integer_to_list(N2, 36)),
548 "-",
549 string:casefold(integer_to_list(N3, 36))
550 ]),
551
:-(
list_to_binary(List).
552
553 db_info() ->
554
:-(
Url = case os:getenv("COUCHDB_URL") of
555 false ->
556
:-(
<<"http://localhost:5984">>;
557 Str when is_list(Str) ->
558
:-(
list_to_binary(Str)
559 end,
560
:-(
#{url=>Url}.
561
562 sleep(Stage) ->
563
:-(
timer:sleep(round(1000 * rand:uniform() + 100 * math:exp(Stage))).
Line Hits Source