Add support for ets match, ets select, and friends

This commit is contained in:
Joseph Wayne Norton 2011-11-19 23:39:23 +09:00
parent e3c129b105
commit 836a009ab1
22 changed files with 4908 additions and 653 deletions

257
README.md
View file

@ -9,7 +9,8 @@ __Authors:__ Joseph Wayne Norton ([`norton@alum.mit.edu`](mailto:norton@alum.mit
storage implementation. LETS tries to address some bad properties of
ETS and DETS. ETS is limited by physical memory. DETS is limited by
a 2 GB file size limitation and does not implement ordered sets. LETS
has neither of these limitations.</p>
has neither of these limitations. Data can also be automatically
compressed using the Snappy compression library.</p>
<p>For testing and comparison purposes, LETS supports three
implementations:</p>
<ul>
@ -30,35 +31,10 @@ implementations:</p>
</li>
</ul>
<p>LETS is not intended to be an exact clone of ETS. The currently
supported APIs are:</p>
supported ETS APIs are:</p>
<ul>
<li>
<p>
<tt>new/2</tt>
</p>
</li>
<li>
<p>
<tt>destroy/2</tt> <em>only driver and nif implementations</em>
</p>
</li>
<li>
<p>
<tt>repair/2</tt> <em>only driver and nif implementations</em>
</p>
</li>
<li>
<p>
<tt>insert/2</tt>
</p>
</li>
<li>
<p>
<tt>insert_new/2</tt> <em>only the ets implementation</em>
</p>
</li>
<li>
<p>
<tt>delete/1</tt>
</p>
</li>
@ -69,12 +45,7 @@ supported APIs are:</p>
</li>
<li>
<p>
<tt>delete_all_objects/1</tt> <em>only the ets implementation</em>
</p>
</li>
<li>
<p>
<tt>lookup/2</tt>
<tt>delete_all_objects/1</tt> <em>only ets implementation</em>
</p>
</li>
<li>
@ -84,7 +55,17 @@ supported APIs are:</p>
</li>
<li>
<p>
<tt>next/2</tt>
<tt>foldl/3</tt>
</p>
</li>
<li>
<p>
<tt>foldr/3</tt>
</p>
</li>
<li>
<p>
<tt>info/1</tt> <em>only a subset of items</em>
</p>
</li>
<li>
@ -94,6 +75,126 @@ supported APIs are:</p>
</li>
<li>
<p>
<tt>insert/2</tt>
</p>
</li>
<li>
<p>
<tt>insert_new/2</tt> <em>only ets implementation</em>
</p>
</li>
<li>
<p>
<tt>last/1</tt>
</p>
</li>
<li>
<p>
<tt>lookup/2</tt>
</p>
</li>
<li>
<p>
<tt>lookup_element/3</tt>
</p>
</li>
<li>
<p>
<tt>match/1</tt>
</p>
</li>
<li>
<p>
<tt>match/2</tt>
</p>
</li>
<li>
<p>
<tt>match/3</tt>
</p>
</li>
<li>
<p>
<tt>match_delete/2</tt>
</p>
</li>
<li>
<p>
<tt>match_object/1</tt>
</p>
</li>
<li>
<p>
<tt>match_object/2</tt>
</p>
</li>
<li>
<p>
<tt>match_object/3</tt>
</p>
</li>
<li>
<p>
<tt>member/2</tt>
</p>
</li>
<li>
<p>
<tt>new/2</tt>
</p>
</li>
<li>
<p>
<tt>next/2</tt>
</p>
</li>
<li>
<p>
<tt>prev/2</tt>
</p>
</li>
<li>
<p>
<tt>select/1</tt>
</p>
</li>
<li>
<p>
<tt>select/2</tt>
</p>
</li>
<li>
<p>
<tt>select/3</tt>
</p>
</li>
<li>
<p>
<tt>select_count/2</tt>
</p>
</li>
<li>
<p>
<tt>select_delete/2</tt>
</p>
</li>
<li>
<p>
<tt>select_reverse/1</tt>
</p>
</li>
<li>
<p>
<tt>select_reverse/2</tt>
</p>
</li>
<li>
<p>
<tt>select_reverse/3</tt>
</p>
</li>
<li>
<p>
<tt>tab2list/1</tt>
</p>
</li>
@ -127,7 +228,7 @@ please read further.</p>
<h3 id="_where_should_i_start">Where should I start?</h3>
<p>This README is the only bit of documentation right now.</p>
<p>The QC (a.k.a. QuickCheck, Proper, etc.) tests underneath the
<p>The QC (a.k.a. QuickCheck, PropEr, etc.) tests underneath the
"tests/qc" directory should be helpful for understanding the
specification and behavior of ETS and LETS. These QC tests also
illustrate several strategies for testing Erlang Driver-based and
@ -148,6 +249,11 @@ provides an ordered mapping from string keys to string values.</p>
<p>See <a href="http://code.google.com/p/leveldb/">http://code.google.com/p/leveldb/</a> for further details.</p>
<h3 id="_what_is_snappy">What is Snappy?</h3>
<p>Snappy is a fast compression/decompression library written at Google.</p>
<p>See <a href="http://code.google.com/p/snappy/">http://code.google.com/p/snappy/</a> for further details.</p>
<h2 id="_to_download">To download</h2>
@ -415,7 +521,36 @@ Run 5,000 QuickCheck tests
<pre><tt>$ cd working-directory-name/src/lib/lets/.eunit
$ erl -smp +A 5 -pz ../../sext/ebin -pz ../../qc/ebin
1> qc_statem_lets:run(5000).
1> qc_statem_lets:qc_run(5000).
....
OK, passed 5000 tests
9.022% {delete,ok}
7.800% {new,ok}
4.535% {match_delete,ok}
4.491% {lookup,ok}
4.399% {select,ok}
4.352% {select_delete,ok}
4.348% {tab2list,ok}
4.341% {member,ok}
4.334% {last,ok}
4.315% {foldl,ok}
4.308% {select_reverse,ok}
4.301% {select_count,ok}
4.293% {select31,ok}
4.264% {first,ok}
4.216% {foldr,ok}
4.202% {match_object,ok}
4.184% {match,ok}
4.056% {insert,ok}
3.997% {prev,ok}
3.774% {next,ok}
3.416% {lookup_element,{error,badarg}}
1.298% {insert_new,ok}
0.757% {lookup_element,ok}
0.516% {next,{error,badarg}}
0.483% {prev,{error,badarg}}
true
.......</tt></pre>
@ -424,7 +559,7 @@ $ erl -smp +A 5 -pz ../../sext/ebin -pz ../../qc/ebin
Tip
</td>
<td class="content">For testing LevelDB directly using the C bindings, try
<tt>qc_statemc_lets:run(5000)</tt>.</td>
<tt>qc_statemc_lets:qc_run(5000)</tt>.</td>
</tr></table>
</li>
@ -432,12 +567,12 @@ Tip
<h2 id="_to_test_proper">To test - Proper</h2>
<h2 id="_to_test_proper">To test - PropEr</h2>
<ol class="arabic">
<li>
<p>
Make sure Proper is in your Erlang code path. One simple way to
Make sure PropEr is in your Erlang code path. One simple way to
accomplish this is by adding the code path to your <tt>~/.erlang</tt>
resource file.
</p>
@ -448,7 +583,7 @@ Make sure Proper is in your Erlang code path. One simple way to
</li>
<li>
<p>
Compile for Proper
Compile for PropEr
</p>
@ -459,14 +594,43 @@ $ make compile-proper proper-compile</tt></pre>
</li>
<li>
<p>
Run 5,000 Proper tests
Run 5,000 PropEr tests
</p>
<pre><tt>$ cd working-directory-name/src/lib/lets/.eunit
$ erl -smp +A 5 -pz ../../sext/ebin -pz ../../qc/ebin
1> qc_statem_lets:run(5000).
1> qc_statem_lets:qc_run(5000).
....
OK: Passed 5000 test(s).
11% {new,ok}
8% {delete,ok}
4% {member,ok}
4% {select,ok}
4% {select_count,ok}
4% {select_reverse,ok}
4% {lookup,ok}
4% {match_object,ok}
4% {tab2list,ok}
4% {last,ok}
4% {match,ok}
4% {foldl,ok}
4% {match_delete,ok}
3% {prev,ok}
3% {select31,ok}
3% {select_delete,ok}
3% {foldr,ok}
3% {insert,ok}
3% {first,ok}
3% {next,ok}
3% {lookup_element,{error,badarg}}
1% {insert_new,ok}
0% {prev,{error,badarg}}
0% {lookup_element,ok}
0% {next,{error,badarg}}
true
.......</tt></pre>
</li>
@ -484,7 +648,7 @@ Documentation
<ul>
<li>
<p>
Explain how to run QuickCheck/Proper tests using a new rebar
Explain how to run QuickCheck/PropEr tests using a new rebar
plugin.
</p>
</li>
@ -549,6 +713,13 @@ New APIs (TBD)
(<a href="http://code.google.com/p/leveldb/issues/detail?id=43">http://code.google.com/p/leveldb/issues/detail?id=43</a>)
</p>
</li>
<li>
<p>
Add custom (i.e. not supported by native ETS) APIs for providing
access to LevelDB's iterators for <tt>drv</tt> and <tt>nif</tt> backend
implementations.
</p>
</li>
</ul>
</li>
<li>

View file

@ -35,24 +35,32 @@
#define LETS_BADARG 0x00
#define LETS_TRUE 0x01
#define LETS_END_OF_TABLE 0x02
#define LETS_BINARY 0x03
#define LETS_FALSE 0x02
#define LETS_END_OF_TABLE 0x03
#define LETS_BINARY 0x04
#define LETS_OPEN6 0x00 // same as OPEN
#define LETS_DESTROY6 0x01 // same as DESTROY
#define LETS_REPAIR6 0x02 // same as REPAIR
#define LETS_INSERT2 0x03
#define LETS_INSERT3 0x04
#define LETS_INSERT_NEW2 0x05
#define LETS_INSERT_NEW3 0x06
#define LETS_DELETE1 0x07
#define LETS_DELETE2 0x08
#define LETS_DELETE_ALL_OBJECTS1 0x09
#define LETS_LOOKUP2 0x0A
#define LETS_FIRST1 0x0B
#define LETS_NEXT2 0x0C
#define LETS_INFO_MEMORY1 0x0D
#define LETS_INFO_SIZE1 0x0E
#define LETS_DELETE1 0x03
#define LETS_DELETE2 0x04
#define LETS_DELETE_ALL_OBJECTS1 0x05
#define LETS_FIRST1 0x06
#define LETS_FIRST_ITER1 0x07
#define LETS_INFO_MEMORY1 0x08
#define LETS_INFO_SIZE1 0x09
#define LETS_INSERT2 0x0A
#define LETS_INSERT3 0x0B
#define LETS_INSERT_NEW2 0x0C
#define LETS_INSERT_NEW3 0x0D
#define LETS_LAST1 0x0E
#define LETS_LAST_ITER1 0x0F
#define LETS_LOOKUP2 0x10
#define LETS_MEMBER2 0x11
#define LETS_NEXT2 0x12
#define LETS_NEXT_ITER2 0x13
#define LETS_PREV2 0x14
#define LETS_PREV_ITER2 0x15
// DrvData
typedef struct {
@ -121,10 +129,24 @@ static void lets_async_delete2(void* async_data);
// static void lets_async_delete_all_objects1(void* async_data);
static void lets_output_lookup2(DrvData* d, char* buf, int len, int* index, int items);
static void lets_async_lookup2(void* async_data);
static void lets_output_member2(DrvData* d, char* buf, int len, int* index, int items);
static void lets_async_member2(void* async_data);
static void lets_output_first1(DrvData* d, char* buf, int len, int* index, int items);
static void lets_async_first1(void* async_data);
static void lets_output_first_iter1(DrvData* d, char* buf, int len, int* index, int items);
static void lets_async_first_iter1(void* async_data);
static void lets_output_last1(DrvData* d, char* buf, int len, int* index, int items);
static void lets_async_last1(void* async_data);
static void lets_output_last_iter1(DrvData* d, char* buf, int len, int* index, int items);
static void lets_async_last_iter1(void* async_data);
static void lets_output_next2(DrvData* d, char* buf, int len, int* index, int items);
static void lets_async_next2(void* async_data);
static void lets_output_next_iter2(DrvData* d, char* buf, int len, int* index, int items);
static void lets_async_next_iter2(void* async_data);
static void lets_output_prev2(DrvData* d, char* buf, int len, int* index, int items);
static void lets_async_prev2(void* async_data);
static void lets_output_prev_iter2(DrvData* d, char* buf, int len, int* index, int items);
static void lets_async_prev_iter2(void* async_data);
// static void lets_output_info_memory1(DrvData* d, char* buf, int len, int* index, int items);
// static void lets_async_info_memory1(void* async_data);
// static void lets_output_info_size1(DrvData* d, char* buf, int len, int* index, int items);
@ -358,16 +380,51 @@ drv_output(ErlDrvData handle, char* buf, int len)
if (ng) GOTOBADARG;
lets_output_lookup2(d, buf, len, &index, items);
break;
case LETS_MEMBER2:
ng = (items != 1 || !d->impl.alive);
if (ng) GOTOBADARG;
lets_output_member2(d, buf, len, &index, items);
break;
case LETS_FIRST1:
ng = (items != 0 || !d->impl.alive);
if (ng) GOTOBADARG;
lets_output_first1(d, buf, len, &index, items);
break;
case LETS_FIRST_ITER1:
ng = (items != 0 || !d->impl.alive);
if (ng) GOTOBADARG;
lets_output_first_iter1(d, buf, len, &index, items);
break;
case LETS_LAST1:
ng = (items != 0 || !d->impl.alive);
if (ng) GOTOBADARG;
lets_output_last1(d, buf, len, &index, items);
break;
case LETS_LAST_ITER1:
ng = (items != 0 || !d->impl.alive);
if (ng) GOTOBADARG;
lets_output_last_iter1(d, buf, len, &index, items);
break;
case LETS_NEXT2:
ng = (items != 1 || !d->impl.alive);
if (ng) GOTOBADARG;
lets_output_next2(d, buf, len, &index, items);
break;
case LETS_NEXT_ITER2:
ng = (items != 1 || !d->impl.alive);
if (ng) GOTOBADARG;
lets_output_next_iter2(d, buf, len, &index, items);
break;
case LETS_PREV2:
ng = (items != 1 || !d->impl.alive);
if (ng) GOTOBADARG;
lets_output_prev2(d, buf, len, &index, items);
break;
case LETS_PREV_ITER2:
ng = (items != 1 || !d->impl.alive);
if (ng) GOTOBADARG;
lets_output_prev_iter2(d, buf, len, &index, items);
break;
case LETS_INFO_MEMORY1:
ng = (items != 0 || !d->impl.alive);
if (ng) GOTOBADARG;
@ -402,6 +459,9 @@ drv_ready_async(ErlDrvData handle, ErlDrvThreadData async_data)
case LETS_TRUE:
driver_send_int(d, LETS_TRUE, a->caller);
break;
case LETS_FALSE:
driver_send_int(d, LETS_FALSE, a->caller);
break;
case LETS_END_OF_TABLE:
driver_send_int(d, LETS_END_OF_TABLE, a->caller);
break;
@ -819,6 +879,7 @@ lets_async_delete2(void* async_data)
a->reply = LETS_TRUE;
}
}
static void
lets_output_lookup2(DrvData* d, char* buf, int len, int* index, int items)
{
@ -894,6 +955,73 @@ lets_async_lookup2(void* async_data)
delete it;
}
static void
lets_output_member2(DrvData* d, char* buf, int len, int* index, int items)
{
DrvAsync* drv_async = NULL;
int ng;
char *key;
long keylen;
ng = ei_inspect_binary(buf, index, (void**) &key, &keylen);
if (ng) GOTOBADARG;
if (d->impl.async) {
drv_async = new DrvAsync(d, driver_caller(d->port), LETS_MEMBER2, (const char*) key, keylen);
if (!drv_async) {
GOTOBADARG;
}
driver_async(d->port, NULL, lets_async_member2, drv_async, drv_async_free);
} else {
leveldb::Iterator* it = d->impl.db->NewIterator(d->impl.db_read_options);
if (!it) {
GOTOBADARG;
}
leveldb::Slice skey((const char*) key, keylen);
it->Seek(skey);
if (!it->Valid() || it->key().compare(skey) != 0) {
driver_send_int(d, LETS_FALSE);
delete it;
return;
}
driver_send_int(d, LETS_TRUE);
delete it;
}
return;
badarg:
if (drv_async) { delete drv_async; }
driver_send_int(d, LETS_BADARG);
return;
}
static void
lets_async_member2(void* async_data)
{
DrvAsync* a = (DrvAsync*) async_data;
assert(a != NULL);
DrvData* d = a->drvdata;
leveldb::Iterator* it = d->impl.db->NewIterator(d->impl.db_read_options);
if (!it) {
a->reply = LETS_BADARG;
return;
}
leveldb::Slice skey((const char*) a->binary->orig_bytes, a->binary->orig_size);
it->Seek(skey);
if (!it->Valid() || it->key().compare(skey) != 0) {
a->reply = LETS_FALSE;
delete it;
return;
}
a->reply = LETS_TRUE;
delete it;
}
static void
lets_output_first1(DrvData* d, char* buf, int len, int* index, int items)
{
@ -961,6 +1089,208 @@ lets_async_first1(void* async_data)
delete it;
}
static void
lets_output_first_iter1(DrvData* d, char* buf, int len, int* index, int items)
{
DrvAsync* drv_async = NULL;
if (d->impl.async) {
drv_async = new DrvAsync(d, driver_caller(d->port), LETS_FIRST_ITER1);
if (!drv_async) {
GOTOBADARG;
}
driver_async(d->port, NULL, lets_async_first_iter1, drv_async, drv_async_free);
} else {
leveldb::Iterator* it = d->impl.db->NewIterator(d->impl.db_read_options);
if (!it) {
GOTOBADARG;
}
it->SeekToFirst();
if (!it->Valid()) {
driver_send_int(d, LETS_END_OF_TABLE);
delete it;
return;
}
driver_send_buf(d, it->value().data(), it->value().size());
delete it;
}
return;
badarg:
if (drv_async) { delete drv_async; }
driver_send_int(d, LETS_BADARG);
return;
}
static void
lets_async_first_iter1(void* async_data)
{
DrvAsync* a = (DrvAsync*) async_data;
assert(a != NULL);
DrvData* d = a->drvdata;
leveldb::Iterator* it = d->impl.db->NewIterator(d->impl.db_read_options);
if (!it) {
a->reply = LETS_BADARG;
return;
}
it->SeekToFirst();
if (!it->Valid()) {
a->reply = LETS_END_OF_TABLE;
delete it;
return;
}
ErlDrvBinary* binary = driver_alloc_binary(it->value().size());
if (binary) {
memcpy(binary->orig_bytes, it->value().data(), binary->orig_size);
a->binary = binary;
a->reply = LETS_BINARY;
} else {
a->reply = LETS_BADARG;
}
delete it;
}
static void
lets_output_last1(DrvData* d, char* buf, int len, int* index, int items)
{
DrvAsync* drv_async = NULL;
if (d->impl.async) {
drv_async = new DrvAsync(d, driver_caller(d->port), LETS_LAST1);
if (!drv_async) {
GOTOBADARG;
}
driver_async(d->port, NULL, lets_async_last1, drv_async, drv_async_free);
} else {
leveldb::Iterator* it = d->impl.db->NewIterator(d->impl.db_read_options);
if (!it) {
GOTOBADARG;
}
it->SeekToLast();
if (!it->Valid()) {
driver_send_int(d, LETS_END_OF_TABLE);
delete it;
return;
}
driver_send_buf(d, it->key().data(), it->key().size());
delete it;
}
return;
badarg:
if (drv_async) { delete drv_async; }
driver_send_int(d, LETS_BADARG);
return;
}
static void
lets_async_last1(void* async_data)
{
DrvAsync* a = (DrvAsync*) async_data;
assert(a != NULL);
DrvData* d = a->drvdata;
leveldb::Iterator* it = d->impl.db->NewIterator(d->impl.db_read_options);
if (!it) {
a->reply = LETS_BADARG;
return;
}
it->SeekToLast();
if (!it->Valid()) {
a->reply = LETS_END_OF_TABLE;
delete it;
return;
}
ErlDrvBinary* binary = driver_alloc_binary(it->key().size());
if (binary) {
memcpy(binary->orig_bytes, it->key().data(), binary->orig_size);
a->binary = binary;
a->reply = LETS_BINARY;
} else {
a->reply = LETS_BADARG;
}
delete it;
}
static void
lets_output_last_iter1(DrvData* d, char* buf, int len, int* index, int items)
{
DrvAsync* drv_async = NULL;
if (d->impl.async) {
drv_async = new DrvAsync(d, driver_caller(d->port), LETS_LAST_ITER1);
if (!drv_async) {
GOTOBADARG;
}
driver_async(d->port, NULL, lets_async_last_iter1, drv_async, drv_async_free);
} else {
leveldb::Iterator* it = d->impl.db->NewIterator(d->impl.db_read_options);
if (!it) {
GOTOBADARG;
}
it->SeekToLast();
if (!it->Valid()) {
driver_send_int(d, LETS_END_OF_TABLE);
delete it;
return;
}
driver_send_buf(d, it->value().data(), it->value().size());
delete it;
}
return;
badarg:
if (drv_async) { delete drv_async; }
driver_send_int(d, LETS_BADARG);
return;
}
static void
lets_async_last_iter1(void* async_data)
{
DrvAsync* a = (DrvAsync*) async_data;
assert(a != NULL);
DrvData* d = a->drvdata;
leveldb::Iterator* it = d->impl.db->NewIterator(d->impl.db_read_options);
if (!it) {
a->reply = LETS_BADARG;
return;
}
it->SeekToLast();
if (!it->Valid()) {
a->reply = LETS_END_OF_TABLE;
delete it;
return;
}
ErlDrvBinary* binary = driver_alloc_binary(it->value().size());
if (binary) {
memcpy(binary->orig_bytes, it->value().data(), binary->orig_size);
a->binary = binary;
a->reply = LETS_BINARY;
} else {
a->reply = LETS_BADARG;
}
delete it;
}
static void
lets_output_next2(DrvData* d, char* buf, int len, int* index, int items)
{
@ -1052,3 +1382,268 @@ lets_async_next2(void* async_data)
delete it;
}
static void
lets_output_next_iter2(DrvData* d, char* buf, int len, int* index, int items)
{
DrvAsync* drv_async = NULL;
int ng;
char *key;
long keylen;
ng = ei_inspect_binary(buf, index, (void**) &key, &keylen);
if (ng) GOTOBADARG;
if (d->impl.async) {
drv_async = new DrvAsync(d, driver_caller(d->port), LETS_NEXT_ITER2, (const char*) key, keylen);
if (!drv_async) {
GOTOBADARG;
}
driver_async(d->port, NULL, lets_async_next_iter2, drv_async, drv_async_free);
} else {
leveldb::Iterator* it = d->impl.db->NewIterator(d->impl.db_read_options);
if (!it) {
GOTOBADARG;
}
leveldb::Slice skey((const char*) key, keylen);
it->Seek(skey);
if (!it->Valid()) {
driver_send_int(d, LETS_END_OF_TABLE);
delete it;
return;
}
if (it->key().compare(skey) == 0) {
it->Next();
if (!it->Valid()) {
driver_send_int(d, LETS_END_OF_TABLE);
delete it;
return;
}
}
driver_send_buf(d, it->value().data(), it->value().size());
delete it;
}
return;
badarg:
if (drv_async) { delete drv_async; }
driver_send_int(d, LETS_BADARG);
}
static void
lets_async_next_iter2(void* async_data)
{
DrvAsync* a = (DrvAsync*) async_data;
assert(a != NULL);
DrvData* d = a->drvdata;
leveldb::Iterator* it = d->impl.db->NewIterator(d->impl.db_read_options);
if (!it) {
a->reply = LETS_BADARG;
return;
}
leveldb::Slice skey((const char*) a->binary->orig_bytes, a->binary->orig_size);
it->Seek(skey);
if (!it->Valid()) {
a->reply = LETS_END_OF_TABLE;
delete it;
return;
}
if (it->key().compare(skey) == 0) {
it->Next();
if (!it->Valid()) {
a->reply = LETS_END_OF_TABLE;
delete it;
return;
}
}
ErlDrvBinary* binary = driver_realloc_binary(a->binary, it->value().size());
if (binary) {
memcpy(binary->orig_bytes, it->value().data(), binary->orig_size);
a->binary = binary;
a->reply = LETS_BINARY;
} else {
a->reply = LETS_BADARG;
}
delete it;
}
static void
lets_output_prev2(DrvData* d, char* buf, int len, int* index, int items)
{
DrvAsync* drv_async = NULL;
int ng;
char *key;
long keylen;
ng = ei_inspect_binary(buf, index, (void**) &key, &keylen);
if (ng) GOTOBADARG;
if (d->impl.async) {
drv_async = new DrvAsync(d, driver_caller(d->port), LETS_PREV2, (const char*) key, keylen);
if (!drv_async) {
GOTOBADARG;
}
driver_async(d->port, NULL, lets_async_prev2, drv_async, drv_async_free);
} else {
leveldb::Iterator* it = d->impl.db->NewIterator(d->impl.db_read_options);
if (!it) {
GOTOBADARG;
}
leveldb::Slice skey((const char*) key, keylen);
it->Seek(skey);
if (!it->Valid()) {
it->SeekToLast();
} else {
it->Prev();
}
if (!it->Valid()) {
driver_send_int(d, LETS_END_OF_TABLE);
delete it;
return;
}
driver_send_buf(d, it->key().data(), it->key().size());
delete it;
}
return;
badarg:
if (drv_async) { delete drv_async; }
driver_send_int(d, LETS_BADARG);
}
static void
lets_async_prev2(void* async_data)
{
DrvAsync* a = (DrvAsync*) async_data;
assert(a != NULL);
DrvData* d = a->drvdata;
leveldb::Iterator* it = d->impl.db->NewIterator(d->impl.db_read_options);
if (!it) {
a->reply = LETS_BADARG;
return;
}
leveldb::Slice skey((const char*) a->binary->orig_bytes, a->binary->orig_size);
it->Seek(skey);
if (!it->Valid()) {
it->SeekToLast();
} else {
it->Prev();
}
if (!it->Valid()) {
a->reply = LETS_END_OF_TABLE;
delete it;
return;
}
ErlDrvBinary* binary = driver_realloc_binary(a->binary, it->key().size());
if (binary) {
memcpy(binary->orig_bytes, it->key().data(), binary->orig_size);
a->binary = binary;
a->reply = LETS_BINARY;
} else {
a->reply = LETS_BADARG;
}
delete it;
}
static void
lets_output_prev_iter2(DrvData* d, char* buf, int len, int* index, int items)
{
DrvAsync* drv_async = NULL;
int ng;
char *key;
long keylen;
ng = ei_inspect_binary(buf, index, (void**) &key, &keylen);
if (ng) GOTOBADARG;
if (d->impl.async) {
drv_async = new DrvAsync(d, driver_caller(d->port), LETS_PREV_ITER2, (const char*) key, keylen);
if (!drv_async) {
GOTOBADARG;
}
driver_async(d->port, NULL, lets_async_prev_iter2, drv_async, drv_async_free);
} else {
leveldb::Iterator* it = d->impl.db->NewIterator(d->impl.db_read_options);
if (!it) {
GOTOBADARG;
}
leveldb::Slice skey((const char*) key, keylen);
it->Seek(skey);
if (!it->Valid()) {
it->SeekToLast();
} else {
it->Prev();
}
if (!it->Valid()) {
driver_send_int(d, LETS_END_OF_TABLE);
delete it;
return;
}
driver_send_buf(d, it->value().data(), it->value().size());
delete it;
}
return;
badarg:
if (drv_async) { delete drv_async; }
driver_send_int(d, LETS_BADARG);
}
static void
lets_async_prev_iter2(void* async_data)
{
DrvAsync* a = (DrvAsync*) async_data;
assert(a != NULL);
DrvData* d = a->drvdata;
leveldb::Iterator* it = d->impl.db->NewIterator(d->impl.db_read_options);
if (!it) {
a->reply = LETS_BADARG;
return;
}
leveldb::Slice skey((const char*) a->binary->orig_bytes, a->binary->orig_size);
it->Seek(skey);
if (!it->Valid()) {
it->SeekToLast();
} else {
it->Prev();
}
if (!it->Valid()) {
a->reply = LETS_END_OF_TABLE;
delete it;
return;
}
ErlDrvBinary* binary = driver_realloc_binary(a->binary, it->value().size());
if (binary) {
memcpy(binary->orig_bytes, it->value().data(), binary->orig_size);
a->binary = binary;
a->reply = LETS_BINARY;
} else {
a->reply = LETS_BADARG;
}
delete it;
}

View file

@ -60,8 +60,15 @@ static ErlNifFunc nif_funcs[] =
{"impl_delete", 2, lets_nif_delete2},
{"impl_delete_all_objects", 1, lets_nif_delete_all_objects1},
{"impl_lookup", 2, lets_nif_lookup2},
{"impl_member", 2, lets_nif_member2},
{"impl_first", 1, lets_nif_first1},
{"impl_first_iter", 1, lets_nif_first_iter1},
{"impl_last", 1, lets_nif_last1},
{"impl_last_iter", 1, lets_nif_last_iter1},
{"impl_next", 2, lets_nif_next2},
{"impl_next_iter", 2, lets_nif_next_iter2},
{"impl_prev", 2, lets_nif_prev2},
{"impl_prev_iter", 2, lets_nif_prev_iter2},
{"impl_info_memory", 1, lets_nif_info_memory1},
{"impl_info_size", 1, lets_nif_info_size1},
};
@ -448,11 +455,45 @@ lets_nif_lookup2(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
return blob;
}
ERL_NIF_TERM
lets_nif_member2(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
lets_nif_handle* h;
ErlNifBinary key;
leveldb::Status status;
if (!enif_get_resource(env, argv[0], lets_nif_RESOURCE, (void**)&h)) {
return MAKEBADARG(env, status);
}
if (!enif_inspect_binary(env, argv[1], &key)) {
return MAKEBADARG(env, status);
}
if (!h->impl.alive) {
return MAKEBADARG(env, status);
}
leveldb::Iterator* it = h->impl.db->NewIterator(h->impl.db_read_options);
if (!it) {
return MAKEBADARG(env, status);
}
leveldb::Slice skey((const char*) key.data, key.size);
it->Seek(skey);
if (!it->Valid() || it->key().compare(skey) != 0) {
delete it;
return lets_atom_false;
}
delete it;
return lets_atom_true;
}
ERL_NIF_TERM
lets_nif_first1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
lets_nif_handle* h;
ERL_NIF_TERM first_key = 0;
ERL_NIF_TERM first = 0;
leveldb::Status status;
if (!enif_get_resource(env, argv[0], lets_nif_RESOURCE, (void**)&h)) {
@ -475,7 +516,7 @@ lets_nif_first1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
}
size_t size = it->key().size();
unsigned char* k = enif_make_new_binary(env, size, &first_key);
unsigned char* k = enif_make_new_binary(env, size, &first);
if (!k) {
delete it;
return MAKEBADARG(env, status);
@ -483,7 +524,121 @@ lets_nif_first1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
memcpy(k, it->key().data(), size);
delete it;
return first_key;
return first;
}
ERL_NIF_TERM
lets_nif_first_iter1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
lets_nif_handle* h;
ERL_NIF_TERM first = 0;
leveldb::Status status;
if (!enif_get_resource(env, argv[0], lets_nif_RESOURCE, (void**)&h)) {
return MAKEBADARG(env, status);
}
if (!h->impl.alive) {
return MAKEBADARG(env, status);
}
leveldb::Iterator* it = h->impl.db->NewIterator(h->impl.db_read_options);
if (!it) {
return MAKEBADARG(env, status);
}
it->SeekToFirst();
if (!it->Valid()) {
delete it;
return lets_atom_end_of_table;
}
size_t size = it->value().size();
unsigned char* k = enif_make_new_binary(env, size, &first);
if (!k) {
delete it;
return MAKEBADARG(env, status);
}
memcpy(k, it->value().data(), size);
delete it;
return first;
}
ERL_NIF_TERM
lets_nif_last1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
lets_nif_handle* h;
ERL_NIF_TERM last = 0;
leveldb::Status status;
if (!enif_get_resource(env, argv[0], lets_nif_RESOURCE, (void**)&h)) {
return MAKEBADARG(env, status);
}
if (!h->impl.alive) {
return MAKEBADARG(env, status);
}
leveldb::Iterator* it = h->impl.db->NewIterator(h->impl.db_read_options);
if (!it) {
return MAKEBADARG(env, status);
}
it->SeekToLast();
if (!it->Valid()) {
delete it;
return lets_atom_end_of_table;
}
size_t size = it->key().size();
unsigned char* k = enif_make_new_binary(env, size, &last);
if (!k) {
delete it;
return MAKEBADARG(env, status);
}
memcpy(k, it->key().data(), size);
delete it;
return last;
}
ERL_NIF_TERM
lets_nif_last_iter1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
lets_nif_handle* h;
ERL_NIF_TERM last = 0;
leveldb::Status status;
if (!enif_get_resource(env, argv[0], lets_nif_RESOURCE, (void**)&h)) {
return MAKEBADARG(env, status);
}
if (!h->impl.alive) {
return MAKEBADARG(env, status);
}
leveldb::Iterator* it = h->impl.db->NewIterator(h->impl.db_read_options);
if (!it) {
return MAKEBADARG(env, status);
}
it->SeekToLast();
if (!it->Valid()) {
delete it;
return lets_atom_end_of_table;
}
size_t size = it->value().size();
unsigned char* k = enif_make_new_binary(env, size, &last);
if (!k) {
delete it;
return MAKEBADARG(env, status);
}
memcpy(k, it->value().data(), size);
delete it;
return last;
}
ERL_NIF_TERM
@ -491,7 +646,7 @@ lets_nif_next2(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
lets_nif_handle* h;
ErlNifBinary key;
ERL_NIF_TERM next_key = 0;
ERL_NIF_TERM next = 0;
leveldb::Status status;
if (!enif_get_resource(env, argv[0], lets_nif_RESOURCE, (void**)&h)) {
@ -526,7 +681,7 @@ lets_nif_next2(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
}
size_t size = it->key().size();
unsigned char* k = enif_make_new_binary(env, size, &next_key);
unsigned char* k = enif_make_new_binary(env, size, &next);
if (!k) {
delete it;
return MAKEBADARG(env, status);
@ -534,7 +689,156 @@ lets_nif_next2(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
memcpy(k, it->key().data(), size);
delete it;
return next_key;
return next;
}
ERL_NIF_TERM
lets_nif_next_iter2(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
lets_nif_handle* h;
ErlNifBinary key;
ERL_NIF_TERM next = 0;
leveldb::Status status;
if (!enif_get_resource(env, argv[0], lets_nif_RESOURCE, (void**)&h)) {
return MAKEBADARG(env, status);
}
if (!enif_inspect_binary(env, argv[1], &key)) {
return MAKEBADARG(env, status);
}
if (!h->impl.alive) {
return MAKEBADARG(env, status);
}
leveldb::Iterator* it = h->impl.db->NewIterator(h->impl.db_read_options);
if (!it) {
return MAKEBADARG(env, status);
}
leveldb::Slice skey((const char*) key.data, key.size);
it->Seek(skey);
if (!it->Valid()) {
delete it;
return lets_atom_end_of_table;
}
if (it->key().compare(skey) == 0) {
it->Next();
if (!it->Valid()) {
delete it;
return lets_atom_end_of_table;
}
}
size_t size = it->value().size();
unsigned char* k = enif_make_new_binary(env, size, &next);
if (!k) {
delete it;
return MAKEBADARG(env, status);
}
memcpy(k, it->value().data(), size);
delete it;
return next;
}
ERL_NIF_TERM
lets_nif_prev2(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
lets_nif_handle* h;
ErlNifBinary key;
ERL_NIF_TERM prev = 0;
leveldb::Status status;
if (!enif_get_resource(env, argv[0], lets_nif_RESOURCE, (void**)&h)) {
return MAKEBADARG(env, status);
}
if (!enif_inspect_binary(env, argv[1], &key)) {
return MAKEBADARG(env, status);
}
if (!h->impl.alive) {
return MAKEBADARG(env, status);
}
leveldb::Iterator* it = h->impl.db->NewIterator(h->impl.db_read_options);
if (!it) {
return MAKEBADARG(env, status);
}
leveldb::Slice skey((const char*) key.data, key.size);
it->Seek(skey);
if (!it->Valid()) {
it->SeekToLast();
} else {
it->Prev();
}
if (!it->Valid()) {
delete it;
return lets_atom_end_of_table;
}
size_t size = it->key().size();
unsigned char* k = enif_make_new_binary(env, size, &prev);
if (!k) {
delete it;
return MAKEBADARG(env, status);
}
memcpy(k, it->key().data(), size);
delete it;
return prev;
}
ERL_NIF_TERM
lets_nif_prev_iter2(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
lets_nif_handle* h;
ErlNifBinary key;
ERL_NIF_TERM prev = 0;
leveldb::Status status;
if (!enif_get_resource(env, argv[0], lets_nif_RESOURCE, (void**)&h)) {
return MAKEBADARG(env, status);
}
if (!enif_inspect_binary(env, argv[1], &key)) {
return MAKEBADARG(env, status);
}
if (!h->impl.alive) {
return MAKEBADARG(env, status);
}
leveldb::Iterator* it = h->impl.db->NewIterator(h->impl.db_read_options);
if (!it) {
return MAKEBADARG(env, status);
}
leveldb::Slice skey((const char*) key.data, key.size);
it->Seek(skey);
if (!it->Valid()) {
it->SeekToLast();
} else {
it->Prev();
}
if (!it->Valid()) {
delete it;
return lets_atom_end_of_table;
}
size_t size = it->value().size();
unsigned char* k = enif_make_new_binary(env, size, &prev);
if (!k) {
delete it;
return MAKEBADARG(env, status);
}
memcpy(k, it->value().data(), size);
delete it;
return prev;
}
ERL_NIF_TERM

View file

@ -53,10 +53,24 @@ extern "C" {
ERL_NIF_TERM
lets_nif_lookup2(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
ERL_NIF_TERM
lets_nif_member2(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
ERL_NIF_TERM
lets_nif_first1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
ERL_NIF_TERM
lets_nif_first_iter1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
ERL_NIF_TERM
lets_nif_last1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
ERL_NIF_TERM
lets_nif_last_iter1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
ERL_NIF_TERM
lets_nif_next2(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
ERL_NIF_TERM
lets_nif_next_iter2(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
ERL_NIF_TERM
lets_nif_prev2(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
ERL_NIF_TERM
lets_nif_prev_iter2(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
ERL_NIF_TERM
lets_nif_info_memory1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
ERL_NIF_TERM
lets_nif_info_size1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);

View file

@ -9,7 +9,8 @@ __Authors:__ Joseph Wayne Norton ([`norton@alum.mit.edu`](mailto:norton@alum.mit
storage implementation. LETS tries to address some bad properties of
ETS and DETS. ETS is limited by physical memory. DETS is limited by
a 2 GB file size limitation and does not implement ordered sets. LETS
has neither of these limitations.</p>
has neither of these limitations. Data can also be automatically
compressed using the Snappy compression library.</p>
<p>For testing and comparison purposes, LETS supports three
implementations:</p>
<ul>
@ -30,35 +31,10 @@ implementations:</p>
</li>
</ul>
<p>LETS is not intended to be an exact clone of ETS. The currently
supported APIs are:</p>
supported ETS APIs are:</p>
<ul>
<li>
<p>
<tt>new/2</tt>
</p>
</li>
<li>
<p>
<tt>destroy/2</tt> <em>only driver and nif implementations</em>
</p>
</li>
<li>
<p>
<tt>repair/2</tt> <em>only driver and nif implementations</em>
</p>
</li>
<li>
<p>
<tt>insert/2</tt>
</p>
</li>
<li>
<p>
<tt>insert_new/2</tt> <em>only the ets implementation</em>
</p>
</li>
<li>
<p>
<tt>delete/1</tt>
</p>
</li>
@ -69,12 +45,7 @@ supported APIs are:</p>
</li>
<li>
<p>
<tt>delete_all_objects/1</tt> <em>only the ets implementation</em>
</p>
</li>
<li>
<p>
<tt>lookup/2</tt>
<tt>delete_all_objects/1</tt> <em>only ets implementation</em>
</p>
</li>
<li>
@ -84,7 +55,17 @@ supported APIs are:</p>
</li>
<li>
<p>
<tt>next/2</tt>
<tt>foldl/3</tt>
</p>
</li>
<li>
<p>
<tt>foldr/3</tt>
</p>
</li>
<li>
<p>
<tt>info/1</tt> <em>only a subset of items</em>
</p>
</li>
<li>
@ -94,6 +75,126 @@ supported APIs are:</p>
</li>
<li>
<p>
<tt>insert/2</tt>
</p>
</li>
<li>
<p>
<tt>insert_new/2</tt> <em>only ets implementation</em>
</p>
</li>
<li>
<p>
<tt>last/1</tt>
</p>
</li>
<li>
<p>
<tt>lookup/2</tt>
</p>
</li>
<li>
<p>
<tt>lookup_element/3</tt>
</p>
</li>
<li>
<p>
<tt>match/1</tt>
</p>
</li>
<li>
<p>
<tt>match/2</tt>
</p>
</li>
<li>
<p>
<tt>match/3</tt>
</p>
</li>
<li>
<p>
<tt>match_delete/2</tt>
</p>
</li>
<li>
<p>
<tt>match_object/1</tt>
</p>
</li>
<li>
<p>
<tt>match_object/2</tt>
</p>
</li>
<li>
<p>
<tt>match_object/3</tt>
</p>
</li>
<li>
<p>
<tt>member/2</tt>
</p>
</li>
<li>
<p>
<tt>new/2</tt>
</p>
</li>
<li>
<p>
<tt>next/2</tt>
</p>
</li>
<li>
<p>
<tt>prev/2</tt>
</p>
</li>
<li>
<p>
<tt>select/1</tt>
</p>
</li>
<li>
<p>
<tt>select/2</tt>
</p>
</li>
<li>
<p>
<tt>select/3</tt>
</p>
</li>
<li>
<p>
<tt>select_count/2</tt>
</p>
</li>
<li>
<p>
<tt>select_delete/2</tt>
</p>
</li>
<li>
<p>
<tt>select_reverse/1</tt>
</p>
</li>
<li>
<p>
<tt>select_reverse/2</tt>
</p>
</li>
<li>
<p>
<tt>select_reverse/3</tt>
</p>
</li>
<li>
<p>
<tt>tab2list/1</tt>
</p>
</li>
@ -127,7 +228,7 @@ please read further.</p>
<h3 id="_where_should_i_start">Where should I start?</h3>
<p>This README is the only bit of documentation right now.</p>
<p>The QC (a.k.a. QuickCheck, Proper, etc.) tests underneath the
<p>The QC (a.k.a. QuickCheck, PropEr, etc.) tests underneath the
"tests/qc" directory should be helpful for understanding the
specification and behavior of ETS and LETS. These QC tests also
illustrate several strategies for testing Erlang Driver-based and
@ -148,6 +249,11 @@ provides an ordered mapping from string keys to string values.</p>
<p>See <a href="http://code.google.com/p/leveldb/">http://code.google.com/p/leveldb/</a> for further details.</p>
<h3 id="_what_is_snappy">What is Snappy?</h3>
<p>Snappy is a fast compression/decompression library written at Google.</p>
<p>See <a href="http://code.google.com/p/snappy/">http://code.google.com/p/snappy/</a> for further details.</p>
<h2 id="_to_download">To download</h2>
@ -415,7 +521,36 @@ Run 5,000 QuickCheck tests
<pre><tt>$ cd working-directory-name/src/lib/lets/.eunit
$ erl -smp +A 5 -pz ../../sext/ebin -pz ../../qc/ebin
1> qc_statem_lets:run(5000).
1> qc_statem_lets:qc_run(5000).
....
OK, passed 5000 tests
9.022% {delete,ok}
7.800% {new,ok}
4.535% {match_delete,ok}
4.491% {lookup,ok}
4.399% {select,ok}
4.352% {select_delete,ok}
4.348% {tab2list,ok}
4.341% {member,ok}
4.334% {last,ok}
4.315% {foldl,ok}
4.308% {select_reverse,ok}
4.301% {select_count,ok}
4.293% {select31,ok}
4.264% {first,ok}
4.216% {foldr,ok}
4.202% {match_object,ok}
4.184% {match,ok}
4.056% {insert,ok}
3.997% {prev,ok}
3.774% {next,ok}
3.416% {lookup_element,{error,badarg}}
1.298% {insert_new,ok}
0.757% {lookup_element,ok}
0.516% {next,{error,badarg}}
0.483% {prev,{error,badarg}}
true
.......</tt></pre>
@ -424,7 +559,7 @@ $ erl -smp +A 5 -pz ../../sext/ebin -pz ../../qc/ebin
Tip
</td>
<td class="content">For testing LevelDB directly using the C bindings, try
<tt>qc_statemc_lets:run(5000)</tt>.</td>
<tt>qc_statemc_lets:qc_run(5000)</tt>.</td>
</tr></table>
</li>
@ -432,12 +567,12 @@ Tip
<h2 id="_to_test_proper">To test - Proper</h2>
<h2 id="_to_test_proper">To test - PropEr</h2>
<ol class="arabic">
<li>
<p>
Make sure Proper is in your Erlang code path. One simple way to
Make sure PropEr is in your Erlang code path. One simple way to
accomplish this is by adding the code path to your <tt>~/.erlang</tt>
resource file.
</p>
@ -448,7 +583,7 @@ Make sure Proper is in your Erlang code path. One simple way to
</li>
<li>
<p>
Compile for Proper
Compile for PropEr
</p>
@ -459,14 +594,43 @@ $ make compile-proper proper-compile</tt></pre>
</li>
<li>
<p>
Run 5,000 Proper tests
Run 5,000 PropEr tests
</p>
<pre><tt>$ cd working-directory-name/src/lib/lets/.eunit
$ erl -smp +A 5 -pz ../../sext/ebin -pz ../../qc/ebin
1> qc_statem_lets:run(5000).
1> qc_statem_lets:qc_run(5000).
....
OK: Passed 5000 test(s).
11% {new,ok}
8% {delete,ok}
4% {member,ok}
4% {select,ok}
4% {select_count,ok}
4% {select_reverse,ok}
4% {lookup,ok}
4% {match_object,ok}
4% {tab2list,ok}
4% {last,ok}
4% {match,ok}
4% {foldl,ok}
4% {match_delete,ok}
3% {prev,ok}
3% {select31,ok}
3% {select_delete,ok}
3% {foldr,ok}
3% {insert,ok}
3% {first,ok}
3% {next,ok}
3% {lookup_element,{error,badarg}}
1% {insert_new,ok}
0% {prev,{error,badarg}}
0% {lookup_element,ok}
0% {next,{error,badarg}}
true
.......</tt></pre>
</li>
@ -484,7 +648,7 @@ Documentation
<ul>
<li>
<p>
Explain how to run QuickCheck/Proper tests using a new rebar
Explain how to run QuickCheck/PropEr tests using a new rebar
plugin.
</p>
</li>
@ -549,6 +713,13 @@ New APIs (TBD)
(<a href="http://code.google.com/p/leveldb/issues/detail?id=43">http://code.google.com/p/leveldb/issues/detail?id=43</a>)
</p>
</li>
<li>
<p>
Add custom (i.e. not supported by native ETS) APIs for providing
access to LevelDB's iterators for <tt>drv</tt> and <tt>nif</tt> backend
implementations.
</p>
</li>
</ul>
</li>
<li>

View file

@ -16,6 +16,14 @@
###<a name="type-cont">cont()</a>##
__abstract datatype__: `cont()`
###<a name="type-db_opts">db_opts()</a>##
@ -56,6 +64,14 @@
###<a name="type-item">item()</a>##
<pre>item() = owner | name | named_table | type | keypos | protection | compressed | async | memory | size</pre>
###<a name="type-key">key()</a>##
@ -64,6 +80,30 @@
###<a name="type-limit">limit()</a>##
<pre>limit() = pos_integer()</pre>
###<a name="type-match">match()</a>##
<pre>match() = term()</pre>
###<a name="type-name">name()</a>##
<pre>name() = atom()</pre>
###<a name="type-object">object()</a>##
@ -80,6 +120,36 @@
###<a name="type-pattern">pattern()</a>##
<pre>pattern() = atom() | tuple()</pre>
<pre><tt>ets:match_pattern() is not exported!</tt></pre>
###<a name="type-pos">pos()</a>##
<pre>pos() = pos_integer()</pre>
###<a name="type-spec">spec()</a>##
<pre>spec() = [ets:match_spec()](ets.md#type-match_spec)</pre>
###<a name="type-tab">tab()</a>##
@ -94,8 +164,8 @@ __abstract datatype__: `tab()`
guaranteed to be atomic and isolated. This function only applies
to the <tt>ets</tt> implementation.</p>.</td></tr><tr><td valign="top"><a href="#destroy-2">destroy/2</a></td><td><p>Destroy the contents of the specified table. This function
only applies to <tt>driver</tt> and <tt>nif</tt> implementations.</p>.</td></tr><tr><td valign="top"><a href="#first-1">first/1</a></td><td><p>Returns the first key <tt>Key</tt> in the table <tt>Tab</tt>. If the table
is empty, <tt><em>$end_of_table</em></tt> will be returned.</p>.</td></tr><tr><td valign="top"><a href="#info-2">info/2</a></td><td><p>Returns information about the table <tt>Tab</tt> as a list of <tt>{Item,
Value}</tt> tuples.</p>
is empty, <tt><em>$end_of_table</em></tt> will be returned.</p>.</td></tr><tr><td valign="top"><a href="#foldl-3">foldl/3</a></td><td><p>Fold from left to right over the elements of the table.</p>.</td></tr><tr><td valign="top"><a href="#foldr-3">foldr/3</a></td><td><p>Fold from right to left over the elements of the table.</p>.</td></tr><tr><td valign="top"><a href="#info-1">info/1</a></td><td><p>Returns information about the table <tt>Tab</tt> as a list of <tt>{Item,
Value}</tt> tuples.</p>.</td></tr><tr><td valign="top"><a href="#info-2">info/2</a></td><td><p>Returns the information associated with <tt>Item</tt> for the table <tt>Tab</tt>.</p>
<pre><tt>Valid +Item+ options are:</tt></pre>
@ -152,11 +222,21 @@ is empty, <tt><em>$end_of_table</em></tt> will be returned.</p>.</td></tr><tr><t
</p>
</li>
</ul>.</td></tr><tr><td valign="top"><a href="#insert-2">insert/2</a></td><td><p>Inserts the object or all of the objects in the list
<tt>ObjectOrObjects</tt> into the table <tt>Tab</tt>.</p>.</td></tr><tr><td valign="top"><a href="#insert_new-2">insert_new/2</a></td><td><p>This function works exactly like <tt>insert/2</tt>, with the
<tt>ObjOrObjs</tt> into the table <tt>Tab</tt>.</p>.</td></tr><tr><td valign="top"><a href="#insert_new-2">insert_new/2</a></td><td><p>This function works exactly like <tt>insert/2</tt>, with the
exception that instead of overwriting objects with the same key, it
simply returns false. This function only applies to the <tt>ets</tt>
implementation.</p>.</td></tr><tr><td valign="top"><a href="#lookup-2">lookup/2</a></td><td><p>Returns a list of all objects with the key <tt>Key</tt> in the table
<tt>Tab</tt>.</p>.</td></tr><tr><td valign="top"><a href="#new-2">new/2</a></td><td><p>Creates a new table and returns a table identifier which can
implementation.</p>.</td></tr><tr><td valign="top"><a href="#last-1">last/1</a></td><td><p>Returns the last key <tt>Key</tt> in the table <tt>Tab</tt>. If the table
is empty, <tt><em>$end_of_table</em></tt> will be returned.</p>.</td></tr><tr><td valign="top"><a href="#lookup-2">lookup/2</a></td><td><p>Returns a list of all objects with the key <tt>Key</tt> in the table
<tt>Tab</tt>.</p>.</td></tr><tr><td valign="top"><a href="#lookup_element-3">lookup_element/3</a></td><td><p>Returns the <tt>Pos</tt>:th element of the object with the key <tt>Key</tt>
in the table <tt>Tab</tt>.</p>.</td></tr><tr><td valign="top"><a href="#match-1">match/1</a></td><td><p>Continues a match started with <tt>match/3</tt>.</p>.</td></tr><tr><td valign="top"><a href="#match-2">match/2</a></td><td><p>Matches the objects in the table <tt>Tab</tt> against the pattern
<tt>Pattern</tt>.</p>.</td></tr><tr><td valign="top"><a href="#match-3">match/3</a></td><td><p>Matches the objects in the table <tt>Tab</tt> against the pattern
<tt>Pattern</tt> and returns a limited (<tt>Limit</tt>) number of matching
objects.</p>.</td></tr><tr><td valign="top"><a href="#match_delete-2">match_delete/2</a></td><td><p>Deletes all objects which match the pattern <tt>Pattern</tt> from the
table <tt>Tab</tt>.</p>.</td></tr><tr><td valign="top"><a href="#match_object-1">match_object/1</a></td><td><p>Continues a match started with <tt>match_object/3</tt>.</p>.</td></tr><tr><td valign="top"><a href="#match_object-2">match_object/2</a></td><td><p>Matches the objects in the table <tt>Tab</tt> against the pattern
<tt>Pattern</tt>.</p>.</td></tr><tr><td valign="top"><a href="#match_object-3">match_object/3</a></td><td><p>Matches the objects in the table <tt>Tab</tt> against the pattern
<tt>Pattern</tt> and returns a limited (<tt>Limit</tt>) number of matching
objects.</p>.</td></tr><tr><td valign="top"><a href="#member-2">member/2</a></td><td><p>Returns <tt>true</tt> if one or more elements in the table <tt>Tab</tt> has
the key <tt>Key</tt>, <tt>false</tt> otherwise.</p>.</td></tr><tr><td valign="top"><a href="#new-2">new/2</a></td><td><p>Creates a new table and returns a table identifier which can
be used in subsequent operations. The table identifier can be sent
to other processes so that a table can be shared between different
processes within a node.</p>
@ -343,11 +423,20 @@ considered complete. The default is <tt>false</tt>.
</li>
</ul>.</td></tr><tr><td valign="top"><a href="#next-2">next/2</a></td><td><p>Returns the next key <tt>Key2</tt>, following the key <tt>Key1</tt> in the
table <tt>Tab</tt>. If there is no next key, <tt><em>$end_of_table</em></tt> is
returned.</p>.</td></tr><tr><td valign="top"><a href="#prev-2">prev/2</a></td><td><p>Returns the previous key <tt>Key2</tt>, following the key <tt>Key1</tt> in
the table <tt>Tab</tt>. If there is no previous key, <tt><em>$end_of_table</em></tt> is
returned.</p>.</td></tr><tr><td valign="top"><a href="#repair-2">repair/2</a></td><td><p>If a table cannot be opened, you may attempt to call this
method to resurrect as much of the contents of the table as
possible. Some data may be lost, so be careful when calling this
function on a table that contains important information. This
function only applies to <tt>driver</tt> and <tt>nif</tt> implementations.</p>.</td></tr><tr><td valign="top"><a href="#tab2list-1">tab2list/1</a></td><td><p>Returns a list of all objects in the table <tt>Tab</tt>. The
function only applies to <tt>driver</tt> and <tt>nif</tt> implementations.</p>.</td></tr><tr><td valign="top"><a href="#select-1">select/1</a></td><td><p>Continues a select started with <tt>select/3</tt>.</p>.</td></tr><tr><td valign="top"><a href="#select-2">select/2</a></td><td><p>Matches the objects in the table <tt>Tab</tt> against the spec
<tt>Spec</tt>.</p>.</td></tr><tr><td valign="top"><a href="#select-3">select/3</a></td><td><p>Matches the objects in the table <tt>Tab</tt> against the spec <tt>Spec</tt>
and returns a limited (<tt>Limit</tt>) number of matching objects.</p>.</td></tr><tr><td valign="top"><a href="#select_count-2">select_count/2</a></td><td><p>Counts all objects which match the spec <tt>Spec</tt> from the
table <tt>Tab</tt> and returns the number matched.</p>.</td></tr><tr><td valign="top"><a href="#select_delete-2">select_delete/2</a></td><td><p>Deletes all objects which match the spec <tt>Spec</tt> from the
table <tt>Tab</tt> and returns the number deleted.</p>.</td></tr><tr><td valign="top"><a href="#select_reverse-1">select_reverse/1</a></td><td><p>Continues a select reverse started with <tt>select_reverse/3</tt>.</p>.</td></tr><tr><td valign="top"><a href="#select_reverse-2">select_reverse/2</a></td><td><p>Matches in reverse the objects in the table <tt>Tab</tt> against the
spec <tt>Spec</tt>.</p>.</td></tr><tr><td valign="top"><a href="#select_reverse-3">select_reverse/3</a></td><td><p>Matches in reverse the objects in the table <tt>Tab</tt> against the
spec <tt>Spec</tt> and returns a limited (<tt>Limit</tt>) number of matching
objects.</p>.</td></tr><tr><td valign="top"><a href="#tab2list-1">tab2list/1</a></td><td><p>Returns a list of all objects in the table <tt>Tab</tt>. The
operation is <strong>not</strong> guaranteed to be atomic and isolated.</p>.</td></tr></table>
@ -412,7 +501,7 @@ __See also:__ [ets:delete_all_objects/1](ets.md#delete_all_objects-1).<a name="d
<pre>destroy(Name::atom(), Options::[opts()](#type-opts)) -&gt; true</pre>
<pre>destroy(Name::[name()](#type-name), Opts::[opts()](#type-opts)) -&gt; true</pre>
<br></br>
@ -437,14 +526,44 @@ only applies to <tt>driver</tt> and <tt>nif</tt> implementations.</p>
is empty, <tt><em>$end_of_table</em></tt> will be returned.</p>
__See also:__ [ets:first/1](ets.md#first-1).<a name="info-2"></a>
__See also:__ [ets:first/1](ets.md#first-1).<a name="foldl-3"></a>
###info/2##
###foldl/3##
<pre>info(Tab::[tab()](#type-tab), Item::atom()) -&gt; term()</pre>
<pre>foldl(Fun, Acc0::term(), Tab::[tab()](#type-tab)) -&gt; Acc1::term()</pre>
<ul class="definitions"><li><pre>Fun = fun((Element::term(), AccIn::term()) -&gt; AccOut::term())</pre></li></ul>
<p>Fold from left to right over the elements of the table.</p>
__See also:__ [ets:foldl/3](ets.md#foldl-3).<a name="foldr-3"></a>
###foldr/3##
<pre>foldr(Fun, Acc0::term(), Tab::[tab()](#type-tab)) -&gt; Acc1::term()</pre>
<ul class="definitions"><li><pre>Fun = fun((Element::term(), AccIn::term()) -&gt; AccOut::term())</pre></li></ul>
<p>Fold from right to left over the elements of the table.</p>
__See also:__ [ets:foldr/3](ets.md#foldr-3).<a name="info-1"></a>
###info/1##
<pre>info(Tab::[tab()](#type-tab)) -&gt; [{[item()](#type-item), term()}]</pre>
<br></br>
@ -454,6 +573,22 @@ __See also:__ [ets:first/1](ets.md#first-1).<a name="info-2"></a>
Value}</tt> tuples.</p>
__See also:__ [info/2](#info-2).<a name="info-2"></a>
###info/2##
<pre>info(Tab::[tab()](#type-tab), Item::[item()](#type-item)) -&gt; term()</pre>
<br></br>
<p>Returns the information associated with <tt>Item</tt> for the table <tt>Tab</tt>.</p>
<pre><tt>Valid +Item+ options are:</tt></pre>
<ul>
@ -517,14 +652,14 @@ __See also:__ [ets:info/2](ets.md#info-2).<a name="insert-2"></a>
<pre>insert(Tab::[tab()](#type-tab), ObjectOrObjects::[object()](#type-object) | [[object()](#type-object)]) -&gt; true</pre>
<pre>insert(Tab::[tab()](#type-tab), ObjOrObjs::[object()](#type-object) | [[object()](#type-object)]) -&gt; true</pre>
<br></br>
<p>Inserts the object or all of the objects in the list
<tt>ObjectOrObjects</tt> into the table <tt>Tab</tt>.</p>
<tt>ObjOrObjs</tt> into the table <tt>Tab</tt>.</p>
__See also:__ [ets:insert/2](ets.md#insert-2).<a name="insert_new-2"></a>
@ -534,7 +669,7 @@ __See also:__ [ets:insert/2](ets.md#insert-2).<a name="insert_new-2"></a>
<pre>insert_new(Tab::[tab()](#type-tab), ObjectOrObjects::[object()](#type-object) | [[object()](#type-object)]) -&gt; true</pre>
<pre>insert_new(Tab::[tab()](#type-tab), ObjOrObjs::[object()](#type-object) | [[object()](#type-object)]) -&gt; true</pre>
<br></br>
@ -546,7 +681,24 @@ simply returns false. This function only applies to the <tt>ets</tt>
implementation.</p>
__See also:__ [ets:insert_new/2](ets.md#insert_new-2).<a name="lookup-2"></a>
__See also:__ [ets:insert_new/2](ets.md#insert_new-2).<a name="last-1"></a>
###last/1##
<pre>last(Tab::[tab()](#type-tab)) -&gt; [key()](#type-key) | '$end_of_table'</pre>
<br></br>
<p>Returns the last key <tt>Key</tt> in the table <tt>Tab</tt>. If the table
is empty, <tt><em>$end_of_table</em></tt> will be returned.</p>
__See also:__ [ets:last/1](ets.md#last-1).<a name="lookup-2"></a>
###lookup/2##
@ -563,14 +715,167 @@ __See also:__ [ets:insert_new/2](ets.md#insert_new-2).<a name="lookup-2"></a>
<tt>Tab</tt>.</p>
__See also:__ [ets:lookup/2](ets.md#lookup-2).<a name="new-2"></a>
__See also:__ [ets:lookup/2](ets.md#lookup-2).<a name="lookup_element-3"></a>
###lookup_element/3##
<pre>lookup_element(Tab::[tab()](#type-tab), Key::[key()](#type-key), Pos::[pos()](#type-pos)) -&gt; term()</pre>
<br></br>
<p>Returns the <tt>Pos</tt>:th element of the object with the key <tt>Key</tt>
in the table <tt>Tab</tt>.</p>
__See also:__ [ets:lookup_element/3](ets.md#lookup_element-3).<a name="match-1"></a>
###match/1##
<pre>match(X1::[cont()](#type-cont)) -&gt; {[[match()](#type-match)], [cont()](#type-cont)} | '$end_of_table'</pre>
<br></br>
<p>Continues a match started with <tt>match/3</tt>.</p>
__See also:__ [ets:match/1](ets.md#match-1).<a name="match-2"></a>
###match/2##
<pre>match(Tab::[tab()](#type-tab), Pattern::[pattern()](#type-pattern)) -&gt; [[match()](#type-match)]</pre>
<br></br>
<p>Matches the objects in the table <tt>Tab</tt> against the pattern
<tt>Pattern</tt>.</p>
__See also:__ [ets:match/2](ets.md#match-2).<a name="match-3"></a>
###match/3##
<pre>match(Tab::[tab()](#type-tab), Pattern::[pattern()](#type-pattern), Limit::[limit()](#type-limit)) -&gt; {[[match()](#type-match)], [cont()](#type-cont)} | '$end_of_table'</pre>
<br></br>
<p>Matches the objects in the table <tt>Tab</tt> against the pattern
<tt>Pattern</tt> and returns a limited (<tt>Limit</tt>) number of matching
objects.</p>
__See also:__ [ets:match/3](ets.md#match-3).<a name="match_delete-2"></a>
###match_delete/2##
<pre>match_delete(Tab::[tab()](#type-tab), Pattern::[pattern()](#type-pattern)) -&gt; true</pre>
<br></br>
<p>Deletes all objects which match the pattern <tt>Pattern</tt> from the
table <tt>Tab</tt>.</p>
__See also:__ [ets:match_delete/2](ets.md#match_delete-2).<a name="match_object-1"></a>
###match_object/1##
<pre>match_object(X1::[cont()](#type-cont)) -&gt; {[[match()](#type-match)], [cont()](#type-cont)} | '$end_of_table'</pre>
<br></br>
<p>Continues a match started with <tt>match_object/3</tt>.</p>
__See also:__ [ets:match_object/1](ets.md#match_object-1).<a name="match_object-2"></a>
###match_object/2##
<pre>match_object(Tab::[tab()](#type-tab), Pattern::[pattern()](#type-pattern)) -&gt; [[match()](#type-match)]</pre>
<br></br>
<p>Matches the objects in the table <tt>Tab</tt> against the pattern
<tt>Pattern</tt>.</p>
__See also:__ [ets:match_object/2](ets.md#match_object-2).<a name="match_object-3"></a>
###match_object/3##
<pre>match_object(Tab::[tab()](#type-tab), Pattern::[pattern()](#type-pattern), Limit::[limit()](#type-limit)) -&gt; {[[match()](#type-match)], [cont()](#type-cont)} | '$end_of_table'</pre>
<br></br>
<p>Matches the objects in the table <tt>Tab</tt> against the pattern
<tt>Pattern</tt> and returns a limited (<tt>Limit</tt>) number of matching
objects.</p>
__See also:__ [ets:match_object/3](ets.md#match_object-3).<a name="member-2"></a>
###member/2##
<pre>member(Tab::[tab()](#type-tab), Key::[key()](#type-key)) -&gt; true | false</pre>
<br></br>
<p>Returns <tt>true</tt> if one or more elements in the table <tt>Tab</tt> has
the key <tt>Key</tt>, <tt>false</tt> otherwise.</p>
__See also:__ [ets:member/2](ets.md#member-2).<a name="new-2"></a>
###new/2##
<pre>new(Name::atom(), Options::[opts()](#type-opts)) -&gt; [tab()](#type-tab)</pre>
<pre>new(Name::[name()](#type-name), Opts::[opts()](#type-opts)) -&gt; [tab()](#type-tab)</pre>
<br></br>
@ -782,14 +1087,32 @@ table <tt>Tab</tt>. If there is no next key, <tt><em>$end_of_table</em></tt> is
returned.</p>
__See also:__ [ets:next/2](ets.md#next-2).<a name="repair-2"></a>
__See also:__ [ets:next/2](ets.md#next-2).<a name="prev-2"></a>
###prev/2##
<pre>prev(Tab::[tab()](#type-tab), Key::[key()](#type-key)) -&gt; [key()](#type-key) | '$end_of_table'</pre>
<br></br>
<p>Returns the previous key <tt>Key2</tt>, following the key <tt>Key1</tt> in
the table <tt>Tab</tt>. If there is no previous key, <tt><em>$end_of_table</em></tt> is
returned.</p>
__See also:__ [ets:prev/2](ets.md#prev-2).<a name="repair-2"></a>
###repair/2##
<pre>repair(Name::atom(), Options::[opts()](#type-opts)) -&gt; true</pre>
<pre>repair(Name::[name()](#type-name), Opts::[opts()](#type-opts)) -&gt; true</pre>
<br></br>
@ -800,7 +1123,142 @@ method to resurrect as much of the contents of the table as
possible. Some data may be lost, so be careful when calling this
function on a table that contains important information. This
function only applies to <tt>driver</tt> and <tt>nif</tt> implementations.</p>
<a name="tab2list-1"></a>
<a name="select-1"></a>
###select/1##
<pre>select(X1::[cont()](#type-cont)) -&gt; {[[match()](#type-match)], [cont()](#type-cont)} | '$end_of_table'</pre>
<br></br>
<p>Continues a select started with <tt>select/3</tt>.</p>
__See also:__ [ets:select/1](ets.md#select-1).<a name="select-2"></a>
###select/2##
<pre>select(Tab::[tab()](#type-tab), Spec::[spec()](#type-spec)) -&gt; [[match()](#type-match)]</pre>
<br></br>
<p>Matches the objects in the table <tt>Tab</tt> against the spec
<tt>Spec</tt>.</p>
__See also:__ [ets:select/2](ets.md#select-2).<a name="select-3"></a>
###select/3##
<pre>select(Tab::[tab()](#type-tab), Spec::[spec()](#type-spec), Limit::[limit()](#type-limit)) -&gt; {[[match()](#type-match)], [cont()](#type-cont)} | '$end_of_table'</pre>
<br></br>
<p>Matches the objects in the table <tt>Tab</tt> against the spec <tt>Spec</tt>
and returns a limited (<tt>Limit</tt>) number of matching objects.</p>
__See also:__ [ets:select/3](ets.md#select-3).<a name="select_count-2"></a>
###select_count/2##
<pre>select_count(Tab::[tab()](#type-tab), Spec::[pattern()](#type-pattern)) -&gt; pos_integer()</pre>
<br></br>
<p>Counts all objects which match the spec <tt>Spec</tt> from the
table <tt>Tab</tt> and returns the number matched.</p>
__See also:__ [ets:select_count/2](ets.md#select_count-2).<a name="select_delete-2"></a>
###select_delete/2##
<pre>select_delete(Tab::[tab()](#type-tab), Spec::[pattern()](#type-pattern)) -&gt; pos_integer()</pre>
<br></br>
<p>Deletes all objects which match the spec <tt>Spec</tt> from the
table <tt>Tab</tt> and returns the number deleted.</p>
__See also:__ [ets:select_delete/2](ets.md#select_delete-2).<a name="select_reverse-1"></a>
###select_reverse/1##
<pre>select_reverse(X1::[cont()](#type-cont)) -&gt; {[[match()](#type-match)], [cont()](#type-cont)} | '$end_of_table'</pre>
<br></br>
<p>Continues a select reverse started with <tt>select_reverse/3</tt>.</p>
__See also:__ [ets:select_reverse/1](ets.md#select_reverse-1).<a name="select_reverse-2"></a>
###select_reverse/2##
<pre>select_reverse(Tab::[tab()](#type-tab), Spec::[spec()](#type-spec)) -&gt; [[match()](#type-match)]</pre>
<br></br>
<p>Matches in reverse the objects in the table <tt>Tab</tt> against the
spec <tt>Spec</tt>.</p>
__See also:__ [ets:select_reverse/2](ets.md#select_reverse-2).<a name="select_reverse-3"></a>
###select_reverse/3##
<pre>select_reverse(Tab::[tab()](#type-tab), Spec::[spec()](#type-spec), Limit::[limit()](#type-limit)) -&gt; {[[match()](#type-match)], [cont()](#type-cont)} | '$end_of_table'</pre>
<br></br>
<p>Matches in reverse the objects in the table <tt>Tab</tt> against the
spec <tt>Spec</tt> and returns a limited (<tt>Limit</tt>) number of matching
objects.</p>
__See also:__ [ets:select_reverse/3](ets.md#select_reverse-3).<a name="tab2list-1"></a>
###tab2list/1##

View file

@ -12,13 +12,22 @@
##Function Index##
<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#delete-2">delete/2</a></td><td></td></tr><tr><td valign="top"><a href="#delete-3">delete/3</a></td><td></td></tr><tr><td valign="top"><a href="#delete_all_objects-2">delete_all_objects/2</a></td><td></td></tr><tr><td valign="top"><a href="#destroy-4">destroy/4</a></td><td></td></tr><tr><td valign="top"><a href="#first-2">first/2</a></td><td></td></tr><tr><td valign="top"><a href="#info_memory-2">info_memory/2</a></td><td></td></tr><tr><td valign="top"><a href="#info_size-2">info_size/2</a></td><td></td></tr><tr><td valign="top"><a href="#insert-3">insert/3</a></td><td></td></tr><tr><td valign="top"><a href="#insert_new-3">insert_new/3</a></td><td></td></tr><tr><td valign="top"><a href="#lookup-3">lookup/3</a></td><td></td></tr><tr><td valign="top"><a href="#next-3">next/3</a></td><td></td></tr><tr><td valign="top"><a href="#open-4">open/4</a></td><td></td></tr><tr><td valign="top"><a href="#repair-4">repair/4</a></td><td></td></tr><tr><td valign="top"><a href="#tab2list-2">tab2list/2</a></td><td></td></tr></table>
<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#delete-1">delete/1</a></td><td></td></tr><tr><td valign="top"><a href="#delete-2">delete/2</a></td><td></td></tr><tr><td valign="top"><a href="#delete_all_objects-1">delete_all_objects/1</a></td><td></td></tr><tr><td valign="top"><a href="#destroy-4">destroy/4</a></td><td></td></tr><tr><td valign="top"><a href="#first-1">first/1</a></td><td></td></tr><tr><td valign="top"><a href="#foldl-3">foldl/3</a></td><td></td></tr><tr><td valign="top"><a href="#foldr-3">foldr/3</a></td><td></td></tr><tr><td valign="top"><a href="#info_memory-1">info_memory/1</a></td><td></td></tr><tr><td valign="top"><a href="#info_size-1">info_size/1</a></td><td></td></tr><tr><td valign="top"><a href="#insert-2">insert/2</a></td><td></td></tr><tr><td valign="top"><a href="#insert_new-2">insert_new/2</a></td><td></td></tr><tr><td valign="top"><a href="#last-1">last/1</a></td><td></td></tr><tr><td valign="top"><a href="#lookup-2">lookup/2</a></td><td></td></tr><tr><td valign="top"><a href="#lookup_element-3">lookup_element/3</a></td><td></td></tr><tr><td valign="top"><a href="#match-1">match/1</a></td><td></td></tr><tr><td valign="top"><a href="#match-2">match/2</a></td><td></td></tr><tr><td valign="top"><a href="#match-3">match/3</a></td><td></td></tr><tr><td valign="top"><a href="#match_delete-2">match_delete/2</a></td><td></td></tr><tr><td valign="top"><a href="#match_object-1">match_object/1</a></td><td></td></tr><tr><td valign="top"><a href="#match_object-2">match_object/2</a></td><td></td></tr><tr><td valign="top"><a href="#match_object-3">match_object/3</a></td><td></td></tr><tr><td valign="top"><a href="#member-2">member/2</a></td><td></td></tr><tr><td valign="top"><a href="#next-2">next/2</a></td><td></td></tr><tr><td valign="top"><a href="#open-4">open/4</a></td><td></td></tr><tr><td valign="top"><a href="#prev-2">prev/2</a></td><td></td></tr><tr><td valign="top"><a href="#repair-4">repair/4</a></td><td></td></tr><tr><td valign="top"><a href="#select-1">select/1</a></td><td></td></tr><tr><td valign="top"><a href="#select-2">select/2</a></td><td></td></tr><tr><td valign="top"><a href="#select-3">select/3</a></td><td></td></tr><tr><td valign="top"><a href="#select_count-2">select_count/2</a></td><td></td></tr><tr><td valign="top"><a href="#select_delete-2">select_delete/2</a></td><td></td></tr><tr><td valign="top"><a href="#select_reverse-1">select_reverse/1</a></td><td></td></tr><tr><td valign="top"><a href="#select_reverse-2">select_reverse/2</a></td><td></td></tr><tr><td valign="top"><a href="#select_reverse-3">select_reverse/3</a></td><td></td></tr><tr><td valign="top"><a href="#tab2list-1">tab2list/1</a></td><td></td></tr></table>
<a name="functions"></a>
##Function Details##
<a name="delete-1"></a>
###delete/1##
`delete(Tab) -> any()`
<a name="delete-2"></a>
###delete/2##
@ -26,25 +35,16 @@
`delete(Tab, Impl) -> any()`
`delete(Tab, Key) -> any()`
<a name="delete-3"></a>
<a name="delete_all_objects-1"></a>
###delete/3##
###delete_all_objects/1##
`delete(Tab, Impl, Key) -> any()`
<a name="delete_all_objects-2"></a>
###delete_all_objects/2##
`delete_all_objects(Tab, Impl) -> any()`
`delete_all_objects(Tab) -> any()`
<a name="destroy-4"></a>
@ -55,68 +55,176 @@
`destroy(Tab, Options, ReadOptions, WriteOptions) -> any()`
<a name="first-2"></a>
<a name="first-1"></a>
###first/2##
###first/1##
`first(Tab, Impl) -> any()`
`first(Tab) -> any()`
<a name="info_memory-2"></a>
<a name="foldl-3"></a>
###info_memory/2##
###foldl/3##
`info_memory(Tab, Impl) -> any()`
`foldl(Fun, Acc0, Tab) -> any()`
<a name="info_size-2"></a>
<a name="foldr-3"></a>
###info_size/2##
###foldr/3##
`info_size(Tab, Impl) -> any()`
`foldr(Fun, Acc0, Tab) -> any()`
<a name="insert-3"></a>
<a name="info_memory-1"></a>
###insert/3##
###info_memory/1##
`insert(Tab, Impl, Object) -> any()`
`info_memory(Tab) -> any()`
<a name="insert_new-3"></a>
<a name="info_size-1"></a>
###insert_new/3##
###info_size/1##
`insert_new(Tab, Impl, Object) -> any()`
`info_size(Tab) -> any()`
<a name="lookup-3"></a>
<a name="insert-2"></a>
###lookup/3##
###insert/2##
`lookup(Tab, Impl, Key) -> any()`
`insert(Tab, Object) -> any()`
<a name="next-3"></a>
<a name="insert_new-2"></a>
###next/3##
###insert_new/2##
`next(Tab, Impl, Key) -> any()`
`insert_new(Tab, Object) -> any()`
<a name="last-1"></a>
###last/1##
`last(Tab) -> any()`
<a name="lookup-2"></a>
###lookup/2##
`lookup(Tab, Key) -> any()`
<a name="lookup_element-3"></a>
###lookup_element/3##
`lookup_element(Tab, Key, Pos) -> any()`
<a name="match-1"></a>
###match/1##
`match(Cont) -> any()`
<a name="match-2"></a>
###match/2##
`match(Tab, Pattern) -> any()`
<a name="match-3"></a>
###match/3##
`match(Tab, Pattern, Limit) -> any()`
<a name="match_delete-2"></a>
###match_delete/2##
`match_delete(Tab, Pattern) -> any()`
<a name="match_object-1"></a>
###match_object/1##
`match_object(Cont) -> any()`
<a name="match_object-2"></a>
###match_object/2##
`match_object(Tab, Pattern) -> any()`
<a name="match_object-3"></a>
###match_object/3##
`match_object(Tab, Pattern, Limit) -> any()`
<a name="member-2"></a>
###member/2##
`member(Tab, Key) -> any()`
<a name="next-2"></a>
###next/2##
`next(Tab, Key) -> any()`
<a name="open-4"></a>
@ -127,6 +235,15 @@
`open(Tab, Options, ReadOptions, WriteOptions) -> any()`
<a name="prev-2"></a>
###prev/2##
`prev(Tab, Key) -> any()`
<a name="repair-4"></a>
###repair/4##
@ -136,12 +253,84 @@
`repair(Tab, Options, ReadOptions, WriteOptions) -> any()`
<a name="tab2list-2"></a>
<a name="select-1"></a>
###tab2list/2##
###select/1##
`tab2list(Tab, Impl) -> any()`
`select(Cont0) -> any()`
<a name="select-2"></a>
###select/2##
`select(Tab, Spec) -> any()`
<a name="select-3"></a>
###select/3##
`select(Tab, Spec, Limit) -> any()`
<a name="select_count-2"></a>
###select_count/2##
`select_count(Tab, Spec) -> any()`
<a name="select_delete-2"></a>
###select_delete/2##
`select_delete(Tab, Spec) -> any()`
<a name="select_reverse-1"></a>
###select_reverse/1##
`select_reverse(Cont0) -> any()`
<a name="select_reverse-2"></a>
###select_reverse/2##
`select_reverse(Tab, Spec) -> any()`
<a name="select_reverse-3"></a>
###select_reverse/3##
`select_reverse(Tab, Spec, Limit) -> any()`
<a name="tab2list-1"></a>
###tab2list/1##
`tab2list(Tab) -> any()`

View file

@ -12,13 +12,22 @@
##Function Index##
<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#delete-2">delete/2</a></td><td></td></tr><tr><td valign="top"><a href="#delete-3">delete/3</a></td><td></td></tr><tr><td valign="top"><a href="#delete_all_objects-2">delete_all_objects/2</a></td><td></td></tr><tr><td valign="top"><a href="#destroy-1">destroy/1</a></td><td></td></tr><tr><td valign="top"><a href="#first-2">first/2</a></td><td></td></tr><tr><td valign="top"><a href="#info_memory-2">info_memory/2</a></td><td></td></tr><tr><td valign="top"><a href="#info_size-2">info_size/2</a></td><td></td></tr><tr><td valign="top"><a href="#insert-3">insert/3</a></td><td></td></tr><tr><td valign="top"><a href="#insert_new-3">insert_new/3</a></td><td></td></tr><tr><td valign="top"><a href="#lookup-3">lookup/3</a></td><td></td></tr><tr><td valign="top"><a href="#next-3">next/3</a></td><td></td></tr><tr><td valign="top"><a href="#open-1">open/1</a></td><td></td></tr><tr><td valign="top"><a href="#repair-1">repair/1</a></td><td></td></tr><tr><td valign="top"><a href="#tab2list-2">tab2list/2</a></td><td></td></tr></table>
<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#delete-1">delete/1</a></td><td></td></tr><tr><td valign="top"><a href="#delete-2">delete/2</a></td><td></td></tr><tr><td valign="top"><a href="#delete_all_objects-1">delete_all_objects/1</a></td><td></td></tr><tr><td valign="top"><a href="#destroy-1">destroy/1</a></td><td></td></tr><tr><td valign="top"><a href="#first-1">first/1</a></td><td></td></tr><tr><td valign="top"><a href="#foldl-3">foldl/3</a></td><td></td></tr><tr><td valign="top"><a href="#foldr-3">foldr/3</a></td><td></td></tr><tr><td valign="top"><a href="#info_memory-1">info_memory/1</a></td><td></td></tr><tr><td valign="top"><a href="#info_size-1">info_size/1</a></td><td></td></tr><tr><td valign="top"><a href="#insert-2">insert/2</a></td><td></td></tr><tr><td valign="top"><a href="#insert_new-2">insert_new/2</a></td><td></td></tr><tr><td valign="top"><a href="#last-1">last/1</a></td><td></td></tr><tr><td valign="top"><a href="#lookup-2">lookup/2</a></td><td></td></tr><tr><td valign="top"><a href="#lookup_element-3">lookup_element/3</a></td><td></td></tr><tr><td valign="top"><a href="#match-1">match/1</a></td><td></td></tr><tr><td valign="top"><a href="#match-2">match/2</a></td><td></td></tr><tr><td valign="top"><a href="#match-3">match/3</a></td><td></td></tr><tr><td valign="top"><a href="#match_delete-2">match_delete/2</a></td><td></td></tr><tr><td valign="top"><a href="#match_object-1">match_object/1</a></td><td></td></tr><tr><td valign="top"><a href="#match_object-2">match_object/2</a></td><td></td></tr><tr><td valign="top"><a href="#match_object-3">match_object/3</a></td><td></td></tr><tr><td valign="top"><a href="#member-2">member/2</a></td><td></td></tr><tr><td valign="top"><a href="#next-2">next/2</a></td><td></td></tr><tr><td valign="top"><a href="#open-1">open/1</a></td><td></td></tr><tr><td valign="top"><a href="#prev-2">prev/2</a></td><td></td></tr><tr><td valign="top"><a href="#repair-1">repair/1</a></td><td></td></tr><tr><td valign="top"><a href="#select-1">select/1</a></td><td></td></tr><tr><td valign="top"><a href="#select-2">select/2</a></td><td></td></tr><tr><td valign="top"><a href="#select-3">select/3</a></td><td></td></tr><tr><td valign="top"><a href="#select_count-2">select_count/2</a></td><td></td></tr><tr><td valign="top"><a href="#select_delete-2">select_delete/2</a></td><td></td></tr><tr><td valign="top"><a href="#select_reverse-1">select_reverse/1</a></td><td></td></tr><tr><td valign="top"><a href="#select_reverse-2">select_reverse/2</a></td><td></td></tr><tr><td valign="top"><a href="#select_reverse-3">select_reverse/3</a></td><td></td></tr><tr><td valign="top"><a href="#tab2list-1">tab2list/1</a></td><td></td></tr></table>
<a name="functions"></a>
##Function Details##
<a name="delete-1"></a>
###delete/1##
`delete(Tab) -> any()`
<a name="delete-2"></a>
###delete/2##
@ -26,25 +35,16 @@
`delete(Tab, Ets) -> any()`
`delete(Tab, Key) -> any()`
<a name="delete-3"></a>
<a name="delete_all_objects-1"></a>
###delete/3##
###delete_all_objects/1##
`delete(Tab, Ets, Key) -> any()`
<a name="delete_all_objects-2"></a>
###delete_all_objects/2##
`delete_all_objects(Tab, Ets) -> any()`
`delete_all_objects(Tab) -> any()`
<a name="destroy-1"></a>
@ -55,68 +55,176 @@
`destroy(Tab) -> any()`
<a name="first-2"></a>
<a name="first-1"></a>
###first/2##
###first/1##
`first(Tab, Ets) -> any()`
`first(Tab) -> any()`
<a name="info_memory-2"></a>
<a name="foldl-3"></a>
###info_memory/2##
###foldl/3##
`info_memory(Tab, Ets) -> any()`
`foldl(Function, Acc0, Tab) -> any()`
<a name="info_size-2"></a>
<a name="foldr-3"></a>
###info_size/2##
###foldr/3##
`info_size(Tab, Ets) -> any()`
`foldr(Function, Acc0, Tab) -> any()`
<a name="insert-3"></a>
<a name="info_memory-1"></a>
###insert/3##
###info_memory/1##
`insert(Tab, Ets, ObjectOrObjects) -> any()`
`info_memory(Tab) -> any()`
<a name="insert_new-3"></a>
<a name="info_size-1"></a>
###insert_new/3##
###info_size/1##
`insert_new(Tab, Ets, ObjectOrObjects) -> any()`
`info_size(Tab) -> any()`
<a name="lookup-3"></a>
<a name="insert-2"></a>
###lookup/3##
###insert/2##
`lookup(Tab, Ets, Key) -> any()`
`insert(Tab, ObjOrObjs) -> any()`
<a name="next-3"></a>
<a name="insert_new-2"></a>
###next/3##
###insert_new/2##
`next(Tab, Ets, Key) -> any()`
`insert_new(Tab, ObjOrObjs) -> any()`
<a name="last-1"></a>
###last/1##
`last(Tab) -> any()`
<a name="lookup-2"></a>
###lookup/2##
`lookup(Tab, Key) -> any()`
<a name="lookup_element-3"></a>
###lookup_element/3##
`lookup_element(Tab, Key, Pos) -> any()`
<a name="match-1"></a>
###match/1##
`match(Cont) -> any()`
<a name="match-2"></a>
###match/2##
`match(Tab, Pattern) -> any()`
<a name="match-3"></a>
###match/3##
`match(Tab, Pattern, Limit) -> any()`
<a name="match_delete-2"></a>
###match_delete/2##
`match_delete(Tab, Pattern) -> any()`
<a name="match_object-1"></a>
###match_object/1##
`match_object(Cont) -> any()`
<a name="match_object-2"></a>
###match_object/2##
`match_object(Tab, Pattern) -> any()`
<a name="match_object-3"></a>
###match_object/3##
`match_object(Tab, Pattern, Limit) -> any()`
<a name="member-2"></a>
###member/2##
`member(Tab, Key) -> any()`
<a name="next-2"></a>
###next/2##
`next(Tab, Key) -> any()`
<a name="open-1"></a>
@ -127,6 +235,15 @@
`open(Tab) -> any()`
<a name="prev-2"></a>
###prev/2##
`prev(Tab, Key) -> any()`
<a name="repair-1"></a>
###repair/1##
@ -136,12 +253,84 @@
`repair(Tab) -> any()`
<a name="tab2list-2"></a>
<a name="select-1"></a>
###tab2list/2##
###select/1##
`tab2list(Tab, Ets) -> any()`
`select(Cont) -> any()`
<a name="select-2"></a>
###select/2##
`select(Tab, Spec) -> any()`
<a name="select-3"></a>
###select/3##
`select(Tab, Spec, Limit) -> any()`
<a name="select_count-2"></a>
###select_count/2##
`select_count(Tab, Spec) -> any()`
<a name="select_delete-2"></a>
###select_delete/2##
`select_delete(Tab, Spec) -> any()`
<a name="select_reverse-1"></a>
###select_reverse/1##
`select_reverse(Cont) -> any()`
<a name="select_reverse-2"></a>
###select_reverse/2##
`select_reverse(Tab, Spec) -> any()`
<a name="select_reverse-3"></a>
###select_reverse/3##
`select_reverse(Tab, Spec, Limit) -> any()`
<a name="tab2list-1"></a>
###tab2list/1##
`tab2list(Tab) -> any()`

View file

@ -12,13 +12,22 @@
##Function Index##
<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#delete-2">delete/2</a></td><td></td></tr><tr><td valign="top"><a href="#delete-3">delete/3</a></td><td></td></tr><tr><td valign="top"><a href="#delete_all_objects-2">delete_all_objects/2</a></td><td></td></tr><tr><td valign="top"><a href="#destroy-4">destroy/4</a></td><td></td></tr><tr><td valign="top"><a href="#first-2">first/2</a></td><td></td></tr><tr><td valign="top"><a href="#info_memory-2">info_memory/2</a></td><td></td></tr><tr><td valign="top"><a href="#info_size-2">info_size/2</a></td><td></td></tr><tr><td valign="top"><a href="#insert-3">insert/3</a></td><td></td></tr><tr><td valign="top"><a href="#insert_new-3">insert_new/3</a></td><td></td></tr><tr><td valign="top"><a href="#lookup-3">lookup/3</a></td><td></td></tr><tr><td valign="top"><a href="#next-3">next/3</a></td><td></td></tr><tr><td valign="top"><a href="#open-4">open/4</a></td><td></td></tr><tr><td valign="top"><a href="#repair-4">repair/4</a></td><td></td></tr><tr><td valign="top"><a href="#tab2list-2">tab2list/2</a></td><td></td></tr></table>
<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#delete-1">delete/1</a></td><td></td></tr><tr><td valign="top"><a href="#delete-2">delete/2</a></td><td></td></tr><tr><td valign="top"><a href="#delete_all_objects-1">delete_all_objects/1</a></td><td></td></tr><tr><td valign="top"><a href="#destroy-4">destroy/4</a></td><td></td></tr><tr><td valign="top"><a href="#first-1">first/1</a></td><td></td></tr><tr><td valign="top"><a href="#foldl-3">foldl/3</a></td><td></td></tr><tr><td valign="top"><a href="#foldr-3">foldr/3</a></td><td></td></tr><tr><td valign="top"><a href="#info_memory-1">info_memory/1</a></td><td></td></tr><tr><td valign="top"><a href="#info_size-1">info_size/1</a></td><td></td></tr><tr><td valign="top"><a href="#insert-2">insert/2</a></td><td></td></tr><tr><td valign="top"><a href="#insert_new-2">insert_new/2</a></td><td></td></tr><tr><td valign="top"><a href="#last-1">last/1</a></td><td></td></tr><tr><td valign="top"><a href="#lookup-2">lookup/2</a></td><td></td></tr><tr><td valign="top"><a href="#lookup_element-3">lookup_element/3</a></td><td></td></tr><tr><td valign="top"><a href="#match-1">match/1</a></td><td></td></tr><tr><td valign="top"><a href="#match-2">match/2</a></td><td></td></tr><tr><td valign="top"><a href="#match-3">match/3</a></td><td></td></tr><tr><td valign="top"><a href="#match_delete-2">match_delete/2</a></td><td></td></tr><tr><td valign="top"><a href="#match_object-1">match_object/1</a></td><td></td></tr><tr><td valign="top"><a href="#match_object-2">match_object/2</a></td><td></td></tr><tr><td valign="top"><a href="#match_object-3">match_object/3</a></td><td></td></tr><tr><td valign="top"><a href="#member-2">member/2</a></td><td></td></tr><tr><td valign="top"><a href="#next-2">next/2</a></td><td></td></tr><tr><td valign="top"><a href="#open-4">open/4</a></td><td></td></tr><tr><td valign="top"><a href="#prev-2">prev/2</a></td><td></td></tr><tr><td valign="top"><a href="#repair-4">repair/4</a></td><td></td></tr><tr><td valign="top"><a href="#select-1">select/1</a></td><td></td></tr><tr><td valign="top"><a href="#select-2">select/2</a></td><td></td></tr><tr><td valign="top"><a href="#select-3">select/3</a></td><td></td></tr><tr><td valign="top"><a href="#select_count-2">select_count/2</a></td><td></td></tr><tr><td valign="top"><a href="#select_delete-2">select_delete/2</a></td><td></td></tr><tr><td valign="top"><a href="#select_reverse-1">select_reverse/1</a></td><td></td></tr><tr><td valign="top"><a href="#select_reverse-2">select_reverse/2</a></td><td></td></tr><tr><td valign="top"><a href="#select_reverse-3">select_reverse/3</a></td><td></td></tr><tr><td valign="top"><a href="#tab2list-1">tab2list/1</a></td><td></td></tr></table>
<a name="functions"></a>
##Function Details##
<a name="delete-1"></a>
###delete/1##
`delete(Tab) -> any()`
<a name="delete-2"></a>
###delete/2##
@ -26,25 +35,16 @@
`delete(Tab, Impl) -> any()`
`delete(Tab, Key) -> any()`
<a name="delete-3"></a>
<a name="delete_all_objects-1"></a>
###delete/3##
###delete_all_objects/1##
`delete(Tab, Impl, Key) -> any()`
<a name="delete_all_objects-2"></a>
###delete_all_objects/2##
`delete_all_objects(Tab, Impl) -> any()`
`delete_all_objects(Tab) -> any()`
<a name="destroy-4"></a>
@ -55,68 +55,176 @@
`destroy(Tab, Options, ReadOptions, WriteOptions) -> any()`
<a name="first-2"></a>
<a name="first-1"></a>
###first/2##
###first/1##
`first(Tab, Impl) -> any()`
`first(Tab) -> any()`
<a name="info_memory-2"></a>
<a name="foldl-3"></a>
###info_memory/2##
###foldl/3##
`info_memory(Tab, Impl) -> any()`
`foldl(Fun, Acc0, Tab) -> any()`
<a name="info_size-2"></a>
<a name="foldr-3"></a>
###info_size/2##
###foldr/3##
`info_size(Tab, Impl) -> any()`
`foldr(Fun, Acc0, Tab) -> any()`
<a name="insert-3"></a>
<a name="info_memory-1"></a>
###insert/3##
###info_memory/1##
`insert(Tab, Impl, Object) -> any()`
`info_memory(Tab) -> any()`
<a name="insert_new-3"></a>
<a name="info_size-1"></a>
###insert_new/3##
###info_size/1##
`insert_new(Tab, Impl, Object) -> any()`
`info_size(Tab) -> any()`
<a name="lookup-3"></a>
<a name="insert-2"></a>
###lookup/3##
###insert/2##
`lookup(Tab, Impl, Key) -> any()`
`insert(Tab, Object) -> any()`
<a name="next-3"></a>
<a name="insert_new-2"></a>
###next/3##
###insert_new/2##
`next(Tab, Impl, Key) -> any()`
`insert_new(Tab, Object) -> any()`
<a name="last-1"></a>
###last/1##
`last(Tab) -> any()`
<a name="lookup-2"></a>
###lookup/2##
`lookup(Tab, Key) -> any()`
<a name="lookup_element-3"></a>
###lookup_element/3##
`lookup_element(Tab, Key, Pos) -> any()`
<a name="match-1"></a>
###match/1##
`match(Cont) -> any()`
<a name="match-2"></a>
###match/2##
`match(Tab, Pattern) -> any()`
<a name="match-3"></a>
###match/3##
`match(Tab, Pattern, Limit) -> any()`
<a name="match_delete-2"></a>
###match_delete/2##
`match_delete(Tab, Pattern) -> any()`
<a name="match_object-1"></a>
###match_object/1##
`match_object(Cont) -> any()`
<a name="match_object-2"></a>
###match_object/2##
`match_object(Tab, Pattern) -> any()`
<a name="match_object-3"></a>
###match_object/3##
`match_object(Tab, Pattern, Limit) -> any()`
<a name="member-2"></a>
###member/2##
`member(Tab, Key) -> any()`
<a name="next-2"></a>
###next/2##
`next(Tab, Key) -> any()`
<a name="open-4"></a>
@ -127,6 +235,15 @@
`open(Tab, Options, ReadOptions, WriteOptions) -> any()`
<a name="prev-2"></a>
###prev/2##
`prev(Tab, Key) -> any()`
<a name="repair-4"></a>
###repair/4##
@ -136,12 +253,84 @@
`repair(Tab, Options, ReadOptions, WriteOptions) -> any()`
<a name="tab2list-2"></a>
<a name="select-1"></a>
###tab2list/2##
###select/1##
`tab2list(Tab, Impl) -> any()`
`select(Cont0) -> any()`
<a name="select-2"></a>
###select/2##
`select(Tab, Spec) -> any()`
<a name="select-3"></a>
###select/3##
`select(Tab, Spec, Limit) -> any()`
<a name="select_count-2"></a>
###select_count/2##
`select_count(Tab, Spec) -> any()`
<a name="select_delete-2"></a>
###select_delete/2##
`select_delete(Tab, Spec) -> any()`
<a name="select_reverse-1"></a>
###select_reverse/1##
`select_reverse(Cont0) -> any()`
<a name="select_reverse-2"></a>
###select_reverse/2##
`select_reverse(Tab, Spec) -> any()`
<a name="select_reverse-3"></a>
###select_reverse/3##
`select_reverse(Tab, Spec, Limit) -> any()`
<a name="tab2list-1"></a>
###tab2list/1##
`tab2list(Tab) -> any()`

View file

@ -8,7 +8,8 @@ LETS is an alternative Erlang Term Storage using LevelDB as the
storage implementation. LETS tries to address some bad properties of
ETS and DETS. ETS is limited by physical memory. DETS is limited by
a 2 GB file size limitation and does not implement ordered sets. LETS
has neither of these limitations.
has neither of these limitations. Data can also be automatically
compressed using the Snappy compression library.
For testing and comparison purposes, LETS supports three
implementations:
@ -18,20 +19,40 @@ implementations:
- +ets+ Erlang ETS backend
LETS is not intended to be an exact clone of ETS. The currently
supported APIs are:
supported ETS APIs are:
- +new/2+
- +destroy/2+ _only driver and nif implementations_
- +repair/2+ _only driver and nif implementations_
- +insert/2+
- +insert_new/2+ _only the ets implementation_
- +delete/1+
- +delete/2+
- +delete_all_objects/1+ _only the ets implementation_
- +lookup/2+
- +delete_all_objects/1+ _only ets implementation_
- +first/1+
- +next/2+
- +foldl/3+
- +foldr/3+
- +info/1+ _only a subset of items_
- +info/2+ _only a subset of items_
- +insert/2+
- +insert_new/2+ _only ets implementation_
- +last/1+
- +lookup/2+
- +lookup_element/3+
- +match/1+
- +match/2+
- +match/3+
- +match_delete/2+
- +match_object/1+
- +match_object/2+
- +match_object/3+
- +member/2+
- +new/2+
- +next/2+
- +prev/2+
- +select/1+
- +select/2+
- +select/3+
- +select_count/2+
- +select_delete/2+
- +select_reverse/1+
- +select_reverse/2+
- +select_reverse/3+
- +tab2list/1+
_This repository is experimental in nature - use at your own risk and
@ -63,7 +84,7 @@ please read further.
This README is the only bit of documentation right now.
The QC (a.k.a. QuickCheck, Proper, etc.) tests underneath the
The QC (a.k.a. QuickCheck, PropEr, etc.) tests underneath the
"tests/qc" directory should be helpful for understanding the
specification and behavior of ETS and LETS. These QC tests also
illustrate several strategies for testing Erlang Driver-based and
@ -85,6 +106,12 @@ provides an ordered mapping from string keys to string values.
See http://code.google.com/p/leveldb/ for further details.
=== What is Snappy?
Snappy is a fast compression/decompression library written at Google.
See http://code.google.com/p/snappy/ for further details.
== To download
1. Configure your e-mail and name for Git
@ -212,16 +239,45 @@ $ make compile-eqc eqc-compile
$ cd working-directory-name/src/lib/lets/.eunit
$ erl -smp +A 5 -pz ../../sext/ebin -pz ../../qc/ebin
1> qc_statem_lets:run(5000).
1> qc_statem_lets:qc_run(5000).
....
OK, passed 5000 tests
9.022% {delete,ok}
7.800% {new,ok}
4.535% {match_delete,ok}
4.491% {lookup,ok}
4.399% {select,ok}
4.352% {select_delete,ok}
4.348% {tab2list,ok}
4.341% {member,ok}
4.334% {last,ok}
4.315% {foldl,ok}
4.308% {select_reverse,ok}
4.301% {select_count,ok}
4.293% {select31,ok}
4.264% {first,ok}
4.216% {foldr,ok}
4.202% {match_object,ok}
4.184% {match,ok}
4.056% {insert,ok}
3.997% {prev,ok}
3.774% {next,ok}
3.416% {lookup_element,{error,badarg}}
1.298% {insert_new,ok}
0.757% {lookup_element,ok}
0.516% {next,{error,badarg}}
0.483% {prev,{error,badarg}}
true
.......
------
+
TIP: For testing LevelDB directly using the C bindings, try
+qc_statemc_lets:run(5000)+.
+qc_statemc_lets:qc_run(5000)+.
== To test - Proper
== To test - PropEr
1. Make sure Proper is in your Erlang code path. One simple way to
1. Make sure PropEr is in your Erlang code path. One simple way to
accomplish this is by adding the code path to your +~/.erlang+
resource file.
+
@ -229,7 +285,7 @@ TIP: For testing LevelDB directly using the C bindings, try
true = code:add_pathz(os:getenv("HOME")++"/.erlang.d/lib/proper/ebin").
------
2. Compile for Proper
2. Compile for PropEr
+
------
$ cd working-directory-name/src
@ -237,20 +293,49 @@ $ make clean
$ make compile-proper proper-compile
------
3. Run 5,000 Proper tests
3. Run 5,000 PropEr tests
+
------
$ cd working-directory-name/src/lib/lets/.eunit
$ erl -smp +A 5 -pz ../../sext/ebin -pz ../../qc/ebin
1> qc_statem_lets:run(5000).
1> qc_statem_lets:qc_run(5000).
....
OK: Passed 5000 test(s).
11% {new,ok}
8% {delete,ok}
4% {member,ok}
4% {select,ok}
4% {select_count,ok}
4% {select_reverse,ok}
4% {lookup,ok}
4% {match_object,ok}
4% {tab2list,ok}
4% {last,ok}
4% {match,ok}
4% {foldl,ok}
4% {match_delete,ok}
3% {prev,ok}
3% {select31,ok}
3% {select_delete,ok}
3% {foldr,ok}
3% {insert,ok}
3% {first,ok}
3% {next,ok}
3% {lookup_element,{error,badarg}}
1% {insert_new,ok}
0% {prev,{error,badarg}}
0% {lookup_element,ok}
0% {next,{error,badarg}}
true
.......
------
== Roadmap
- Documentation
* Explain how to run QuickCheck/Proper tests using a new rebar
* Explain how to run QuickCheck/PropEr tests using a new rebar
plugin.
* Explain how to build and to run lets with valgrind enabled
OTP/Erlang virtual machine
@ -271,6 +356,10 @@ $ erl -smp +A 5 -pz ../../sext/ebin -pz ../../qc/ebin
* +delete_all_objects/1+
(http://code.google.com/p/leveldb/issues/detail?id=43)
* Add custom (i.e. not supported by native ETS) APIs for providing
access to LevelDB\'s iterators for +drv+ and +nif+ backend
implementations.
- Existing APIs (TBD)
* +delete/1+
(http://code.google.com/p/leveldb/issues/detail?id=48)

BIN
rebar vendored

Binary file not shown.

View file

@ -28,20 +28,108 @@
-export([new/2
, destroy/2
, repair/2
, insert/2
, insert_new/2
, delete/1
, delete/2
, delete_all_objects/1
, lookup/2
, first/1
, next/2
, foldl/3
, foldr/3
, info/1
, info/2
, insert/2
, insert_new/2
, last/1
, lookup/2
, lookup_element/3
, match/2
, match/3
, match/1
, match_delete/2
, match_object/2
, match_object/3
, match_object/1
, member/2
, next/2
, prev/2
, select/2
, select/3
, select/1
, select_count/2
, select_delete/2
, select_reverse/2
, select_reverse/3
, select_reverse/1
, tab2list/1
]).
-export_type([tab/0]).
%%
%% ETS exports
%%
%% -export([all/0
%% , delete/1 %% mnesia
%% , delete/2 %% mnesia
%% , delete_all_objects/1
%% , delete_object/2
%% , file2tab/1
%% , file2tab/2
%% , filter/3 %% mnesia
%% , first/1 %% mnesia
%% , foldl/3 %% mnesia
%% , foldr/3
%% , from_dets/2
%% , fun2ms/1
%% , give_away/3
%% , i/0
%% , i/1
%% , info/1
%% , info/2 %% mnesia
%% , init_table/2 %% mnesia
%% , insert/2 %% mnesia
%% , insert_new/2
%% , is_compiled_ms/1
%% , last/1 %% mnesia
%% , lookup/2 %% mnesia
%% , lookup_element/3 %% mnesia
%% , match/1
%% , match/2 %% mnesia
%% , match/3
%% , match_delete/2 %% mnesia
%% , match_object/1
%% , match_object/2 %% mnesia
%% , match_object/3
%% , match_spec_compile/1
%% , match_spec_run/2 %% mnesia
%% , member/2
%% , new/2 %% mnesia
%% , next/2 %% mnesia
%% , prev/2 %% mnesia
%% , rename/2
%% , repair_continuation/2 %% mnesia
%% , safe_fixtable/2
%% , select/1
%% , select/2
%% , select/3
%% , select_count/2
%% , select_delete/2
%% , select_reverse/1
%% , select_reverse/2
%% , select_reverse/3
%% , setopts/2
%% , slot/2 %% mnesia
%% , tab2file/2
%% , tab2file/3
%% , tab2list/1 %% mnesia
%% , tabfile_info/1
%% , table/1
%% , table/2
%% , test_ms/2
%% , to_dets/2
%% , update_counter/3 %% mnesia
%% , update_element/3
%% ]).
%%%----------------------------------------------------------------------
%%% Types/Specs/Records
@ -60,6 +148,14 @@
-type key() :: binary().
-type object() :: term().
-type name() :: atom().
-type item() :: owner | name | named_table | type | keypos | protection | compressed | async | memory | size.
-type pos() :: pos_integer().
-type pattern() :: atom() | tuple(). %% ets:match_pattern() is not exported!
-type spec() :: ets:match_spec().
-type match() :: term().
-type limit() :: pos_integer().
-opaque cont() :: {cont, tab(), term()}.
%%%----------------------------------------------------------------------
%%% API
@ -164,7 +260,7 @@
%% @end
%% @see ets:new/2
-spec new(Name::atom(), Options::opts()) -> tab().
-spec new(name(), opts()) -> tab().
new(Name, Opts) ->
create(open, Name, Opts).
@ -172,11 +268,10 @@ new(Name, Opts) ->
%% only applies to +driver+ and +nif+ implementations.
%% @end
-spec destroy(Name::atom(), Options::opts()) -> true.
-spec destroy(name(), opts()) -> true.
destroy(Name, Opts) ->
create(destroy, Name, Opts).
%% @doc If a table cannot be opened, you may attempt to call this
%% method to resurrect as much of the contents of the table as
%% possible. Some data may be lost, so be careful when calling this
@ -184,40 +279,10 @@ destroy(Name, Opts) ->
%% function only applies to +driver+ and +nif+ implementations.
%% @end
-spec repair(Name::atom(), Options::opts()) -> true.
-spec repair(name(), opts()) -> true.
repair(Name, Opts) ->
create(repair, Name, Opts).
%% @doc Inserts the object or all of the objects in the list
%% +ObjectOrObjects+ into the table +Tab+.
%% @end
%% @see ets:insert/2
-spec insert(Tab::tab(), ObjectOrObjects::object() | [object()]) -> true.
insert(Tab, ObjectOrObjects) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
{Mod, Impl} ->
Mod:insert(Tab, Impl, ObjectOrObjects)
end.
%% @doc This function works exactly like +insert/2+, with the
%% exception that instead of overwriting objects with the same key, it
%% simply returns false. This function only applies to the +ets+
%% implementation.
%% @end
%% @see ets:insert_new/2
-spec insert_new(tab(), object() | [object()]) -> true.
insert_new(Tab, ObjectOrObjects) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
{Mod, Impl} ->
Mod:insert_new(Tab, Impl, ObjectOrObjects)
end.
%% @doc Deletes the entire table +Tab+.
%% @end
%% @see ets:delete/1
@ -227,8 +292,8 @@ delete(Tab) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
{Mod, Impl} ->
Mod:delete(Tab, Impl)
Mod ->
Mod:delete(Tab)
end.
%% @doc Deletes all objects with the key +Key+ from the table +Tab+.
@ -240,8 +305,8 @@ delete(Tab, Key) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
{Mod, Impl} ->
Mod:delete(Tab, Impl, Key)
Mod ->
Mod:delete(Tab, Key)
end.
%% @doc Delete all objects in the table +Tab+. The operation is
@ -255,22 +320,8 @@ delete_all_objects(Tab) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
{Mod, Impl} ->
Mod:delete_all_objects(Tab, Impl)
end.
%% @doc Returns a list of all objects with the key +Key+ in the table
%% +Tab+.
%% @end
%% @see ets:lookup/2
-spec lookup(tab(), key()) -> [object()].
lookup(Tab, Key) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
{Mod, Impl} ->
Mod:lookup(Tab, Impl, Key)
Mod ->
Mod:delete_all_objects(Tab)
end.
%% @doc Returns the first key +Key+ in the table +Tab+. If the table
@ -283,28 +334,64 @@ first(Tab) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
{Mod, Impl} ->
Mod:first(Tab, Impl)
Mod ->
Mod:first(Tab)
end.
%% @doc Returns the next key +Key2+, following the key +Key1+ in the
%% table +Tab+. If there is no next key, +'$end_of_table'+ is
%% returned.
%% @doc Fold from left to right over the elements of the table.
%% @end
%% @see ets:next/2
%% @see ets:foldl/3
-spec next(tab(), key()) -> key() | '$end_of_table'.
next(Tab, Key) ->
-spec foldl(Fun, Acc0::term(), tab()) -> Acc1::term() when
Fun :: fun((Element::term(), AccIn::term()) -> AccOut::term()).
foldl(Function, Acc0, Tab) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
{Mod, Impl} ->
Mod:next(Tab, Impl, Key)
Mod ->
Mod:foldl(Function, Acc0, Tab)
end.
%% @doc Fold from right to left over the elements of the table.
%% @end
%% @see ets:foldr/3
-spec foldr(Fun, Acc0::term(), tab()) -> Acc1::term() when
Fun :: fun((Element::term(), AccIn::term()) -> AccOut::term()).
foldr(Function, Acc0, Tab) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
Mod ->
Mod:foldr(Function, Acc0, Tab)
end.
%% @doc Returns information about the table +Tab+ as a list of +{Item,
%% Value}+ tuples.
%%
%% @end
%% @see info/2
-spec info(tab()) -> [{item(), term()}].
info(Tab) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
Mod ->
[{owner, Tab#tab.owner},
{name, Tab#tab.name},
{named_table, Tab#tab.named_table},
{type, Tab#tab.type},
{keypos, Tab#tab.keypos},
{protection, Tab#tab.protection},
{compressed, Tab#tab.compressed},
{async, Tab#tab.async},
{memory, Mod:info_memory(Tab)},
{size, Mod:info_size(Tab)}]
end.
%% @doc Returns the information associated with +Item+ for the table +Tab+.
%%
%% Valid +Item+ options are:
%%
%% - +owner+
@ -321,12 +408,12 @@ next(Tab, Key) ->
%% @end
%% @see ets:info/2
-spec info(tab(), atom()) -> term().
-spec info(tab(), item()) -> term().
info(Tab, Item) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
{Mod, Impl} ->
Mod ->
case Item of
owner ->
Tab#tab.owner;
@ -345,14 +432,349 @@ info(Tab, Item) ->
async ->
Tab#tab.async;
memory ->
Mod:info_memory(Tab, Impl);
Mod:info_memory(Tab);
size ->
Mod:info_size(Tab, Impl);
Mod:info_size(Tab);
_ ->
erlang:error(badarg, [Tab, Item])
end
end.
%% @doc Inserts the object or all of the objects in the list
%% +ObjOrObjs+ into the table +Tab+.
%% @end
%% @see ets:insert/2
-spec insert(tab(), object() | [object()]) -> true.
insert(Tab, ObjOrObjs) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
Mod ->
Mod:insert(Tab, ObjOrObjs)
end.
%% @doc This function works exactly like +insert/2+, with the
%% exception that instead of overwriting objects with the same key, it
%% simply returns false. This function only applies to the +ets+
%% implementation.
%% @end
%% @see ets:insert_new/2
-spec insert_new(tab(), object() | [object()]) -> true.
insert_new(Tab, ObjOrObjs) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
Mod ->
Mod:insert_new(Tab, ObjOrObjs)
end.
%% @doc Returns the last key +Key+ in the table +Tab+. If the table
%% is empty, +'$end_of_table'+ will be returned.
%% @end
%% @see ets:last/1
-spec last(tab()) -> key() | '$end_of_table'.
last(Tab) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
Mod ->
Mod:last(Tab)
end.
%% @doc Returns a list of all objects with the key +Key+ in the table
%% +Tab+.
%% @end
%% @see ets:lookup/2
-spec lookup(tab(), key()) -> [object()].
lookup(Tab, Key) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
Mod ->
Mod:lookup(Tab, Key)
end.
%% @doc Returns the +Pos+:th element of the object with the key +Key+
%% in the table +Tab+.
%% @end
%% @see ets:lookup_element/3
-spec lookup_element(tab(), key(), pos()) -> term().
lookup_element(Tab, Key, Pos) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
Mod ->
Mod:lookup_element(Tab, Key, Pos)
end.
%% @doc Matches the objects in the table +Tab+ against the pattern
%% +Pattern+.
%% @end
%% @see ets:match/2
-spec match(tab(), pattern()) -> [match()].
match(Tab, Pattern) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
Mod ->
Mod:match(Tab, Pattern)
end.
%% @doc Matches the objects in the table +Tab+ against the pattern
%% +Pattern+ and returns a limited (+Limit+) number of matching
%% objects.
%% @end
%% @see ets:match/3
-spec match(tab(), pattern(), limit()) -> {[match()],cont()} | '$end_of_table'.
match(Tab, Pattern, Limit) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
Mod ->
wrap_cont_reply(Tab, Mod:match(Tab, Pattern, Limit))
end.
%% @doc Continues a match started with +match/3+.
%% @end
%% @see ets:match/1
-spec match(cont()) -> {[match()],cont()} | '$end_of_table'.
match({cont, Tab, Cont}) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
Mod ->
wrap_cont_reply(Tab, Mod:match(Cont))
end;
match('$end_of_table') ->
'$end_of_table'.
%% @doc Deletes all objects which match the pattern +Pattern+ from the
%% table +Tab+.
%% @end
%% @see ets:match_delete/2
-spec match_delete(tab(), pattern()) -> true.
match_delete(Tab, Pattern) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
Mod ->
Mod:match_delete(Tab, Pattern)
end.
%% @doc Matches the objects in the table +Tab+ against the pattern
%% +Pattern+.
%% @end
%% @see ets:match_object/2
-spec match_object(tab(), pattern()) -> [match()].
match_object(Tab, Pattern) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
Mod ->
Mod:match_object(Tab, Pattern)
end.
%% @doc Matches the objects in the table +Tab+ against the pattern
%% +Pattern+ and returns a limited (+Limit+) number of matching
%% objects.
%% @end
%% @see ets:match_object/3
-spec match_object(tab(), pattern(), limit()) -> {[match()], cont()} | '$end_of_table'.
match_object(Tab, Pattern, Limit) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
Mod ->
wrap_cont_reply(Tab, Mod:match_object(Tab, Pattern, Limit))
end.
%% @doc Continues a match started with +match_object/3+.
%% @end
%% @see ets:match_object/1
-spec match_object(cont()) -> {[match()], cont()} | '$end_of_table'.
match_object({cont, Tab, Cont}) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
Mod ->
wrap_cont_reply(Tab, Mod:match_object(Cont))
end;
match_object('$end_of_table') ->
'$end_of_table'.
%% @doc Returns +true+ if one or more elements in the table +Tab+ has
%% the key +Key+, +false+ otherwise.
%% @end
%% @see ets:member/2
-spec member(tab(), key()) -> true | false.
member(Tab, Key) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
Mod ->
Mod:member(Tab, Key)
end.
%% @doc Returns the next key +Key2+, following the key +Key1+ in the
%% table +Tab+. If there is no next key, +'$end_of_table'+ is
%% returned.
%% @end
%% @see ets:next/2
-spec next(tab(), key()) -> key() | '$end_of_table'.
next(Tab, Key) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
Mod ->
Mod:next(Tab, Key)
end.
%% @doc Returns the previous key +Key2+, following the key +Key1+ in
%% the table +Tab+. If there is no previous key, +'$end_of_table'+ is
%% returned.
%% @end
%% @see ets:prev/2
-spec prev(tab(), key()) -> key() | '$end_of_table'.
prev(Tab, Key) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
Mod ->
Mod:prev(Tab, Key)
end.
%% repair_continuation/2
%% @doc Matches the objects in the table +Tab+ against the spec
%% +Spec+.
%% @end
%% @see ets:select/2
-spec select(tab(), spec()) -> [match()].
select(Tab, Spec) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
Mod ->
Mod:select(Tab, Spec)
end.
%% @doc Matches the objects in the table +Tab+ against the spec +Spec+
%% and returns a limited (+Limit+) number of matching objects.
%% @end
%% @see ets:select/3
-spec select(tab(), spec(), limit()) -> {[match()], cont()} | '$end_of_table'.
select(Tab, Spec, Limit) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
Mod ->
wrap_cont_reply(Tab, Mod:select(Tab, Spec, Limit))
end.
%% @doc Continues a select started with +select/3+.
%% @end
%% @see ets:select/1
-spec select(cont()) -> {[match()], cont()} | '$end_of_table'.
select({cont, Tab, Cont}) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
Mod ->
wrap_cont_reply(Tab, Mod:select(Cont))
end;
select('$end_of_table') ->
'$end_of_table'.
%% @doc Counts all objects which match the spec +Spec+ from the
%% table +Tab+ and returns the number matched.
%% @end
%% @see ets:select_count/2
-spec select_count(tab(), pattern()) -> pos_integer().
select_count(Tab, Spec) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
Mod ->
Mod:select_count(Tab, Spec)
end.
%% @doc Deletes all objects which match the spec +Spec+ from the
%% table +Tab+ and returns the number deleted.
%% @end
%% @see ets:select_delete/2
-spec select_delete(tab(), pattern()) -> pos_integer().
select_delete(Tab, Spec) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
Mod ->
Mod:select_delete(Tab, Spec)
end.
%% @doc Matches in reverse the objects in the table +Tab+ against the
%% spec +Spec+.
%% @end
%% @see ets:select_reverse/2
-spec select_reverse(tab(), spec()) -> [match()].
select_reverse(Tab, Spec) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
Mod ->
Mod:select_reverse(Tab, Spec)
end.
%% @doc Matches in reverse the objects in the table +Tab+ against the
%% spec +Spec+ and returns a limited (+Limit+) number of matching
%% objects.
%% @end
%% @see ets:select_reverse/3
-spec select_reverse(tab(), spec(), limit()) -> {[match()], cont()} | '$end_of_table'.
select_reverse(Tab, Spec, Limit) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
Mod ->
wrap_cont_reply(Tab, Mod:select_reverse(Tab, Spec, Limit))
end.
%% @doc Continues a select reverse started with +select_reverse/3+.
%% @end
%% @see ets:select_reverse/1
-spec select_reverse(cont()) -> {[match()], cont()} | '$end_of_table'.
select_reverse({cont, Tab, Cont}) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
Mod ->
wrap_cont_reply(Tab, Mod:select_reverse(Cont))
end;
select_reverse('$end_of_table') ->
'$end_of_table'.
%% @doc Returns a list of all objects in the table +Tab+. The
%% operation is *not* guaranteed to be atomic and isolated.
%% @end
@ -363,8 +785,8 @@ tab2list(Tab) ->
case check_access(Tab) of
undefined ->
erlang:error(badarg, [Tab]);
{Mod, Impl} ->
Mod:tab2list(Tab, Impl)
Mod ->
Mod:tab2list(Tab)
end.
@ -372,16 +794,17 @@ tab2list(Tab) ->
%%% Internal functions
%%%----------------------------------------------------------------------
check_access(#tab{protection=Protection, owner=Owner, drv=Drv, nif=undefined, ets=undefined})
check_access(#tab{impl=undefined}) ->
undefined;
check_access(#tab{protection=Protection, owner=Owner, impl=Impl})
when Protection==public orelse Owner==self() ->
{lets_drv, Drv};
check_access(#tab{protection=Protection, owner=Owner, drv=undefined, nif=Nif, ets=undefined})
when Protection==public orelse Owner==self() ->
{lets_nif, Nif};
check_access(#tab{protection=Protection, owner=Owner, drv=undefined, nif=undefined, ets=Ets})
when Protection==public orelse Owner==self() ->
{lets_ets, Ets};
if is_port(Impl) ->
lets_drv;
is_atom(Impl) orelse is_integer(Impl) ->
lets_ets;
true ->
lets_nif
end;
check_access(_Tab) ->
undefined.
@ -525,3 +948,10 @@ sub_options(Key, Value, Options, Keys, L, SubKeys) ->
{_NewValue, _} ->
options(Options, Keys, L)
end.
wrap_cont_reply(_Tab, '$end_of_table'=Reply) ->
Reply;
wrap_cont_reply(_Tab, {_Match, '$end_of_table'}=Reply) ->
Reply;
wrap_cont_reply(Tab, {_Match, Cont}=Reply) ->
setelement(2, Reply, {cont, Tab, Cont}).

View file

@ -33,9 +33,7 @@
protection=protected :: public|protected|private,
compressed=false :: boolean(),
async=false :: boolean(),
drv :: port() | undefined,
nif :: nif() | undefined,
ets :: ets:tab() | undefined
impl :: port() | nif() | ets:tab() | undefined
}).
-endif. % -ifndef(lets).

View file

@ -28,17 +28,46 @@
-export([open/4
, destroy/4
, repair/4
, insert/3
, insert_new/3
, delete/1
, delete/2
, delete/3
, delete_all_objects/2
, lookup/3
, first/2
, next/3
, info_memory/2
, info_size/2
, tab2list/2
, delete_all_objects/1
, first/1
%% , first_iter/1
, foldl/3
, foldr/3
%% , nfoldl/4
%% , nfoldr/4
%% , nfoldl/1
%% , nfoldr/1
, info_memory/1
, info_size/1
, insert/2
, insert_new/2
, last/1
%% , last_iter/1
, lookup/2
, lookup_element/3
, match/2
, match/3
, match/1
, match_delete/2
, match_object/2
, match_object/3
, match_object/1
, member/2
, next/2
%% , next_iter/2
, prev/2
%% , prev_iter/2
, select/2
, select/3
, select/1
, select_count/2
, select_delete/2
, select_reverse/2
, select_reverse/3
, select_reverse/1
, tab2list/1
]).
@ -48,24 +77,32 @@
-define(LETS_BADARG, 16#00).
-define(LETS_TRUE, 16#01).
-define(LETS_END_OF_TABLE, 16#02).
-define(LETS_BINARY, 16#03).
-define(LETS_FALSE, 16#02).
-define(LETS_END_OF_TABLE, 16#03).
-define(LETS_BINARY, 16#04).
-define(LETS_OPEN6, 16#00).
-define(LETS_DESTROY6, 16#01).
-define(LETS_REPAIR6, 16#02).
-define(LETS_INSERT2, 16#03).
-define(LETS_INSERT3, 16#04).
-define(LETS_INSERT_NEW2, 16#05).
-define(LETS_INSERT_NEW3, 16#06).
-define(LETS_DELETE1, 16#07).
-define(LETS_DELETE2, 16#08).
-define(LETS_DELETE_ALL_OBJECTS1, 16#09).
-define(LETS_LOOKUP2, 16#0A).
-define(LETS_FIRST1, 16#0B).
-define(LETS_NEXT2, 16#0C).
-define(LETS_INFO_MEMORY1, 16#0D).
-define(LETS_INFO_SIZE1, 16#0E).
-define(LETS_DELETE1, 16#03).
-define(LETS_DELETE2, 16#04).
-define(LETS_DELETE_ALL_OBJECTS1, 16#05).
-define(LETS_FIRST1, 16#06).
-define(LETS_FIRST_ITER1, 16#07).
-define(LETS_INFO_MEMORY1, 16#08).
-define(LETS_INFO_SIZE1, 16#09).
-define(LETS_INSERT2, 16#0A).
-define(LETS_INSERT3, 16#0B).
-define(LETS_INSERT_NEW2, 16#0C).
-define(LETS_INSERT_NEW3, 16#0D).
-define(LETS_LAST1, 16#0E).
-define(LETS_LAST_ITER1, 16#0F).
-define(LETS_LOOKUP2, 16#10).
-define(LETS_MEMBER2, 16#11).
-define(LETS_NEXT2, 16#12).
-define(LETS_NEXT_ITER2, 16#13).
-define(LETS_PREV2, 16#14).
-define(LETS_PREV_ITER2, 16#15).
%%%----------------------------------------------------------------------
@ -99,7 +136,7 @@ open(#tab{name=_Name, named_table=_Named, type=Type, protection=Protection}=Tab,
{value, {path,Path}, NewOptions} = lists:keytake(path, 1, Options),
Impl = impl_open(Type, Protection, Path, NewOptions, ReadOptions, WriteOptions),
%% @TODO implement named Impl (of sorts)
Tab#tab{drv=Impl}.
Tab#tab{impl=Impl}.
destroy(#tab{type=Type, protection=Protection}, Options, ReadOptions, WriteOptions) ->
{value, {path,Path}, NewOptions} = lists:keytake(path, 1, Options),
@ -109,40 +146,16 @@ repair(#tab{type=Type, protection=Protection}, Options, ReadOptions, WriteOption
{value, {path,Path}, NewOptions} = lists:keytake(path, 1, Options),
impl_repair(Type, Protection, Path, NewOptions, ReadOptions, WriteOptions).
insert(#tab{keypos=KeyPos, type=Type}, Impl, Object) when is_tuple(Object) ->
Key = element(KeyPos,Object),
Val = Object,
impl_insert(Impl, encode(Type, Key), encode(Type, Val));
insert(#tab{keypos=KeyPos, type=Type}, Impl, Objects) when is_list(Objects) ->
List = [{encode(Type, element(KeyPos,Object)), encode(Type, Object)} || Object <- Objects ],
impl_insert(Impl, List).
insert_new(#tab{keypos=KeyPos, type=Type}, Impl, Object) when is_tuple(Object) ->
Key = element(KeyPos,Object),
Val = Object,
impl_insert_new(Impl, encode(Type, Key), encode(Type, Val));
insert_new(#tab{keypos=KeyPos, type=Type}, Impl, Objects) when is_list(Objects) ->
List = [{encode(Type, element(KeyPos,Object)), encode(Type, Object)} || Object <- Objects ],
impl_insert_new(Impl, List).
delete(_Tab, Impl) ->
delete(#tab{impl=Impl}) ->
impl_delete(Impl).
delete(#tab{type=Type}, Impl, Key) ->
delete(#tab{type=Type, impl=Impl}, Key) ->
impl_delete(Impl, encode(Type, Key)).
delete_all_objects(_Tab, Impl) ->
delete_all_objects(#tab{impl=Impl}) ->
impl_delete_all_objects(Impl).
lookup(#tab{type=Type}, Impl, Key) ->
case impl_lookup(Impl, encode(Type, Key)) of
'$end_of_table' ->
[];
Object when is_binary(Object) ->
[decode(Type, Object)]
end.
first(#tab{type=Type}, Impl) ->
first(#tab{type=Type, impl=Impl}) ->
case impl_first(Impl) of
'$end_of_table' ->
'$end_of_table';
@ -150,15 +163,31 @@ first(#tab{type=Type}, Impl) ->
decode(Type, Key)
end.
next(#tab{type=Type}, Impl, Key) ->
case impl_next(Impl, encode(Type, Key)) of
first_iter(#tab{type=Type, impl=Impl}) ->
case impl_first_iter(Impl) of
'$end_of_table' ->
'$end_of_table';
Next ->
decode(Type, Next)
Key ->
decode(Type, Key)
end.
info_memory(_Tab, Impl) ->
last(#tab{type=Type, impl=Impl}) ->
case impl_last(Impl) of
'$end_of_table' ->
'$end_of_table';
Key ->
decode(Type, Key)
end.
last_iter(#tab{type=Type, impl=Impl}) ->
case impl_last_iter(Impl) of
'$end_of_table' ->
'$end_of_table';
Key ->
decode(Type, Key)
end.
info_memory(#tab{impl=Impl}) ->
case impl_info_memory(Impl) of
Memory when is_integer(Memory) ->
erlang:round(Memory / erlang:system_info(wordsize));
@ -166,30 +195,286 @@ info_memory(_Tab, Impl) ->
Else
end.
info_size(_Tab, Impl) ->
info_size(#tab{impl=Impl}) ->
impl_info_size(Impl).
tab2list(Tab, Impl) ->
tab2list(Tab, Impl, impl_first(Impl), []).
insert(#tab{keypos=KeyPos, type=Type, impl=Impl}, Object) when is_tuple(Object) ->
Key = element(KeyPos, Object),
Val = Object,
impl_insert(Impl, encode(Type, Key), encode(Type, Val));
insert(#tab{keypos=KeyPos, type=Type, impl=Impl}, Objects) when is_list(Objects) ->
List = [{encode(Type, element(KeyPos, Object)), encode(Type, Object)} || Object <- Objects ],
impl_insert(Impl, List).
tab2list(_Tab, _Impl, '$end_of_table', Acc) ->
lists:reverse(Acc);
tab2list(#tab{type=Type}=Tab, Impl, Key, Acc) ->
NewAcc =
case impl_lookup(Impl, Key) of
insert_new(#tab{keypos=KeyPos, type=Type, impl=Impl}, Object) when is_tuple(Object) ->
Key = element(KeyPos, Object),
Val = Object,
impl_insert_new(Impl, encode(Type, Key), encode(Type, Val));
insert_new(#tab{keypos=KeyPos, type=Type, impl=Impl}, Objects) when is_list(Objects) ->
List = [{encode(Type, element(KeyPos, Object)), encode(Type, Object)} || Object <- Objects ],
impl_insert_new(Impl, List).
lookup(#tab{type=Type, impl=Impl}, Key) ->
case impl_lookup(Impl, encode(Type, Key)) of
'$end_of_table' ->
[];
Object when is_binary(Object) ->
[decode(Type, Object)]
end.
lookup_element(#tab{type=Type, impl=Impl}, Key, Pos) ->
Element =
case impl_lookup(Impl, encode(Type, Key)) of
'$end_of_table' ->
%% @NOTE This is not an atomic operation
Acc;
'$end_of_table';
Object when is_binary(Object) ->
[decode(Type, Object)|Acc]
decode(Type, Object)
end,
tab2list(Tab, Impl, impl_next(Impl, Key), NewAcc).
element(Pos, Element).
member(#tab{type=Type, impl=Impl}, Key) ->
impl_member(Impl, encode(Type, Key)).
next(#tab{type=Type, impl=Impl}, Key) ->
case impl_next(Impl, encode(Type, Key)) of
'$end_of_table' ->
'$end_of_table';
Next ->
decode(Type, Next)
end.
next_iter(#tab{type=Type, impl=Impl}, Key) ->
case impl_next_iter(Impl, encode(Type, Key)) of
'$end_of_table' ->
'$end_of_table';
Next ->
decode(Type, Next)
end.
prev(#tab{type=Type, impl=Impl}, Key) ->
case impl_prev(Impl, encode(Type, Key)) of
'$end_of_table' ->
'$end_of_table';
Prev ->
decode(Type, Prev)
end.
prev_iter(#tab{type=Type, impl=Impl}, Key) ->
case impl_prev_iter(Impl, encode(Type, Key)) of
'$end_of_table' ->
'$end_of_table';
Prev ->
decode(Type, Prev)
end.
foldl(Fun, Acc0, Tab) ->
foldl(Fun, Acc0, Tab, first_iter(Tab)).
foldr(Fun, Acc0, Tab) ->
foldr(Fun, Acc0, Tab, last_iter(Tab)).
nfoldl(Fun, Acc0, Tab, Limit) when Limit > 0 ->
nfoldl(Fun, Acc0, Acc0, Tab, Limit, Limit, first_iter(Tab));
nfoldl(_Fun, _Acc0, _Tab, Limit) ->
exit({badarg,Limit}).
nfoldl('$end_of_table') ->
'$end_of_table';
nfoldl({_Fun, _Acc0, _Tab, _Limit0, '$end_of_table'}) ->
'$end_of_table';
nfoldl({Fun, Acc0, Tab, Limit0, Key}) ->
nfoldl(Fun, Acc0, Acc0, Tab, Limit0, Limit0, next_iter(Tab, Key)).
nfoldr(Fun, Acc0, Tab, Limit) when Limit > 0 ->
nfoldr(Fun, Acc0, Acc0, Tab, Limit, Limit, last_iter(Tab));
nfoldr(_Fun, _Acc0, _Tab, Limit) ->
exit({badarg,Limit}).
nfoldr('$end_of_table') ->
'$end_of_table';
nfoldr({_Fun, _Acc0, _Tab, _Limit0, '$end_of_table'}) ->
'$end_of_table';
nfoldr({Fun, Acc0, Tab, Limit0, Key}) ->
nfoldr(Fun, Acc0, Acc0, Tab, Limit0, Limit0, prev_iter(Tab, Key)).
tab2list(Tab) ->
foldr(fun(X, Acc) -> [X|Acc] end, [], Tab).
match(Tab, Pattern) ->
select(Tab, [{Pattern, [], ['$$']}]).
match(Tab, Pattern, Limit) ->
select(Tab, [{Pattern, [], ['$$']}], Limit).
match(Cont) ->
select(Cont).
match_delete(Tab, Pattern) ->
select_delete(Tab, [{Pattern, [], [true]}]),
true.
match_object(Tab, Pattern) ->
select(Tab, [{Pattern, [], ['$_']}]).
match_object(Tab, Pattern, Limit) ->
select(Tab, [{Pattern, [], ['$_']}], Limit).
match_object(Cont) ->
select(Cont).
select(Tab, Spec) ->
Fun = fun(_Object, Match, Acc) -> [Match|Acc] end,
selectr(Fun, [], Tab, Spec).
select(Tab, Spec, Limit) ->
Fun = fun(_Object, Match, Acc) -> [Match|Acc] end,
case nselectl(Fun, [], Tab, Spec, Limit) of
{Acc, Cont} ->
{lists:reverse(Acc), Cont};
Cont ->
Cont
end.
select(Cont0) ->
case nselectl(Cont0) of
{Acc, Cont} ->
{lists:reverse(Acc), Cont};
Cont ->
Cont
end.
select_count(Tab, Spec) ->
Fun = fun(_Object, true, Acc) ->
Acc + 1;
(_Object, _Match, Acc) ->
Acc
end,
selectl(Fun, 0, Tab, Spec).
select_delete(#tab{keypos=KeyPos}=Tab, Spec) ->
Fun = fun(Object, true, Acc) ->
Key = element(KeyPos, Object),
delete(Tab, Key),
Acc + 1;
(_Object, _Match, Acc) ->
Acc
end,
selectl(Fun, 0, Tab, Spec).
select_reverse(Tab, Spec) ->
Fun = fun(_Object, Match, Acc) -> [Match|Acc] end,
selectl(Fun, [], Tab, Spec).
select_reverse(Tab, Spec, Limit) ->
Fun = fun(_Object, Match, Acc) -> [Match|Acc] end,
case nselectr(Fun, [], Tab, Spec, Limit) of
{Acc, Cont} ->
{lists:reverse(Acc), Cont};
Cont ->
Cont
end.
select_reverse(Cont0) ->
case nselectr(Cont0) of
{Acc, Cont} ->
{lists:reverse(Acc), Cont};
Cont ->
Cont
end.
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
foldl(_Fun, Acc, _Tab, '$end_of_table') ->
Acc;
foldl(Fun, Acc, #tab{keypos=KeyPos}=Tab, Object) ->
Key = element(KeyPos, Object),
foldl(Fun, Fun(Object, Acc), Tab, next_iter(Tab, Key)).
foldr(_Fun, Acc, _Tab, '$end_of_table') ->
Acc;
foldr(Fun, Acc, #tab{keypos=KeyPos}=Tab, Object) ->
Key = element(KeyPos, Object),
foldr(Fun, Fun(Object, Acc), Tab, prev_iter(Tab, Key)).
nfoldl(_Fun, Acc0, Acc0, _Tab, _Limit0, _Limit, '$end_of_table') ->
'$end_of_table';
nfoldl(_Fun, _Acc0, Acc, _Tab, _Limit0, _Limit, '$end_of_table'=Cont) ->
{Acc, Cont};
nfoldl(Fun, Acc0, Acc, #tab{keypos=KeyPos}=Tab, Limit0, Limit, Object) ->
Key = element(KeyPos, Object),
case Fun(Object, Acc) of
{true, NewAcc} ->
if Limit > 1 ->
nfoldl(Fun, Acc0, NewAcc, Tab, Limit0, Limit-1, next_iter(Tab, Key));
true ->
Cont = {Fun, Acc0, Tab, Limit0, Key},
{NewAcc, Cont}
end;
{false, NewAcc} ->
nfoldl(Fun, Acc0, NewAcc, Tab, Limit0, Limit, next_iter(Tab, Key))
end.
nfoldr(_Fun, Acc0, Acc0, _Tab, _Limit0, _Limit, '$end_of_table') ->
'$end_of_table';
nfoldr(_Fun, _Acc0, Acc, _Tab, _Limit0, _Limit, '$end_of_table'=Cont) ->
{Acc, Cont};
nfoldr(Fun, Acc0, Acc, #tab{keypos=KeyPos}=Tab, Limit0, Limit, Object) ->
Key = element(KeyPos, Object),
case Fun(Object, Acc) of
{true, NewAcc} ->
if Limit > 1 ->
nfoldr(Fun, Acc0, NewAcc, Tab, Limit0, Limit-1, prev_iter(Tab, Key));
true ->
Cont = {Fun, Acc0, Tab, Limit0, Key},
{NewAcc, Cont}
end;
{false, NewAcc} ->
nfoldr(Fun, Acc0, NewAcc, Tab, Limit0, Limit, prev_iter(Tab, Key))
end.
selectl(Fun, Acc0, Tab, Spec) ->
foldl(selectfun(Fun, Spec), Acc0, Tab).
selectr(Fun, Acc0, Tab, Spec) ->
foldr(selectfun(Fun, Spec), Acc0, Tab).
nselectl(Fun, Acc0, Tab, Spec, Limit0) ->
nfoldl(nselectfun(Fun, Spec), Acc0, Tab, Limit0).
nselectr(Fun, Acc0, Tab, Spec, Limit0) ->
nfoldr(nselectfun(Fun, Spec), Acc0, Tab, Limit0).
nselectl(Cont) ->
nfoldl(Cont).
nselectr(Cont) ->
nfoldr(Cont).
selectfun(Fun, Spec) ->
CMSpec = ets:match_spec_compile(Spec),
fun(Object, Acc) ->
case ets:match_spec_run([Object], CMSpec) of
[] ->
Acc;
[Match] ->
Fun(Object, Match, Acc)
end
end.
nselectfun(Fun, Spec) ->
CMSpec = ets:match_spec_compile(Spec),
fun(Object, Acc) ->
case ets:match_spec_run([Object], CMSpec) of
[] ->
{false, Acc};
[Match] ->
{true, Fun(Object, Match, Acc)}
end
end.
encode(set, Term) ->
term_to_binary(Term);
encode(ordered_set, Term) ->
@ -200,6 +485,22 @@ decode(set, Term) ->
decode(ordered_set, Term) ->
sext:decode(Term).
call(Impl, Tuple) ->
Data = term_to_binary(Tuple),
port_command(Impl, Data),
receive
{Impl, ?LETS_BINARY, Reply} ->
Reply;
{Impl, ?LETS_TRUE} ->
true;
{Impl, ?LETS_FALSE} ->
false;
{Impl, ?LETS_END_OF_TABLE} ->
'$end_of_table';
{Impl, ?LETS_BADARG} ->
erlang:error(badarg, [Impl])
end.
impl_open(Type, Protection, Path, Options, ReadOptions, WriteOptions) ->
Impl = init(),
true = call(Impl, {?LETS_OPEN6, Type, Protection, Path, Options, ReadOptions, WriteOptions}),
@ -219,18 +520,6 @@ impl_repair(Type, Protection, Path, Options, ReadOptions, WriteOptions) ->
_ = erl_ddll:unload(lets_drv),
true.
impl_insert(Impl, Key, Object) ->
call(Impl, {?LETS_INSERT3, Key, Object}).
impl_insert(Impl, List) ->
call(Impl, {?LETS_INSERT2, List}).
impl_insert_new(Impl, Key, Object) ->
call(Impl, {?LETS_INSERT_NEW3, Key, Object}).
impl_insert_new(Impl, List) ->
call(Impl, {?LETS_INSERT_NEW2, List}).
impl_delete(Impl) ->
Res = call(Impl, {?LETS_DELETE1}),
_ = port_close(Impl),
@ -243,14 +532,17 @@ impl_delete(Impl, Key) ->
impl_delete_all_objects(Impl) ->
call(Impl, {?LETS_DELETE_ALL_OBJECTS1}).
impl_lookup(Impl, Key) ->
call(Impl, {?LETS_LOOKUP2, Key}).
impl_first(Impl) ->
call(Impl, {?LETS_FIRST1}).
impl_next(Impl, Key) ->
call(Impl, {?LETS_NEXT2, Key}).
impl_first_iter(Impl) ->
call(Impl, {?LETS_FIRST_ITER1}).
impl_last(Impl) ->
call(Impl, {?LETS_LAST1}).
impl_last_iter(Impl) ->
call(Impl, {?LETS_LAST_ITER1}).
impl_info_memory(Impl) ->
call(Impl, {?LETS_INFO_MEMORY1}).
@ -258,16 +550,32 @@ impl_info_memory(Impl) ->
impl_info_size(Impl) ->
call(Impl, {?LETS_INFO_SIZE1}).
call(Impl, Tuple) ->
Data = term_to_binary(Tuple),
port_command(Impl, Data),
receive
{Impl, ?LETS_BINARY, Reply} ->
Reply;
{Impl, ?LETS_TRUE} ->
true;
{Impl, ?LETS_END_OF_TABLE} ->
'$end_of_table';
{Impl, ?LETS_BADARG} ->
erlang:error(badarg, [Impl])
end.
impl_insert(Impl, Key, Object) ->
call(Impl, {?LETS_INSERT3, Key, Object}).
impl_insert(Impl, List) ->
call(Impl, {?LETS_INSERT2, List}).
impl_insert_new(Impl, Key, Object) ->
call(Impl, {?LETS_INSERT_NEW3, Key, Object}).
impl_insert_new(Impl, List) ->
call(Impl, {?LETS_INSERT_NEW2, List}).
impl_lookup(Impl, Key) ->
call(Impl, {?LETS_LOOKUP2, Key}).
impl_member(Impl, Key) ->
call(Impl, {?LETS_MEMBER2, Key}).
impl_next(Impl, Key) ->
call(Impl, {?LETS_NEXT2, Key}).
impl_next_iter(Impl, Key) ->
call(Impl, {?LETS_NEXT_ITER2, Key}).
impl_prev(Impl, Key) ->
call(Impl, {?LETS_PREV2, Key}).
impl_prev_iter(Impl, Key) ->
call(Impl, {?LETS_PREV_ITER2, Key}).

View file

@ -28,17 +28,38 @@
-export([open/1
, destroy/1
, repair/1
, insert/3
, insert_new/3
, delete/1
, delete/2
, delete/3
, delete_all_objects/2
, lookup/3
, first/2
, next/3
, info_memory/2
, info_size/2
, tab2list/2
, delete_all_objects/1
, first/1
, foldl/3
, foldr/3
, info_memory/1
, info_size/1
, insert/2
, insert_new/2
, last/1
, lookup/2
, lookup_element/3
, match/2
, match/3
, match/1
, match_delete/2
, match_object/2
, match_object/3
, match_object/1
, member/2
, next/2
, prev/2
, select/2
, select/3
, select/1
, select_count/2
, select_delete/2
, select_reverse/2
, select_reverse/3
, select_reverse/1
, tab2list/1
]).
@ -56,8 +77,8 @@ open(#tab{name=Name, named_table=NamedTable, type=Type, keypos=KeyPos, protectio
[Type, {keypos,KeyPos}, Protection] ++
[named_table || NamedTable ] ++
[compressed || Compressed ],
Ets = ets:new(Name, Opts),
Tab#tab{ets=Ets}.
Impl = ets:new(Name, Opts),
Tab#tab{impl=Impl}.
destroy(#tab{}) ->
true.
@ -65,35 +86,98 @@ destroy(#tab{}) ->
repair(#tab{}) ->
true.
insert(_Tab, Ets, ObjectOrObjects) ->
ets:insert(Ets, ObjectOrObjects).
delete(#tab{impl=Impl}) ->
ets:delete(Impl).
insert_new(_Tab, Ets, ObjectOrObjects) ->
ets:insert_new(Ets, ObjectOrObjects).
delete(#tab{impl=Impl}, Key) ->
ets:delete(Impl, Key).
delete(_Tab, Ets) ->
ets:delete(Ets).
delete_all_objects(#tab{impl=Impl}) ->
ets:delete_all_objects(Impl).
delete(_Tab, Ets, Key) ->
ets:delete(Ets, Key).
first(#tab{impl=Impl}) ->
ets:first(Impl).
delete_all_objects(_Tab, Ets) ->
ets:delete_all_objects(Ets).
foldl(Function, Acc0, #tab{impl=Impl}) ->
ets:foldl(Function, Acc0, Impl).
lookup(_Tab, Ets, Key) ->
ets:lookup(Ets, Key).
foldr(Function, Acc0, #tab{impl=Impl}) ->
ets:foldr(Function, Acc0, Impl).
first(_Tab, Ets) ->
ets:first(Ets).
info_memory(#tab{impl=Impl}) ->
ets:info(Impl, memory).
next(_Tab, Ets, Key) ->
ets:next(Ets, Key).
info_size(#tab{impl=Impl}) ->
ets:info(Impl, size).
info_memory(_Tab, Ets) ->
ets:info(Ets, memory).
insert(#tab{impl=Impl}, ObjOrObjs) ->
ets:insert(Impl, ObjOrObjs).
info_size(_Tab, Ets) ->
ets:info(Ets, size).
insert_new(#tab{impl=Impl}, ObjOrObjs) ->
ets:insert_new(Impl, ObjOrObjs).
tab2list(_Tab, Ets) ->
ets:tab2list(Ets).
last(#tab{impl=Impl}) ->
ets:last(Impl).
lookup(#tab{impl=Impl}, Key) ->
ets:lookup(Impl, Key).
lookup_element(#tab{impl=Impl}, Key, Pos) ->
ets:lookup_element(Impl, Key, Pos).
match(#tab{impl=Impl}, Pattern) ->
ets:match(Impl, Pattern).
match(#tab{impl=Impl}, Pattern, Limit) ->
ets:match(Impl, Pattern, Limit).
match(Cont) ->
ets:match(Cont).
match_delete(#tab{impl=Impl}, Pattern) ->
ets:match_delete(Impl, Pattern).
match_object(#tab{impl=Impl}, Pattern) ->
ets:match_object(Impl, Pattern).
match_object(#tab{impl=Impl}, Pattern, Limit) ->
ets:match_object(Impl, Pattern, Limit).
match_object(Cont) ->
ets:match_object(Cont).
member(#tab{impl=Impl}, Key) ->
ets:member(Impl, Key).
next(#tab{impl=Impl}, Key) ->
ets:next(Impl, Key).
prev(#tab{impl=Impl}, Key) ->
ets:prev(Impl, Key).
select(#tab{impl=Impl}, Spec) ->
ets:select(Impl, Spec).
select(#tab{impl=Impl}, Spec, Limit) ->
ets:select(Impl, Spec, Limit).
select(Cont) ->
ets:select(Cont).
select_count(#tab{impl=Impl}, Spec) ->
ets:select_count(Impl, Spec).
select_delete(#tab{impl=Impl}, Spec) ->
ets:select_delete(Impl, Spec).
select_reverse(#tab{impl=Impl}, Spec) ->
ets:select_reverse(Impl, Spec).
select_reverse(#tab{impl=Impl}, Spec, Limit) ->
ets:select_reverse(Impl, Spec, Limit).
select_reverse(Cont) ->
ets:select_reverse(Cont).
tab2list(#tab{impl=Impl}) ->
ets:tab2list(Impl).

View file

@ -28,19 +28,49 @@
-export([open/4
, destroy/4
, repair/4
, insert/3
, insert_new/3
, delete/1
, delete/2
, delete/3
, delete_all_objects/2
, lookup/3
, first/2
, next/3
, info_memory/2
, info_size/2
, tab2list/2
, delete_all_objects/1
, first/1
%% , first_iter/1
, foldl/3
, foldr/3
%% , nfoldl/4
%% , nfoldr/4
%% , nfoldl/1
%% , nfoldr/1
, info_memory/1
, info_size/1
, insert/2
, insert_new/2
, last/1
%% , last_iter/1
, lookup/2
, lookup_element/3
, match/2
, match/3
, match/1
, match_delete/2
, match_object/2
, match_object/3
, match_object/1
, member/2
, next/2
%% , next_iter/2
, prev/2
%% , prev_iter/2
, select/2
, select/3
, select/1
, select_count/2
, select_delete/2
, select_reverse/2
, select_reverse/3
, select_reverse/1
, tab2list/1
]).
-on_load(init/0).
@ -69,7 +99,7 @@ open(#tab{name=_Name, named_table=_Named, type=Type, protection=Protection}=Tab,
{value, {path,Path}, NewOptions} = lists:keytake(path, 1, Options),
Impl = impl_open(Type, Protection, Path, NewOptions, ReadOptions, WriteOptions),
%% @TODO implement named Impl (of sorts)
Tab#tab{nif=Impl}.
Tab#tab{impl=Impl}.
destroy(#tab{type=Type, protection=Protection}, Options, ReadOptions, WriteOptions) ->
{value, {path,Path}, NewOptions} = lists:keytake(path, 1, Options),
@ -79,40 +109,16 @@ repair(#tab{type=Type, protection=Protection}, Options, ReadOptions, WriteOption
{value, {path,Path}, NewOptions} = lists:keytake(path, 1, Options),
impl_repair(Type, Protection, Path, NewOptions, ReadOptions, WriteOptions).
insert(#tab{keypos=KeyPos, type=Type}, Impl, Object) when is_tuple(Object) ->
Key = element(KeyPos,Object),
Val = Object,
impl_insert(Impl, encode(Type, Key), encode(Type, Val));
insert(#tab{keypos=KeyPos, type=Type}, Impl, Objects) when is_list(Objects) ->
List = [{encode(Type, element(KeyPos,Object)), encode(Type, Object)} || Object <- Objects ],
impl_insert(Impl, List).
insert_new(#tab{keypos=KeyPos, type=Type}, Impl, Object) when is_tuple(Object) ->
Key = element(KeyPos,Object),
Val = Object,
impl_insert_new(Impl, encode(Type, Key), encode(Type, Val));
insert_new(#tab{keypos=KeyPos, type=Type}, Impl, Objects) when is_list(Objects) ->
List = [{encode(Type, element(KeyPos,Object)), encode(Type, Object)} || Object <- Objects ],
impl_insert_new(Impl, List).
delete(_Tab, Impl) ->
delete(#tab{impl=Impl}) ->
impl_delete(Impl).
delete(#tab{type=Type}, Impl, Key) ->
delete(#tab{type=Type, impl=Impl}, Key) ->
impl_delete(Impl, encode(Type, Key)).
delete_all_objects(_Tab, Impl) ->
delete_all_objects(#tab{impl=Impl}) ->
impl_delete_all_objects(Impl).
lookup(#tab{type=Type}, Impl, Key) ->
case impl_lookup(Impl, encode(Type, Key)) of
'$end_of_table' ->
[];
Object when is_binary(Object) ->
[decode(Type, Object)]
end.
first(#tab{type=Type}, Impl) ->
first(#tab{type=Type, impl=Impl}) ->
case impl_first(Impl) of
'$end_of_table' ->
'$end_of_table';
@ -120,15 +126,31 @@ first(#tab{type=Type}, Impl) ->
decode(Type, Key)
end.
next(#tab{type=Type}, Impl, Key) ->
case impl_next(Impl, encode(Type, Key)) of
first_iter(#tab{type=Type, impl=Impl}) ->
case impl_first_iter(Impl) of
'$end_of_table' ->
'$end_of_table';
Next ->
decode(Type, Next)
Key ->
decode(Type, Key)
end.
info_memory(_Tab, Impl) ->
last(#tab{type=Type, impl=Impl}) ->
case impl_last(Impl) of
'$end_of_table' ->
'$end_of_table';
Key ->
decode(Type, Key)
end.
last_iter(#tab{type=Type, impl=Impl}) ->
case impl_last_iter(Impl) of
'$end_of_table' ->
'$end_of_table';
Key ->
decode(Type, Key)
end.
info_memory(#tab{impl=Impl}) ->
case impl_info_memory(Impl) of
Memory when is_integer(Memory) ->
erlang:round(Memory / erlang:system_info(wordsize));
@ -136,30 +158,286 @@ info_memory(_Tab, Impl) ->
Else
end.
info_size(_Tab, Impl) ->
info_size(#tab{impl=Impl}) ->
impl_info_size(Impl).
tab2list(Tab, Impl) ->
tab2list(Tab, Impl, impl_first(Impl), []).
insert(#tab{keypos=KeyPos, type=Type, impl=Impl}, Object) when is_tuple(Object) ->
Key = element(KeyPos, Object),
Val = Object,
impl_insert(Impl, encode(Type, Key), encode(Type, Val));
insert(#tab{keypos=KeyPos, type=Type, impl=Impl}, Objects) when is_list(Objects) ->
List = [{encode(Type, element(KeyPos, Object)), encode(Type, Object)} || Object <- Objects ],
impl_insert(Impl, List).
tab2list(_Tab, _Impl, '$end_of_table', Acc) ->
lists:reverse(Acc);
tab2list(#tab{type=Type}=Tab, Impl, Key, Acc) ->
NewAcc =
case impl_lookup(Impl, Key) of
insert_new(#tab{keypos=KeyPos, type=Type, impl=Impl}, Object) when is_tuple(Object) ->
Key = element(KeyPos, Object),
Val = Object,
impl_insert_new(Impl, encode(Type, Key), encode(Type, Val));
insert_new(#tab{keypos=KeyPos, type=Type, impl=Impl}, Objects) when is_list(Objects) ->
List = [{encode(Type, element(KeyPos, Object)), encode(Type, Object)} || Object <- Objects ],
impl_insert_new(Impl, List).
lookup(#tab{type=Type, impl=Impl}, Key) ->
case impl_lookup(Impl, encode(Type, Key)) of
'$end_of_table' ->
[];
Object when is_binary(Object) ->
[decode(Type, Object)]
end.
lookup_element(#tab{type=Type, impl=Impl}, Key, Pos) ->
Element =
case impl_lookup(Impl, encode(Type, Key)) of
'$end_of_table' ->
%% @NOTE This is not an atomic operation
Acc;
'$end_of_table';
Object when is_binary(Object) ->
[decode(Type, Object)|Acc]
decode(Type, Object)
end,
tab2list(Tab, Impl, impl_next(Impl, Key), NewAcc).
element(Pos, Element).
member(#tab{type=Type, impl=Impl}, Key) ->
impl_member(Impl, encode(Type, Key)).
next(#tab{type=Type, impl=Impl}, Key) ->
case impl_next(Impl, encode(Type, Key)) of
'$end_of_table' ->
'$end_of_table';
Next ->
decode(Type, Next)
end.
next_iter(#tab{type=Type, impl=Impl}, Key) ->
case impl_next_iter(Impl, encode(Type, Key)) of
'$end_of_table' ->
'$end_of_table';
Next ->
decode(Type, Next)
end.
prev(#tab{type=Type, impl=Impl}, Key) ->
case impl_prev(Impl, encode(Type, Key)) of
'$end_of_table' ->
'$end_of_table';
Prev ->
decode(Type, Prev)
end.
prev_iter(#tab{type=Type, impl=Impl}, Key) ->
case impl_prev_iter(Impl, encode(Type, Key)) of
'$end_of_table' ->
'$end_of_table';
Prev ->
decode(Type, Prev)
end.
foldl(Fun, Acc0, Tab) ->
foldl(Fun, Acc0, Tab, first_iter(Tab)).
foldr(Fun, Acc0, Tab) ->
foldr(Fun, Acc0, Tab, last_iter(Tab)).
nfoldl(Fun, Acc0, Tab, Limit) when Limit > 0 ->
nfoldl(Fun, Acc0, Acc0, Tab, Limit, Limit, first_iter(Tab));
nfoldl(_Fun, _Acc0, _Tab, Limit) ->
exit({badarg,Limit}).
nfoldl('$end_of_table') ->
'$end_of_table';
nfoldl({_Fun, _Acc0, _Tab, _Limit0, '$end_of_table'}) ->
'$end_of_table';
nfoldl({Fun, Acc0, Tab, Limit0, Key}) ->
nfoldl(Fun, Acc0, Acc0, Tab, Limit0, Limit0, next_iter(Tab, Key)).
nfoldr(Fun, Acc0, Tab, Limit) when Limit > 0 ->
nfoldr(Fun, Acc0, Acc0, Tab, Limit, Limit, last_iter(Tab));
nfoldr(_Fun, _Acc0, _Tab, Limit) ->
exit({badarg,Limit}).
nfoldr('$end_of_table') ->
'$end_of_table';
nfoldr({_Fun, _Acc0, _Tab, _Limit0, '$end_of_table'}) ->
'$end_of_table';
nfoldr({Fun, Acc0, Tab, Limit0, Key}) ->
nfoldr(Fun, Acc0, Acc0, Tab, Limit0, Limit0, prev_iter(Tab, Key)).
tab2list(Tab) ->
foldr(fun(X, Acc) -> [X|Acc] end, [], Tab).
match(Tab, Pattern) ->
select(Tab, [{Pattern, [], ['$$']}]).
match(Tab, Pattern, Limit) ->
select(Tab, [{Pattern, [], ['$$']}], Limit).
match(Cont) ->
select(Cont).
match_delete(Tab, Pattern) ->
select_delete(Tab, [{Pattern, [], [true]}]),
true.
match_object(Tab, Pattern) ->
select(Tab, [{Pattern, [], ['$_']}]).
match_object(Tab, Pattern, Limit) ->
select(Tab, [{Pattern, [], ['$_']}], Limit).
match_object(Cont) ->
select(Cont).
select(Tab, Spec) ->
Fun = fun(_Object, Match, Acc) -> [Match|Acc] end,
selectr(Fun, [], Tab, Spec).
select(Tab, Spec, Limit) ->
Fun = fun(_Object, Match, Acc) -> [Match|Acc] end,
case nselectl(Fun, [], Tab, Spec, Limit) of
{Acc, Cont} ->
{lists:reverse(Acc), Cont};
Cont ->
Cont
end.
select(Cont0) ->
case nselectl(Cont0) of
{Acc, Cont} ->
{lists:reverse(Acc), Cont};
Cont ->
Cont
end.
select_count(Tab, Spec) ->
Fun = fun(_Object, true, Acc) ->
Acc + 1;
(_Object, _Match, Acc) ->
Acc
end,
selectl(Fun, 0, Tab, Spec).
select_delete(#tab{keypos=KeyPos}=Tab, Spec) ->
Fun = fun(Object, true, Acc) ->
Key = element(KeyPos, Object),
delete(Tab, Key),
Acc + 1;
(_Object, _Match, Acc) ->
Acc
end,
selectl(Fun, 0, Tab, Spec).
select_reverse(Tab, Spec) ->
Fun = fun(_Object, Match, Acc) -> [Match|Acc] end,
selectl(Fun, [], Tab, Spec).
select_reverse(Tab, Spec, Limit) ->
Fun = fun(_Object, Match, Acc) -> [Match|Acc] end,
case nselectr(Fun, [], Tab, Spec, Limit) of
{Acc, Cont} ->
{lists:reverse(Acc), Cont};
Cont ->
Cont
end.
select_reverse(Cont0) ->
case nselectr(Cont0) of
{Acc, Cont} ->
{lists:reverse(Acc), Cont};
Cont ->
Cont
end.
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
foldl(_Fun, Acc, _Tab, '$end_of_table') ->
Acc;
foldl(Fun, Acc, #tab{keypos=KeyPos}=Tab, Object) ->
Key = element(KeyPos, Object),
foldl(Fun, Fun(Object, Acc), Tab, next_iter(Tab, Key)).
foldr(_Fun, Acc, _Tab, '$end_of_table') ->
Acc;
foldr(Fun, Acc, #tab{keypos=KeyPos}=Tab, Object) ->
Key = element(KeyPos, Object),
foldr(Fun, Fun(Object, Acc), Tab, prev_iter(Tab, Key)).
nfoldl(_Fun, Acc0, Acc0, _Tab, _Limit0, _Limit, '$end_of_table') ->
'$end_of_table';
nfoldl(_Fun, _Acc0, Acc, _Tab, _Limit0, _Limit, '$end_of_table'=Cont) ->
{Acc, Cont};
nfoldl(Fun, Acc0, Acc, #tab{keypos=KeyPos}=Tab, Limit0, Limit, Object) ->
Key = element(KeyPos, Object),
case Fun(Object, Acc) of
{true, NewAcc} ->
if Limit > 1 ->
nfoldl(Fun, Acc0, NewAcc, Tab, Limit0, Limit-1, next_iter(Tab, Key));
true ->
Cont = {Fun, Acc0, Tab, Limit0, Key},
{NewAcc, Cont}
end;
{false, NewAcc} ->
nfoldl(Fun, Acc0, NewAcc, Tab, Limit0, Limit, next_iter(Tab, Key))
end.
nfoldr(_Fun, Acc0, Acc0, _Tab, _Limit0, _Limit, '$end_of_table') ->
'$end_of_table';
nfoldr(_Fun, _Acc0, Acc, _Tab, _Limit0, _Limit, '$end_of_table'=Cont) ->
{Acc, Cont};
nfoldr(Fun, Acc0, Acc, #tab{keypos=KeyPos}=Tab, Limit0, Limit, Object) ->
Key = element(KeyPos, Object),
case Fun(Object, Acc) of
{true, NewAcc} ->
if Limit > 1 ->
nfoldr(Fun, Acc0, NewAcc, Tab, Limit0, Limit-1, prev_iter(Tab, Key));
true ->
Cont = {Fun, Acc0, Tab, Limit0, Key},
{NewAcc, Cont}
end;
{false, NewAcc} ->
nfoldr(Fun, Acc0, NewAcc, Tab, Limit0, Limit, prev_iter(Tab, Key))
end.
selectl(Fun, Acc0, Tab, Spec) ->
foldl(selectfun(Fun, Spec), Acc0, Tab).
selectr(Fun, Acc0, Tab, Spec) ->
foldr(selectfun(Fun, Spec), Acc0, Tab).
nselectl(Fun, Acc0, Tab, Spec, Limit0) ->
nfoldl(nselectfun(Fun, Spec), Acc0, Tab, Limit0).
nselectr(Fun, Acc0, Tab, Spec, Limit0) ->
nfoldr(nselectfun(Fun, Spec), Acc0, Tab, Limit0).
nselectl(Cont) ->
nfoldl(Cont).
nselectr(Cont) ->
nfoldr(Cont).
selectfun(Fun, Spec) ->
CMSpec = ets:match_spec_compile(Spec),
fun(Object, Acc) ->
case ets:match_spec_run([Object], CMSpec) of
[] ->
Acc;
[Match] ->
Fun(Object, Match, Acc)
end
end.
nselectfun(Fun, Spec) ->
CMSpec = ets:match_spec_compile(Spec),
fun(Object, Acc) ->
case ets:match_spec_run([Object], CMSpec) of
[] ->
{false, Acc};
[Match] ->
{true, Fun(Object, Match, Acc)}
end
end.
encode(set, Term) ->
term_to_binary(Term);
encode(ordered_set, Term) ->
@ -182,6 +460,33 @@ impl_destroy(_Type, _Protection, _Path, _Options, _ReadOptions, _WriteOptions) -
impl_repair(_Type, _Protection, _Path, _Options, _ReadOptions, _WriteOptions) ->
?NIF_STUB.
impl_delete(_Impl) ->
?NIF_STUB.
impl_delete(_Impl, _Key) ->
?NIF_STUB.
impl_delete_all_objects(_Impl) ->
?NIF_STUB.
impl_first(_Impl) ->
?NIF_STUB.
impl_first_iter(_Impl) ->
?NIF_STUB.
impl_last(_Impl) ->
?NIF_STUB.
impl_last_iter(_Impl) ->
?NIF_STUB.
impl_info_memory(_Impl) ->
?NIF_STUB.
impl_info_size(_Impl) ->
?NIF_STUB.
impl_insert(_Impl, _Key, _Object) ->
?NIF_STUB.
@ -194,26 +499,20 @@ impl_insert_new(_Impl, _Key, _Object) ->
impl_insert_new(_Impl, _List) ->
?NIF_STUB.
impl_delete(_Impl) ->
?NIF_STUB.
impl_delete(_Impl, _Key) ->
?NIF_STUB.
impl_delete_all_objects(_Impl) ->
?NIF_STUB.
impl_lookup(_Impl, _Key) ->
?NIF_STUB.
impl_first(_Impl) ->
impl_member(_Impl, _Key) ->
?NIF_STUB.
impl_next(_Impl, _Key) ->
?NIF_STUB.
impl_info_memory(_Impl) ->
impl_next_iter(_Impl, _Key) ->
?NIF_STUB.
impl_info_size(_Impl) ->
impl_prev(_Impl, _Key) ->
?NIF_STUB.
impl_prev_iter(_Impl, _Key) ->
?NIF_STUB.

View file

@ -33,15 +33,37 @@
, new/3
, destroy/3
, repair/3
, insert/2
, insert_new/2
, delete/1
, delete/2
, delete_all_objects/1
, lookup/2
, first/1
, next/2
, foldl/3
, foldr/3
, info/1
, info/2
, insert/2
, insert_new/2
, last/1
, lookup/2
, lookup_element/3
, match/2
, match/3
, match/1
, match_delete/2
, match_object/2
, match_object/3
, match_object/1
, member/2
, next/2
, prev/2
, select/2
, select/3
, select/1
, select_count/2
, select_delete/2
, select_reverse/2
, select_reverse/3
, select_reverse/1
, tab2list/1
]).
@ -49,7 +71,7 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-record(state, {tab}).
-record(state, {tab, name, conts=dict:new()}).
%%%===================================================================
@ -76,12 +98,6 @@ destroy(Tab, Name, Options) ->
repair(Tab, Name, Options) ->
qc_lets_raw:repair(Tab, Name, Options).
insert(Tab, ObjOrObjs) ->
gen_server:call(Tab, {insert, ObjOrObjs}).
insert_new(Tab, ObjOrObjs) ->
gen_server:call(Tab, {insert_new, ObjOrObjs}).
delete(Tab) ->
gen_server:call(Tab, delete).
@ -91,17 +107,101 @@ delete(Tab, Key) ->
delete_all_objects(Tab) ->
gen_server:call(Tab, delete_all_objects).
first(Tab) ->
gen_server:call(Tab, first).
foldl(Function, Acc0, Tab) ->
gen_server:call(Tab, {foldl, Function, Acc0}).
foldr(Function, Acc0, Tab) ->
gen_server:call(Tab, {foldr, Function, Acc0}).
info(Tab) ->
gen_server:call(Tab, info).
info(Tab, Item) ->
gen_server:call(Tab, {info, Item}).
insert(Tab, ObjOrObjs) ->
gen_server:call(Tab, {insert, ObjOrObjs}).
insert_new(Tab, ObjOrObjs) ->
gen_server:call(Tab, {insert_new, ObjOrObjs}).
last(Tab) ->
gen_server:call(Tab, last).
lookup(Tab, Key) ->
gen_server:call(Tab, {lookup, Key}).
first(Tab) ->
gen_server:call(Tab, first).
lookup_element(Tab, Key, Pos) ->
gen_server:call(Tab, {lookup_element, Key, Pos}).
match(Tab, Pattern) ->
gen_server:call(Tab, {match, Pattern}).
match(Tab, Pattern, Limit) ->
gen_server:call(Tab, {match, Pattern, Limit}).
match({wcont, _, _}=Cont0) ->
{Tab, Cont} = unwrap_cont(Cont0),
gen_server:call(Tab, {match_cont, Cont});
match('$end_of_table') ->
'$end_of_table'.
match_delete(Tab, Pattern) ->
gen_server:call(Tab, {match_delete, Pattern}).
match_object(Tab, Pattern) ->
gen_server:call(Tab, {match_object, Pattern}).
match_object(Tab, Pattern, Limit) ->
gen_server:call(Tab, {match_object, Pattern, Limit}).
match_object({wcont, _, _}=Cont0) ->
{Tab, Cont} = unwrap_cont(Cont0),
gen_server:call(Tab, {match_object_cont, Cont});
match_object('$end_of_table') ->
'$end_of_table'.
member(Tab, Key) ->
gen_server:call(Tab, {member, Key}).
next(Tab, Key) ->
gen_server:call(Tab, {next, Key}).
info(Tab, Item) ->
gen_server:call(Tab, {info, Item}).
prev(Tab, Key) ->
gen_server:call(Tab, {prev, Key}).
select(Tab, Spec) ->
gen_server:call(Tab, {select, Spec}).
select(Tab, Spec, Limit) ->
gen_server:call(Tab, {select, Spec, Limit}).
select({wcont, _, _}=Cont0) ->
{Tab, Cont} = unwrap_cont(Cont0),
gen_server:call(Tab, {select_cont, Cont});
select('$end_of_table') ->
'$end_of_table'.
select_count(Tab, Spec) ->
gen_server:call(Tab, {select_count, Spec}).
select_delete(Tab, Spec) ->
gen_server:call(Tab, {select_delete, Spec}).
select_reverse(Tab, Spec) ->
gen_server:call(Tab, {select_reverse, Spec}).
select_reverse(Tab, Spec, Limit) ->
gen_server:call(Tab, {select_reverse, Spec, Limit}).
select_reverse({wcont, _, _}=Cont0) ->
{Tab, Cont} = unwrap_cont(Cont0),
gen_server:call(Tab, {select_reverse_cont, Cont});
select_reverse('$end_of_table') ->
'$end_of_table'.
tab2list(Tab) ->
gen_server:call(Tab, tab2list).
@ -117,19 +217,13 @@ tab2list(Tab) ->
%%--------------------------------------------------------------------
init([Name, Options]) ->
Tab = qc_lets_raw:new(Name, Options),
{ok, #state{tab=Tab}}.
{ok, #state{tab=Tab, name=Name}}.
%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling call messages
%%--------------------------------------------------------------------
handle_call({insert, ObjOrObjs}, _From, #state{tab=Tab}=State) ->
Reply = qc_lets_raw:insert(Tab, ObjOrObjs),
{reply, Reply, State};
handle_call({insert_new, ObjOrObjs}, _From, #state{tab=Tab}=State) ->
Reply = qc_lets_raw:insert_new(Tab, ObjOrObjs),
{reply, Reply, State};
handle_call(delete, _From, #state{tab=Tab}=State) ->
Reply = qc_lets_raw:delete(Tab),
{stop, normal, Reply, State#state{tab=undefined}};
@ -139,18 +233,94 @@ handle_call({delete, Key}, _From, #state{tab=Tab}=State) ->
handle_call(delete_all_objects, _From, #state{tab=Tab}=State) ->
Reply = qc_lets_raw:delete_all_objects(Tab),
{reply, Reply, State};
handle_call({lookup, Key}, _From, #state{tab=Tab}=State) ->
Reply = qc_lets_raw:lookup(Tab, Key),
{reply, Reply, State};
handle_call(first, _From, #state{tab=Tab}=State) ->
Reply = qc_lets_raw:first(Tab),
{reply, Reply, State};
handle_call({next, Key}, _From, #state{tab=Tab}=State) ->
Reply = qc_lets_raw:next(Tab, Key),
handle_call({foldl, Function, Acc0}, _From, #state{tab=Tab}=State) ->
Reply = qc_lets_raw:foldl(Function, Acc0, Tab),
{reply, Reply, State};
handle_call({foldr, Function, Acc0}, _From, #state{tab=Tab}=State) ->
Reply = qc_lets_raw:foldr(Function, Acc0, Tab),
{reply, Reply, State};
handle_call(info, _From, #state{tab=Tab}=State) ->
Reply = qc_lets_raw:info(Tab),
{reply, Reply, State};
handle_call({info, Item}, _From, #state{tab=Tab}=State) ->
Reply = qc_lets_raw:info(Tab, Item),
{reply, Reply, State};
handle_call({insert, ObjOrObjs}, _From, #state{tab=Tab}=State) ->
Reply = qc_lets_raw:insert(Tab, ObjOrObjs),
{reply, Reply, State};
handle_call({insert_new, ObjOrObjs}, _From, #state{tab=Tab}=State) ->
Reply = qc_lets_raw:insert_new(Tab, ObjOrObjs),
{reply, Reply, State};
handle_call(last, _From, #state{tab=Tab}=State) ->
Reply = qc_lets_raw:last(Tab),
{reply, Reply, State};
handle_call({lookup, Key}, _From, #state{tab=Tab}=State) ->
Reply = qc_lets_raw:lookup(Tab, Key),
{reply, Reply, State};
handle_call({lookup_element, Key, Pos}, _From, #state{tab=Tab}=State) ->
Reply = qc_lets_raw:lookup_element(Tab, Key, Pos),
{reply, Reply, State};
handle_call({match, Pattern}, _From, #state{tab=Tab}=State) ->
Reply = qc_lets_raw:match(Tab, Pattern),
{reply, Reply, State};
handle_call({match, Pattern, Limit}, _From, #state{tab=Tab}=State) ->
{Reply, NewState} = wrap_cont(qc_lets_raw:match(Tab, Pattern, Limit), State),
{reply, Reply, NewState};
handle_call({match_cont, Cont0}, _From, State0) ->
{Cont, State} = unwrap_cont(Cont0, State0),
{Reply, NewState} = wrap_cont(qc_lets_raw:match(Cont), State),
{reply, Reply, NewState};
handle_call({match_delete, Pattern}, _From, #state{tab=Tab}=State) ->
Reply = qc_lets_raw:match_delete(Tab, Pattern),
{reply, Reply, State};
handle_call({match_object, Pattern}, _From, #state{tab=Tab}=State) ->
Reply = qc_lets_raw:match_object(Tab, Pattern),
{reply, Reply, State};
handle_call({match_object, Pattern, Limit}, _From, #state{tab=Tab}=State) ->
{Reply, NewState} = wrap_cont(qc_lets_raw:match_object(Tab, Pattern, Limit), State),
{reply, Reply, NewState};
handle_call({match_object_cont, Cont0}, _From, State0) ->
{Cont, State} = unwrap_cont(Cont0, State0),
{Reply, NewState} = wrap_cont(qc_lets_raw:match_object(Cont), State),
{reply, Reply, NewState};
handle_call({member, Key}, _From, #state{tab=Tab}=State) ->
Reply = qc_lets_raw:member(Tab, Key),
{reply, Reply, State};
handle_call({next, Key}, _From, #state{tab=Tab}=State) ->
Reply = qc_lets_raw:next(Tab, Key),
{reply, Reply, State};
handle_call({prev, Key}, _From, #state{tab=Tab}=State) ->
Reply = qc_lets_raw:prev(Tab, Key),
{reply, Reply, State};
handle_call({select_count, Spec}, _From, #state{tab=Tab}=State) ->
Reply = qc_lets_raw:select_count(Tab, Spec),
{reply, Reply, State};
handle_call({select_delete, Spec}, _From, #state{tab=Tab}=State) ->
Reply = qc_lets_raw:select_delete(Tab, Spec),
{reply, Reply, State};
handle_call({select, Spec}, _From, #state{tab=Tab}=State) ->
Reply = qc_lets_raw:select(Tab, Spec),
{reply, Reply, State};
handle_call({select, Spec, Limit}, _From, #state{tab=Tab}=State) ->
{Reply, NewState} = wrap_cont(qc_lets_raw:select(Tab, Spec, Limit), State),
{reply, Reply, NewState};
handle_call({select_cont, Cont0}, _From, State0) ->
{Cont, State} = unwrap_cont(Cont0, State0),
{Reply, NewState} = wrap_cont(qc_lets_raw:select(Cont), State),
{reply, Reply, NewState};
handle_call({select_reverse, Spec}, _From, #state{tab=Tab}=State) ->
Reply = qc_lets_raw:select_reverse(Tab, Spec),
{reply, Reply, State};
handle_call({select_reverse, Spec, Limit}, _From, #state{tab=Tab}=State) ->
{Reply, NewState} = wrap_cont(qc_lets_raw:select_reverse(Tab, Spec, Limit), State),
{reply, Reply, NewState};
handle_call({select_reverse_cont, Cont0}, _From, State0) ->
{Cont, State} = unwrap_cont(Cont0, State0),
{Reply, NewState} = wrap_cont(qc_lets_raw:select_reverse(Cont), State),
{reply, Reply, NewState};
handle_call(tab2list, _From, #state{tab=Tab}=State) ->
Reply = qc_lets_raw:tab2list(Tab),
{reply, Reply, State}.
@ -193,5 +363,28 @@ code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%% Internal
%%%===================================================================
wrap_cont({'EXIT', _}=Err, State) ->
{Err, State};
wrap_cont('$end_of_table'=Reply, State) ->
{Reply, State};
wrap_cont({_Match, '$end_of_table'}=Reply, State) ->
{Reply, State};
wrap_cont({_Match, Cont}=Reply, #state{name=Name, conts=Conts}=State) ->
ServerRef = {Name, node()},
Ref = make_ref(),
NewState = State#state{conts=dict:store(Ref, Cont, Conts)},
{setelement(2, Reply, wrap_cont({ServerRef, Ref})), NewState}.
unwrap_cont(Ref, #state{conts=Conts}=State) ->
Cont = dict:fetch(Ref, Conts),
NewState = State#state{conts=dict:erase(Ref, Conts)},
{Cont, NewState}.
wrap_cont({ServerRef, Cont}) ->
{wcont, ServerRef, Cont}.
unwrap_cont({wcont, ServerRef, Cont}) ->
{ServerRef, Cont}.

View file

@ -33,15 +33,37 @@
, new/3
, destroy/3
, repair/3
, insert/2
, insert_new/2
, delete/1
, delete/2
, delete_all_objects/1
, lookup/2
, first/1
, next/2
, foldl/3
, foldr/3
, info/1
, info/2
, insert/2
, insert_new/2
, last/1
, lookup/2
, lookup_element/3
, match/2
, match/3
, match/1
, match_delete/2
, match_object/2
, match_object/3
, match_object/1
, member/2
, next/2
, prev/2
, select/2
, select/3
, select/1
, select_count/2
, select_delete/2
, select_reverse/2
, select_reverse/3
, select_reverse/1
, tab2list/1
]).
@ -75,12 +97,6 @@ repair(_Tab, Name, Options) ->
%% _Tab is to help control generators and shrinking
catch lets:repair(Name, filter_options(Options)).
insert(Tab, ObjOrObjs) ->
catch lets:insert(Tab, ObjOrObjs).
insert_new(Tab, ObjOrObjs) ->
catch lets:insert_new(Tab, ObjOrObjs).
delete(Tab) ->
catch lets:delete(Tab).
@ -90,17 +106,89 @@ delete(Tab, Key) ->
delete_all_objects(Tab) ->
catch lets:delete_all_objects(Tab).
first(Tab) ->
catch lets:first(Tab).
foldl(Function, Acc0, Tab) ->
catch lets:foldl(Function, Acc0, Tab).
foldr(Function, Acc0, Tab) ->
catch lets:foldr(Function, Acc0, Tab).
info(Tab) ->
catch lets:info(Tab).
info(Tab, Item) ->
catch lets:info(Tab, Item).
insert(Tab, ObjOrObjs) ->
catch lets:insert(Tab, ObjOrObjs).
insert_new(Tab, ObjOrObjs) ->
catch lets:insert_new(Tab, ObjOrObjs).
last(Tab) ->
catch lets:last(Tab).
lookup(Tab, Key) ->
catch lets:lookup(Tab, Key).
first(Tab) ->
catch lets:first(Tab).
lookup_element(Tab, Key, Pos) ->
catch lets:lookup_element(Tab, Key, Pos).
match(Tab, Pattern) ->
catch lets:match(Tab, Pattern).
match(Tab, Pattern, Limit) ->
catch lets:match(Tab, Pattern, Limit).
match(Cont) ->
catch lets:match(Cont).
match_delete(Tab, Pattern) ->
catch lets:match_delete(Tab, Pattern).
match_object(Tab, Pattern) ->
catch lets:match_object(Tab, Pattern).
match_object(Tab, Pattern, Limit) ->
catch lets:match_object(Tab, Pattern, Limit).
match_object(Cont) ->
catch lets:match_object(Cont).
member(Tab, Key) ->
catch lets:member(Tab, Key).
next(Tab, Key) ->
catch lets:next(Tab, Key).
info(Tab, Item) ->
catch lets:info(Tab, Item).
prev(Tab, Key) ->
catch lets:prev(Tab, Key).
select(Tab, Spec) ->
catch lets:select(Tab, Spec).
select(Tab, Spec, Limit) ->
catch lets:select(Tab, Spec, Limit).
select(Cont) ->
catch lets:select(Cont).
select_count(Tab, Spec) ->
catch lets:select_count(Tab, Spec).
select_delete(Tab, Spec) ->
catch lets:select_delete(Tab, Spec).
select_reverse(Tab, Spec) ->
catch lets:select_reverse(Tab, Spec).
select_reverse(Tab, Spec, Limit) ->
catch lets:select_reverse(Tab, Spec, Limit).
select_reverse(Cont) ->
catch lets:select_reverse(Cont).
tab2list(Tab) ->
catch lets:tab2list(Tab).

View file

@ -31,15 +31,37 @@
, new/3
, destroy/3
, repair/3
, insert/2
, insert_new/2
, delete/1
, delete/2
, delete_all_objects/1
, lookup/2
, first/1
, next/2
, foldl/3
, foldr/3
, info/1
, info/2
, insert/2
, insert_new/2
, last/1
, lookup/2
, lookup_element/3
, match/2
, match/3
, match/1
, match_delete/2
, match_object/2
, match_object/3
, match_object/1
, member/2
, next/2
, prev/2
, select/2
, select/3
, select/1
, select_count/2
, select_delete/2
, select_reverse/2
, select_reverse/3
, select_reverse/1
, tab2list/1
]).
@ -68,12 +90,6 @@ destroy(Tab, Name, Options) ->
repair(Tab, Name, Options) ->
qc_lets_raw:repair(Tab, Name, Options).
insert(Tab, ObjOrObjs) ->
qc_lets_proxy:insert(Tab, ObjOrObjs).
insert_new(Tab, ObjOrObjs) ->
qc_lets_proxy:insert_new(Tab, ObjOrObjs).
delete(Tab) ->
qc_lets_proxy:delete(Tab).
@ -83,24 +99,96 @@ delete(Tab, Key) ->
delete_all_objects(Tab) ->
qc_lets_proxy:delete_all_objects(Tab).
first(Tab) ->
qc_lets_proxy:first(Tab).
foldl(Function, Acc0, Tab) ->
qc_lets_proxy:foldl(Function, Acc0, Tab).
foldr(Function, Acc0, Tab) ->
qc_lets_proxy:foldr(Function, Acc0, Tab).
info(Tab) ->
qc_lets_proxy:info(Tab).
info(Tab, Item) ->
qc_lets_proxy:info(Tab, Item).
insert(Tab, ObjOrObjs) ->
qc_lets_proxy:insert(Tab, ObjOrObjs).
insert_new(Tab, ObjOrObjs) ->
qc_lets_proxy:insert_new(Tab, ObjOrObjs).
last(Tab) ->
qc_lets_proxy:last(Tab).
lookup(Tab, Key) ->
qc_lets_proxy:lookup(Tab, Key).
first(Tab) ->
qc_lets_proxy:first(Tab).
lookup_element(Tab, Key, Pos) ->
qc_lets_proxy:lookup_element(Tab, Key, Pos).
match(Tab, Pattern) ->
qc_lets_proxy:match(Tab, Pattern).
match(Tab, Pattern, Limit) ->
qc_lets_proxy:match(Tab, Pattern, Limit).
match(Cont) ->
qc_lets_proxy:match(Cont).
match_delete(Tab, Pattern) ->
qc_lets_proxy:match_delete(Tab, Pattern).
match_object(Tab, Pattern) ->
qc_lets_proxy:match_object(Tab, Pattern).
match_object(Tab, Pattern, Limit) ->
qc_lets_proxy:match_object(Tab, Pattern, Limit).
match_object(Cont) ->
qc_lets_proxy:match_object(Cont).
member(Tab, Key) ->
qc_lets_proxy:member(Tab, Key).
next(Tab, Key) ->
qc_lets_proxy:next(Tab, Key).
info(Tab, Item) ->
qc_lets_proxy:info(Tab, Item).
prev(Tab, Key) ->
qc_lets_proxy:prev(Tab, Key).
select(Tab, Spec) ->
qc_lets_proxy:select(Tab, Spec).
select(Tab, Spec, Limit) ->
qc_lets_proxy:select(Tab, Spec, Limit).
select(Cont) ->
qc_lets_proxy:select(Cont).
select_count(Tab, Spec) ->
qc_lets_proxy:select_count(Tab, Spec).
select_delete(Tab, Spec) ->
qc_lets_proxy:select_delete(Tab, Spec).
select_reverse(Tab, Spec) ->
qc_lets_proxy:select_reverse(Tab, Spec).
select_reverse(Tab, Spec, Limit) ->
qc_lets_proxy:select_reverse(Tab, Spec, Limit).
select_reverse(Cont) ->
qc_lets_proxy:select_reverse(Cont).
tab2list(Tab) ->
qc_lets_proxy:tab2list(Tab).
%%%===================================================================
%%% Internal functions
%%% Internal
%%%===================================================================
stop_slave() ->

View file

@ -39,6 +39,7 @@
, first/1, first/2
, last/1, last/2
, next/2, next/3
, prev/2, prev/3
]).
@ -291,6 +292,39 @@ next(Db, Key, Options) ->
free_ptr(LenPtr1)
end.
prev(Db, Key) ->
Options = leveldb:leveldb_readoptions_create(),
try
prev(Db, Key, Options)
after
leveldb:leveldb_readoptions_destroy(Options)
end.
prev(Db, Key, Options) ->
Iter = leveldb:leveldb_create_iterator(Db, Options),
LenPtr = lenptr(),
LenPtr1 = lenptr(),
try
leveldb:leveldb_iter_seek(Iter, binary_to_list(Key), byte_size(Key)),
case leveldb:leveldb_iter_valid(Iter) of
0 ->
last(Db, Options);
1 ->
leveldb:leveldb_iter_prev(Iter),
case leveldb:leveldb_iter_valid(Iter) of
0 ->
true;
1 ->
KeyPtr1 = leveldb:leveldb_iter_key(Iter, LenPtr1),
read_binary(KeyPtr1, LenPtr1)
end
end
after
leveldb:leveldb_iter_destroy(Iter),
free_ptr(LenPtr),
free_ptr(LenPtr1)
end.
%%%===================================================================
%%% Internal
%%%===================================================================

View file

@ -28,10 +28,13 @@
-behaviour(qc_statem).
-export([command_gen/2]).
-export([initial_state/0, state_is_sane/1, next_state/3, precondition/2, postcondition/3]).
-export([commands_setup/1, commands_teardown/1, commands_teardown/2]).
-export([setup/1, teardown/1, teardown/2, aggregate/1]).
%% @TODO remove at time of db, db_read, db_write options testing
%% DEBUG
-compile(export_all).
%% Implementation
-export([match31/3, match_object31/3, select31/3, select_reverse31/3]).
%% @NOTE For boilerplate exports, see "qc_statem.hrl"
-include_lib("qc/include/qc_statem.hrl").
@ -50,7 +53,13 @@
-define(FLOAT_KEYS, [float(Key) || Key <- ?INT_KEYS]).
-define(BINARY_KEYS, [term_to_binary(Key) || Key <- ?INT_KEYS]).
-record(obj, {key :: integer() | float() | binary(), val :: integer() | float() | binary()}).
-type key() :: integer() | float() | binary() | atom().
-type val() :: integer() | float() | binary() | atom().
-record(obj, {
key :: key(),
val :: val()
}).
-type obj() :: #obj{}.
-type ets_type() :: set | ordered_set. %% default is set
@ -82,33 +91,53 @@ serial_command_gen(_Mod,#state{tab=undefined}=S) ->
%% @TODO ++ [{call,?IMPL,destroy,[undefined,?TAB,gen_options(destroy,S)]}]
%% @TODO ++ [{call,?IMPL,repair,[undefined,?TAB,gen_options(repair,S)]}]
);
serial_command_gen(_Mod,#state{tab=Tab, type=Type}=S) ->
%% @TODO insert/3, insert_new/3, delete/3, delete_all_objs/2 write_gen_options
%% @TODO lookup/3 read_gen_options
serial_command_gen(_Mod,#state{tab=Tab, type=Type, impl=Impl}=S) ->
%% @TODO gen_db_write_options/2
%% @TODO gen_db_read_options/2
%% @TODO info/1, info/2
oneof([{call,?IMPL,insert,[Tab,oneof([gen_obj(S),gen_objs(S)])]}]
++ [{call,?IMPL,insert_new,[Tab,oneof([gen_obj(S),gen_objs(S)])]} || Type =:= ets]
++ [{call,?IMPL,insert_new,[Tab,oneof([gen_obj(S),gen_objs(S)])]} || Impl =:= ets]
++ [{call,?IMPL,delete,[Tab]}]
++ [{call,?IMPL,delete,[Tab,gen_key(S)]}]
++ [{call,?IMPL,delete_all_objs,[Tab]} || Type =:= ets]
++ [{call,?IMPL,delete_all_objects,[Tab]} || Type =:= ets]
++ [{call,?IMPL,member,[Tab,gen_key(S)]}]
++ [{call,?IMPL,lookup,[Tab,gen_key(S)]}]
++ [{call,?IMPL,lookup_element,[Tab,gen_key(S),choose(1,record_info(size,obj))]}]
++ [{call,?IMPL,first,[Tab]}]
++ [{call,?IMPL,last,[Tab]}]
++ [{call,?IMPL,next,[Tab,gen_key(S)]}]
%% @TODO info
++ [{call,?IMPL,prev,[Tab,gen_key(S)]}]
++ [{call,?IMPL,foldl,[fun(X,Acc) -> [X|Acc] end, [], Tab]}]
++ [{call,?IMPL,foldr,[fun(X,Acc) -> [X|Acc] end, [], Tab]}]
++ [{call,?IMPL,tab2list,[Tab]}]
++ [{call,?IMPL,match,[Tab, gen_pattern(S)]}]
%%++ [{call,?MODULE,match31,[Tab, gen_pattern(S), gen_pos_integer()]}]
++ [{call,?IMPL,match_delete,[Tab, gen_pattern(S)]}]
++ [{call,?IMPL,match_object,[Tab, gen_pattern(S)]}]
%%++ [{call,?MODULE,match_object31,[Tab, gen_pattern(S), gen_pos_integer()]}]
++ [{call,?IMPL,select,[Tab, gen_spec(S)]}]
++ [{call,?MODULE,select31,[Tab, gen_spec(S), gen_pos_integer()]}]
++ [{call,?IMPL,select_count,[Tab, gen_spec_true(S)]}]
++ [{call,?IMPL,select_delete,[Tab, gen_spec_true(S)]}]
++ [{call,?IMPL,select_reverse,[Tab, gen_spec(S)]}]
%%++ [{call,?MODULE,select_reverse31,[Tab, gen_spec(S), gen_pos_integer()]}]
).
parallel_command_gen(_Mod,#state{tab=undefined, type=undefined, impl=undefined}=S) ->
{call,?IMPL,new,[?TAB,gen_options(new,S)]};
parallel_command_gen(_Mod,#state{tab=Tab, type=Type}=S) ->
%% @TODO insert/3, insert_new/3, delete_all_objs/2 write_gen_options
%% @TODO lookup/3 read_gen_options
%% @TODO gen_db_write_options/2
%% @TODO gen_db_read_options/2
oneof([{call,?IMPL,insert,[Tab,oneof([gen_obj(S),gen_objs(S)])]}]
++ [{call,?IMPL,insert_new,[Tab,oneof([gen_obj(S),gen_objs(S)])]} || Type =:= ets]
++ [{call,?IMPL,delete,[Tab,gen_key(S)]}]
++ [{call,?IMPL,delete_all_objs,[Tab]} || Type =:= ets]
++ [{call,?IMPL,member,[Tab,gen_key(S)]}]
++ [{call,?IMPL,lookup,[Tab,gen_key(S)]}]
++ [{call,?IMPL,lookup_element,[Tab,gen_key(S),choose(1,record_info(size,obj))]}]
++ [{call,?IMPL,first,[Tab]}]
++ [{call,?IMPL,last,[Tab]}]
++ [{call,?IMPL,next,[Tab,gen_key(S)]}]
++ [{call,?IMPL,prev,[Tab,gen_key(S)]}]
).
-spec initial_state() -> #state{}.
@ -175,30 +204,41 @@ next_state(#state{exists=Exists}=S, _V, {call,_,delete,[_Tab]}) ->
S#state{tab=undefined, exists=Exists};
next_state(S, _V, {call,_,delete,[_Tab,Key]}) ->
S#state{objs=keydelete(Key, S)};
next_state(#state{impl=ets}=S, _V, {call,_,delete_all_objs,[_Tab]}) ->
next_state(#state{impl=ets}=S, _V, {call,_,delete_all_objects,[_Tab]}) ->
S#state{objs=[]};
next_state(S, _V, {call,_,delete_all_objs,[_Tab]}) ->
next_state(S, _V, {call,_,delete_all_objects,[_Tab]}) ->
S;
next_state(S, _V, {call,_,match_delete,[_Tab,Pattern]}) ->
match_delete(S, Pattern);
next_state(S, _V, {call,_,select_delete,[_Tab,Spec]}) ->
select_delete(S, Spec);
next_state(S, _V, {call,_,_,_}) ->
S.
-spec precondition(#state{}, tuple()) -> boolean().
precondition(#state{tab=undefined, type=undefined, impl=undefined}, {call,_,new,[?TAB,Options]}) ->
L = proplists:get_value(db, Options, []),
proplists:get_bool(create_if_missing, L) andalso proplists:get_bool(error_if_exists, L);
precondition(#state{tab=Tab}, {call,_,new,[?TAB,_Options]}) ->
Tab =:= undefined;
precondition(#state{tab=undefined, type=undefined, impl=undefined}, {call,_,new,[_Tab,?TAB,Options]}) ->
L = proplists:get_value(db, Options, []),
proplists:get_bool(create_if_missing, L) andalso proplists:get_bool(error_if_exists, L);
precondition(#state{tab=Tab}, {call,_,new,[_Tab,?TAB,_Options]}) ->
Tab =:= undefined;
Drv = proplists:get_bool(drv, Options),
Nif = proplists:get_bool(nif, Options),
if Drv orelse Nif ->
L = proplists:get_value(db, Options, []),
proplists:get_bool(create_if_missing, L) andalso proplists:get_bool(error_if_exists, L);
true ->
true
end;
precondition(#state{tab=_Tab}, {call,_,new,[?TAB,_Options]}) ->
false;
precondition(#state{tab=undefined, type=undefined, impl=undefined}, {call,_,new,[_Tab,?TAB,_Options]}) ->
false;
precondition(#state{tab=Tab}, {call,_,new,[_Tab,?TAB,_Options]}) ->
Tab =:= undefined;
precondition(#state{tab=undefined, type=undefined, impl=undefined}, {call,_,destroy,[_Tab,?TAB,_Options]}) ->
false;
precondition(#state{tab=Tab}, {call,_,destroy,[_Tab,?TAB,_Options]}) ->
Tab =:= undefined;
precondition(#state{tab=undefined, type=undefined, impl=undefined}, {call,_,repair,[_Tab,?TAB,_Options]}) ->
false;
precondition(#state{tab=Tab}, {call,_,repair,[_Tab,?TAB,_Options]}) ->
Tab =:= undefined;
precondition(_S, {call,_,_,_}) ->
true.
@ -230,12 +270,18 @@ postcondition(_S, {call,_,delete,[_Tab]}, Res) ->
Res =:= true;
postcondition(_S, {call,_,delete,[_Tab,_Key]}, Res) ->
Res =:= true;
postcondition(#state{impl=ets}=_S, {call,_,delete_all_objs,[_Tab]}, Res) ->
postcondition(#state{impl=ets}=_S, {call,_,delete_all_objects,[_Tab]}, Res) ->
Res =:= true;
postcondition(_S, {call,_,delete_all_objs,[_Tab]}, {'EXIT',{badarg,_}}) ->
postcondition(_S, {call,_,delete_all_objects,[_Tab]}, {'EXIT',{badarg,_}}) ->
true;
postcondition(S, {call,_,member,[_Tab,Key]}, Res) ->
Res =:= keymember(Key, S);
postcondition(S, {call,_,lookup,[_Tab,Key]}, Res) ->
Res =:= keyfind(Key, S);
postcondition(S, {call,_,lookup_element,[_Tab,Key,_Pos]}, {'EXIT',{badarg,_}}) ->
not keymember(Key, S);
postcondition(S, {call,_,lookup_element,[_Tab,Key,Pos]}, Res) ->
[Res] =:= keyposfind(Key, Pos, S);
postcondition(#state{objs=[]}, {call,_,first,[_Tab]}, Res) ->
Res =:= '$end_of_table';
postcondition(#state{type=set}=S, {call,_,first,[_Tab]}, Res) ->
@ -243,6 +289,13 @@ postcondition(#state{type=set}=S, {call,_,first,[_Tab]}, Res) ->
postcondition(#state{type=ordered_set}=S, {call,_,first,[_Tab]}, Res) ->
#obj{key=K} = hd(sort(S)),
Res =:= K;
postcondition(#state{objs=[]}, {call,_,last,[_Tab]}, Res) ->
Res =:= '$end_of_table';
postcondition(#state{type=set}=S, {call,_,last,[_Tab]}, Res) ->
keymember(Res, S);
postcondition(#state{type=ordered_set}=S, {call,_,last,[_Tab]}, Res) ->
#obj{key=K} = hd(rsort(S)),
Res =:= K;
postcondition(#state{impl=ets, type=set}=S, {call,_,next,[_Tab, Key]}, {'EXIT',{badarg,_}}) ->
not keymember(Key, S);
postcondition(#state{impl=ets, type=set}=S, {call,_,next,[_Tab, Key]}, '$end_of_table') ->
@ -253,8 +306,6 @@ postcondition(#state{type=set}, {call,_,next,[_Tab, _Key]}, '$end_of_table') ->
true;
postcondition(#state{type=set}=S, {call,_,next,[_Tab, _Key]}, Res) ->
keymember(Res, S);
postcondition(#state{type=ordered_set, objs=[]}, {call,_,next,[_Tab, _Key]}, Res) ->
Res =:= '$end_of_table';
postcondition(#state{type=ordered_set}=S, {call,_,next,[_Tab, Key]}, Res) ->
case lists:dropwhile(fun(#obj{key=X}) -> lteq(X, Key, S) end, sort(S)) of
[] ->
@ -262,30 +313,103 @@ postcondition(#state{type=ordered_set}=S, {call,_,next,[_Tab, Key]}, Res) ->
[#obj{key=K}|_] ->
Res =:= K
end;
postcondition(#state{impl=ets, type=set}=S, {call,_,prev,[_Tab, Key]}, {'EXIT',{badarg,_}}) ->
not keymember(Key, S);
postcondition(#state{impl=ets, type=set}=S, {call,_,prev,[_Tab, Key]}, '$end_of_table') ->
keymember(Key, S);
postcondition(#state{impl=ets, type=set}=S, {call,_,prev,[_Tab, Key]}, Res) ->
keymember(Key, S) andalso keymember(Res, S);
postcondition(#state{type=set}, {call,_,prev,[_Tab, _Key]}, '$end_of_table') ->
true;
postcondition(#state{type=set}=S, {call,_,prev,[_Tab, _Key]}, Res) ->
keymember(Res, S);
postcondition(#state{type=ordered_set}=S, {call,_,prev,[_Tab, Key]}, Res) ->
case lists:dropwhile(fun(#obj{key=X}) -> gteq(X, Key, S) end, rsort(S)) of
[] ->
Res =:= '$end_of_table';
[#obj{key=K}|_] ->
Res =:= K
end;
postcondition(#state{type=set}=S, {call,_,foldl,[_Function,_Acc0,_Tab]}, Res) ->
[] =:= (S#state.objs -- Res);
postcondition(#state{type=ordered_set}=S, {call,_,foldl,[_Function,_Acc0,_Tab]}, Res) ->
rsort(S) =:= Res;
postcondition(#state{type=set}=S, {call,_,foldr,[_Function,_Acc0,_Tab]}, Res) ->
[] =:= (S#state.objs -- Res);
postcondition(#state{type=ordered_set}=S, {call,_,foldr,[_Function,_Acc0,_Tab]}, Res) ->
sort(S) =:= Res;
postcondition(#state{type=set}=S, {call,_,tab2list,[_Tab]}, Res) ->
[] =:= (S#state.objs -- Res);
postcondition(#state{type=ordered_set}=S, {call,_,tab2list,[_Tab]}, Res) ->
sort(S) =:= Res;
postcondition(#state{type=set}=S, {call,_,match,[_Tab,Pattern]}, Res) ->
[] =:= (match(S, Pattern) -- Res);
postcondition(#state{type=ordered_set}=S, {call,_,match,[_Tab,Pattern]}, Res) ->
match(S, Pattern) =:= Res;
postcondition(#state{type=set}=S, {call,_,match31,[_Tab,Pattern,_Limit]}, Res) ->
[] =:= (match(S, Pattern) -- Res);
postcondition(#state{type=ordered_set}=S, {call,_,match31,[_Tab,Pattern,_Limit]}, Res) ->
match(S, Pattern) =:= Res;
postcondition(_S, {call,_,match_delete,[_Tab,_Pattern]}, Res) ->
Res;
postcondition(#state{type=set}=S, {call,_,match_object,[_Tab,Pattern]}, Res) ->
[] =:= (match_object(S, Pattern) -- Res);
postcondition(#state{type=ordered_set}=S, {call,_,match_object,[_Tab,Pattern]}, Res) ->
match_object(S, Pattern) =:= Res;
postcondition(#state{type=set}=S, {call,_,match_object31,[_Tab,Pattern,_Limit]}, Res) ->
[] =:= (match_object(S, Pattern) -- Res);
postcondition(#state{type=ordered_set}=S, {call,_,match_object31,[_Tab,Pattern,_Limit]}, Res) ->
match_object(S, Pattern) =:= Res;
postcondition(#state{type=set}=S, {call,_,select,[_Tab,Spec]}, Res) ->
[] =:= (select(S, Spec) -- Res);
postcondition(#state{type=ordered_set}=S, {call,_,select,[_Tab,Spec]}, Res) ->
select(S, Spec) =:= Res;
postcondition(#state{type=set}=S, {call,_,select31,[_Tab,Spec,_Limit]}, Res) ->
[] =:= (select(S, Spec) -- Res);
postcondition(#state{type=ordered_set}=S, {call,_,select31,[_Tab,Spec,_Limit]}, Res) ->
select(S, Spec) =:= Res;
postcondition(S, {call,_,select_count,[_Tab,Spec]}, Res) ->
select_count(S, Spec) =:= Res;
postcondition(S, {call,_,select_delete,[_Tab,Spec]}, Res) ->
select_count(S, Spec) =:= Res;
postcondition(#state{type=set}=S, {call,_,select_reverse,[_Tab,Spec]}, Res) ->
[] =:= (select_reverse(S, Spec) -- Res);
postcondition(#state{type=ordered_set}=S, {call,_,select_reverse,[_Tab,Spec]}, Res) ->
select_reverse(S, Spec) =:= Res;
postcondition(#state{type=set}=S, {call,_,select_reverse31,[_Tab,Spec,_Limit]}, Res) ->
[] =:= (select_reverse(S, Spec) -- Res);
postcondition(#state{type=ordered_set}=S, {call,_,select_reverse31,[_Tab,Spec,_Limit]}, Res) ->
select_reverse(S, Spec) =:= Res;
postcondition(_S, {call,_,_,_}, _Res) ->
false.
-spec commands_setup(boolean()) -> {ok, term()}.
commands_setup(_Hard) ->
-spec setup(boolean()) -> {ok, term()}.
setup(_Hard) ->
?IMPL:teardown(?TAB),
{ok, unused}.
-spec commands_teardown(term()) -> ok.
commands_teardown(unused) ->
-spec teardown(term()) -> ok.
teardown(unused) ->
?IMPL:teardown(?TAB),
ok.
-spec commands_teardown(term(), #state{}) -> ok.
commands_teardown(Ref, _State) ->
commands_teardown(Ref).
-spec teardown(term(), #state{}) -> ok.
teardown(Ref, _State) ->
teardown(Ref).
-spec aggregate([{integer(), term(), term(), #state{}}])
-> [{atom(), atom(), integer() | term()}].
aggregate(L) ->
[ {Cmd,filter_reply(Reply)} || {_N,{set,_,{call,_,Cmd,_}},Reply,_State} <- L ].
filter_reply({'EXIT',{Err,_}}) ->
{error,Err};
filter_reply(_) ->
ok.
%%%----------------------------------------------------------------------
%%% Internal
%%% Internal - Generators
%%%----------------------------------------------------------------------
gen_options(Op,#state{tab=undefined, type=undefined, impl=undefined}=S) ->
@ -335,7 +459,7 @@ gen_boolean() ->
oneof([true, false]).
gen_pos_integer() ->
nat().
?LET(N, nat(), N+1).
gen_ets_type() ->
noshrink(oneof([set, ordered_set])).
@ -379,6 +503,23 @@ gen_obj(#state{objs=Objs}) ->
gen_objs(S) ->
frequency([{9, non_empty(list(gen_obj(S)))}, {1, list(gen_obj(S))}]).
gen_pattern(S) ->
oneof([{'$1', '$2', '$3'}
, #obj{key=oneof(['_',gen_key(S)]), val='$1'}
, #obj{key='$1', val=oneof(['_',gen_val()])}
]).
gen_spec(S) ->
[{gen_pattern(S), [], [oneof(['$$', '$_'])]}].
gen_spec_true(S) ->
[{gen_pattern(S), [], [true]}].
%%%----------------------------------------------------------------------
%%% Internal - Model
%%%----------------------------------------------------------------------
insert_objs(S, []) ->
S;
insert_objs(S, [#obj{key=K}=Obj|T]) ->
@ -426,11 +567,142 @@ keydelete(X, L, S) ->
keyreplace(X, Y, #state{objs=L}=S) ->
lists:map(fun(Z=#obj{key=K}) -> case eq(X, K, S) of true -> Y; false -> Z end end, L).
keymember(X, S) ->
[] =/= keyfind(X, S).
keyfind(X, #state{objs=L}=S) ->
lists:filter(fun(#obj{key=K}) -> eq(X, K, S) end, L).
keymember(X, S) ->
[] =/= keyfind(X, S).
keyposfind(X, Pos, S) ->
[ element(Pos, Obj) || Obj <- keyfind(X, S) ].
match(S, Pattern) ->
select(S, [{Pattern, [], ['$$']}]).
match(S, Pattern, Limit) ->
select(S, [{Pattern, [], ['$$']}], Limit).
match_cont(S, Pattern, Limit, StartKey) ->
ContObjs = lists:dropwhile(fun(#obj{key=X}) -> lteq(X, StartKey, S) end, sort(S)),
match(S#state{objs=ContObjs}, Pattern, Limit).
match_delete(#state{objs=L}=S, Pattern) ->
S#state{objs=L -- match_object(S, Pattern)}.
match_object(S, Pattern) ->
match_object(S, Pattern, undefined).
match_object(S, Pattern, Limit) ->
select(S, [{Pattern, [], ['$_']}], Limit).
match_object_cont(S, Pattern, Limit, StartKey) ->
ContObjs = lists:dropwhile(fun(#obj{key=X}) -> lteq(X, StartKey, S) end, sort(S)),
match_object(S#state{objs=ContObjs}, Pattern, Limit).
match_object_reverse(S, Pattern) ->
match_object_reverse(S, Pattern, undefined).
match_object_reverse(S, Pattern, Limit) ->
select_reverse(S, [{Pattern, [], ['$_']}], Limit).
match_object_reverse_cont(S, Pattern, Limit, StartKey) ->
ContObjs = lists:dropwhile(fun(#obj{key=X}) -> gteq(X, StartKey, S) end, rsort(S)),
match_object_reverse(S#state{objs=ContObjs}, Pattern, Limit).
select(S, Spec) ->
select(S, Spec, undefined).
select(S, Spec, Limit) ->
case select1(S, sort(S), Spec) of
[] ->
[];
Match when Limit =:= undefined ->
Match;
Match ->
lists:sublist(Match, Limit)
end.
select_cont(S, Spec, Limit, StartKey) ->
ContObjs = lists:dropwhile(fun(#obj{key=X}) -> lteq(X, StartKey, S) end, sort(S)),
select(S#state{objs=ContObjs}, Spec, Limit).
select_count(S, Spec) ->
select1(S, sort(S), Spec).
select_delete(#state{objs=L}=S, [{Pattern, [], [true]}]) ->
S#state{objs=L -- match_object(S, Pattern)}.
select_reverse(S, Spec) ->
select_reverse(S, Spec, undefined).
select_reverse(S, Spec, Limit) ->
case select1(S, rsort(S), Spec) of
[] ->
[];
Match when Limit =:= undefined ->
Match;
Match ->
lists:sublist(Match, Limit)
end.
select_reverse_cont(S, Spec, Limit, StartKey) ->
ContObjs = lists:dropwhile(fun(#obj{key=X}) -> gteq(X, StartKey, S) end, rsort(S)),
select_reverse(S#state{objs=ContObjs}, Spec, Limit).
select1(#state{impl=ets}=S, L, Spec) ->
select2(S#state{type=set}, L, Spec);
select1(S, L, Spec) ->
select2(S, L, Spec).
%% simple and limited select implementation
select2(S, L, [{Pattern, [], [Result]}]) ->
case Pattern of
{'$1', '$2', '$3'} ->
case Result of
'$$' ->
[ [obj, X, Y] || #obj{key=X,val=Y} <- L ];
'$_' ->
[ X || X <- L ];
true ->
length(L)
end;
#obj{key='_', val='$1'} ->
case Result of
'$$' ->
[ [X] || #obj{val=X} <- L ];
'$_' ->
[ X || X <- L ];
true ->
length(L)
end;
#obj{key='$1', val='_'} ->
case Result of
'$$' ->
[ [X] || #obj{key=X} <- L ];
'$_' ->
[ X || X <- L ];
true ->
length(L)
end;
#obj{key=P, val='$1'} ->
case Result of
'$$' ->
[ [X] || #obj{key=Y, val=X} <- L, eq(Y, P, S) ];
'$_' ->
[ X || #obj{key=Y}=X <- L, eq(Y, P, S) ];
true ->
length([ X || #obj{key=Y}=X <- L, eq(Y, P, S) ])
end;
#obj{key='$1', val=P} ->
case Result of
'$$' ->
[ [X] || #obj{key=X, val=Y} <- L, eq(Y, P, S) ];
'$_' ->
[ X || #obj{val=Y}=X <- L, eq(Y, P, S) ];
true ->
length([ X || #obj{val=Y}=X <- L, eq(Y, P, S) ])
end
end.
eq(X, Y, #state{type=set, impl=ets}) ->
X =:= Y;
@ -457,9 +729,69 @@ lteq(X, Y, #state{type=set}) ->
lteq(X, Y, #state{type=ordered_set}) ->
sext:encode(X) =< sext:encode(Y).
gteq(X, Y, #state{impl=ets}) ->
X >= Y;
gteq(X, Y, #state{type=set}) ->
term_to_binary(X) >= term_to_binary(Y);
gteq(X, Y, #state{type=ordered_set}) ->
sext:encode(X) >= sext:encode(Y).
sort(#state{impl=ets, objs=L}) ->
lists:sort(L);
sort(#state{objs=L}) ->
[ sext:decode(X) || X <- lists:sort([ sext:encode(Y) || Y <- L ]) ].
rsort(S) ->
lists:reverse(sort(S)).
%%%----------------------------------------------------------------------
%%% Internal - Implementation
%%%----------------------------------------------------------------------
%% @NOTE simplify test model by combining match/3 and match/1 into
%% single match31/3 command
match31(Tab, Pattern, Limit) ->
catch match31(?IMPL:match(Tab, Pattern, Limit), Pattern, Limit, []).
match31('$end_of_table', _Pattern, _Limit, Acc) ->
Acc;
match31({Match, Cont}, Pattern, Limit, Acc) when length(Match) =< Limit ->
match31(?IMPL:match(Cont), Pattern, Limit, Acc ++ Match).
%% @NOTE simplify test model by combining match_object/3 and
%% match_object/1 into single match_object31/3 command
match_object31(Tab, Pattern, Limit) ->
catch match_object31(?IMPL:match_object(Tab, Pattern, Limit), Pattern, Limit, []).
match_object31('$end_of_table', _Pattern, _Limit, Acc) ->
Acc;
match_object31({Match, Cont}, Pattern, Limit, Acc) when length(Match) =< Limit ->
match_object31(?IMPL:match_object(Cont), Pattern, Limit, Acc ++ Match).
%% @NOTE simplify test model by combining select/3 and select/1 into
%% single select31/3 command
select31(Tab, Spec, Limit) ->
catch select31(?IMPL:select(Tab, Spec, Limit), Spec, Limit, []).
select31('$end_of_table', _Spec, _Limit, Acc) ->
Acc;
select31({Match, Cont}, Spec, Limit, Acc) when length(Match) =< Limit ->
select31(?IMPL:select(Cont), Spec, Limit, Acc ++ Match).
%% @NOTE simplify test model by combining select_reverse/3 and
%% select_reverse/1 into single select_reverse31/3 command
select_reverse31(Tab, Spec, Limit) ->
catch select_reverse31(?IMPL:select_reverse(Tab, Spec, Limit), Spec, Limit, []).
select_reverse31('$end_of_table', _Spec, _Limit, Acc) ->
Acc;
select_reverse31({Match, Cont}, Spec, Limit, Acc) when length(Match) =< Limit ->
select_reverse31(?IMPL:select_reverse(Cont), Spec, Limit, Acc ++ Match).
-endif. %% -ifdef(QC).

View file

@ -29,7 +29,7 @@
-behaviour(qc_statem).
-export([command_gen/2]).
-export([initial_state/0, state_is_sane/1, next_state/3, precondition/2, postcondition/3]).
-export([commands_setup/1, commands_teardown/1, commands_teardown/2]).
-export([setup/1, teardown/1, teardown/2, aggregate/1]).
%% @NOTE For boilerplate exports, see "qc_statem.hrl"
-include_lib("eqc/include/eqc_c.hrl").
@ -77,7 +77,8 @@ serial_command_gen(_Mod,#state{db=Db}=S) ->
{call,?IMPL,get,[Db,gen_key(S)]},
{call,?IMPL,first,[Db]},
{call,?IMPL,last,[Db]},
{call,?IMPL,next,[Db,gen_key(S)]}
{call,?IMPL,next,[Db,gen_key(S)]},
{call,?IMPL,prev,[Db,gen_key(S)]}
]).
parallel_command_gen(_Mod,#state{db=undefined}) ->
@ -168,32 +169,46 @@ postcondition(S, {call,_,next,[_Db,Key]}, Res) ->
[#obj{key=K}|_] ->
Res =:= K
end;
postcondition(S, {call,_,prev,[_Db,Key]}, Res) ->
case lists:dropwhile(fun(#obj{key=X}) -> X >= Key end, rsort_objs(S)) of
[] ->
Res;
[#obj{key=K}|_] ->
Res =:= K
end;
postcondition(_S, {call,_,_,_}, _Res) ->
false.
-spec commands_setup(boolean()) -> {ok, term()}.
commands_setup(_Hard) ->
-spec setup(boolean()) -> {ok, term()}.
setup(_Hard) ->
?IMPL:setup(),
teardown(),
{ok, unused}.
-spec commands_teardown(term()) -> ok.
commands_teardown(unused) ->
-spec teardown(term()) -> ok.
teardown(unused) ->
teardown(),
ok.
-spec commands_teardown(term(), #state{}) -> ok.
commands_teardown(Ref, _State) ->
commands_teardown(Ref).
-spec teardown(term(), #state{}) -> ok.
teardown(Ref, _State) ->
teardown(Ref).
-spec aggregate([{integer(), term(), term(), #state{}}])
-> [{atom(), atom(), integer() | term()}].
aggregate(L) ->
[ {Cmd,filter_reply(Reply)} || {_N,{set,_,{call,_,Cmd,_}},Reply,_State} <- L ].
filter_reply({'EXIT',{Err,_}}) ->
{error,Err};
filter_reply(_) ->
ok.
%%%----------------------------------------------------------------------
%%% Internal
%%% Internal - Generators
%%%----------------------------------------------------------------------
teardown() ->
?IMPL:teardown().
gen_bytes() ->
?LET(B, list(choose(0,127)), list_to_binary(B)).
@ -216,6 +231,11 @@ gen_obj(#state{objs=[]}) ->
gen_obj(#state{objs=Objs}) ->
oneof([oneof(Objs), gen_obj()]).
%%%----------------------------------------------------------------------
%%% Internal - Model
%%%----------------------------------------------------------------------
insert_obj(S, #obj{key=K}=Obj) ->
case keymember(K, S) of
false ->
@ -238,6 +258,9 @@ get_val(S, K) ->
sort_objs(#state{objs=Objs}) ->
lists:sort(Objs).
rsort_objs(S) ->
lists:reverse(sort_objs(S)).
keydelete(X, #state{objs=L}) ->
lists:filter(fun(#obj{key=K}) -> K =/= X end, L).
@ -250,5 +273,14 @@ keyfind(X, #state{objs=L}) ->
keymember(X, S) ->
[] /= keyfind(X, S).
%%%----------------------------------------------------------------------
%%% Internal - Implementation
%%%----------------------------------------------------------------------
teardown() ->
?IMPL:teardown().
-endif. %% -ifdef(EQC).
-endif. %% -ifdef(QC).