Skip to content
This repository has been archived by the owner on May 27, 2022. It is now read-only.

Commit

Permalink
Merge pull request #24 from peterzeller/improved-types
Browse files Browse the repository at this point in the history
Improved interface for dialyzer
  • Loading branch information
bieniusa committed May 28, 2018
2 parents 656a8c6 + 714a2c7 commit 998cafd
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 49 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Expand Up @@ -5,7 +5,7 @@ otp_release:
- 20.1
install:
- mkdir -p ~/.config/rebar3/
- echo "{plugins, [rebar3_proper]}." > ~/.config/rebar3/rebar.config
- echo "{plugins, [{rebar3_proper, \"0.9.0\"}]}." > ~/.config/rebar3/rebar.config
- make
- ./rebar3 update
script:
Expand Down
91 changes: 78 additions & 13 deletions README.md
@@ -1,33 +1,98 @@
# Antidote CRDT library
# Antidote CRDT library
[![Build Status](https://travis-ci.org/SyncFree/antidote_crdt.svg?branch=master)](https://travis-ci.org/SyncFree/antidote_crdt)

CRDT implementations to use with Antidote.
Operation based CRDT implementations to use with Antidote.

# API

The `antidote_crdt` module provides the API below.
For a more detailed description of the different data types, the [Antidote Documentation](http://syncfree.github.io/antidote/crdts.html) provides more information.

```erlang
% The CRDTs supported by Antidote:
-type typ() ::
antidote_crdt_counter_pn % PN-Counter aka Positive Negative Counter
| antidote_crdt_counter_b % Bounded Counter
| antidote_crdt_counter_fat % Fat Counter
| antidote_crdt_flag_ew % Enable Wins Flag aka EW-Flag
| antidote_crdt_flag_dw % Disable Wins Flag DW-Flag
| antidote_crdt_set_go % Grow Only Set aka G-Set
| antidote_crdt_set_aw % Add Wins Set aka AW-Set
| antidote_crdt_set_rw % Remove Wins Set aka RW-Set
| antidote_crdt_register_lww % Last Writer Wins Register aka LWW-Reg
| antidote_crdt_register_mv % MultiValue Register aka MV-Reg
| antidote_crdt_map_go % Grow Only Map aka G-Map
| antidote_crdt_map_rr. % Recursive Resets Map aka RR-Map

% Note: the crdt and effect types are not correct, the tags just help to find errors
% The State of a CRDT:
-opaque crdt() :: ...
% The downstream effect, which has to be applied at each replica
-opaque effect() :: ...
% The update operation, consisting of operation name and parameters
% (e.g. {increment, 1} to increment a counter by one)
-type update() :: {atom(), term()}.
% Result of reading a CRDT (state without meta data)
-type value() :: term().
```

```erlang
% Check if the given type is supported by Antidote
-spec is_type(typ()) -> boolean().

% Returns the initial CRDT state for the given Type
-spec new(typ()) -> crdt().

% Reads the value from a CRDT state
-spec value(typ(), crdt()) -> any().

% Computes the downstream effect for a given update operation and current state.
% This has to be called once at the source replica.
% The effect must then be applied on all replicas using the update function.
% For some update operation it is not necessary to provide the current state
% and the atom 'ignore' can be passed instead (see function require_state_downstream).
-spec downstream(typ(), update(), crdt() | ignore) -> {ok, effect()} | {error, reason()}.

% Updates the state of a CRDT by applying a downstream effect calculated
% using the downstream function.
% For most types the update function must be called in causal order:
% if Eff2 was calculated on a state where Eff1 was already replied,
% then Eff1 has to be applied before Eff2 on all replicas.
-spec update(typ(), effect(), crdt()) -> {ok, crdt()}.

% Checks whether the current state is required by the downstream function
% for a specific type and update operation
-spec require_state_downstream(typ(), update()) -> boolean().

% Checks whether the given update operation is valid for the given type
-spec is_operation(typ(), update()) -> boolean().
```

# Development

Use the following `make` targets to build and test the CRDT library:


# compile
make compile
# run unit tests:
make test
# check types:
make dialyzer
# check code style:
make lint
# compile
make compile
# run unit tests:
make test
# check types:
make dialyzer
# check code style:
make lint


## PropEr tests

To run the property based tests in the test directory install the [rebar3 PropEr plugin](https://www.rebar3.org/docs/using-available-plugins#proper) by adding the following line to `~/.config/rebar3/rebar.config`:

{plugins, [rebar3_proper]}.
{plugins, [{rebar3_proper, "0.9.0"}]}.

Then execute the tests with:

make proper
make proper

For more control, you can run PropEr manually and specify parameters like the tested module or the number of generated tests:

rebar3 proper -n 1000 -m prop_crdt_orset
rebar3 proper -n 1000 -m prop_crdt_orset
150 changes: 115 additions & 35 deletions src/antidote_crdt.erl
Expand Up @@ -22,7 +22,7 @@
%% Naming pattern of antidote crdts: <type>_<semantics>
%% if there is only one kind of semantics implemented for a certain type
%% only the type is used in the name e.g. rga
%% counter_pn: PN-Counter aka Posistive Negative Counter
%% counter_pn: PN-Counter aka Positive Negative Counter
%% counter_b: Bounded Counter
%% counter_fat: Fat Counter
%% integer: Integer (Experimental)
Expand All @@ -35,48 +35,128 @@
%% register_mv: MultiValue Register aka MV-Reg
%% map_go: Grow Only Map aka G-Map
%% map_aw: Add Wins Map aka AW-Map (Experimental)
%% map_rr: Recursive Resets Map akak RR-Map
%% map_rr: Recursive Resets Map aka RR-Map
%% rga: Replicated Growable Array (Experimental)



-module(antidote_crdt).

-include("antidote_crdt.hrl").

-define(CRDTS, [antidote_crdt_counter_pn,
antidote_crdt_counter_b,
antidote_crdt_counter_fat,
antidote_crdt_flag_ew,
antidote_crdt_flag_dw,
antidote_crdt_set_go,
antidote_crdt_set_aw,
antidote_crdt_set_rw,
antidote_crdt_register_lww,
antidote_crdt_register_mv,
antidote_crdt_map_go,
antidote_crdt_map_rr]).

-export([is_type/1
]).

-callback new() -> crdt().
-callback value(crdt()) -> value().
-callback downstream(update(), crdt()) -> {ok, effect()} | {error, reason()}.
-callback update(effect(), crdt()) -> {ok, crdt()}.

% The CRDTs supported by Antidote:
-type typ() ::
antidote_crdt_counter_pn
| antidote_crdt_counter_b
| antidote_crdt_counter_fat
| antidote_crdt_flag_ew
| antidote_crdt_flag_dw
| antidote_crdt_set_go
| antidote_crdt_set_aw
| antidote_crdt_set_rw
| antidote_crdt_register_lww
| antidote_crdt_register_mv
| antidote_crdt_map_go
| antidote_crdt_map_rr.

% Note: the crdt and effect types are not correct, the tags just help to find errors
% The State of a CRDT:
-opaque crdt() :: {antidote_crdt, state, term()}.
% The downstream effect, which has to be applied at each replica
-opaque effect() :: {antidote_crdt, effect, term()}.
% The update operation, consisting of operation name and parameters
% (e.g. {increment, 1} to increment a counter by one)
-type update() :: {atom(), term()}.
% Result of reading a CRDT (state without meta data)
-type value() :: term().

% reason for an error
-type reason() :: term().

-export_type([
crdt/0,
update/0,
effect/0,
value/0,
typ/0
]).


-type internal_crdt() :: term().
-type internal_effect() :: term().

-export([is_type/1, new/1, value/2, downstream/3, update/3, require_state_downstream/2, is_operation/2]).

% Callbacks implemented by each concrete CRDT implementation
-callback new() -> internal_crdt().
-callback value(internal_crdt()) -> value().
-callback downstream(update(), internal_crdt()) -> {ok, internal_effect()} | {error, reason()}.
-callback update(internal_effect(), internal_crdt()) -> {ok, internal_crdt()}.
-callback require_state_downstream(update()) -> boolean().
-callback is_operation(update()) -> boolean(). %% Type check
-callback is_operation(update()) -> boolean(). %% Type check

-callback equal(internal_crdt(), internal_crdt()) -> boolean().
-callback to_binary(internal_crdt()) -> binary().
-callback from_binary(binary()) -> {ok, internal_crdt()} | {error, reason()}.

% Check if the given type is supported by Antidote
-spec is_type(typ()) -> boolean().
is_type(antidote_crdt_counter_pn) -> true;
is_type(antidote_crdt_counter_b) -> true;
is_type(antidote_crdt_counter_fat) -> true;
is_type(antidote_crdt_flag_ew) -> true;
is_type(antidote_crdt_flag_dw) -> true;
is_type(antidote_crdt_set_go) -> true;
is_type(antidote_crdt_set_aw) -> true;
is_type(antidote_crdt_set_rw) -> true;
is_type(antidote_crdt_register_lww) -> true;
is_type(antidote_crdt_register_mv) -> true;
is_type(antidote_crdt_map_go) -> true;
is_type(antidote_crdt_map_rr) -> true;
is_type(_) -> false.


% Returns the initial CRDT state for the given Type
-spec new(typ()) -> crdt().
new(Type) ->
true = is_type(Type),
Type:new().

% Reads the value from a CRDT state
-spec value(typ(), crdt()) -> any().
value(Type, State) ->
true = is_type(Type),
Type:value(State).

-callback equal(crdt(), crdt()) -> boolean().
-callback to_binary(crdt()) -> binary().
-callback from_binary(binary()) -> {ok, crdt()} | {error, reason()}.
% Computes the downstream effect for a given update operation and current state.
% This has to be called once at the source replica.
% The effect must then be applied on all replicas using the update function.
% For some update operation it is not necessary to provide the current state
% and the atom 'ignore' can be passed instead (see function require_state_downstream).
-spec downstream(typ(), update(), crdt() | ignore) -> {ok, effect()} | {error, reason()}.
downstream(Type, Update, State) ->
true = is_type(Type),
true = Type:is_operation(Update),
Type:downstream(Update, State).

%% Following callbacks taken from riak_dt
%% Not sure if it is useful for antidote
%-callback stats(crdt()) -> [{atom(), number()}].
%-callback stat(atom(), crdt()) -> number() | undefined.
% Updates the state of a CRDT by applying a downstream effect calculated
% using the downstream function.
% For most types the update function must be called in causal order:
% if Eff2 was calculated on a state where Eff1 was already replied,
% then Eff1 has to be applied before Eff2 on all replicas.
-spec update(typ(), effect(), crdt()) -> {ok, crdt()}.
update(Type, Effect, State) ->
true = is_type(Type),
Type:update(Effect, State).

is_type(Type) ->
is_atom(Type) andalso lists:member(Type, ?CRDTS).
% Checks whether the current state is required by the downstream function
% for a specific type and update operation
-spec require_state_downstream(typ(), update()) -> boolean().
require_state_downstream(Type, Update) ->
true = is_type(Type),
Type:require_state_downstream(Update).

%% End of Module.
% Checks whether the given update operation is valid for the given type
-spec is_operation(typ(), update()) -> boolean().
is_operation(Type, Update) ->
true = is_type(Type),
Type:is_operation(Update).

0 comments on commit 998cafd

Please sign in to comment.