├── LICENSE ├── Makefile ├── README.md ├── c_src ├── build_deps.sh ├── esphi.c └── esphi.h ├── examples └── example1 │ ├── Makefile │ ├── README.md │ ├── erlang.mk │ └── src │ ├── example1.erl │ ├── example1_app.erl │ ├── example1_dbowner.erl │ ├── example1_sup.erl │ ├── example1_worker.erl │ └── example1_workers_sup.erl ├── include └── esphi.hrl ├── rebar ├── rebar.config ├── rebar.config.script ├── src ├── esphi.app.src └── esphi.erl └── tools.mk /LICENSE: -------------------------------------------------------------------------------- 1 | Apache 2 License -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REBAR ?= ./rebar 2 | 3 | all: compile 4 | 5 | get-deps: 6 | ./c_src/build_deps.sh get-deps 7 | 8 | deps: 9 | ${REBAR} get-deps 10 | 11 | rm-deps: 12 | ./c_src/build_deps.sh rm-deps 13 | 14 | compile: deps 15 | ${REBAR} compile 16 | 17 | clean: 18 | ${REBAR} clean 19 | 20 | include tools.mk 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Esphi - an Erlang dirty-NIF-based interface to Sophia embeddable database 2 | ========= 3 | Sophia database on github: [https://github.com/pmwkaa/sophia](https://github.com/pmwkaa/sophia) 4 | Sophia home page: [sophia.systems](http://sophia.systems/) 5 | 6 | The library is loosely based on [eleveldb](https://github.com/basho/eleveldb). 7 | 8 | 9 | The library uses experimental dirty NIF feature from the latest Erlang. In order to build it and 10 | use it you need to have recent Erlang distribution compiled with the `--enable-dirty-schedulers` 11 | configuration flag. It is likely possible to use these nifs with standard schedulers too, as the Sophia 12 | in most cases should be quite fast. To do this, edit the `esphi.c` file and remove all references to 13 | ERL_NIF_DIRTY_JOB_IO_BOUND. 14 | 15 | The library should be thread-safe. Pay attention: at the moment the `*_close()` functions won't 16 | delete or dispose pointers in any way, they just would set a flag which prevents the object from 17 | being used any more (this behaviour is going to be changed in future versions). All destructors are 18 | called only when Erlang garbage collects references to the allocated structures, which contain disposable 19 | pointers. So in order to avoid memory leaks ensure that the garbage collector can actually collect old 20 | references, just like with binaries. It is better to use short-lived processes for cursors and transactions, 21 | which are usually created and destroyed very often. 22 | Cursors are not thread safe. Iterating over cursors from multiple erlang processes simultaneously won't crash 23 | the VM but you will get `stop` errors instead of data. Also be careful with transactions, don't commit/rollback 24 | the same transaction from multiple processes simultaneously. 25 | 26 | 27 | Terminology 28 | --------- 29 | The esphi and sophia docs have different names for objects, so pay attention to it when you read the docs: 30 | ``` 31 | | Sophia | Esphi | 32 | |------------------------| 33 | | environment | database | 34 | | database | table | 35 | 36 | ``` 37 | 38 | 39 | What works (in Sophia terms): 40 | --------- 41 | * Multiple environments, each with multiple databases 42 | * All string and int configuration parameters 43 | * Databases with string and int64 keys 44 | * Transactions 45 | * Cursors with prefix, order and seek 46 | * Get/Put/Delete operations, with keys and values of int or string type 47 | * Upsert, for counters only (new_value = old_value + increment) 48 | * Async backups 49 | 50 | What is not implemented yet: 51 | --------- 52 | * Point-in-Time Views 53 | * Multikeys 54 | * Function and object configuration parameters 55 | * Anything async 56 | * Sync backups 57 | * More types of upserts, besides counters. It should be easy to add more though 58 | * Other features which I'm not avare of yet 59 | 60 | 61 | Some things to know: 62 | --------- 63 | * !!!! Seems like reopening databases and tables is not safe at the moment, as the the shutdown itself is 64 | initiated by erlang garbage collector, which for long-running proceses is quite unpredictable. As 65 | the result, data corruption is probably possible. So after you opened a DB keep it opened until 66 | you restart the VM. Or ensure somehow that the garbage collector collected all references to the DB 67 | and all it's dependent objects, and Sophia itself has completed all its background operations. 68 | * All references, including cursors, are freed only when erlang garbage collects the reference. Table and DB 69 | references won't get garbage collected until all the objects which reference them get garbage collected. 70 | This behaviour might change in future versions with deterministic `close`. 71 | * Please don't serialize references to the Sophia objects in any way, including attempts to send them to 72 | other Erlang nodes. In the best case it just won't work. 73 | * Sophia does not store DB schema. You need to supply schema and configuration parameters each time you 74 | open the database. 75 | * Many operations, such as dropping a table, doing a backup etc are async. You may encounter multiple issues doing 76 | this in concurrent mode. For example, if you initiate backup when another backup is still running, 77 | the new backup will be just silently ignored. 78 | * Typechecking is weak. For example, when you Get as integer some value, which contains random binary, 79 | you will get either `{error, type}` or some `int` value if the data fits in any of int types, 80 | which is probably not what you would want too. 81 | * There's race between error and returning its' description with the `get_last_error`. 82 | * Check return codes. Many params can be set only with the `esphi_db_open` call, and if you set 83 | anything manually, check if the operation was actually successful or not. 84 | 85 | 86 | Usage 87 | =========== 88 | `$ make && erl -pa ebin` 89 | 90 | Open database in `/tmp/db1`, with three tables, `t1`, `t2`, `t3`. The `t2` table has integer key; 91 | the `t3` table is used for counters. Pay attention to the ` "db.t3.index.upsert" => "__COUNTER__"` 92 | construction - it is the only parameter which is preprocessed before getting sent to the Sophia, 93 | the `__COUNTER__` special value is replaced with a pointer to the merge function with 94 | `Val = OldVal + NewVal` semantics. The full list of parameters can be found in the Sophia documentation. 95 | 96 | ```Erlang 97 | {ok, Db} = esphi:esphi_db_open( 98 | "/tmp/db1", 99 | ["t1", "t2", "t3"], 100 | #{"db.t2.index.key" => "u64", "db.t3.index.upsert" => "__COUNTER__", "backup.path" => "/tmp/backup"} 101 | ). 102 | ``` 103 | 104 | Open tables 105 | ----------- 106 | 107 | ```Erlang 108 | {ok, T1} = esphi:esphi_table_open(Db, "t1"). 109 | {ok, T2} = esphi:esphi_table_open(Db, "t2"). 110 | {ok, T3} = esphi:esphi_table_open(Db, "t3"). 111 | {error, {-1, ErrMsg}} = esphi:esphi_table_open(Db, "t4"). 112 | ok = esphi:esphi_table_create(Db, "t4", #{}). 113 | {ok, T4} = esphi:esphi_table_open(Db, "t4"). 114 | ``` 115 | 116 | Some CRUD, without transactions 117 | ------------ 118 | Both keys and values can be of either of iolist() or integer() types. iolist() will be converted to binary(). 119 | The last parameter in the `esphi_get` fuction can be either `0` - return binary or `1` - return int. 120 | ```Erlang 121 | ok = esphi:esphi_put(T1, <<"k1">>, <<"v1">>). 122 | ok = esphi:esphi_put(T1, 10, <<"v2">>). 123 | ok = esphi:esphi_put(T1, <<"k2">>, 1000). 124 | ok = esphi:esphi_put(T1, 10, <<"v2">>). 125 | <<"v1">> = esphi:esphi_get(T1, <<"k1">>, 0). 126 | {error,type} = esphi:esphi_get(T1, <<"k1">>, 1). 127 | <<"v2">> = esphi:esphi_get(T1, 10, 0). 128 | <<232,3,0,0,0,0,0,0>> = esphi:esphi_get(T1, <<"k2">>, 0). 129 | 1000 = esphi:esphi_get(T1, <<"k2">>, 1). 130 | ok = esphi:esphi_delete(T1, 10). 131 | not_found = esphi:esphi_get(T1, 10, 0). 132 | ``` 133 | 134 | Counters 135 | ----------- 136 | Unlike in Mnesia, it is impossible to get the result of increment immediatelly, without doing an explicit 137 | read. Moreover, if you think about using transactions to do update_counter and then read - don't. You will 138 | get a lot of automatic rollbacks under the load, as transactions use optimistic locking and will 139 | refuse to commit if value has been changed by another transaction. So use these counters for 140 | counting likes, not for generating sequences of unique IDs. For sequences serialize it with a `gen_server`. 141 | Under the hood the counters are implemented with Upsert feature of the Sophia. Don't forget to configure 142 | the table, which is going to be used to store counters, with the 143 | `"db.[table].index.upsert" => "__COUNTER__"` parameter. 144 | 145 | ```Erlang 146 | ok = esphi:esphi_update_counter(T3, "cnt", 1000). 147 | ok = esphi:esphi_update_counter(T3, "cnt", 1). 148 | 1001 = esphi:esphi_get(T3, "cnt", 1). 149 | ``` 150 | You may also use counters with transactions. 151 | 152 | Transactions 153 | ------------ 154 | Transactions can span multiple tables. Optimistic locking, so if there are two tranactions updating 155 | the same keys, the second one will be automatically rolled back on commit. 156 | Possible errors of commit: 157 | `{error, disposed}` - Transaction has been already disposed; 158 | `{error, rolled_back}` - Transaction rolled back because of conflict with another transaction; 159 | `{error, lock}` - Deadlocked with another transaction. You can manually roll back some of the locked 160 | transactions and repeat commit again; 161 | `{error, busy}` - The transaction is about to get commited by another process 162 | 163 | 164 | ```Erlang 165 | {ok, A1} = esphi:esphi_transaction_begin(Db). 166 | {ok, A2} = esphi:esphi_transaction_begin(Db). 167 | {ok, A3} = esphi:esphi_transaction_begin(Db). 168 | ok = esphi:esphi_put(T1, A1, <<"tk">>, <<"vk">>). 169 | ok = esphi:esphi_put(T1, A2, <<"tk">>, <<"vk2">>). 170 | ok = esphi:esphi_update_counter(T3, A1, "cnt", 1). 171 | ok = esphi:esphi_put(T1, A3, <<"tk">>, <<"vk3">>). 172 | ok = esphi:esphi_put(T2, A1, <<"k2">>, <<"v2">>). 173 | ok = esphi:esphi_put(T2, A2, <<"k2">>, 10). 174 | ok = esphi:esphi_transaction_commit(A1). 175 | ok = esphi:esphi_transaction_rollback(A3). 176 | {error, rolled_back} = esphi:esphi_transaction_commit(A2). 177 | <<"vk">> = esphi:esphi_get_s(T1, <<"tk">>). 178 | ``` 179 | 180 | Cursors 181 | ----------- 182 | One way only, ascending and descending order, with optional prefix filter for key (for binary keys only). 183 | Allowed orders: 184 | `>=` - traverse with increasing order 185 | `>` - traverse with increasing order, skip first key for prefix 186 | `<=` - traverse with decreasing order 187 | `<` - traverse with decreasing order, skip first key for prefix 188 | The second parameter in the `esphi_cursor_next`: `0` to return just key, `1` to return both key and value. 189 | The third and fourth parameters have the same meaning as the last parameter in `esphi_get` function, for 190 | key (former) and for value (latter). 191 | Put `0` in the second parameter of the `esphi_cursor_open` if you don't want to do prefix filter (for 192 | example for the tables with `int` keys you can't do prefix seek even if you want so). Put `""` in the 193 | last parameter if you don't want to do seek. 194 | ```Erlang 195 | ok = esphi:esphi_put(T1, <<"sa1">>, <<"v1">>). 196 | ok = esphi:esphi_put(T1, <<"sa2">>, <<"v2">>). 197 | ok = esphi:esphi_put(T1, <<"sa3">>, <<"v3">>). 198 | ok = esphi:esphi_put(T1, <<"z0">>, <<"vz0">>). 199 | ok = esphi:esphi_put(T1, <<"sa4">>, <<"v4">>). 200 | {ok, C2} = esphi:esphi_cursor_open(T1, "s", '>=', "sa2"). 201 | {<<"sa2">>,<<"v2">>} = esphi:esphi_cursor_next(C2, 1, 0, 0). 202 | <<"sa3">> = esphi:esphi_cursor_next(C2, 0, 0, 0). 203 | {<<"sa4">>,<<"v4">>} = esphi:esphi_cursor_next(C2, 1, 0, 0). 204 | stop = esphi:esphi_cursor_next(C2, 1, 0, 0). 205 | ``` 206 | 207 | Backup 208 | ----------- 209 | Backups are async. Wait for the previous backup to finish its' job before running the next one. See 210 | http://sophia.systems/v2.1/conf/backup.html and http://sophia.systems/v2.1/admin/backup.html 211 | You must set `"backup.path"` parameter in `esphi_db_open`, else backups won't work at all. 212 | 213 | ```Erlang 214 | ok = esphi:esphi_backup(Db). 215 | 0 = esphi:esphi_param_get(Db, "backup.active", 1). %%or 1 216 | 1 = esphi:esphi_param_get(Db, "backup.last", 1). 217 | 1 = esphi:esphi_param_get(Db, "backup.last_complete", 1). 218 | ``` -------------------------------------------------------------------------------- /c_src/build_deps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # /bin/sh on Solaris is not a POSIX compatible shell, but /usr/bin/ksh is. 3 | if [ `uname -s` = 'SunOS' -a "${POSIX_SHELL}" != "true" ]; then 4 | POSIX_SHELL="true" 5 | export POSIX_SHELL 6 | exec /usr/bin/ksh $0 $@ 7 | fi 8 | unset POSIX_SHELL # clear it so if we invoke other scripts, they run as ksh as well 9 | 10 | SOPHIA_VSN="" 11 | 12 | 13 | set -e 14 | 15 | if [ `basename $PWD` != "c_src" ]; then 16 | # originally "pushd c_src" of bash 17 | # but no need to use directory stack push here 18 | cd c_src 19 | fi 20 | 21 | BASEDIR="$PWD" 22 | 23 | # detecting gmake and if exists use it 24 | # if not use make 25 | # (code from github.com/tuncer/re2/c_src/build_deps.sh 26 | which gmake 1>/dev/null 2>/dev/null && MAKE=gmake 27 | MAKE=${MAKE:-make} 28 | 29 | # Changed "make" to $MAKE 30 | 31 | case "$1" in 32 | rm-deps) 33 | rm -rf sophia 34 | ;; 35 | 36 | clean) 37 | if [ -d sophia ]; then 38 | (cd sophia && $MAKE clean) 39 | fi 40 | # rm -f ../priv/leveldb_repair ../priv/sst_scan ../priv/sst_rewrite ../priv/perf_dump 41 | ;; 42 | 43 | test) 44 | export CFLAGS="$CFLAGS -I $BASEDIR/system/include" 45 | export CXXFLAGS="$CXXFLAGS -I $BASEDIR/system/include" 46 | export LDFLAGS="$LDFLAGS -L$BASEDIR/system/lib" 47 | export LD_LIBRARY_PATH="$BASEDIR/system/lib:$LD_LIBRARY_PATH" 48 | export SOPHIA_VSN="SOPHIA_VSN" 49 | 50 | (cd sophia && $MAKE check) 51 | 52 | ;; 53 | 54 | get-deps) 55 | if [ ! -d sophia ]; then 56 | git clone https://github.com/pmwkaa/sophia/ 57 | (cd sophia && git checkout $SOPHIA_VSN) 58 | fi 59 | ;; 60 | 61 | *) 62 | 63 | export CFLAGS="$CFLAGS -I $BASEDIR/system/include" 64 | export CXXFLAGS="$CXXFLAGS -I $BASEDIR/system/include" 65 | export LDFLAGS="$LDFLAGS -L$BASEDIR/system/lib" 66 | export LD_LIBRARY_PATH="$BASEDIR/system/lib:$LD_LIBRARY_PATH" 67 | export SOPHIA_VSN="$SOPHIAROCKSDB_VSN" 68 | 69 | if [ ! -d sophia ]; then 70 | git clone https://github.com/pmwkaa/sophia/ 71 | (cd sophia && git checkout $SOPHIA_VSN) 72 | fi 73 | (cd sophia && $MAKE -j 3) 74 | 75 | ;; 76 | esac 77 | -------------------------------------------------------------------------------- /c_src/esphi.c: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------- 2 | // 3 | // based on eleveldb: Erlang Wrapper for LevelDB (http://code.google.com/p/leveldb/) 4 | // 5 | // 6 | //------------------------------------------------------------------- 7 | #include "esphi.h" 8 | 9 | 10 | //namespace esphi { 11 | // 12 | //// Atoms (initialized in on_load) 13 | ERL_NIF_TERM ATOM_TRUE; 14 | ERL_NIF_TERM ATOM_FALSE; 15 | ERL_NIF_TERM ATOM_OK; 16 | ERL_NIF_TERM ATOM_ERROR; 17 | ERL_NIF_TERM ATOM_NOT_FOUND; 18 | ERL_NIF_TERM ATOM_INVALID_REF; 19 | ERL_NIF_TERM ATOM_STOP; 20 | ERL_NIF_TERM ATOM_ROLLEDBACK; 21 | ERL_NIF_TERM ATOM_LOCK; 22 | ERL_NIF_TERM ATOM_UNKNOWN; 23 | ERL_NIF_TERM ATOM_TYPE; 24 | ERL_NIF_TERM ATOM_DISPOSED; 25 | 26 | 27 | typedef struct { 28 | volatile int disposed; 29 | void *env; 30 | } sphia_env; 31 | ErlNifResourceType* sphiaEnvResource; 32 | 33 | 34 | typedef struct { 35 | volatile int disposed; 36 | void *db; 37 | sphia_env *spenv; 38 | 39 | } sphia_db; 40 | ErlNifResourceType* sphiaDbResource; 41 | 42 | typedef struct { 43 | volatile int disposed; 44 | void *tran; 45 | sphia_env *spenv; 46 | 47 | } sphia_tran; 48 | ErlNifResourceType* sphiaTranResource; 49 | 50 | 51 | typedef struct { 52 | volatile int disposed; 53 | void *cur; 54 | sphia_db *db; 55 | void *o; 56 | 57 | } sphia_cur; 58 | ErlNifResourceType* sphiaCurResource; 59 | 60 | static void env_dispose(ErlNifEnv *env, void *c) { 61 | sphia_env* spenv = (sphia_env*)c; 62 | spenv->disposed = 1; 63 | } 64 | static void env_dispose_(ErlNifEnv *env, void *c) { 65 | sphia_env* spenv = (sphia_env*)c; 66 | void *p = __sync_lock_test_and_set(&spenv->env, NULL); 67 | if (p != NULL ) { 68 | sp_destroy(p); 69 | } 70 | } 71 | 72 | static void db_dispose(ErlNifEnv *env, void *c) { 73 | sphia_db* db = (sphia_db*)c; 74 | db->disposed = 1; 75 | } 76 | 77 | static void db_dispose_(ErlNifEnv *env, void *c) { 78 | sphia_db* db = (sphia_db*)c; 79 | void *p = __sync_lock_test_and_set(&db->db, NULL); 80 | if (p != NULL ){ 81 | sp_destroy(p); 82 | } 83 | enif_release_resource(db->spenv); 84 | } 85 | 86 | static void tran_dispose(ErlNifEnv *env, void *c) { 87 | sphia_tran* tran = (sphia_tran*)c; 88 | tran->disposed = 1; 89 | } 90 | 91 | static void tran_dispose_(ErlNifEnv *env, void *c) { 92 | sphia_tran* tran = (sphia_tran*)c; 93 | void *p = __sync_lock_test_and_set(&tran->tran, NULL); 94 | if (p != NULL ){ 95 | sp_destroy(p); 96 | } 97 | enif_release_resource(tran->spenv); 98 | } 99 | 100 | static void cur_dispose(ErlNifEnv *env, void *c) { 101 | sphia_cur* cur = (sphia_cur*)c; 102 | cur->disposed = 1; 103 | } 104 | 105 | static void cur_dispose_(ErlNifEnv *env, void *c) { 106 | sphia_cur* cur = (sphia_cur*)c; 107 | void *o = __sync_lock_test_and_set(&cur->o, NULL); 108 | if (o != NULL ){ 109 | sp_destroy(o); 110 | } 111 | void *p = __sync_lock_test_and_set(&cur->cur, NULL); 112 | if (p != NULL ){ 113 | sp_destroy(p); 114 | } 115 | enif_release_resource(cur->db); 116 | } 117 | 118 | static char* join(const char* a, const char* b) { 119 | size_t lena = strlen(a); 120 | size_t lenb = strlen(b); 121 | char* buf = enif_alloc(lena + lenb + 1); 122 | memcpy(buf, a, lena); 123 | memcpy(buf + lena, b, lenb); 124 | buf[lena + lenb] = 0x0; 125 | return buf; 126 | } 127 | 128 | static bool startswith(const char *prefix, const char *str) { 129 | return strncmp(prefix, str, strlen(prefix)) == 0; 130 | } 131 | 132 | static char* bcstr(ErlNifBinary* b) { 133 | char* buf = enif_alloc(b->size + 1); 134 | strncpy(buf, (const char *)b->data, b->size); 135 | buf[b->size] = 0x0; 136 | return buf; 137 | } 138 | 139 | 140 | static int 141 | upsert_callback_counter(char **result, 142 | char **key, int *key_size, int key_count, 143 | char *src, int src_size, 144 | char *upsert, int upsert_size, 145 | void *arg) 146 | { 147 | (void)key; 148 | (void)key_size; 149 | (void)key_count; 150 | (void)arg; 151 | assert(upsert != NULL); 152 | char *c = malloc(upsert_size); 153 | if (c == NULL) 154 | return -1; 155 | *result = c; 156 | if (src == NULL) { 157 | memcpy(c, upsert, upsert_size); 158 | return upsert_size; 159 | } 160 | assert(src_size == upsert_size); 161 | memcpy(c, src, src_size); 162 | *(ErlNifSInt64*)c += *(ErlNifSInt64*)upsert; 163 | return upsert_size; 164 | } 165 | 166 | 167 | 168 | 169 | bool endsWith (char* base, char* str) { 170 | int blen = strlen(base); 171 | int slen = strlen(str); 172 | return (blen >= slen) && (0 == strcmp(base + blen - slen, str)); 173 | } 174 | 175 | static int sophia_set_sparam(sphia_env* spenv, char* ckey, ErlNifBinary* bvalue) { 176 | int rc; 177 | char * cvalue; 178 | 179 | cvalue = bcstr(bvalue); 180 | if (endsWith(ckey, ".index.upsert") && (strcmp(cvalue, "__COUNTER__") == 0)) 181 | rc = sp_setstring(spenv->env, ckey, (char*)(intptr_t)upsert_callback_counter, 0); 182 | else 183 | rc = sp_setstring(spenv->env, ckey, cvalue, 0); 184 | enif_free(cvalue); 185 | return rc; 186 | } 187 | 188 | 189 | static int sophia_apply_params(ErlNifEnv* env, sphia_env* spenv, ERL_NIF_TERM map) { 190 | ErlNifMapIterator iter; 191 | ERL_NIF_TERM key, value; 192 | ErlNifBinary bkey, bvalue; 193 | ErlNifSInt64 ivalue; 194 | int rc; 195 | char* ckey; 196 | 197 | rc = 0; 198 | enif_map_iterator_create(env, map, &iter, ERL_NIF_MAP_ITERATOR_FIRST); 199 | while (enif_map_iterator_get_pair(env, &iter, &key, &value)) { 200 | enif_inspect_iolist_as_binary(env, key, &bkey); 201 | ckey = bcstr(&bkey); 202 | if (enif_get_int64(env, value, &ivalue)) { 203 | rc = sp_setint(spenv->env, ckey, ivalue); 204 | } else { 205 | enif_inspect_iolist_as_binary(env, value, &bvalue); 206 | rc = sophia_set_sparam(spenv, ckey, &bvalue); 207 | 208 | } 209 | enif_free(ckey); 210 | if (rc == -1) { 211 | break; 212 | } 213 | enif_map_iterator_next(env, &iter); 214 | } 215 | enif_map_iterator_destroy(env, &iter); 216 | return rc; 217 | 218 | 219 | } 220 | 221 | static bool sophia_validate_map(ErlNifEnv* env, ERL_NIF_TERM map) { 222 | ErlNifMapIterator iter; 223 | ERL_NIF_TERM key, value; 224 | ErlNifBinary bkey, bvalue; 225 | ErlNifSInt64 ivalue; 226 | 227 | enif_map_iterator_create(env, map, &iter, ERL_NIF_MAP_ITERATOR_FIRST); 228 | while (enif_map_iterator_get_pair(env, &iter, &key, &value)) { 229 | if (!enif_inspect_iolist_as_binary(env, key, &bkey) || 230 | (!enif_inspect_iolist_as_binary(env, value, &bvalue) && !enif_get_int64(env, value, &ivalue)) 231 | ) { 232 | enif_map_iterator_destroy(env, &iter); 233 | return false; 234 | } 235 | enif_map_iterator_next(env, &iter); 236 | } 237 | enif_map_iterator_destroy(env, &iter); 238 | return true; 239 | } 240 | 241 | static ERL_NIF_TERM sofia_error(ErlNifEnv* env, sphia_env* spenv, int code, bool dispose) { 242 | ErlNifBinary err_binary; 243 | int size; 244 | char *error = (char *)sp_getstring(spenv->env, "sophia.error", &size); 245 | 246 | if (!size) { 247 | enif_alloc_binary(0, &err_binary); 248 | } else { 249 | enif_alloc_binary(size-1, &err_binary); 250 | strncpy((char*)err_binary.data, error, size-1); 251 | } 252 | free(error); 253 | if (dispose) { 254 | env_dispose(env, spenv); 255 | enif_release_resource(spenv); 256 | } 257 | return enif_make_tuple2(env, ATOM_ERROR, 258 | enif_make_tuple2(env, enif_make_int(env, code), enif_make_binary(env, &err_binary)) 259 | ); 260 | } 261 | 262 | static ERL_NIF_TERM env_disposed(ErlNifEnv* env) { 263 | return enif_make_tuple2(env, ATOM_ERROR, ATOM_DISPOSED); 264 | } 265 | static ERL_NIF_TERM to_bin(ErlNifEnv* env, void* val, int size) { 266 | ErlNifBinary bvalue; 267 | enif_alloc_binary(size, &bvalue); 268 | memcpy((char*)bvalue.data, val, size); 269 | return enif_make_binary(env, &bvalue); 270 | } 271 | static ERL_NIF_TERM to_int(ErlNifEnv* env, void* val, int size, bool *ok) { 272 | *ok = true; 273 | if (size == sizeof(ErlNifSInt64)) 274 | return enif_make_int64(env, *(ErlNifSInt64*)val); 275 | else if (size == sizeof(int)) 276 | return enif_make_int(env, *(int*)val); 277 | else if (size == sizeof(long)) 278 | return enif_make_long(env, *(long*)val); 279 | else { 280 | *ok = false; 281 | return ATOM_TYPE; 282 | } 283 | } 284 | 285 | 286 | 287 | static ERL_NIF_TERM esphi_print_info(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 288 | sphia_env* spenv; 289 | if (!enif_get_resource(env, argv[0], sphiaEnvResource, (void **)&spenv)) { 290 | return enif_make_badarg(env); 291 | } 292 | if (spenv->disposed == 1) 293 | return env_disposed(env); 294 | 295 | void *cursor = sp_getobject(spenv->env , NULL); 296 | void *ptr = NULL; 297 | while ((ptr = sp_get(cursor, ptr))) { 298 | char *key = (char*)sp_getstring(ptr, "key", NULL); 299 | char *value = (char*)sp_getstring(ptr, "value", NULL); 300 | printf("%s", key); 301 | if (value) 302 | printf(" = %s\r\n", value); 303 | else 304 | printf(" = \r\n"); 305 | } 306 | sp_destroy(cursor); 307 | 308 | return ATOM_OK; 309 | 310 | } 311 | 312 | 313 | 314 | static ERL_NIF_TERM esphi_last_error(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 315 | sphia_env* spenv; 316 | ErlNifBinary err_binary; 317 | int size; 318 | if (!enif_get_resource(env, argv[0], sphiaEnvResource, (void **)&spenv)) { 319 | return enif_make_badarg(env); 320 | } 321 | if (spenv->disposed == 1) 322 | return env_disposed(env); 323 | 324 | 325 | char *error = (char *)sp_getstring(spenv->env, "sophia.error", &size); 326 | if (!size) { 327 | enif_alloc_binary(0, &err_binary); 328 | } else { 329 | enif_alloc_binary(size-1, &err_binary); 330 | strncpy((char*)err_binary.data, error, size-1); 331 | } 332 | free(error); 333 | return enif_make_binary(env, &err_binary); 334 | 335 | 336 | } 337 | 338 | static ERL_NIF_TERM esphi_transaction_commit(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 339 | sphia_tran* tran; 340 | int rc; 341 | if (!enif_get_resource(env, argv[0], sphiaTranResource, (void **)&tran)) { 342 | return enif_make_badarg(env); 343 | } 344 | if (tran->disposed == 1) 345 | return env_disposed(env); 346 | void *p = __sync_lock_test_and_set(&tran->tran, NULL); 347 | rc = sp_commit(p); 348 | switch (rc) { 349 | case -1: 350 | return sofia_error(env, tran->spenv, -1, false); 351 | case 0: 352 | tran_dispose(env, tran); 353 | return ATOM_OK; 354 | case 1: 355 | tran_dispose(env, tran); 356 | return enif_make_tuple2(env, ATOM_ERROR, ATOM_ROLLEDBACK); 357 | case 2: 358 | __sync_lock_test_and_set(&tran->tran, p); 359 | return enif_make_tuple2(env, ATOM_ERROR, ATOM_LOCK); 360 | default: 361 | assert(0); 362 | // tran_dispose(env, tran); 363 | // return enif_make_tuple2(env, ATOM_ERROR, ATOM_UNKNOWN); 364 | 365 | } 366 | } 367 | 368 | static ERL_NIF_TERM esphi_transaction_rollback(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 369 | sphia_tran* tran; 370 | if (!enif_get_resource(env, argv[0], sphiaTranResource, (void **)&tran)) { 371 | return enif_make_badarg(env); 372 | } 373 | tran_dispose(env, tran); 374 | return ATOM_OK; 375 | 376 | } 377 | 378 | static ERL_NIF_TERM esphi_transaction_begin(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 379 | sphia_tran* tran; 380 | sphia_env* spenv; 381 | ERL_NIF_TERM res; 382 | 383 | if (!enif_get_resource(env, argv[0], sphiaEnvResource, (void **)&spenv)) { 384 | return enif_make_badarg(env); 385 | } 386 | if (spenv->disposed == 1) 387 | return env_disposed(env); 388 | 389 | tran = (sphia_tran*)enif_alloc_resource(sphiaTranResource, sizeof(sphia_tran)); 390 | if (tran == NULL) return enif_make_badarg(env); 391 | 392 | tran->tran = sp_begin(spenv->env); 393 | tran->spenv = spenv; 394 | tran->disposed = 0; 395 | 396 | if (tran->tran == NULL) { 397 | enif_release_resource(tran); 398 | return sofia_error(env, spenv, -1, false); 399 | } else { 400 | res = enif_make_resource(env, tran); 401 | enif_release_resource(tran); 402 | enif_keep_resource(spenv); 403 | return enif_make_tuple2(env, ATOM_OK, res); 404 | } 405 | 406 | } 407 | 408 | static ERL_NIF_TERM esphi_cursor_open(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 409 | sphia_db* db; 410 | sphia_cur* cur; 411 | ERL_NIF_TERM res; 412 | ErlNifBinary bprefix; 413 | char order[10]; 414 | ErlNifBinary bkey; 415 | ErlNifSInt64 ikey; 416 | bool is_binary; 417 | bool use_prefix; 418 | int no_prefix; 419 | if (!enif_get_resource(env, argv[0], sphiaDbResource, (void **)&db) || 420 | (!(use_prefix = enif_inspect_iolist_as_binary(env, argv[1], &bprefix)) && !(enif_get_int(env, argv[1], &no_prefix))) || 421 | !enif_get_atom(env, argv[2], order, 10, ERL_NIF_LATIN1) || 422 | (!(is_binary = enif_inspect_iolist_as_binary(env, argv[3], &bkey)) && !enif_get_int64(env, argv[3], &ikey))) { 423 | return enif_make_badarg(env); 424 | } 425 | if (!use_prefix && (no_prefix != 0)) 426 | return enif_make_badarg(env); 427 | 428 | if ((db->disposed == 1) || (db->spenv->disposed == 1)) 429 | return env_disposed(env); 430 | cur = (sphia_cur*)enif_alloc_resource(sphiaCurResource, sizeof(sphia_cur)); 431 | if (cur == NULL) return enif_make_badarg(env); 432 | cur->cur = sp_cursor(db->spenv->env); 433 | 434 | if (cur->cur == NULL) { 435 | enif_release_resource(cur); 436 | return sofia_error(env, db->spenv, -1, false); 437 | } 438 | 439 | cur->o = sp_document(db->db); 440 | if (cur->o == NULL) { 441 | enif_release_resource(cur); 442 | return sofia_error(env, db->spenv, -1, false); 443 | } 444 | sp_setstring(cur->o, "order", order, 0); 445 | if (use_prefix) 446 | sp_setstring(cur->o, "prefix", bprefix.data, bprefix.size); 447 | 448 | if (is_binary && (bkey.size > 0)) 449 | sp_setstring(cur->o, "key", bkey.data, bkey.size); 450 | else if (!is_binary) 451 | sp_setstring(cur->o, "key", &ikey, sizeof(ikey)); 452 | 453 | cur->disposed = 0; 454 | cur->db = db; 455 | 456 | res = enif_make_resource(env, cur); 457 | enif_release_resource(cur); 458 | enif_keep_resource(db); 459 | return enif_make_tuple2(env, ATOM_OK, res); 460 | 461 | } 462 | 463 | static ERL_NIF_TERM esphi_cursor_close(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 464 | sphia_cur* cur; 465 | if (!enif_get_resource(env, argv[0], sphiaCurResource, (void **)&cur)) { 466 | return enif_make_badarg(env); 467 | } 468 | cur_dispose(env, cur); 469 | return ATOM_OK; 470 | } 471 | 472 | static ERL_NIF_TERM esphi_cursor_next(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 473 | sphia_cur* cur; 474 | int key_only; 475 | int res_key_type, res_val_type; 476 | void* o; 477 | ERL_NIF_TERM res_val, res; 478 | 479 | if (!enif_get_resource(env, argv[0], sphiaCurResource, (void **)&cur) || 480 | !enif_get_int(env, argv[1], &key_only) || 481 | !enif_get_int(env, argv[2], &res_key_type) || 482 | !enif_get_int(env, argv[3], &res_val_type)) { 483 | return enif_make_badarg(env); 484 | } 485 | if (((key_only != SOPHIA_KEY_ONLY) && (key_only != SOPHIA_FULL_OBJECT)) || 486 | ((res_key_type != SOPHIA_BINARY) && (res_key_type != SOPHIA_INT)) || 487 | ((res_val_type != SOPHIA_BINARY) && (res_val_type != SOPHIA_INT))) 488 | return enif_make_badarg(env); 489 | if ((cur->disposed == 1) || (cur->db->disposed == 1) || (cur->db->spenv->disposed == 1)) 490 | return env_disposed(env); 491 | o = __sync_lock_test_and_set(&cur->o, NULL); 492 | if (!o) 493 | return ATOM_STOP; 494 | o = sp_get(cur->cur, o); 495 | if (o) { 496 | int size; 497 | bool ok = true; 498 | void *key = sp_getstring(o, "key", &size); 499 | if (res_key_type == SOPHIA_BINARY) { 500 | res = to_bin(env, key, size); 501 | } else { 502 | res = to_int(env, key, size, &ok); 503 | } 504 | if (!ok) 505 | res = enif_make_tuple2(env, ATOM_ERROR, ATOM_TYPE); 506 | else if (key_only == SOPHIA_FULL_OBJECT) { 507 | ok = true; 508 | void *value = sp_getstring(o, "value", &size); 509 | if (res_val_type == SOPHIA_BINARY) { 510 | res_val = to_bin(env, value, size); 511 | } else { 512 | res_val = to_int(env, value, size, &ok); 513 | } 514 | if (!ok) 515 | res = enif_make_tuple2(env, ATOM_ERROR, ATOM_TYPE); 516 | else { 517 | res = enif_make_tuple2(env, res, res_val); 518 | } 519 | } 520 | __sync_lock_test_and_set(&cur->o, o); 521 | 522 | } else { 523 | res = ATOM_STOP; 524 | } 525 | return res; 526 | 527 | } 528 | 529 | static ERL_NIF_TERM esphi_table_close(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 530 | sphia_db* db; 531 | if (!enif_get_resource(env, argv[0], sphiaDbResource, (void **)&db)) { 532 | return enif_make_badarg(env); 533 | } 534 | db_dispose(env, db); 535 | return ATOM_OK; 536 | } 537 | 538 | static ERL_NIF_TERM esphi_table_open(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 539 | ErlNifBinary btable; 540 | sphia_db* db; 541 | sphia_env* spenv; 542 | char* ctable, * ctable0; 543 | ERL_NIF_TERM res; 544 | 545 | if (!enif_get_resource(env, argv[0], sphiaEnvResource, (void **)&spenv)|| 546 | !enif_inspect_iolist_as_binary(env, argv[1], &btable)) { 547 | return enif_make_badarg(env); 548 | } 549 | if (spenv->disposed == 1) 550 | return env_disposed(env); 551 | db = (sphia_db*)enif_alloc_resource(sphiaDbResource, sizeof(sphia_db)); 552 | if (db == NULL) return enif_make_badarg(env); 553 | ctable = bcstr(&btable); 554 | if (!startswith("db.", ctable)) { 555 | ctable0 = join("db.", ctable); 556 | enif_free(ctable); 557 | ctable = ctable0; 558 | } 559 | 560 | 561 | 562 | db->db = sp_getobject(spenv->env, ctable); 563 | enif_free(ctable); 564 | db->spenv = spenv; 565 | db->disposed = 0; 566 | 567 | if (db->db == NULL) { 568 | enif_release_resource(db); 569 | return sofia_error(env, spenv, -1, false); 570 | } else { 571 | res = enif_make_resource(env, db); 572 | enif_release_resource(db); 573 | enif_keep_resource(spenv); 574 | return enif_make_tuple2(env, ATOM_OK, res); 575 | } 576 | } 577 | 578 | static ERL_NIF_TERM esphi_param_get(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 579 | sphia_env* spenv; 580 | ErlNifBinary bkey, bvalue; 581 | char* ckey, * cvalue; 582 | void *val; 583 | ErlNifSInt64 ivalue; 584 | int size; 585 | int res_type; 586 | 587 | if (!enif_get_resource(env, argv[0], sphiaEnvResource, (void **)&spenv) || 588 | !enif_inspect_iolist_as_binary(env, argv[1], &bkey) || 589 | !enif_get_int(env, argv[2], &res_type)) { 590 | return enif_make_badarg(env); 591 | } 592 | if ((res_type != SOPHIA_BINARY) && (res_type != SOPHIA_INT)) 593 | return enif_make_badarg(env); 594 | 595 | if (spenv->disposed == 1) 596 | return env_disposed(env); 597 | 598 | if (res_type == SOPHIA_BINARY) { 599 | ckey = bcstr(&bkey); 600 | val = sp_getstring(spenv->env, ckey, &size); 601 | enif_free(ckey); 602 | if (val == NULL) { 603 | return sofia_error(env, spenv, -1, false); 604 | } else { 605 | cvalue = (char *)val; 606 | enif_alloc_binary(size-1, &bvalue); 607 | strncpy((char*)bvalue.data, cvalue, size-1); 608 | return enif_make_binary(env, &bvalue); 609 | } 610 | } else { 611 | ckey = bcstr(&bkey); 612 | ivalue = sp_getint(spenv->env, ckey); 613 | enif_free(ckey); 614 | if (ivalue == -1) { 615 | return sofia_error(env, spenv, ivalue, false); 616 | } else { 617 | return enif_make_int64(env, ivalue); 618 | } 619 | } 620 | 621 | } 622 | 623 | 624 | static ERL_NIF_TERM esphi_param_set(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 625 | sphia_env* spenv; 626 | ErlNifBinary bkey, bvalue; 627 | ErlNifSInt64 ivalue; 628 | bool is_binary; 629 | char* ckey; 630 | int rc; 631 | 632 | if (!enif_get_resource(env, argv[0], sphiaEnvResource, (void **)&spenv) || 633 | !enif_inspect_iolist_as_binary(env, argv[1], &bkey) || 634 | (!(is_binary = enif_inspect_iolist_as_binary(env, argv[2], &bvalue)) && !enif_get_int64(env, argv[2], &ivalue))) { 635 | return enif_make_badarg(env); 636 | } 637 | if (spenv->disposed == 1) 638 | return env_disposed(env); 639 | ckey = bcstr(&bkey); 640 | if (is_binary) { 641 | rc = sophia_set_sparam(spenv, ckey, &bvalue); 642 | } else { 643 | rc = sp_setint(spenv->env, ckey, ivalue); 644 | } 645 | 646 | enif_free(ckey); 647 | if (rc == -1) { 648 | return sofia_error(env, spenv, rc, false); 649 | } else { 650 | return ATOM_OK; 651 | } 652 | 653 | } 654 | 655 | static ERL_NIF_TERM esphi_put(ErlNifEnv* env, sphia_db* db, void* dbt, bool is_key_binary, 656 | bool is_val_binary, ErlNifBinary* bkey, ErlNifBinary* bval, ErlNifSInt64 ikey, ErlNifSInt64 ival) { 657 | int rc; 658 | 659 | void *o = sp_document(db->db); 660 | if (is_key_binary) { 661 | rc = sp_setstring(o, "key", bkey->data, bkey->size); 662 | } else { 663 | rc = sp_setstring(o, "key", &ikey, sizeof(ikey)); 664 | } 665 | if (rc == -1) { 666 | return sofia_error(env, db->spenv, rc, false); 667 | } 668 | 669 | if (is_val_binary) { 670 | rc = sp_setstring(o, "value", bval->data, bval->size); 671 | } else { 672 | rc = sp_setstring(o, "value", &ival, sizeof(ival)); 673 | } 674 | if (rc == -1) { 675 | return sofia_error(env, db->spenv, rc, false); 676 | } 677 | rc = sp_set(dbt, o); 678 | 679 | if (rc == -1) { 680 | return sofia_error(env, db->spenv, rc, false); 681 | } else { 682 | return ATOM_OK; 683 | } 684 | } 685 | 686 | static ERL_NIF_TERM esphi_put_notran(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 687 | sphia_db* db; 688 | ErlNifBinary bkey, bval; 689 | ErlNifSInt64 ikey, ival; 690 | bool is_key_binary, is_val_binary; 691 | 692 | if (!enif_get_resource(env, argv[0], sphiaDbResource, (void **)&db) || 693 | (!(is_key_binary = enif_inspect_iolist_as_binary(env, argv[1], &bkey)) && !enif_get_int64(env, argv[1], &ikey)) || 694 | (!(is_val_binary = enif_inspect_iolist_as_binary(env, argv[2], &bval)) && !enif_get_int64(env, argv[2], &ival))) { 695 | return enif_make_badarg(env); 696 | } 697 | if (db->disposed == 1) 698 | return env_disposed(env); 699 | 700 | return esphi_put(env, db, db->db, is_key_binary, is_val_binary, &bkey, &bval, ikey, ival); 701 | 702 | } 703 | 704 | static ERL_NIF_TERM esphi_put_tran(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 705 | sphia_db* db; 706 | sphia_tran* tran; 707 | ErlNifBinary bkey, bval; 708 | ErlNifSInt64 ikey, ival; 709 | bool is_key_binary, is_val_binary; 710 | 711 | if (!enif_get_resource(env, argv[0], sphiaDbResource, (void **)&db) || 712 | !enif_get_resource(env, argv[1], sphiaTranResource, (void **)&tran) || 713 | (!(is_key_binary = enif_inspect_iolist_as_binary(env, argv[2], &bkey)) && !enif_get_int64(env, argv[2], &ikey)) || 714 | (!(is_val_binary = enif_inspect_iolist_as_binary(env, argv[3], &bval)) && !enif_get_int64(env, argv[3], &ival))) { 715 | return enif_make_badarg(env); 716 | } 717 | if ((db->disposed == 1) || (tran->disposed == 1)) 718 | return env_disposed(env); 719 | 720 | return esphi_put(env, db, tran->tran, is_key_binary, is_val_binary, &bkey, &bval, ikey, ival); 721 | 722 | } 723 | 724 | static ERL_NIF_TERM esphi_update_counter(ErlNifEnv* env, sphia_db* db, void* dbt, 725 | bool is_key_binary, ErlNifBinary* bkey, ErlNifSInt64 ikey, ErlNifSInt64 ival) { 726 | int rc; 727 | void *o = sp_document(db->db); 728 | if (is_key_binary) { 729 | rc = sp_setstring(o, "key", bkey->data, bkey->size); 730 | } else { 731 | rc = sp_setstring(o, "key", &ikey, sizeof(ikey)); 732 | } 733 | if (rc == -1) { 734 | return sofia_error(env, db->spenv, rc, false); 735 | } 736 | 737 | rc = sp_setstring(o, "value", &ival, sizeof(ival)); 738 | 739 | if (rc == -1) { 740 | return sofia_error(env, db->spenv, rc, false); 741 | } 742 | 743 | rc = sp_upsert(dbt, o); 744 | 745 | if (rc == -1) { 746 | return sofia_error(env, db->spenv, rc, false); 747 | } else { 748 | return ATOM_OK; 749 | } 750 | 751 | } 752 | 753 | static ERL_NIF_TERM esphi_update_counter_notran(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 754 | sphia_db* db; 755 | ErlNifBinary bkey; 756 | ErlNifSInt64 ikey, ival; 757 | bool is_key_binary; 758 | 759 | if (!enif_get_resource(env, argv[0], sphiaDbResource, (void **)&db) || 760 | (!(is_key_binary = enif_inspect_iolist_as_binary(env, argv[1], &bkey)) && !enif_get_int64(env, argv[1], &ikey)) || 761 | !enif_get_int64(env, argv[2], &ival)) { 762 | return enif_make_badarg(env); 763 | } 764 | if (db->disposed == 1) 765 | return env_disposed(env); 766 | return esphi_update_counter(env, db, db->db, is_key_binary, &bkey, ikey, ival); 767 | 768 | } 769 | 770 | static ERL_NIF_TERM esphi_update_counter_tran(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 771 | sphia_db* db; 772 | sphia_tran* tran; 773 | ErlNifBinary bkey; 774 | ErlNifSInt64 ikey, ival; 775 | bool is_key_binary; 776 | 777 | if (!enif_get_resource(env, argv[0], sphiaDbResource, (void **)&db) || 778 | !enif_get_resource(env, argv[1], sphiaTranResource, (void **)&tran) || 779 | (!(is_key_binary = enif_inspect_iolist_as_binary(env, argv[2], &bkey)) && !enif_get_int64(env, argv[2], &ikey)) || 780 | !enif_get_int64(env, argv[3], &ival)) { 781 | return enif_make_badarg(env); 782 | } 783 | if ((db->disposed == 1) || (tran->disposed == 1)) 784 | return env_disposed(env); 785 | return esphi_update_counter(env, db, tran->tran, is_key_binary, &bkey, ikey, ival); 786 | } 787 | 788 | static ERL_NIF_TERM esphi_get(ErlNifEnv* env, sphia_db* db, void* dbt, bool is_binary, ErlNifBinary* bkey, 789 | ErlNifSInt64 ikey, int res_type) { 790 | int rc; 791 | ERL_NIF_TERM res; 792 | 793 | void *o = sp_document(db->db); 794 | if (is_binary) { 795 | rc = sp_setstring(o, "key", bkey->data, bkey->size); 796 | } else { 797 | rc = sp_setstring(o, "key", &ikey, sizeof(ikey)); 798 | } 799 | if (rc == -1) { 800 | return sofia_error(env, db->spenv, rc, false); 801 | } 802 | 803 | o = sp_get(dbt, o); 804 | if (o) { 805 | 806 | int size; 807 | bool ok = true; 808 | 809 | void *value = sp_getstring(o, "value", &size); 810 | if (res_type == SOPHIA_BINARY) { 811 | res = to_bin(env, value, size); 812 | } else { 813 | res = to_int(env, value, size, &ok); 814 | } 815 | sp_destroy(o); 816 | if (ok) 817 | return res; 818 | else 819 | return enif_make_tuple2(env, ATOM_ERROR, ATOM_TYPE); 820 | } else { 821 | return ATOM_NOT_FOUND; 822 | } 823 | 824 | } 825 | 826 | static ERL_NIF_TERM esphi_get_notran(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 827 | sphia_db* db; 828 | ErlNifBinary bkey; 829 | ErlNifSInt64 ikey; 830 | bool is_binary; 831 | int res_type; 832 | if (!enif_get_resource(env, argv[0], sphiaDbResource, (void **)&db) || 833 | (!(is_binary = enif_inspect_iolist_as_binary(env, argv[1], &bkey)) && !enif_get_int64(env, argv[1], &ikey)) || 834 | !enif_get_int(env, argv[2], &res_type)) { 835 | return enif_make_badarg(env); 836 | } 837 | if ((res_type != SOPHIA_BINARY) && (res_type != SOPHIA_INT)) 838 | return enif_make_badarg(env); 839 | 840 | if (db->disposed == 1) 841 | return env_disposed(env); 842 | return esphi_get(env, db, db->db, is_binary, &bkey, ikey, res_type); 843 | } 844 | 845 | static ERL_NIF_TERM esphi_get_tran(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 846 | sphia_db* db; 847 | sphia_tran* tran; 848 | ErlNifBinary bkey; 849 | ErlNifSInt64 ikey; 850 | bool is_binary; 851 | int res_type; 852 | if (!enif_get_resource(env, argv[0], sphiaDbResource, (void **)&db) || 853 | !enif_get_resource(env, argv[1], sphiaTranResource, (void **)&tran) || 854 | (!(is_binary = enif_inspect_iolist_as_binary(env, argv[2], &bkey)) && !enif_get_int64(env, argv[2], &ikey)) || 855 | !enif_get_int(env, argv[3], &res_type)) { 856 | return enif_make_badarg(env); 857 | } 858 | if ((res_type != SOPHIA_BINARY) && (res_type != SOPHIA_INT)) 859 | return enif_make_badarg(env); 860 | 861 | if ((db->disposed == 1) || (tran->disposed == 1)) 862 | return env_disposed(env); 863 | return esphi_get(env, db, tran->tran, is_binary, &bkey, ikey, res_type); 864 | } 865 | 866 | static ERL_NIF_TERM esphi_delete(ErlNifEnv* env, sphia_db* db, void* dbt, bool is_binary, ErlNifBinary* bkey, ErlNifSInt64 ikey){ 867 | int rc; 868 | void *o = sp_document(db->db); 869 | if (is_binary) { 870 | rc = sp_setstring(o, "key", bkey->data, bkey->size); 871 | } else { 872 | rc = sp_setstring(o, "key", &ikey, sizeof(ikey)); 873 | } 874 | if (rc == -1) { 875 | return sofia_error(env, db->spenv, rc, false); 876 | } 877 | rc = sp_delete(dbt, o); 878 | if (rc == -1) { 879 | return sofia_error(env, db->spenv, rc, false); 880 | } else { 881 | return ATOM_OK; 882 | } 883 | } 884 | 885 | static ERL_NIF_TERM esphi_delete_notran(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 886 | sphia_db* db; 887 | ErlNifBinary bkey; 888 | ErlNifSInt64 ikey; 889 | bool is_binary; 890 | if (!enif_get_resource(env, argv[0], sphiaDbResource, (void **)&db) || 891 | (!(is_binary = enif_inspect_iolist_as_binary(env, argv[1], &bkey)) && !enif_get_int64(env, argv[1], &ikey))) { 892 | return enif_make_badarg(env); 893 | } 894 | if (db->disposed == 1) 895 | return env_disposed(env); 896 | return esphi_delete(env, db, db->db, is_binary, &bkey, ikey); 897 | } 898 | 899 | static ERL_NIF_TERM esphi_delete_tran(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 900 | sphia_db* db; 901 | sphia_tran* tran; 902 | ErlNifBinary bkey; 903 | ErlNifSInt64 ikey; 904 | bool is_binary; 905 | if (!enif_get_resource(env, argv[0], sphiaDbResource, (void **)&db) || 906 | !enif_get_resource(env, argv[1], sphiaTranResource, (void **)&tran) || 907 | (!(is_binary = enif_inspect_iolist_as_binary(env, argv[2], &bkey)) && !enif_get_int64(env, argv[2], &ikey))) { 908 | return enif_make_badarg(env); 909 | } 910 | if ((db->disposed == 1) || (tran->disposed == 1)) 911 | return env_disposed(env); 912 | return esphi_delete(env, db, tran->tran, is_binary, &bkey, ikey); 913 | } 914 | 915 | static ERL_NIF_TERM esphi_backup(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 916 | sphia_env* spenv; 917 | int rc; 918 | if (!enif_get_resource(env, argv[0], sphiaEnvResource, (void **)&spenv)) { 919 | return enif_make_badarg(env); 920 | } 921 | if (spenv->disposed == 1) 922 | return env_disposed(env); 923 | 924 | rc = sp_setint(spenv->env, "backup.run", 0); 925 | if (rc == -1) { 926 | return sofia_error(env, spenv, rc, false); 927 | } else { 928 | return ATOM_OK; 929 | } 930 | 931 | } 932 | 933 | 934 | static ERL_NIF_TERM esphi_table_create(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 935 | sphia_env* spenv; 936 | int rc; 937 | size_t mlen; 938 | char* cname; 939 | ErlNifBinary dbname; 940 | 941 | 942 | if (!enif_get_resource(env, argv[0], sphiaEnvResource, (void **)&spenv) || 943 | !enif_inspect_iolist_as_binary(env, argv[1], &dbname) || 944 | !enif_get_map_size(env, argv[2], &mlen) ) { 945 | return enif_make_badarg(env); 946 | } 947 | if (!sophia_validate_map(env, argv[1])) { 948 | return enif_make_badarg(env); 949 | } 950 | 951 | if (spenv->disposed == 1) 952 | return env_disposed(env); 953 | 954 | cname = bcstr(&dbname); 955 | rc = sp_setstring(spenv->env, "db", cname, 0); 956 | enif_free(cname); 957 | if (rc == -1) { 958 | return sofia_error(env, spenv, rc, false); 959 | } 960 | 961 | rc = sophia_apply_params(env, spenv, argv[2]); 962 | if (rc == -1) { 963 | return sofia_error(env, spenv, rc, false); 964 | } else { 965 | return ATOM_OK; 966 | } 967 | } 968 | 969 | static ERL_NIF_TERM esphi_table_drop(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 970 | sphia_db* db; 971 | int rc; 972 | if (!enif_get_resource(env, argv[0], sphiaDbResource, (void **)&db)) { 973 | return enif_make_badarg(env); 974 | } 975 | if (db->disposed == 1) 976 | return env_disposed(env); 977 | rc = sp_drop(db->db); 978 | if (rc == -1) { 979 | return sofia_error(env, db->spenv, rc, false); 980 | } else { 981 | return ATOM_OK; 982 | } 983 | } 984 | 985 | 986 | 987 | static ERL_NIF_TERM esphi_db_open(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 988 | ErlNifBinary path; 989 | unsigned llen; 990 | size_t mlen; 991 | ERL_NIF_TERM h, t; 992 | ErlNifBinary dbname; 993 | sphia_env* spenv; 994 | ERL_NIF_TERM res; 995 | 996 | char* cvalue; 997 | char* cpath; 998 | 999 | int rc; 1000 | 1001 | 1002 | 1003 | if (!enif_inspect_iolist_as_binary(env, argv[0], &path) || 1004 | !enif_get_list_length(env, argv[1], &llen) || 1005 | !enif_get_map_size(env, argv[2], &mlen) ) { 1006 | return enif_make_badarg(env); 1007 | } 1008 | 1009 | //validate db names, must be iolist each 1010 | ERL_NIF_TERM p = argv[1]; 1011 | while (enif_get_list_cell(env, p, &h, &t)) { 1012 | if (!enif_inspect_iolist_as_binary(env, h, &dbname)) { 1013 | return enif_make_badarg(env); 1014 | } 1015 | p = t; 1016 | } 1017 | 1018 | //validate params, must be iolist=>iolist|int each 1019 | if (!sophia_validate_map(env, argv[1])) { 1020 | return enif_make_badarg(env); 1021 | } 1022 | 1023 | spenv = (sphia_env*)enif_alloc_resource(sphiaEnvResource, sizeof(sphia_env)); 1024 | if (spenv == NULL) return enif_make_badarg(env); 1025 | 1026 | spenv->env = sp_env(); 1027 | spenv->disposed = 0; 1028 | 1029 | //path to the env 1030 | cpath = bcstr(&path); 1031 | rc = sp_setstring(spenv->env, "sophia.path", cpath, 0); 1032 | enif_free(cpath); 1033 | if (rc == -1){ 1034 | return sofia_error(env, spenv, rc, true); 1035 | } 1036 | 1037 | //databases 1038 | p = argv[1]; 1039 | while (enif_get_list_cell(env, p, &h, &t)) { 1040 | enif_inspect_iolist_as_binary(env, h, &dbname); 1041 | cvalue = bcstr(&dbname); 1042 | rc = sp_setstring(spenv->env, "db", cvalue, 0); 1043 | enif_free(cvalue); 1044 | if (rc == -1) { 1045 | return sofia_error(env, spenv, rc, true); 1046 | } 1047 | p = t; 1048 | } 1049 | 1050 | //params 1051 | rc = sophia_apply_params(env, spenv, argv[2]); 1052 | if (rc == -1){ 1053 | return sofia_error(env, spenv, rc, true); 1054 | } 1055 | 1056 | rc = sp_open(spenv->env); 1057 | if (rc == -1){ 1058 | return sofia_error(env, spenv, rc, true); 1059 | } 1060 | 1061 | 1062 | res = enif_make_resource(env, spenv); 1063 | enif_release_resource(spenv); 1064 | 1065 | return enif_make_tuple2(env, ATOM_OK, res); 1066 | 1067 | } 1068 | 1069 | static ERL_NIF_TERM esphi_db_close(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { 1070 | sphia_env* spenv; 1071 | if (!enif_get_resource(env, argv[0], sphiaEnvResource, (void **)&spenv)) { 1072 | return enif_make_badarg(env); 1073 | } 1074 | env_dispose(env, spenv); 1075 | return ATOM_OK; 1076 | 1077 | } 1078 | 1079 | 1080 | static void on_unload(ErlNifEnv *env, void *priv_data) 1081 | { 1082 | 1083 | } 1084 | 1085 | 1086 | static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) { 1087 | int ret_val = 0; 1088 | sphiaEnvResource = enif_open_resource_type(env, NULL, "sphiaEnvResource", &env_dispose_, ERL_NIF_RT_CREATE, NULL); 1089 | if(sphiaEnvResource == NULL) return -1; 1090 | 1091 | sphiaDbResource = enif_open_resource_type(env, NULL, "sphiaDbResource", &db_dispose_, ERL_NIF_RT_CREATE, NULL); 1092 | if(sphiaDbResource == NULL) return -1; 1093 | 1094 | sphiaTranResource = enif_open_resource_type(env, NULL, "sphiaTranResource", &tran_dispose_, ERL_NIF_RT_CREATE, NULL); 1095 | if(sphiaTranResource == NULL) return -1; 1096 | 1097 | sphiaCurResource = enif_open_resource_type(env, NULL, "sphiaCurResource", &cur_dispose_, ERL_NIF_RT_CREATE, NULL); 1098 | if(sphiaCurResource == NULL) return -1; 1099 | 1100 | 1101 | #define ATOM(Id, Value) { Id = enif_make_atom(env, Value); } 1102 | ATOM(ATOM_OK, "ok"); 1103 | ATOM(ATOM_ERROR, "error"); 1104 | ATOM(ATOM_TRUE, "true"); 1105 | ATOM(ATOM_FALSE, "false"); 1106 | ATOM(ATOM_INVALID_REF, "invalid_ref"); 1107 | 1108 | 1109 | ATOM(ATOM_NOT_FOUND, "not_found"); 1110 | 1111 | ATOM(ATOM_STOP, "stop"); 1112 | ATOM(ATOM_ROLLEDBACK, "rolled_back"); 1113 | ATOM(ATOM_LOCK, "lock"); 1114 | ATOM(ATOM_UNKNOWN, "unknown"); 1115 | ATOM(ATOM_TYPE, "type"); 1116 | 1117 | ATOM(ATOM_DISPOSED, "disposed"); 1118 | 1119 | 1120 | #undef ATOM 1121 | 1122 | 1123 | return ret_val; 1124 | } 1125 | 1126 | 1127 | static ErlNifFunc nif_funcs[] = 1128 | { 1129 | {"esphi_db_open", 3, esphi_db_open, ERL_NIF_DIRTY_JOB_IO_BOUND}, 1130 | {"esphi_db_close", 1, esphi_db_close}, 1131 | 1132 | {"esphi_param_set", 3, esphi_param_set}, 1133 | {"esphi_param_get", 3, esphi_param_get}, 1134 | {"esphi_print_info", 1, esphi_print_info}, 1135 | {"esphi_last_error", 1, esphi_last_error}, 1136 | 1137 | {"esphi_table_open", 2, esphi_table_open, ERL_NIF_DIRTY_JOB_IO_BOUND}, 1138 | {"esphi_table_close", 1, esphi_table_close}, 1139 | {"esphi_table_create", 3, esphi_table_create}, 1140 | {"esphi_table_drop", 1, esphi_table_drop, ERL_NIF_DIRTY_JOB_IO_BOUND}, 1141 | 1142 | {"esphi_transaction_begin", 1, esphi_transaction_begin}, 1143 | {"esphi_transaction_commit", 1, esphi_transaction_commit, ERL_NIF_DIRTY_JOB_IO_BOUND}, 1144 | {"esphi_transaction_rollback", 1, esphi_transaction_rollback}, 1145 | 1146 | {"esphi_delete", 2, esphi_delete_notran, ERL_NIF_DIRTY_JOB_IO_BOUND}, 1147 | {"esphi_delete", 3, esphi_delete_tran, ERL_NIF_DIRTY_JOB_IO_BOUND}, 1148 | 1149 | {"esphi_put", 3, esphi_put_notran, ERL_NIF_DIRTY_JOB_IO_BOUND}, 1150 | {"esphi_put", 4, esphi_put_tran, ERL_NIF_DIRTY_JOB_IO_BOUND}, 1151 | 1152 | {"esphi_update_counter", 3, esphi_update_counter_notran, ERL_NIF_DIRTY_JOB_IO_BOUND}, 1153 | {"esphi_update_counter", 4, esphi_update_counter_tran, ERL_NIF_DIRTY_JOB_IO_BOUND}, 1154 | 1155 | 1156 | {"esphi_get", 3, esphi_get_notran, ERL_NIF_DIRTY_JOB_IO_BOUND}, 1157 | {"esphi_get", 4, esphi_get_tran, ERL_NIF_DIRTY_JOB_IO_BOUND}, 1158 | 1159 | {"esphi_cursor_open", 4, esphi_cursor_open, ERL_NIF_DIRTY_JOB_IO_BOUND}, 1160 | {"esphi_cursor_close", 1, esphi_cursor_close}, 1161 | {"esphi_cursor_next", 4, esphi_cursor_next, ERL_NIF_DIRTY_JOB_IO_BOUND}, 1162 | 1163 | 1164 | 1165 | {"esphi_backup", 1, esphi_backup} 1166 | 1167 | }; 1168 | 1169 | 1170 | ERL_NIF_INIT(esphi, nif_funcs, &on_load, NULL, NULL, &on_unload) 1171 | 1172 | -------------------------------------------------------------------------------- /c_src/esphi.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ifndef ESPHI_H_ 4 | #define ESPHI_H_ 5 | //#define ERL_NIF_DIRTY_SCHEDULER_SUPPORT 6 | #include 7 | #include 8 | //#include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "erl_nif.h" 15 | #include "sophia/sophia.h" 16 | 17 | #define SOPHIA_BINARY 0 18 | #define SOPHIA_INT 1 19 | 20 | #define SOPHIA_KEY_ONLY 0 21 | #define SOPHIA_FULL_OBJECT 1 22 | 23 | #endif -------------------------------------------------------------------------------- /examples/example1/Makefile: -------------------------------------------------------------------------------- 1 | PROJECT = example1 2 | DEPS = esphi 3 | NO_AUTOPATCH = esphi 4 | dep_esphi = git https://github.com/brigadier/esphi master 5 | #export ERL_TOP=~/.kerl/builds/18.2/otp_src_18.2/ 6 | #SHELL_ERL=$(ERL_TOP)/bin/cerl -debug 7 | include erlang.mk 8 | -------------------------------------------------------------------------------- /examples/example1/README.md: -------------------------------------------------------------------------------- 1 | 1. Build Erlang 18 or newer with the `--enable-dirty-schedulers` configuration flag. 2 | 2. $ make && make shell 3 | 3. example1:start(1). %%where 1 - 1 minute to run the test -------------------------------------------------------------------------------- /examples/example1/src/example1.erl: -------------------------------------------------------------------------------- 1 | -module(example1). 2 | 3 | %% API 4 | -export([start/1]). 5 | 6 | start(N) -> 7 | application:start(esphi), 8 | application:load(example1), 9 | application:set_env(example1, time, N * 60 * 1000), 10 | application:start(example1). 11 | -------------------------------------------------------------------------------- /examples/example1/src/example1_app.erl: -------------------------------------------------------------------------------- 1 | -module(example1_app). 2 | -behaviour(application). 3 | 4 | -export([start/2]). 5 | -export([stop/1]). 6 | 7 | start(_Type, _Args) -> 8 | example1_sup:start_link(). 9 | 10 | stop(_State) -> 11 | ok. 12 | -------------------------------------------------------------------------------- /examples/example1/src/example1_dbowner.erl: -------------------------------------------------------------------------------- 1 | -module(example1_dbowner). 2 | 3 | -behaviour(gen_server). 4 | 5 | %% API 6 | -export([start_link/0, pong/0]). 7 | 8 | %% gen_server callbacks 9 | -export([init/1, 10 | handle_call/3, 11 | handle_cast/2, 12 | handle_info/2, 13 | terminate/2, 14 | code_change/3]). 15 | 16 | -define(SERVER, ?MODULE). 17 | 18 | -define(REOPEN, 30000). 19 | -define(NWORKERS, 1000). 20 | 21 | -record(state, {sec = 0, db = undefined, t1 = undefined, t2 = undefined, t3 = undefined}). 22 | 23 | 24 | %%%=================================================================== 25 | %%% API 26 | %%%=================================================================== 27 | 28 | start_link() -> 29 | gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). 30 | 31 | pong() -> 32 | gen_server:cast(?SERVER, pong). 33 | 34 | %%%=================================================================== 35 | %%% gen_server callbacks 36 | %%%=================================================================== 37 | 38 | 39 | init([]) -> 40 | erlang:display(init), 41 | random:seed(erlang:unique_integer()), 42 | {ok, Time} = application:get_env(example1, time), 43 | erlang:send_after(Time, self(), quit), 44 | erlang:send_after(0, self(), dbopen), 45 | erlang:send_after(1, self(), start_workers), 46 | erlang:send_after(0, self(), tick), 47 | 48 | {ok, #state{}}. 49 | 50 | 51 | 52 | handle_call(_Request, _From, State) -> 53 | {reply, ok, State}. 54 | 55 | 56 | handle_cast(pong, #state{db = Db, t1 = T1, t2 = T2, t3 = T3} = State) -> 57 | {ok, _} = supervisor:start_child(example1_workers_sup, [self(), Db, T1, T2, T3]), 58 | {noreply, State}; 59 | handle_cast(_Request, State) -> 60 | {noreply, State}. 61 | 62 | handle_info(start_workers, #state{db = Db, t1 = T1, t2 = T2, t3 = T3} = State) -> 63 | erlang:display(start_workers), 64 | lists:foreach( 65 | fun(_) -> 66 | {ok, _} = supervisor:start_child(example1_workers_sup, [self(), Db, T1, T2, T3]) 67 | end, 68 | 69 | lists:seq(1, ?NWORKERS) 70 | ), 71 | {noreply, State}; 72 | 73 | handle_info(dbopen, #state{db = Db, t1 = T1, t2 = T2, t3 = T3} = State) -> 74 | erlang:display(dbopen), 75 | if 76 | T1 =/= undefined -> esphi:esphi_table_close(T1); 77 | true -> ok 78 | end, 79 | if 80 | T2 =/= undefined -> esphi:esphi_table_close(T2); 81 | true -> ok 82 | end, 83 | if 84 | T3 =/= undefined -> esphi:esphi_table_close(T3); 85 | true -> ok 86 | end, 87 | if 88 | Db =/= undefined -> esphi:esphi_db_close(Db); 89 | true -> ok 90 | end, 91 | 92 | {ok, DBN} = esphi:esphi_db_open( 93 | "/tmp/db", 94 | ["t1", "t2", "t3"], 95 | #{"db.t2.index.key" => "u64", 96 | "db.t3.index.upsert" => "__COUNTER__", 97 | "backup.path" => "/tmp/backup", 98 | "sophia.recover" => 1 99 | }), 100 | {ok, T1N} = esphi:esphi_table_open(DBN, "t1"), 101 | {ok, T2N} = esphi:esphi_table_open(DBN, "t2"), 102 | {ok, T3N} = esphi:esphi_table_open(DBN, "t3"), 103 | erlang:display(open), 104 | %%this would likely damage the database 105 | %% erlang:send_after(random:uniform(?REOPEN), self(), dbopen), 106 | 107 | {noreply, State#state{db = DBN, t1 = T1N, t2 = T2N, t3 = T3N}}; 108 | 109 | handle_info(quit, #state{db = Db} = State) -> 110 | io:format("Qutting...~n"), 111 | ok = esphi:esphi_db_close(Db), 112 | {stop, normal, State}; 113 | 114 | handle_info(tick, #state{sec = Sec} = State) -> 115 | io:format("\r~B \r", [Sec]), 116 | erlang:send_after(1000, self(), tick), 117 | {noreply, State#state{sec = Sec + 1}}; 118 | 119 | 120 | handle_info(_Info, State) -> 121 | {noreply, State}. 122 | 123 | 124 | 125 | terminate(_Reason, _State) -> 126 | io:format("Done, bye~n"), 127 | ok. 128 | 129 | 130 | 131 | code_change(_OldVsn, State, _Extra) -> 132 | {ok, State}. 133 | 134 | %%%=================================================================== 135 | %%% Internal functions 136 | %%%=================================================================== 137 | -------------------------------------------------------------------------------- /examples/example1/src/example1_sup.erl: -------------------------------------------------------------------------------- 1 | -module(example1_sup). 2 | -behaviour(supervisor). 3 | 4 | -export([start_link/0]). 5 | -export([init/1]). 6 | 7 | start_link() -> 8 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 9 | 10 | init([]) -> 11 | Procs = [ 12 | #{id => example1_workers_sup, 13 | start => {example1_workers_sup, start_link, []}, 14 | restart => transient, 15 | shutdown => brutal_kill, 16 | type => supervisor, 17 | modules => [example1_workers_sup]}, 18 | #{id => example1_dbowner, 19 | start => {example1_dbowner, start_link, []}, 20 | restart => transient, 21 | shutdown => brutal_kill, 22 | type => worker, 23 | modules => [example1_dbowner]} 24 | ], 25 | {ok, {{one_for_one, 1, 5}, Procs}}. 26 | -------------------------------------------------------------------------------- /examples/example1/src/example1_worker.erl: -------------------------------------------------------------------------------- 1 | -module(example1_worker). 2 | -compile(nowarn_unused_function). 3 | -compile(nowarn_unused_vars). 4 | -include_lib("esphi/include/esphi.hrl"). 5 | -behaviour(gen_server). 6 | 7 | %% API 8 | -export([start_link/5]). 9 | 10 | %% gen_server callbacks 11 | -export([init/1, 12 | handle_call/3, 13 | handle_cast/2, 14 | handle_info/2, 15 | terminate/2, 16 | code_change/3]). 17 | 18 | -define(SERVER, ?MODULE). 19 | 20 | -record(state, {disp_pid, db = undefined, t1 = undefined, t2 = undefined, t3 = undefined}). 21 | 22 | %%%=================================================================== 23 | %%% API 24 | %%%=================================================================== 25 | 26 | start_link(Disp, Db, T1, T2, T3) -> 27 | gen_server:start_link(?MODULE, [Disp, Db, T1, T2, T3], []). 28 | 29 | %%%=================================================================== 30 | %%% gen_server callbacks 31 | %%%=================================================================== 32 | 33 | 34 | init([Disp, Db, T1, T2, T3]) -> 35 | random:seed(erlang:unique_integer()), 36 | gen_server:cast(self(), go), 37 | {ok, #state{disp_pid = Disp, db = Db, t1 = T1, t2 = T2, t3 = T3}}. 38 | 39 | 40 | 41 | 42 | handle_call(_Request, _From, State) -> 43 | {reply, ok, State}. 44 | 45 | 46 | handle_cast(go, #state{db = Db, t1 = T1, t2 = T2} = State) -> 47 | %% donothing(), 48 | test_crud(Db, T1, T2), 49 | test_tran(Db, T1, T2), 50 | test_cursor(Db, T1, T2), 51 | %% test_rollbacks(Db, T1, T2), 52 | %% test_locks(Db, T1, T2), 53 | 54 | 55 | {stop, normal, State}; 56 | handle_cast(_Request, State) -> 57 | {noreply, State}. 58 | 59 | 60 | 61 | handle_info(_Info, State) -> 62 | {noreply, State}. 63 | 64 | 65 | 66 | terminate(_Reason, _State) -> 67 | example1_dbowner:pong(), 68 | ok. 69 | 70 | 71 | 72 | code_change(_OldVsn, State, _Extra) -> 73 | {ok, State}. 74 | 75 | %%%=================================================================== 76 | %%% Internal functions 77 | %%%=================================================================== 78 | randbin() -> 79 | float_to_binary(random:uniform()). 80 | randint() -> 81 | random:uniform(100000000). 82 | randord() -> 83 | element(random:uniform(4), {'>', '>=', '<', '<='}). 84 | randbin(N) -> 85 | <> = randbin(), 86 | R. 87 | 88 | donothing() -> 89 | timer:sleep(500). 90 | 91 | test_cursor(_Db, T1, _T2) -> 92 | case esphi:esphi_cursor_open(T1, randbin(3), randord(), randbin(4)) of 93 | {ok, C} -> 94 | _R1 = esphi:esphi_cursor_next(C, ?SOPHIA_FULL_OBJECT, ?SOPHIA_BINARY, ?SOPHIA_BINARY), 95 | %% io:format("~p~n", [R1]), 96 | _R2 = esphi:esphi_cursor_next(C, ?SOPHIA_FULL_OBJECT, ?SOPHIA_BINARY, ?SOPHIA_BINARY), 97 | %% io:format("~p~n", [R2]), 98 | esphi:esphi_cursor_close(C); 99 | _E -> ok 100 | %% io:format("~p~n", [E]) 101 | 102 | end. 103 | 104 | test_crud(_Db, T1, T2) -> 105 | Randi = randint(), 106 | Randb2 = randbin(), 107 | Randb = randbin(), 108 | 109 | ok = esphi:esphi_put(T2, randint(), randbin()), 110 | ok = esphi:esphi_put(T1, randbin(), randbin()), 111 | 112 | ok = esphi:esphi_put(T1, Randb, randbin()), 113 | ok = esphi:esphi_put(T2, Randi, randbin()), 114 | ok = esphi:esphi_put(T1, Randb2, randint()), 115 | 116 | 117 | _R1 = esphi:esphi_get(T2, randint(), ?SOPHIA_BINARY), 118 | _R2 = esphi:esphi_get(T1, randbin(), ?SOPHIA_BINARY), 119 | 120 | %%can be not_found, as the data can be deleted from another process 121 | %%remove esphi_delete call to prevent not_found 122 | V1 = esphi:esphi_get(T1, Randb, ?SOPHIA_BINARY), 123 | true = is_binary(V1) orelse is_tuple(V1) orelse V1 == not_found, 124 | V2 = esphi:esphi_get(T2, Randi, ?SOPHIA_BINARY), 125 | true = is_binary(V2) orelse is_tuple(V2) orelse V2 == not_found, 126 | V3 = esphi:esphi_get(T1, Randb2, ?SOPHIA_INT), 127 | true = is_integer(V3) orelse is_tuple(V3) orelse V3 == not_found, 128 | esphi:esphi_delete(T1, Randb2). 129 | test_tran(Db, T1, T2) -> 130 | case esphi:esphi_transaction_begin(Db) of 131 | {ok, Tr} -> 132 | esphi:esphi_put(T2, Tr, randint(), randbin()), 133 | esphi:esphi_put(T1, Tr, randbin(), randbin()), 134 | case esphi:esphi_transaction_commit(Tr) of 135 | ok -> ok; 136 | {error, lock} -> esphi:esphi_transaction_rollback(Tr); 137 | {error, rolled_back} -> ok; 138 | {error, disposed} -> ok 139 | end; 140 | {error, _} -> ok 141 | 142 | end, 143 | case esphi:esphi_transaction_begin(Db) of 144 | {ok, Tr2} -> 145 | esphi:esphi_put(T2, Tr2, randint(), randbin()), 146 | esphi:esphi_put(T1, Tr2, randbin(), randbin()), 147 | esphi:esphi_transaction_rollback(Tr2); 148 | {error, _} -> ok 149 | 150 | end. 151 | 152 | 153 | 154 | test_rollbacks(Db, _T1, T2) -> 155 | case esphi:esphi_transaction_begin(Db) of 156 | {ok, Tr3} -> 157 | esphi:esphi_put(T2, Tr3, 100, randbin()), 158 | timer:sleep(500), 159 | case esphi:esphi_transaction_commit(Tr3) of 160 | ok -> ok; 161 | {error, lock} -> esphi:esphi_transaction_rollback(Tr3); 162 | {error, rolled_back} -> ok; 163 | {error, disposed} -> ok; 164 | {error, busy} -> ok 165 | end; 166 | {error, _} -> ok 167 | 168 | end. 169 | 170 | test_locks(Db, _T1, T2) -> 171 | case esphi:esphi_transaction_begin(Db) of 172 | {ok, Tr4} -> 173 | Rnd = rand:uniform(), 174 | if 175 | Rnd >= 0.5 -> 176 | esphi:esphi_put(T2, Tr4, 10, randbin()), 177 | esphi:esphi_put(T2, Tr4, 100, randbin()); 178 | true -> 179 | esphi:esphi_put(T2, Tr4, 100, randbin()), 180 | esphi:esphi_put(T2, Tr4, 10, randbin()) 181 | end, 182 | 183 | case esphi:esphi_transaction_commit(Tr4) of 184 | ok -> ok; 185 | {error, lock} -> 186 | io:format("LOCK~n"), 187 | esphi:esphi_transaction_rollback(Tr4); 188 | {error, rolled_back} -> ok; 189 | {error, disposed} -> ok; 190 | {error, busy} -> ok 191 | end; 192 | {error, _} -> ok 193 | 194 | end. 195 | 196 | %% lists:foreach(fun erlang:garbage_collect/1, processes()). -------------------------------------------------------------------------------- /examples/example1/src/example1_workers_sup.erl: -------------------------------------------------------------------------------- 1 | -module(example1_workers_sup). 2 | 3 | -behaviour(supervisor). 4 | 5 | %% API 6 | -export([start_link/0]). 7 | 8 | %% Supervisor callbacks 9 | -export([init/1]). 10 | 11 | -define(SERVER, ?MODULE). 12 | 13 | 14 | start_link() -> 15 | supervisor:start_link({local, ?SERVER}, ?MODULE, []). 16 | 17 | %%%=================================================================== 18 | %%% Supervisor callbacks 19 | %%%=================================================================== 20 | 21 | init([]) -> 22 | ChildSpecs = [ 23 | #{id => example1_worker, 24 | start => {example1_worker, start_link, []}, 25 | restart => transient, 26 | shutdown => brutal_kill, 27 | type => worker, 28 | modules => [example1_worker]} 29 | ], 30 | 31 | {ok, {{simple_one_for_one, 1, 5}, ChildSpecs}}. 32 | 33 | 34 | -------------------------------------------------------------------------------- /include/esphi.hrl: -------------------------------------------------------------------------------- 1 | -define(SOPHIA_BINARY, 0). 2 | -define(SOPHIA_INT, 1). 3 | -define(SOPHIA_NOPREFIX, 0). 4 | -define(SOPHIA_COUNTER, "__COUNTER__"). 5 | -define(SOPHIA_KEY_ONLY, 0). 6 | -define(SOPHIA_FULL_OBJECT, 1). -------------------------------------------------------------------------------- /rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brigadier/esphi/71a518e9384b9090e7035f81ad6c62eeaa3b3d04/rebar -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {require_otp_vsn, "18"}. 2 | {eunit_opts, [verbose]}. 3 | {so_name, "esphi.so"}. 4 | 5 | {xref_checks, [undefined_function_calls]}. 6 | 7 | {port_sources, ["c_src/*.c"]}. 8 | 9 | {erl_opts, [warnings_as_errors, debug_info]}. 10 | 11 | {deps, [ 12 | %% {cuttlefish, ".*", {git, "git://github.com/basho/cuttlefish.git", {tag, "2.0.1"}}} 13 | ]}. 14 | 15 | {port_env, [ 16 | %% Make sure to set -fPIC when compiling leveldb 17 | {"CFLAGS", "$CFLAGS -O2 -DNDEBUG -std=c99 -pedantic -Wall -pthread"}, 18 | {"CXXFLAGS", "$CXXFLAGS -O2 -DNDEBUG -pedantic -Wall -pthread "}, 19 | {"DRV_CFLAGS", "$DRV_CFLAGS -O3 -Wall -I c_src/sophia/include"}, 20 | {"DRV_LDFLAGS", "$DRV_LDFLAGS c_src/sophia/libsophia.a"} 21 | ]}. 22 | 23 | {pre_hooks, [{'get-deps', "c_src/build_deps.sh get-deps"}, 24 | {compile, "c_src/build_deps.sh"}]}. 25 | 26 | {post_hooks, [{clean, "c_src/build_deps.sh clean"}]}. 27 | -------------------------------------------------------------------------------- /rebar.config.script: -------------------------------------------------------------------------------- 1 | %%-*- mode: erlang -*- 2 | 3 | %% Set the minimum Mac target to 10.8 for 10.9 or greater. This runtime 4 | %% check is needed since rebar's system_architecture check looks only at 5 | %% the version of the OS used to build the Erlang runtime, not the version 6 | %% actually running. 7 | case os:type() of 8 | {unix,darwin} -> 9 | Opt = " -mmacosx-version-min=10.8", 10 | [Mjr|_] = string:tokens(os:cmd("/usr/bin/uname -r"), "."), 11 | Major = list_to_integer(Mjr), 12 | if 13 | Major >= 13 -> 14 | Flags = ["CFLAGS", "CXXFLAGS", "DRV_CFLAGS", "DRV_LDFLAGS"], 15 | {port_env, Vals} = lists:keyfind(port_env, 1, CONFIG), 16 | Fold = fun({Flag,Val}, Acc) -> 17 | case lists:member(Flag, Flags) of 18 | true -> 19 | [{Flag, Val++Opt}|Acc]; 20 | false -> 21 | Acc 22 | end 23 | end, 24 | NewVals = lists:foldl(Fold, [], Vals), 25 | lists:keyreplace(port_env, 1, CONFIG, {port_env, NewVals}); 26 | true -> 27 | CONFIG 28 | end; 29 | _ -> 30 | CONFIG 31 | end. 32 | -------------------------------------------------------------------------------- /src/esphi.app.src: -------------------------------------------------------------------------------- 1 | {application, esphi, 2 | [ 3 | {description, "Sophia Erlang wrapper."}, 4 | {vsn, "0.9.0"}, 5 | {registered, []}, 6 | {applications, [ 7 | kernel, 8 | stdlib 9 | ]}, 10 | {env, [ 11 | 12 | ]} 13 | ]}. 14 | -------------------------------------------------------------------------------- /src/esphi.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% @author evgeny 3 | %%% @copyright (C) 2016, 4 | %%% @doc 5 | %%% 6 | %%% @end 7 | %%% Created : 21. янв 2016 18:45 8 | %%%------------------------------------------------------------------- 9 | -module(esphi). 10 | -on_load(on_load/0). 11 | 12 | -type sophia_ref() :: any(). 13 | 14 | -type sophia_db() :: sophia_ref(). 15 | -type sophia_table() :: sophia_ref(). 16 | -type sophia_cursor() :: sophia_ref(). 17 | -type sophia_transaction() :: sophia_ref(). 18 | 19 | -type sophia_order() :: '>' | '<' | '>=' | '<='. 20 | -type sophia_return_type() :: 1|0. %%1 - int 0 - binary> 21 | 22 | -export_type([sophia_db/0, sophia_table/0, sophia_cursor/0, sophia_transaction/0, sophia_order/0]). 23 | 24 | %% API 25 | -export([ 26 | esphi_db_open/3 27 | , esphi_db_close/1 28 | , esphi_print_info/1 29 | , esphi_param_set/3 30 | , esphi_param_get/3 31 | , esphi_last_error/1 32 | , esphi_table_open/2 33 | , esphi_table_close/1 34 | , esphi_table_create/3 35 | , esphi_table_drop/1 36 | , esphi_transaction_begin/1 37 | , esphi_transaction_commit/1 38 | , esphi_transaction_rollback/1 39 | , esphi_delete/3 40 | , esphi_delete/2 41 | , esphi_put/4 42 | , esphi_put/3 43 | , esphi_get/3 44 | , esphi_get/4 45 | , esphi_update_counter/3 46 | , esphi_update_counter/4 47 | , esphi_backup/1 48 | , esphi_cursor_open/4 49 | , esphi_cursor_close/1 50 | , esphi_cursor_next/4 51 | ]). 52 | 53 | on_load() -> 54 | init(). 55 | 56 | init() -> 57 | SoName = case code:priv_dir(?MODULE) of 58 | {error, bad_name} -> 59 | case code:which(?MODULE) of 60 | Filename when is_list(Filename) -> 61 | filename:join([filename:dirname(Filename), "../priv", "esphi"]); 62 | _ -> 63 | filename:join("../priv", "esphi") 64 | end; 65 | Dir -> 66 | filename:join(Dir, "esphi") 67 | end, 68 | erlang:load_nif(SoName, application:get_all_env(esphi)). 69 | 70 | 71 | 72 | 73 | 74 | -spec esphi_db_open(iolist(), [iolist()], map()) -> {ok, sophia_db()} | {error, {integer(), binary()}}. 75 | esphi_db_open(_Path, _Schema, _Params) -> 76 | erlang:nif_error({error, not_loaded}). 77 | 78 | -spec esphi_db_close(sophia_db()) -> ok. 79 | esphi_db_close(_DBref) -> 80 | erlang:nif_error({error, not_loaded}). 81 | 82 | -spec esphi_param_set(sophia_db(), iolist(), iolist()|integer()) -> ok | {error, disposed} | {error, {integer(), binary()}}. 83 | esphi_param_set(_DBref, _Key, _Val) -> 84 | erlang:nif_error({error, not_loaded}). 85 | 86 | 87 | -spec esphi_param_get(sophia_db(), iolist(), sophia_return_type()) -> binary() | {error, disposed|type} | {error, {integer(), binary()}}. 88 | esphi_param_get(_DBref, _Key, _ValueType) -> 89 | erlang:nif_error({error, not_loaded}). 90 | 91 | -spec esphi_print_info(sophia_db()) -> ok. 92 | esphi_print_info(_DBref) -> 93 | erlang:nif_error({error, not_loaded}). 94 | 95 | -spec esphi_last_error(sophia_db()) -> binary() | {error, {integer(), binary()}}. 96 | esphi_last_error(_DBref) -> 97 | erlang:nif_error({error, not_loaded}). 98 | 99 | 100 | -spec esphi_table_open(sophia_db(), iolist()) -> {ok, sophia_table()} | {error, disposed} | {error, {integer(), binary()}}. 101 | esphi_table_open(_DBref, _TName) -> 102 | erlang:nif_error({error, not_loaded}). 103 | 104 | -spec esphi_table_close(sophia_table()) -> ok. 105 | esphi_table_close(_TRef) -> 106 | erlang:nif_error({error, not_loaded}). 107 | 108 | -spec esphi_table_create(sophia_db(), iolist(), map()) -> ok | {error, disposed} | {error, {integer(), binary()}}. 109 | esphi_table_create(_DBref, _TName, _Params) -> 110 | erlang:nif_error({error, not_loaded}). 111 | 112 | -spec esphi_table_drop(sophia_table()) -> ok | {error, disposed} | {error, {integer(), binary()}}. 113 | esphi_table_drop(_Tref) -> 114 | erlang:nif_error({error, not_loaded}). 115 | 116 | 117 | 118 | -spec esphi_transaction_begin(sophia_db()) -> {ok, sophia_transaction()} | {error, disposed} | {error, {integer(), binary()}}. 119 | esphi_transaction_begin(_DBref) -> 120 | erlang:nif_error({error, not_loaded}). 121 | 122 | -spec esphi_transaction_commit(sophia_transaction()) -> ok | {error, disposed|rolled_back|lock|unknown|busy} | {error, {integer(), binary()}}. 123 | esphi_transaction_commit(_TranRef) -> 124 | erlang:nif_error({error, not_loaded}). 125 | 126 | -spec esphi_transaction_rollback(sophia_transaction()) -> ok. 127 | esphi_transaction_rollback(_TranRef) -> 128 | erlang:nif_error({error, not_loaded}). 129 | 130 | 131 | 132 | -spec esphi_delete(sophia_table(), iolist()|integer()) -> ok | {error, disposed} | {error, {integer(), binary()}}. 133 | esphi_delete(_Tref, _Key) -> 134 | erlang:nif_error({error, not_loaded}). 135 | 136 | -spec esphi_delete(sophia_table(), sophia_transaction(), iolist()|integer()) -> ok | {error, disposed} | {error, {integer(), binary()}}. 137 | esphi_delete(_Tref, _TranRef, _Key) -> 138 | erlang:nif_error({error, not_loaded}). 139 | 140 | 141 | -spec esphi_put(sophia_table(), iolist()|integer(), iolist()|integer()) -> ok | {error, disposed} | {error, {integer(), binary()}}. 142 | esphi_put(_Tref, _Key, _Value) -> 143 | erlang:nif_error({error, not_loaded}). 144 | 145 | -spec esphi_put(sophia_table(), sophia_transaction(), iolist()|integer(), iolist()|integer()) -> ok | {error, disposed} | {error, {integer(), binary()}}. 146 | esphi_put(_Tref, _TranRef, _Key, _Value) -> 147 | erlang:nif_error({error, not_loaded}). 148 | 149 | -spec esphi_get(sophia_table(), sophia_transaction(), iolist()|integer(), sophia_return_type()) -> integer() | not_found | {error, disposed|type} | {error, {integer(), binary()}}. 150 | esphi_get(_Tref, _TranRef, _Key, _ValueType) -> 151 | erlang:nif_error({error, not_loaded}). 152 | 153 | -spec esphi_get(sophia_table(), iolist()|integer(), sophia_return_type()) -> integer() | not_found | {error, disposed} | {error, {integer(), binary()}}. 154 | esphi_get(_Tref, _Key, _ValueType) -> 155 | erlang:nif_error({error, not_loaded}). 156 | 157 | 158 | -spec esphi_update_counter(sophia_table(), iolist()|integer(), integer()) -> ok | not_found | {error, disposed} | {error, {integer(), binary()}}. 159 | esphi_update_counter(_Tref, _Key, _Value) -> 160 | erlang:nif_error({error, not_loaded}). 161 | 162 | -spec esphi_update_counter(sophia_table(), sophia_transaction(), iolist()|integer(), integer()) -> ok | not_found | {error, disposed} | {error, {integer(), binary()}}. 163 | 164 | esphi_update_counter(_Tref, _TranRef, _Key, _Value) -> 165 | erlang:nif_error({error, not_loaded}). 166 | 167 | 168 | -spec esphi_cursor_open(sophia_table(), iolist()|0, sophia_order(), integer()|iolist()) -> {ok, sophia_cursor()} | {error, disposed} | {error, {integer(), binary()}}. 169 | esphi_cursor_open(_Tref, _Prefix, _Order, _Seek) -> 170 | erlang:nif_error({error, not_loaded}). 171 | 172 | -spec esphi_cursor_close(sophia_cursor()) -> ok. 173 | esphi_cursor_close(_Cref) -> 174 | erlang:nif_error({error, not_loaded}). 175 | 176 | 177 | -spec esphi_cursor_next(sophia_cursor(), 0|1, sophia_return_type(), sophia_return_type()) -> binary() | {error, disposed|type}| {error, {integer(), binary()}}. 178 | esphi_cursor_next(_Cref, _KeyOnly, _KeyType, _ValueType) -> 179 | erlang:nif_error({error, not_loaded}). 180 | 181 | 182 | 183 | -spec esphi_backup(sophia_db()) -> ok. 184 | esphi_backup(_DBref) -> 185 | erlang:nif_error({error, not_loaded}). -------------------------------------------------------------------------------- /tools.mk: -------------------------------------------------------------------------------- 1 | REBAR ?= ./rebar 2 | 3 | compile-no-deps: 4 | ${REBAR} compile skip_deps=true 5 | 6 | test: compile 7 | ${REBAR} eunit skip_deps=true 8 | 9 | docs: 10 | ${REBAR} doc skip_deps=true 11 | 12 | xref: compile 13 | ${REBAR} xref skip_deps=true 14 | 15 | PLT ?= $(HOME)/.combo_dialyzer_plt 16 | LOCAL_PLT = .local_dialyzer_plt 17 | DIALYZER_FLAGS ?= -Wunmatched_returns 18 | 19 | ${PLT}: compile 20 | @if [ -f $(PLT) ]; then \ 21 | dialyzer --check_plt --plt $(PLT) --apps $(DIALYZER_APPS) && \ 22 | dialyzer --add_to_plt --plt $(PLT) --output_plt $(PLT) --apps $(DIALYZER_APPS) ; test $$? -ne 1; \ 23 | else \ 24 | dialyzer --build_plt --output_plt $(PLT) --apps $(DIALYZER_APPS); test $$? -ne 1; \ 25 | fi 26 | 27 | ${LOCAL_PLT}: compile 28 | @if [ -d deps ]; then \ 29 | if [ -f $(LOCAL_PLT) ]; then \ 30 | dialyzer --check_plt --plt $(LOCAL_PLT) deps/*/ebin && \ 31 | dialyzer --add_to_plt --plt $(LOCAL_PLT) --output_plt $(LOCAL_PLT) deps/*/ebin ; test $$? -ne 1; \ 32 | else \ 33 | dialyzer --build_plt --output_plt $(LOCAL_PLT) deps/*/ebin ; test $$? -ne 1; \ 34 | fi \ 35 | fi 36 | 37 | dialyzer-run: 38 | @echo "==> $(shell basename $(shell pwd)) (dialyzer)" 39 | @if [ -f $(LOCAL_PLT) ]; then \ 40 | PLTS="$(PLT) $(LOCAL_PLT)"; \ 41 | else \ 42 | PLTS=$(PLT); \ 43 | fi; \ 44 | if [ -f dialyzer.ignore-warnings ]; then \ 45 | if [ $$(grep -cvE '[^[:space:]]' dialyzer.ignore-warnings) -ne 0 ]; then \ 46 | echo "ERROR: dialyzer.ignore-warnings contains a blank/empty line, this will match all messages!"; \ 47 | exit 1; \ 48 | fi; \ 49 | dialyzer $(DIALYZER_FLAGS) --plts $${PLTS} -c ebin > dialyzer_warnings ; \ 50 | egrep -v "^\s*(done|Checking|Proceeding|Compiling)" dialyzer_warnings | grep -F -f dialyzer.ignore-warnings -v > dialyzer_unhandled_warnings ; \ 51 | cat dialyzer_unhandled_warnings ; \ 52 | [ $$(cat dialyzer_unhandled_warnings | wc -l) -eq 0 ] ; \ 53 | else \ 54 | dialyzer $(DIALYZER_FLAGS) --plts $${PLTS} -c ebin; \ 55 | fi 56 | 57 | dialyzer-quick: compile-no-deps dialyzer-run 58 | 59 | dialyzer: ${PLT} ${LOCAL_PLT} dialyzer-run 60 | 61 | cleanplt: 62 | @echo 63 | @echo "Are you sure? It takes several minutes to re-build." 64 | @echo Deleting $(PLT) and $(LOCAL_PLT) in 5 seconds. 65 | @echo 66 | sleep 5 67 | rm $(PLT) 68 | rm $(LOCAL_PLT) 69 | 70 | --------------------------------------------------------------------------------