diff --git a/doc/lets.md b/doc/lets.md
index 5db689e..5ea32e6 100644
--- a/doc/lets.md
+++ b/doc/lets.md
@@ -16,6 +16,14 @@
+###cont()##
+
+
+
+__abstract datatype__: `cont()`
+
+
+
###db_opts()##
@@ -56,6 +64,14 @@
+###item()##
+
+
+
+item() = owner | name | named_table | type | keypos | protection | compressed | async | memory | size
+
+
+
###key()##
@@ -64,6 +80,30 @@
+###limit()##
+
+
+
+limit() = pos_integer()
+
+
+
+###match()##
+
+
+
+match() = term()
+
+
+
+###name()##
+
+
+
+name() = atom()
+
+
+
###object()##
@@ -80,6 +120,36 @@
+###pattern()##
+
+
+
+pattern() = atom() | tuple()
+
+
+
+
+ets:match_pattern() is not exported!
+
+
+
+
+###pos()##
+
+
+
+pos() = pos_integer()
+
+
+
+###spec()##
+
+
+
+spec() = [ets:match_spec()](ets.md#type-match_spec)
+
+
+
###tab()##
@@ -94,8 +164,8 @@ __abstract datatype__: `tab()`
guaranteed to be atomic and isolated. This function only applies
to the ets implementation..destroy/2 | Destroy the contents of the specified table. This function
only applies to driver and nif implementations. . |
first/1 | Returns the first key Key in the table Tab. If the table
-is empty, $end_of_table will be returned. . |
info/2 | Returns information about the table Tab as a list of {Item,
- Value} tuples.
+is empty, $end_of_table will be returned.. |
foldl/3 | Fold from left to right over the elements of the table. . |
foldr/3 | Fold from right to left over the elements of the table. . |
info/1 | Returns information about the table Tab as a list of {Item,
+ Value} tuples. . |
info/2 | Returns the information associated with Item for the table Tab.
Valid +Item+ options are:
@@ -152,11 +222,21 @@ is empty, $end_of_table will be returned.. |
.
insert/2 | Inserts the object or all of the objects in the list
-ObjectOrObjects into the table Tab. . |
insert_new/2 | This function works exactly like insert/2, with the
+ObjOrObjs into the table Tab. . |
insert_new/2 | 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. . |
lookup/2 | Returns a list of all objects with the key Key in the table
-Tab. . |
new/2 | Creates a new table and returns a table identifier which can
+implementation. . |
last/1 | Returns the last key Key in the table Tab. If the table
+is empty, $end_of_table will be returned. . |
lookup/2 | Returns a list of all objects with the key Key in the table
+Tab. . |
lookup_element/3 | Returns the Pos:th element of the object with the key Key
+in the table Tab. . |
match/1 | Continues a match started with match/3. . |
match/2 | Matches the objects in the table Tab against the pattern
+Pattern. . |
match/3 | Matches the objects in the table Tab against the pattern
+Pattern and returns a limited (Limit) number of matching
+objects. . |
match_delete/2 | Deletes all objects which match the pattern Pattern from the
+table Tab. . |
match_object/1 | Continues a match started with match_object/3. . |
match_object/2 | Matches the objects in the table Tab against the pattern
+Pattern. . |
match_object/3 | Matches the objects in the table Tab against the pattern
+Pattern and returns a limited (Limit) number of matching
+objects. . |
member/2 | Returns true if one or more elements in the table Tab has
+the key Key, false otherwise. . |
new/2 | 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.
@@ -343,11 +423,20 @@ considered complete. The default is false.
. |
next/2 | Returns the next key Key2, following the key Key1 in the
table Tab. If there is no next key, $end_of_table is
+returned. . |
prev/2 | Returns the previous key Key2, following the key Key1 in
+the table Tab. If there is no previous key, $end_of_table is
returned. . |
repair/2 | 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 driver and nif implementations. . |
tab2list/1 | Returns a list of all objects in the table Tab. The
+function only applies to driver and nif implementations. . |
select/1 | Continues a select started with select/3. . |
select/2 | Matches the objects in the table Tab against the spec
+Spec. . |
select/3 | Matches the objects in the table Tab against the spec Spec
+and returns a limited (Limit) number of matching objects. . |
select_count/2 | Counts all objects which match the spec Spec from the
+table Tab and returns the number matched. . |
select_delete/2 | Deletes all objects which match the spec Spec from the
+table Tab and returns the number deleted. . |
select_reverse/1 | Continues a select reverse started with select_reverse/3. . |
select_reverse/2 | Matches in reverse the objects in the table Tab against the
+spec Spec. . |
select_reverse/3 | Matches in reverse the objects in the table Tab against the
+spec Spec and returns a limited (Limit) number of matching
+objects. . |
tab2list/1 | Returns a list of all objects in the table Tab. The
operation is not guaranteed to be atomic and isolated. . |
@@ -412,7 +501,7 @@ __See also:__ [ets:delete_all_objects/1](ets.md#delete_all_objects-1).
+__See also:__ [ets:first/1](ets.md#first-1).
-###info/2##
+###foldl/3##
-info(Tab::[tab()](#type-tab), Item::atom()) -> term()
+foldl(Fun, Acc0::term(), Tab::[tab()](#type-tab)) -> Acc1::term()
+
+
+
+
+Fold from left to right over the elements of the table.
+
+
+__See also:__ [ets:foldl/3](ets.md#foldl-3).
+
+###foldr/3##
+
+
+
+
+foldr(Fun, Acc0::term(), Tab::[tab()](#type-tab)) -> Acc1::term()
+
+
+
+
+Fold from right to left over the elements of the table.
+
+
+__See also:__ [ets:foldr/3](ets.md#foldr-3).
+
+###info/1##
+
+
+
+
+info(Tab::[tab()](#type-tab)) -> [{[item()](#type-item), term()}]
@@ -454,6 +573,22 @@ __See also:__ [ets:first/1](ets.md#first-1).
Value} tuples.
+__See also:__ [info/2](#info-2).
+
+###info/2##
+
+
+
+
+info(Tab::[tab()](#type-tab), Item::[item()](#type-item)) -> term()
+
+
+
+
+
+Returns the information associated with Item for the table Tab.
+
+
Valid +Item+ options are:
@@ -517,14 +652,14 @@ __See also:__ [ets:info/2](ets.md#info-2).
-
insert(Tab::[tab()](#type-tab), ObjectOrObjects::[object()](#type-object) | [[object()](#type-object)]) -> true
+insert(Tab::[tab()](#type-tab), ObjOrObjs::[object()](#type-object) | [[object()](#type-object)]) -> true
Inserts the object or all of the objects in the list
-ObjectOrObjects into the table Tab.
+ObjOrObjs into the table Tab.
__See also:__ [ets:insert/2](ets.md#insert-2).
@@ -534,7 +669,7 @@ __See also:__ [ets:insert/2](ets.md#insert-2).
-insert_new(Tab::[tab()](#type-tab), ObjectOrObjects::[object()](#type-object) | [[object()](#type-object)]) -> true
+insert_new(Tab::[tab()](#type-tab), ObjOrObjs::[object()](#type-object) | [[object()](#type-object)]) -> true
@@ -546,7 +681,24 @@ simply returns false. This function only applies to the ets
implementation.
-__See also:__ [ets:insert_new/2](ets.md#insert_new-2).
+__See also:__ [ets:insert_new/2](ets.md#insert_new-2).
+
+###last/1##
+
+
+
+
+last(Tab::[tab()](#type-tab)) -> [key()](#type-key) | '$end_of_table'
+
+
+
+
+
+Returns the last key Key in the table Tab. If the table
+is empty, $end_of_table will be returned.
+
+
+__See also:__ [ets:last/1](ets.md#last-1).
###lookup/2##
@@ -563,14 +715,167 @@ __See also:__ [ets:insert_new/2](ets.md#insert_new-2).
Tab.
-__See also:__ [ets:lookup/2](ets.md#lookup-2).
+__See also:__ [ets:lookup/2](ets.md#lookup-2).
+
+###lookup_element/3##
+
+
+
+
+lookup_element(Tab::[tab()](#type-tab), Key::[key()](#type-key), Pos::[pos()](#type-pos)) -> term()
+
+
+
+
+
+Returns the Pos:th element of the object with the key Key
+in the table Tab.
+
+
+__See also:__ [ets:lookup_element/3](ets.md#lookup_element-3).
+
+###match/1##
+
+
+
+
+match(X1::[cont()](#type-cont)) -> {[[match()](#type-match)], [cont()](#type-cont)} | '$end_of_table'
+
+
+
+
+
+Continues a match started with match/3.
+
+
+__See also:__ [ets:match/1](ets.md#match-1).
+
+###match/2##
+
+
+
+
+match(Tab::[tab()](#type-tab), Pattern::[pattern()](#type-pattern)) -> [[match()](#type-match)]
+
+
+
+
+
+Matches the objects in the table Tab against the pattern
+Pattern.
+
+
+__See also:__ [ets:match/2](ets.md#match-2).
+
+###match/3##
+
+
+
+
+match(Tab::[tab()](#type-tab), Pattern::[pattern()](#type-pattern), Limit::[limit()](#type-limit)) -> {[[match()](#type-match)], [cont()](#type-cont)} | '$end_of_table'
+
+
+
+
+
+Matches the objects in the table Tab against the pattern
+Pattern and returns a limited (Limit) number of matching
+objects.
+
+
+__See also:__ [ets:match/3](ets.md#match-3).
+
+###match_delete/2##
+
+
+
+
+match_delete(Tab::[tab()](#type-tab), Pattern::[pattern()](#type-pattern)) -> true
+
+
+
+
+
+Deletes all objects which match the pattern Pattern from the
+table Tab.
+
+
+__See also:__ [ets:match_delete/2](ets.md#match_delete-2).
+
+###match_object/1##
+
+
+
+
+match_object(X1::[cont()](#type-cont)) -> {[[match()](#type-match)], [cont()](#type-cont)} | '$end_of_table'
+
+
+
+
+
+Continues a match started with match_object/3.
+
+
+__See also:__ [ets:match_object/1](ets.md#match_object-1).
+
+###match_object/2##
+
+
+
+
+match_object(Tab::[tab()](#type-tab), Pattern::[pattern()](#type-pattern)) -> [[match()](#type-match)]
+
+
+
+
+
+Matches the objects in the table Tab against the pattern
+Pattern.
+
+
+__See also:__ [ets:match_object/2](ets.md#match_object-2).
+
+###match_object/3##
+
+
+
+
+match_object(Tab::[tab()](#type-tab), Pattern::[pattern()](#type-pattern), Limit::[limit()](#type-limit)) -> {[[match()](#type-match)], [cont()](#type-cont)} | '$end_of_table'
+
+
+
+
+
+Matches the objects in the table Tab against the pattern
+Pattern and returns a limited (Limit) number of matching
+objects.
+
+
+__See also:__ [ets:match_object/3](ets.md#match_object-3).
+
+###member/2##
+
+
+
+
+member(Tab::[tab()](#type-tab), Key::[key()](#type-key)) -> true | false
+
+
+
+
+
+Returns true if one or more elements in the table Tab has
+the key Key, false otherwise.
+
+
+__See also:__ [ets:member/2](ets.md#member-2).
###new/2##
-new(Name::atom(), Options::[opts()](#type-opts)) -> [tab()](#type-tab)
+new(Name::[name()](#type-name), Opts::[opts()](#type-opts)) -> [tab()](#type-tab)
@@ -782,14 +1087,32 @@ table Tab. If there is no next key, $end_of_table is
returned.
-__See also:__ [ets:next/2](ets.md#next-2).
+__See also:__ [ets:next/2](ets.md#next-2).
+
+###prev/2##
+
+
+
+
+prev(Tab::[tab()](#type-tab), Key::[key()](#type-key)) -> [key()](#type-key) | '$end_of_table'
+
+
+
+
+
+Returns the previous key Key2, following the key Key1 in
+the table Tab. If there is no previous key, $end_of_table is
+returned.
+
+
+__See also:__ [ets:prev/2](ets.md#prev-2).
###repair/2##
-repair(Name::atom(), Options::[opts()](#type-opts)) -> true
+repair(Name::[name()](#type-name), Opts::[opts()](#type-opts)) -> true
@@ -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 driver and nif implementations.
-
+
+
+###select/1##
+
+
+
+
+select(X1::[cont()](#type-cont)) -> {[[match()](#type-match)], [cont()](#type-cont)} | '$end_of_table'
+
+
+
+
+
+Continues a select started with select/3.
+
+
+__See also:__ [ets:select/1](ets.md#select-1).
+
+###select/2##
+
+
+
+
+select(Tab::[tab()](#type-tab), Spec::[spec()](#type-spec)) -> [[match()](#type-match)]
+
+
+
+
+
+Matches the objects in the table Tab against the spec
+Spec.
+
+
+__See also:__ [ets:select/2](ets.md#select-2).
+
+###select/3##
+
+
+
+
+select(Tab::[tab()](#type-tab), Spec::[spec()](#type-spec), Limit::[limit()](#type-limit)) -> {[[match()](#type-match)], [cont()](#type-cont)} | '$end_of_table'
+
+
+
+
+
+Matches the objects in the table Tab against the spec Spec
+and returns a limited (Limit) number of matching objects.
+
+
+__See also:__ [ets:select/3](ets.md#select-3).
+
+###select_count/2##
+
+
+
+
+select_count(Tab::[tab()](#type-tab), Spec::[pattern()](#type-pattern)) -> pos_integer()
+
+
+
+
+
+Counts all objects which match the spec Spec from the
+table Tab and returns the number matched.
+
+
+__See also:__ [ets:select_count/2](ets.md#select_count-2).
+
+###select_delete/2##
+
+
+
+
+select_delete(Tab::[tab()](#type-tab), Spec::[pattern()](#type-pattern)) -> pos_integer()
+
+
+
+
+
+Deletes all objects which match the spec Spec from the
+table Tab and returns the number deleted.
+
+
+__See also:__ [ets:select_delete/2](ets.md#select_delete-2).
+
+###select_reverse/1##
+
+
+
+
+select_reverse(X1::[cont()](#type-cont)) -> {[[match()](#type-match)], [cont()](#type-cont)} | '$end_of_table'
+
+
+
+
+
+Continues a select reverse started with select_reverse/3.
+
+
+__See also:__ [ets:select_reverse/1](ets.md#select_reverse-1).
+
+###select_reverse/2##
+
+
+
+
+select_reverse(Tab::[tab()](#type-tab), Spec::[spec()](#type-spec)) -> [[match()](#type-match)]
+
+
+
+
+
+Matches in reverse the objects in the table Tab against the
+spec Spec.
+
+
+__See also:__ [ets:select_reverse/2](ets.md#select_reverse-2).
+
+###select_reverse/3##
+
+
+
+
+select_reverse(Tab::[tab()](#type-tab), Spec::[spec()](#type-spec), Limit::[limit()](#type-limit)) -> {[[match()](#type-match)], [cont()](#type-cont)} | '$end_of_table'
+
+
+
+
+
+Matches in reverse the objects in the table Tab against the
+spec Spec and returns a limited (Limit) number of matching
+objects.
+
+
+__See also:__ [ets:select_reverse/3](ets.md#select_reverse-3).
###tab2list/1##
diff --git a/doc/lets_drv.md b/doc/lets_drv.md
index d70a155..8016aa7 100644
--- a/doc/lets_drv.md
+++ b/doc/lets_drv.md
@@ -12,13 +12,22 @@
##Function Index##
-
+
##Function Details##
+
+
+###delete/1##
+
+
+
+
+`delete(Tab) -> any()`
+
###delete/2##
@@ -26,25 +35,16 @@
-`delete(Tab, Impl) -> any()`
+`delete(Tab, Key) -> any()`
-
+
-###delete/3##
+###delete_all_objects/1##
-`delete(Tab, Impl, Key) -> any()`
-
-
-
-###delete_all_objects/2##
-
-
-
-
-`delete_all_objects(Tab, Impl) -> any()`
+`delete_all_objects(Tab) -> any()`
@@ -55,68 +55,176 @@
`destroy(Tab, Options, ReadOptions, WriteOptions) -> any()`
-
+
-###first/2##
+###first/1##
-`first(Tab, Impl) -> any()`
+`first(Tab) -> any()`
-
+
-###info_memory/2##
+###foldl/3##
-`info_memory(Tab, Impl) -> any()`
+`foldl(Fun, Acc0, Tab) -> any()`
-
+
-###info_size/2##
+###foldr/3##
-`info_size(Tab, Impl) -> any()`
+`foldr(Fun, Acc0, Tab) -> any()`
-
+
-###insert/3##
+###info_memory/1##
-`insert(Tab, Impl, Object) -> any()`
+`info_memory(Tab) -> any()`
-
+
-###insert_new/3##
+###info_size/1##
-`insert_new(Tab, Impl, Object) -> any()`
+`info_size(Tab) -> any()`
-
+
-###lookup/3##
+###insert/2##
-`lookup(Tab, Impl, Key) -> any()`
+`insert(Tab, Object) -> any()`
-
+
-###next/3##
+###insert_new/2##
-`next(Tab, Impl, Key) -> any()`
+`insert_new(Tab, Object) -> any()`
+
+
+
+###last/1##
+
+
+
+
+`last(Tab) -> any()`
+
+
+
+###lookup/2##
+
+
+
+
+`lookup(Tab, Key) -> any()`
+
+
+
+###lookup_element/3##
+
+
+
+
+`lookup_element(Tab, Key, Pos) -> any()`
+
+
+
+###match/1##
+
+
+
+
+`match(Cont) -> any()`
+
+
+
+###match/2##
+
+
+
+
+`match(Tab, Pattern) -> any()`
+
+
+
+###match/3##
+
+
+
+
+`match(Tab, Pattern, Limit) -> any()`
+
+
+
+###match_delete/2##
+
+
+
+
+`match_delete(Tab, Pattern) -> any()`
+
+
+
+###match_object/1##
+
+
+
+
+`match_object(Cont) -> any()`
+
+
+
+###match_object/2##
+
+
+
+
+`match_object(Tab, Pattern) -> any()`
+
+
+
+###match_object/3##
+
+
+
+
+`match_object(Tab, Pattern, Limit) -> any()`
+
+
+
+###member/2##
+
+
+
+
+`member(Tab, Key) -> any()`
+
+
+
+###next/2##
+
+
+
+
+`next(Tab, Key) -> any()`
@@ -127,6 +235,15 @@
`open(Tab, Options, ReadOptions, WriteOptions) -> any()`
+
+
+###prev/2##
+
+
+
+
+`prev(Tab, Key) -> any()`
+
###repair/4##
@@ -136,12 +253,84 @@
`repair(Tab, Options, ReadOptions, WriteOptions) -> any()`
-
+
-###tab2list/2##
+###select/1##
-`tab2list(Tab, Impl) -> any()`
+`select(Cont0) -> any()`
+
+
+
+###select/2##
+
+
+
+
+`select(Tab, Spec) -> any()`
+
+
+
+###select/3##
+
+
+
+
+`select(Tab, Spec, Limit) -> any()`
+
+
+
+###select_count/2##
+
+
+
+
+`select_count(Tab, Spec) -> any()`
+
+
+
+###select_delete/2##
+
+
+
+
+`select_delete(Tab, Spec) -> any()`
+
+
+
+###select_reverse/1##
+
+
+
+
+`select_reverse(Cont0) -> any()`
+
+
+
+###select_reverse/2##
+
+
+
+
+`select_reverse(Tab, Spec) -> any()`
+
+
+
+###select_reverse/3##
+
+
+
+
+`select_reverse(Tab, Spec, Limit) -> any()`
+
+
+
+###tab2list/1##
+
+
+
+
+`tab2list(Tab) -> any()`
diff --git a/doc/lets_ets.md b/doc/lets_ets.md
index ad37f21..3ad4ecb 100644
--- a/doc/lets_ets.md
+++ b/doc/lets_ets.md
@@ -12,13 +12,22 @@
##Function Index##
-
+
##Function Details##
+
+
+###delete/1##
+
+
+
+
+`delete(Tab) -> any()`
+
###delete/2##
@@ -26,25 +35,16 @@
-`delete(Tab, Ets) -> any()`
+`delete(Tab, Key) -> any()`
-
+
-###delete/3##
+###delete_all_objects/1##
-`delete(Tab, Ets, Key) -> any()`
-
-
-
-###delete_all_objects/2##
-
-
-
-
-`delete_all_objects(Tab, Ets) -> any()`
+`delete_all_objects(Tab) -> any()`
@@ -55,68 +55,176 @@
`destroy(Tab) -> any()`
-
+
-###first/2##
+###first/1##
-`first(Tab, Ets) -> any()`
+`first(Tab) -> any()`
-
+
-###info_memory/2##
+###foldl/3##
-`info_memory(Tab, Ets) -> any()`
+`foldl(Function, Acc0, Tab) -> any()`
-
+
-###info_size/2##
+###foldr/3##
-`info_size(Tab, Ets) -> any()`
+`foldr(Function, Acc0, Tab) -> any()`
-
+
-###insert/3##
+###info_memory/1##
-`insert(Tab, Ets, ObjectOrObjects) -> any()`
+`info_memory(Tab) -> any()`
-
+
-###insert_new/3##
+###info_size/1##
-`insert_new(Tab, Ets, ObjectOrObjects) -> any()`
+`info_size(Tab) -> any()`
-
+
-###lookup/3##
+###insert/2##
-`lookup(Tab, Ets, Key) -> any()`
+`insert(Tab, ObjOrObjs) -> any()`
-
+
-###next/3##
+###insert_new/2##
-`next(Tab, Ets, Key) -> any()`
+`insert_new(Tab, ObjOrObjs) -> any()`
+
+
+
+###last/1##
+
+
+
+
+`last(Tab) -> any()`
+
+
+
+###lookup/2##
+
+
+
+
+`lookup(Tab, Key) -> any()`
+
+
+
+###lookup_element/3##
+
+
+
+
+`lookup_element(Tab, Key, Pos) -> any()`
+
+
+
+###match/1##
+
+
+
+
+`match(Cont) -> any()`
+
+
+
+###match/2##
+
+
+
+
+`match(Tab, Pattern) -> any()`
+
+
+
+###match/3##
+
+
+
+
+`match(Tab, Pattern, Limit) -> any()`
+
+
+
+###match_delete/2##
+
+
+
+
+`match_delete(Tab, Pattern) -> any()`
+
+
+
+###match_object/1##
+
+
+
+
+`match_object(Cont) -> any()`
+
+
+
+###match_object/2##
+
+
+
+
+`match_object(Tab, Pattern) -> any()`
+
+
+
+###match_object/3##
+
+
+
+
+`match_object(Tab, Pattern, Limit) -> any()`
+
+
+
+###member/2##
+
+
+
+
+`member(Tab, Key) -> any()`
+
+
+
+###next/2##
+
+
+
+
+`next(Tab, Key) -> any()`
@@ -127,6 +235,15 @@
`open(Tab) -> any()`
+
+
+###prev/2##
+
+
+
+
+`prev(Tab, Key) -> any()`
+
###repair/1##
@@ -136,12 +253,84 @@
`repair(Tab) -> any()`
-
+
-###tab2list/2##
+###select/1##
-`tab2list(Tab, Ets) -> any()`
+`select(Cont) -> any()`
+
+
+
+###select/2##
+
+
+
+
+`select(Tab, Spec) -> any()`
+
+
+
+###select/3##
+
+
+
+
+`select(Tab, Spec, Limit) -> any()`
+
+
+
+###select_count/2##
+
+
+
+
+`select_count(Tab, Spec) -> any()`
+
+
+
+###select_delete/2##
+
+
+
+
+`select_delete(Tab, Spec) -> any()`
+
+
+
+###select_reverse/1##
+
+
+
+
+`select_reverse(Cont) -> any()`
+
+
+
+###select_reverse/2##
+
+
+
+
+`select_reverse(Tab, Spec) -> any()`
+
+
+
+###select_reverse/3##
+
+
+
+
+`select_reverse(Tab, Spec, Limit) -> any()`
+
+
+
+###tab2list/1##
+
+
+
+
+`tab2list(Tab) -> any()`
diff --git a/doc/lets_nif.md b/doc/lets_nif.md
index b9114c8..9285904 100644
--- a/doc/lets_nif.md
+++ b/doc/lets_nif.md
@@ -12,13 +12,22 @@
##Function Index##
-
+
##Function Details##
+
+
+###delete/1##
+
+
+
+
+`delete(Tab) -> any()`
+
###delete/2##
@@ -26,25 +35,16 @@
-`delete(Tab, Impl) -> any()`
+`delete(Tab, Key) -> any()`
-
+
-###delete/3##
+###delete_all_objects/1##
-`delete(Tab, Impl, Key) -> any()`
-
-
-
-###delete_all_objects/2##
-
-
-
-
-`delete_all_objects(Tab, Impl) -> any()`
+`delete_all_objects(Tab) -> any()`
@@ -55,68 +55,176 @@
`destroy(Tab, Options, ReadOptions, WriteOptions) -> any()`
-
+
-###first/2##
+###first/1##
-`first(Tab, Impl) -> any()`
+`first(Tab) -> any()`
-
+
-###info_memory/2##
+###foldl/3##
-`info_memory(Tab, Impl) -> any()`
+`foldl(Fun, Acc0, Tab) -> any()`
-
+
-###info_size/2##
+###foldr/3##
-`info_size(Tab, Impl) -> any()`
+`foldr(Fun, Acc0, Tab) -> any()`
-
+
-###insert/3##
+###info_memory/1##
-`insert(Tab, Impl, Object) -> any()`
+`info_memory(Tab) -> any()`
-
+
-###insert_new/3##
+###info_size/1##
-`insert_new(Tab, Impl, Object) -> any()`
+`info_size(Tab) -> any()`
-
+
-###lookup/3##
+###insert/2##
-`lookup(Tab, Impl, Key) -> any()`
+`insert(Tab, Object) -> any()`
-
+
-###next/3##
+###insert_new/2##
-`next(Tab, Impl, Key) -> any()`
+`insert_new(Tab, Object) -> any()`
+
+
+
+###last/1##
+
+
+
+
+`last(Tab) -> any()`
+
+
+
+###lookup/2##
+
+
+
+
+`lookup(Tab, Key) -> any()`
+
+
+
+###lookup_element/3##
+
+
+
+
+`lookup_element(Tab, Key, Pos) -> any()`
+
+
+
+###match/1##
+
+
+
+
+`match(Cont) -> any()`
+
+
+
+###match/2##
+
+
+
+
+`match(Tab, Pattern) -> any()`
+
+
+
+###match/3##
+
+
+
+
+`match(Tab, Pattern, Limit) -> any()`
+
+
+
+###match_delete/2##
+
+
+
+
+`match_delete(Tab, Pattern) -> any()`
+
+
+
+###match_object/1##
+
+
+
+
+`match_object(Cont) -> any()`
+
+
+
+###match_object/2##
+
+
+
+
+`match_object(Tab, Pattern) -> any()`
+
+
+
+###match_object/3##
+
+
+
+
+`match_object(Tab, Pattern, Limit) -> any()`
+
+
+
+###member/2##
+
+
+
+
+`member(Tab, Key) -> any()`
+
+
+
+###next/2##
+
+
+
+
+`next(Tab, Key) -> any()`
@@ -127,6 +235,15 @@
`open(Tab, Options, ReadOptions, WriteOptions) -> any()`
+
+
+###prev/2##
+
+
+
+
+`prev(Tab, Key) -> any()`
+
###repair/4##
@@ -136,12 +253,84 @@
`repair(Tab, Options, ReadOptions, WriteOptions) -> any()`
-
+
-###tab2list/2##
+###select/1##
-`tab2list(Tab, Impl) -> any()`
+`select(Cont0) -> any()`
+
+
+
+###select/2##
+
+
+
+
+`select(Tab, Spec) -> any()`
+
+
+
+###select/3##
+
+
+
+
+`select(Tab, Spec, Limit) -> any()`
+
+
+
+###select_count/2##
+
+
+
+
+`select_count(Tab, Spec) -> any()`
+
+
+
+###select_delete/2##
+
+
+
+
+`select_delete(Tab, Spec) -> any()`
+
+
+
+###select_reverse/1##
+
+
+
+
+`select_reverse(Cont0) -> any()`
+
+
+
+###select_reverse/2##
+
+
+
+
+`select_reverse(Tab, Spec) -> any()`
+
+
+
+###select_reverse/3##
+
+
+
+
+`select_reverse(Tab, Spec, Limit) -> any()`
+
+
+
+###tab2list/1##
+
+
+
+
+`tab2list(Tab) -> any()`
diff --git a/doc/overview.edoc b/doc/overview.edoc
index 6672fd8..f84d573 100644
--- a/doc/overview.edoc
+++ b/doc/overview.edoc
@@ -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)
diff --git a/src/lets.erl b/src/lets.erl
index b9c8897..4cfbd42 100644
--- a/src/lets.erl
+++ b/src/lets.erl
@@ -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}).
diff --git a/src/lets.hrl b/src/lets.hrl
index 2dd4510..86c5bdd 100644
--- a/src/lets.hrl
+++ b/src/lets.hrl
@@ -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).
diff --git a/src/lets_drv.erl b/src/lets_drv.erl
index 7c4f706..16b0499 100644
--- a/src/lets_drv.erl
+++ b/src/lets_drv.erl
@@ -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}).
diff --git a/src/lets_ets.erl b/src/lets_ets.erl
index 91b7215..2d54590 100644
--- a/src/lets_ets.erl
+++ b/src/lets_ets.erl
@@ -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).
diff --git a/src/lets_nif.erl b/src/lets_nif.erl
index f8e316f..35c0de6 100644
--- a/src/lets_nif.erl
+++ b/src/lets_nif.erl
@@ -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.
diff --git a/test/qc/qc_lets_proxy.erl b/test/qc/qc_lets_proxy.erl
index 4d2a3b0..d5c18d2 100644
--- a/test/qc/qc_lets_proxy.erl
+++ b/test/qc/qc_lets_proxy.erl
@@ -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}.
diff --git a/test/qc/qc_lets_raw.erl b/test/qc/qc_lets_raw.erl
index 7ee9456..ca2ac01 100644
--- a/test/qc/qc_lets_raw.erl
+++ b/test/qc/qc_lets_raw.erl
@@ -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).
diff --git a/test/qc/qc_lets_slave_proxy.erl b/test/qc/qc_lets_slave_proxy.erl
index 027a86b..9957cb4 100644
--- a/test/qc/qc_lets_slave_proxy.erl
+++ b/test/qc/qc_lets_slave_proxy.erl
@@ -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() ->
diff --git a/test/qc/qc_leveldb.erl b/test/qc/qc_leveldb.erl
index dad2a90..591218e 100644
--- a/test/qc/qc_leveldb.erl
+++ b/test/qc/qc_leveldb.erl
@@ -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
%%%===================================================================
diff --git a/test/qc/qc_statem_lets.erl b/test/qc/qc_statem_lets.erl
index 58a7f9e..273a848 100644
--- a/test/qc/qc_statem_lets.erl
+++ b/test/qc/qc_statem_lets.erl
@@ -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).
diff --git a/test/qc/qc_statemc_lets.erl b/test/qc/qc_statemc_lets.erl
index a883830..2ca0d3b 100644
--- a/test/qc/qc_statemc_lets.erl
+++ b/test/qc/qc_statemc_lets.erl
@@ -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).