Add transaction option to disallow writes

Add `disallow_writes | allow_writes` transaction options.

If the transaction hasn't done any writes yet, it is possible to set the
`disallow_writes` option, and then every write attempt will throw a
`writes_not_allowed` error.
diff --git a/c_src/atom_names.h b/c_src/atom_names.h
index 5e4e220..bd4471b 100644
--- a/c_src/atom_names.h
+++ b/c_src/atom_names.h
@@ -33,6 +33,7 @@
 
 ATOM_MAP(invalid_future_type);
 
+ATOM_MAP(writes_not_allowed);
 
 // Network Options
 ATOM_MAP(local_address);
@@ -98,6 +99,8 @@
 ATOM_MAP(used_during_commit_protection_disable);
 ATOM_MAP(read_lock_aware);
 ATOM_MAP(size_limit);
+ATOM_MAP(allow_writes);
+ATOM_MAP(disallow_writes);
 
 
 // Streaming mode
diff --git a/c_src/main.c b/c_src/main.c
index e24826b..4eb35a0 100644
--- a/c_src/main.c
+++ b/c_src/main.c
@@ -817,6 +817,7 @@
 
     t->txid = 0;
     t->read_only = true;
+    t->writes_allowed = true;
 
     ret = enif_make_resource(env, t);
     enif_release_resource(t);
@@ -855,6 +856,18 @@
         return enif_make_badarg(env);
     }
 
+    if(IS_ATOM(argv[1], allow_writes)) {
+        t->writes_allowed = true;
+        return ATOM_ok;
+    } else if (IS_ATOM(argv[1], disallow_writes)) {
+        if(!t->read_only) {
+            return enif_make_badarg(env);
+        }
+        t->writes_allowed = false;
+        return ATOM_ok;
+    }
+
+
     if(IS_ATOM(argv[1], causal_write_risky)) {
         option = FDB_TR_OPTION_CAUSAL_WRITE_RISKY;
     } else if(IS_ATOM(argv[1], causal_read_risky)) {
@@ -1295,6 +1308,10 @@
         return enif_make_badarg(env);
     }
 
+    if(!t->writes_allowed) {
+        return enif_raise_exception(env, ATOM_writes_not_allowed);
+    }
+
     if(!enif_inspect_binary(env, argv[1], &key)) {
         return enif_make_badarg(env);
     }
@@ -1346,6 +1363,10 @@
         return enif_make_badarg(env);
     }
 
+    if(!t->writes_allowed) {
+        return enif_raise_exception(env, ATOM_writes_not_allowed);
+    }
+
     if(!enif_inspect_binary(env, argv[1], &key)) {
         return enif_make_badarg(env);
     }
@@ -1388,6 +1409,10 @@
         return enif_make_badarg(env);
     }
 
+    if(!t->writes_allowed) {
+        return enif_raise_exception(env, ATOM_writes_not_allowed);
+    }
+
     if(!enif_inspect_binary(env, argv[1], &skey)) {
         return enif_make_badarg(env);
     }
@@ -1441,6 +1466,10 @@
         return enif_make_badarg(env);
     }
 
+    if(!t->writes_allowed) {
+        return enif_raise_exception(env, ATOM_writes_not_allowed);
+    }
+
     if(!enif_inspect_binary(env, argv[1], &key)) {
         return enif_make_badarg(env);
     }
@@ -1801,6 +1830,9 @@
     if(IS_ATOM(argv[3], read)) {
         rtype = FDB_CONFLICT_RANGE_TYPE_READ;
     } else if(IS_ATOM(argv[3], write)) {
+        if(!t->writes_allowed) {
+            return enif_raise_exception(env, ATOM_writes_not_allowed);
+        }
         rtype = FDB_CONFLICT_RANGE_TYPE_WRITE;
     } else {
         return enif_make_badarg(env);
@@ -1935,6 +1967,42 @@
 
 
 static ERL_NIF_TERM
+erlfdb_transaction_get_writes_allowed(
+        ErlNifEnv* env,
+        int argc,
+        const ERL_NIF_TERM argv[]
+    )
+{
+    ErlFDBSt* st = (ErlFDBSt*) enif_priv_data(env);
+    ErlFDBTransaction* t;
+    void* res;
+
+    if(st->lib_state != ErlFDB_CONNECTED) {
+        return enif_make_badarg(env);
+    }
+
+    if(argc != 1) {
+        return enif_make_badarg(env);
+    }
+
+    if(!enif_get_resource(env, argv[0], ErlFDBTransactionRes, &res)) {
+        return enif_make_badarg(env);
+    }
+    t = (ErlFDBTransaction*) res;
+
+    if(!erlfdb_transaction_is_owner(env, t)) {
+        return enif_make_badarg(env);
+    }
+
+    if(t->writes_allowed) {
+        return ATOM_true;
+    } else {
+        return ATOM_false;
+    }
+}
+
+
+static ERL_NIF_TERM
 erlfdb_get_error(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
 {
     int erl_err;
@@ -2043,6 +2111,7 @@
     NIF_FUNC(erlfdb_transaction_get_approximate_size, 1),
     NIF_FUNC(erlfdb_transaction_get_next_tx_id, 1),
     NIF_FUNC(erlfdb_transaction_is_read_only, 1),
+    NIF_FUNC(erlfdb_transaction_get_writes_allowed, 1),
 
     NIF_FUNC(erlfdb_get_error, 1),
     NIF_FUNC(erlfdb_error_predicate, 2)
diff --git a/c_src/resources.h b/c_src/resources.h
index db61a1f..bee88f6 100644
--- a/c_src/resources.h
+++ b/c_src/resources.h
@@ -60,6 +60,7 @@
     ERL_NIF_TERM owner;
     unsigned int txid;
     bool read_only;
+    bool writes_allowed;
 } ErlFDBTransaction;
 
 
diff --git a/src/erlfdb.erl b/src/erlfdb.erl
index 97b3bad..d4e7d8a 100644
--- a/src/erlfdb.erl
+++ b/src/erlfdb.erl
@@ -109,6 +109,7 @@
     % Transaction status
     get_next_tx_id/1,
     is_read_only/1,
+    get_writes_allowed/1,
 
     % Locality
     get_addresses_for_key/2,
@@ -592,6 +593,13 @@
     is_read_only(?GET_TX(SS)).
 
 
+get_writes_allowed(?IS_TX = Tx) ->
+    erlfdb_nif:transaction_get_writes_allowed(Tx);
+
+get_writes_allowed(?IS_SS = SS) ->
+    get_writes_allowed(?GET_TX(SS)).
+
+
 get_addresses_for_key(?IS_DB = Db, Key) ->
     transactional(Db, fun(Tx) ->
         wait(get_addresses_for_key(Tx, Key))
diff --git a/src/erlfdb_nif.erl b/src/erlfdb_nif.erl
index 3a4b6e2..755fe7c 100644
--- a/src/erlfdb_nif.erl
+++ b/src/erlfdb_nif.erl
@@ -52,6 +52,7 @@
     transaction_add_conflict_range/4,
     transaction_get_next_tx_id/1,
     transaction_is_read_only/1,
+    transaction_get_writes_allowed/1,
     transaction_get_approximate_size/1,
 
     get_error/1,
@@ -142,7 +143,9 @@
     lock_aware |
     used_during_commit_protection_disable |
     read_lock_aware |
-    size_limit.
+    size_limit |
+    allow_writes |
+    disallow_writes.
 
 
 -type streaming_mode() ::
@@ -427,6 +430,11 @@
     erlfdb_transaction_is_read_only(Tx).
 
 
+-spec transaction_get_writes_allowed(transaction()) -> true | false.
+transaction_get_writes_allowed({erlfdb_transaction, Tx}) ->
+    erlfdb_transaction_get_writes_allowed(Tx).
+
+
 -spec get_error(integer()) -> binary().
 get_error(Error) ->
     erlfdb_get_error(Error).
@@ -567,6 +575,7 @@
     ) -> ?NOT_LOADED.
 erlfdb_transaction_get_next_tx_id(_Transaction) -> ?NOT_LOADED.
 erlfdb_transaction_is_read_only(_Transaction) -> ?NOT_LOADED.
+erlfdb_transaction_get_writes_allowed(_Transaction) -> ?NOT_LOADED.
 erlfdb_transaction_get_approximate_size(_Transaction) -> ?NOT_LOADED.
 
 
diff --git a/test/erlfdb_03_transaction_size_test.erl b/test/erlfdb_03_transaction_options_test.erl
similarity index 62%
rename from test/erlfdb_03_transaction_size_test.erl
rename to test/erlfdb_03_transaction_options_test.erl
index c18401a..7a68074 100644
--- a/test/erlfdb_03_transaction_size_test.erl
+++ b/test/erlfdb_03_transaction_options_test.erl
@@ -10,7 +10,7 @@
 % License for the specific language governing permissions and limitations under
 % the License.
 
--module(erlfdb_03_transaction_size_test).
+-module(erlfdb_03_transaction_options_test).
 
 -include_lib("eunit/include/eunit.hrl").
 
@@ -35,5 +35,29 @@
     end)).
 
 
+writes_allowed_test() ->
+    Db1 = erlfdb_util:get_test_db(),
+    ?assertError(writes_not_allowed, erlfdb:transactional(Db1, fun(Tx) ->
+        ?assert(erlfdb:get_writes_allowed(Tx)),
+
+        erlfdb:set_option(Tx, disallow_writes),
+        ?assert(not erlfdb:get_writes_allowed(Tx)),
+
+        erlfdb:set_option(Tx, allow_writes),
+        ?assert(erlfdb:get_writes_allowed(Tx)),
+
+        erlfdb:set_option(Tx, disallow_writes),
+        erlfdb:set(Tx, gen(10), gen(10))
+    end)).
+
+
+once_writes_happend_cannot_disallow_them_test() ->
+    Db1 = erlfdb_util:get_test_db(),
+    ?assertError(badarg, erlfdb:transactional(Db1, fun(Tx) ->
+        ok = erlfdb:set(Tx, gen(10), gen(10)),
+        erlfdb:set_option(Tx, disallow_writes)
+    end)).
+
+
 gen(Size) ->
     crypto:strong_rand_bytes(Size).