├── .gitignore ├── .travis.yml ├── AUTHORS ├── CHANGELOG ├── LICENSE ├── Makefile ├── README.md ├── bench ├── basho_bench_driver_cberl.erl ├── cberl_bench.config ├── macbook_cberl.png └── macmini_cberl_new.png ├── c_src ├── callbacks.c ├── callbacks.h ├── cb.c ├── cb.h ├── cberl.h ├── cberl_nif.c ├── cberl_nif.h ├── queue.c └── queue.h ├── include └── cberl.hrl ├── rebar ├── rebar.config ├── src ├── cberl.app.src ├── cberl.erl ├── cberl_app.erl ├── cberl_nif.erl ├── cberl_sup.erl ├── cberl_transcoder.erl └── cberl_worker.erl └── test ├── cberl_tests.erl ├── cberl_transcoder_tests.erl ├── cberl_view_tests.erl └── couchbase_connection.hrl /.gitignore: -------------------------------------------------------------------------------- 1 | ebin/ 2 | .eunit 3 | deps 4 | priv 5 | *.o 6 | *.beam 7 | *.plt 8 | erl_crash.dump 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: erlang 2 | otp_release: 3 | - 17.0 4 | - R16B03-1 5 | - R16B03 6 | - R16B02 7 | - R16B01 8 | - R16B 9 | before_script: 10 | - sudo wget -O /etc/apt/sources.list.d/couchbase.list http://packages.couchbase.com/ubuntu/couchbase-ubuntu1204.list 11 | - sudo apt-get update 12 | - sudo apt-get install libcouchbase2-libevent libcouchbase-dev --force-yes 13 | script: ./rebar compile && ./rebar skip_deps=true tests=cberl_transcoder_test eunit 14 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | William Cummings 2 | Zachary Hueras 3 | Xavier Robledo 4 | Ali Yakamercan 5 | Philipp Fehre 6 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | -- 1.0.5 (2014-12-17) 2 | Fix append function with newer versions of libcouchbase. 3 | 4 | -- 1.0.4 (2014-12-17) 5 | Fix potential crash with cberl:view. 6 | 7 | -- 1.0.3 (2014-11-19) 8 | Support detailed libcouchbase errors, when possible. 9 | 10 | -- 1.0.2 (2014-11-06) 11 | Refine connection logic, support IOlists as keys. 12 | 13 | -- 1.0.1 (2014-08-21) 14 | Centralize test host functionality into one file. 15 | 16 | -- 1.0.0 (2014-08-19) 17 | Fix bug with cberl:touch causing a segfault. 18 | 19 | -- 0.0.7 (2014-08-19) 20 | Match errors in decode_query_resp and pass up. 21 | 22 | -- 0.0.6 (2014-07-03) 23 | Add flush functionality. 24 | 25 | -- 0.0.5 (2014-06-28) 26 | Add set, create for design documents. 27 | Setup Travis-CI integration 28 | 29 | -- 0.0.4 (2014-03-24) 30 | Fix lock and unlock methods, and correct compilation error. 31 | Update type specifications and preliminary code cleanup. 32 | 33 | -- 0.0.3 (2014-02-19) 34 | Cleanup of the package and version bump with new compilation method. 35 | 36 | -- 0.0.2 (2013-02-28) 37 | Rewritten w/ threading, fixing a number of bugs 38 | 39 | -- 0.0.1 40 | Initial version 41 | 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2012-2014 Chitika Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2012 Erlware, LLC. All Rights Reserved. 2 | # 3 | # This file is provided to you under the Apache License, 4 | # Version 2.0 (the "License"); you may not use this file 5 | # except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, 11 | # software distributed under the License is distributed on an 12 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | # KIND, either express or implied. See the License for the 14 | # specific language governing permissions and limitations 15 | # under the License. 16 | # 17 | 18 | ERLFLAGS= -pa $(CURDIR)/.eunit -pa $(CURDIR)/ebin -pa $(CURDIR)/deps/*/ebin 19 | 20 | DEPS_PLT=$(CURDIR)/.deps_plt 21 | DEPS=erts kernel stdlib 22 | 23 | # ============================================================================= 24 | # Verify that the programs we need to run are installed on this system 25 | # ============================================================================= 26 | ERL = $(shell which erl) 27 | 28 | ifeq ($(ERL),) 29 | $(error "Erlang not available on this system") 30 | endif 31 | 32 | REBAR=$(CURDIR)/rebar 33 | 34 | ifeq ($(REBAR),) 35 | $(error "Rebar not available on this system") 36 | endif 37 | 38 | .PHONY: all compile doc clean test dialyzer typer shell distclean pdf \ 39 | update-deps clean-common-test-data rebuild 40 | 41 | all: deps compile dialyzer test 42 | 43 | # ============================================================================= 44 | # Rules to build the system 45 | # ============================================================================= 46 | 47 | deps: 48 | $(REBAR) get-deps 49 | $(REBAR) compile 50 | 51 | update-deps: 52 | $(REBAR) update-deps 53 | $(REBAR) compile 54 | 55 | compile: 56 | $(REBAR) skip_deps=true compile 57 | 58 | doc: 59 | $(REBAR) skip_deps=true doc 60 | 61 | eunit: compile clean-common-test-data 62 | $(REBAR) skip_deps=true eunit 63 | 64 | test: compile eunit 65 | 66 | $(DEPS_PLT): 67 | @echo Building local plt at $(DEPS_PLT) 68 | @echo 69 | dialyzer --output_plt $(DEPS_PLT) --build_plt \ 70 | --apps $(DEPS) -r deps 71 | 72 | dialyzer: $(DEPS_PLT) 73 | dialyzer --fullpath --plt $(DEPS_PLT) -Wrace_conditions -r ./ebin 74 | 75 | typer: 76 | typer --plt $(DEPS_PLT) -r ./src 77 | 78 | shell: deps compile 79 | # You often want *rebuilt* rebar tests to be available to the 80 | # shell you have to call eunit (to get the tests 81 | # rebuilt). However, eunit runs the tests, which probably 82 | # fails (thats probably why You want them in the shell). This 83 | # runs eunit but tells make to ignore the result. 84 | - @$(REBAR) skip_deps=true eunit 85 | @$(ERL) $(ERLFLAGS) 86 | 87 | pdf: 88 | pandoc README.md -o README.pdf 89 | 90 | clean: 91 | - rm -rf $(CURDIR)/test/*.beam 92 | - rm -rf $(CURDIR)/logs 93 | - rm -rf $(CURDIR)/ebin 94 | $(REBAR) skip_deps=true clean 95 | 96 | distclean: clean 97 | - rm -rf $(DEPS_PLT) 98 | - rm -rvf $(CURDIR)/deps 99 | 100 | rebuild: distclean deps compile escript dialyzer test -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CBERL 2 | ==== 3 | 4 | [![Build Status](https://travis-ci.org/chitika/cberl.svg?branch=master)](https://travis-ci.org/chitika/cberl) 5 | 6 | NIF based Erlang bindings for couchbase based on libcouchbase. 7 | CBERL is at early stage of development, it only supports very basic functionality. Please submit bugs and patches if you find any. 8 | Tested on OSX, Debian Squeeze and Amazon Linux. 9 | 10 | Quick Setup/Start 11 | --------- 12 | 13 | First you must have libcouchbase installed. 14 | 15 | On OSX install [homebrew](http://mxcl.github.com/homebrew/,"homebrew") if you haven't already then run: 16 | 17 | ```shell 18 | brew update && brew install libcouchbase 19 | ``` 20 | 21 | On Amazon linux: 22 | 23 | ```shell 24 | sudo wget -O/etc/yum.repos.d/couchbase.repo http://packages.couchbase.com/rpm/couchbase-centos62-x86_64.repo 25 | sudo yum check-update 26 | sudo yum install --enablerepo=epel libcouchbase2 libcouchbase-devel 27 | ``` 28 | 29 | For installing libcouchbase on other systems visit http://www.couchbase.com/develop/c/current. 30 | 31 | Then: 32 | 33 | ```shell 34 | git clone git@github.com:chitika/cberl.git 35 | cd cberl 36 | # assuming you have rebar in your path 37 | rebar get-deps compile 38 | ``` 39 | 40 | Or just include it as a dependency in your rebar config. 41 | 42 | 43 | Example 44 | ------- 45 | 46 | Make sure you have couchbase running on localhost or use cberl:new(Host) instead. 47 | 48 | ```erlang 49 | % create a connection pool of 5 connections named cberl_default 50 | % you can provide more argument like host, username, password, 51 | % bucket and transcoder - look at https://github.com/wcummings/cberl/blob/master/src/cberl.erl for more detail 52 | cberl:start_link(cberl_default, 5). 53 | {ok, <0.33.0>} 54 | % Poolname, Key, Expire - 0 for infinity, Value 55 | cberl:set(cberl_default, <<"fkey">>, 0, <<"cberl">>). 56 | ok 57 | cberl:get(cberl_default, <<"fkey">>). 58 | {<<"fkey">>, ReturnedCasValue, <<"cberl">>} 59 | ``` 60 | 61 | For more information on all the functions -> ./rebar doc (most of documentation is out of date right now) 62 | 63 | Views 64 | ----- 65 | 66 | cberl has support for querying views via the view/4 functions: 67 | 68 | ```erlang 69 | cberl:view(cberl_default, "all", "all", []). 70 | {ok,{1, 71 | [[{<<"id">>,<<"test">>}, 72 | {<<"key">>,<<"test">>}, 73 | {<<"value">>,null}]]}} 74 | ``` 75 | 76 | Shorthand for foldl, foldr and foreach are also provided. 77 | 78 | N1QL 79 | ----- 80 | 81 | cberl has support for N1QL queries via the n1ql/4 and n1ql/5 functions: 82 | 83 | ```erlang 84 | Dog = {[{<<"type">>,<<"dog">>}, 85 | {<<"name">>,<<"tom">>}, 86 | {<<"age">>,5}, 87 | {<<"color">>,<<"white">>}]}. 88 | cberl:set(cberl_default, <<"tom">>, 0, Dog). 89 | cberl:n1ql(cberl_default, <<"SELECT * FROM default WHERE type=$1 and age=$2 and color=$3">>, [<<"\"dog\"">>, <<"5">>, <<"\"white\"">>], false). 90 | {ok,{[{<<"requestID">>, 91 | <<"a00b80e8-aea8-4a23-a0a9-5aba26d2b48f">>}, 92 | {<<"signature">>,{[{<<"*">>,<<"*">>}]}}, 93 | {<<"results">>,[]}, 94 | {<<"status">>,<<"success">>}, 95 | {<<"metrics">>, 96 | {[{<<"elapsedTime">>,<<"21.644116ms">>}, 97 | {<<"executionTime">>,<<"21.60625ms">>}, 98 | {<<"resultCount">>,1}, 99 | {<<"resultSize">>,171}]}}]}, 100 | [{[{<<"default">>, 101 | {[{<<"age">>,5}, 102 | {<<"color">>,<<"white">>}, 103 | {<<"name">>,<<"tom">>}, 104 | {<<"type">>,<<"dog">>}]}}]}]} 105 | ``` 106 | 107 | Custom Transcoders 108 | ----- 109 | 110 | You can have your custom transcoders, your transcoder must export 3 functions: 111 | 112 | __encode_value/2:__ 113 | 114 | Takes in an encoder|encoder list and the original value and turns it into a binary. 115 | 116 | __decode_value/2:__ 117 | 118 | Takes in a flag (from couchbase) and the value (as binary) and turns it into the actual value. 119 | 120 | __flag/1:__ 121 | 122 | Turns an encoder_name (or list of them) into an integer. This value is sent to CB during set operations and this is what you get in decode value. You must return a value for 'standard' encoder if you are not planning to specify an encoder for every set operation. 123 | 124 | Check out [cberl_transcoder.erl](https://github.com/wcummings/cberl/blob/master/src/cberl_transcoder.erl) it is pretty straightforward. 125 | -------------------------------------------------------------------------------- /bench/basho_bench_driver_cberl.erl: -------------------------------------------------------------------------------- 1 | -module(basho_bench_driver_cberl). 2 | 3 | -export([new/1, 4 | run/4 5 | ]). 6 | 7 | -include("basho_bench.hrl"). 8 | 9 | new(_Id) -> 10 | PoolSize = basho_bench_config:get(cberl_pool_size, 5), 11 | HostPort = basho_bench_config:get(cberl_hostport, "localhost:8091"), 12 | UserName = basho_bench_config:get(cberl_username, ""), 13 | Password = basho_bench_config:get(cberl_password, ""), 14 | cberl:start_link(bench_testing, PoolSize, HostPort, UserName, Password), 15 | {ok, bench_testing}. 16 | 17 | run(get, KeyGen, _ValueGen, PoolName) -> 18 | Key = list_to_binary(KeyGen()), 19 | case cberl:get(PoolName, Key) of 20 | {Key, _Cas, _Value} -> 21 | {ok, PoolName}; 22 | {Key, {error, key_enoent}} -> 23 | {ok, PoolName}; 24 | {Key, {error, Error}} -> 25 | {error, Error, PoolName}; 26 | Error -> 27 | {error, Error, PoolName} 28 | end; 29 | run(set, KeyGen, _ValueGen, PoolName) -> 30 | Key = list_to_binary(KeyGen()), 31 | Value = rand_val(50), 32 | case cberl:set(PoolName, Key, 60, Value) of 33 | ok -> 34 | {ok, PoolName}; 35 | Error -> 36 | {error, Error, PoolName} 37 | end. 38 | 39 | rand_val(N) -> 40 | rand_val(N, []). 41 | 42 | rand_val(0, Acc) -> 43 | Acc; 44 | rand_val(N, Acc) -> 45 | rand_val(N - 1, [random:uniform(26) + 96 | Acc]). 46 | -------------------------------------------------------------------------------- /bench/cberl_bench.config: -------------------------------------------------------------------------------- 1 | {mode, max}. 2 | 3 | % Run X operations per second per worker 4 | % {mode, {rate, X}} 5 | 6 | {duration, 1}. 7 | 8 | {concurrent, 100}. 9 | 10 | {driver, basho_bench_driver_cberl}. 11 | 12 | %% code path to cberl/jiffy/poolboy 13 | {code_paths, ["*/cberl", 14 | "*/cberl/deps/poolboy", 15 | "*/cberl/deps/jiffy"]}. 16 | 17 | {key_generator, {int_to_str, {uniform_int, 100000}}}. 18 | 19 | {value_generator, {uniform_bin, 100, 2000}}. 20 | 21 | {operations, [{set, 1}, {get, 1}]}. 22 | 23 | %% cberl configs 24 | {cberl_pool_size, 5}. 25 | {cberl_hostport, "localhost:8091"}. 26 | {cberl_username, ""}. 27 | {cberl_password, ""}. 28 | -------------------------------------------------------------------------------- /bench/macbook_cberl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chitika/cberl/400190257bfc290def84491bdbf082507e78e5c5/bench/macbook_cberl.png -------------------------------------------------------------------------------- /bench/macmini_cberl_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chitika/cberl/400190257bfc290def84491bdbf082507e78e5c5/bench/macmini_cberl_new.png -------------------------------------------------------------------------------- /c_src/callbacks.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "erl_nif.h" 6 | #include "callbacks.h" 7 | 8 | void get_callback(lcb_t instance, 9 | const void *cookie, 10 | lcb_error_t error, 11 | const lcb_get_resp_t *item) 12 | { 13 | struct libcouchbase_callback_m *cbm; 14 | cbm = (struct libcouchbase_callback_m *)cookie; 15 | cbm->ret[cbm->currKey] = malloc(sizeof(struct libcouchbase_callback)); 16 | cbm->ret[cbm->currKey]->key = malloc(item->v.v0.nkey); 17 | memcpy(cbm->ret[cbm->currKey]->key, item->v.v0.key, item->v.v0.nkey); 18 | cbm->ret[cbm->currKey]->nkey = item->v.v0.nkey; 19 | cbm->ret[cbm->currKey]->error = error; 20 | cbm->ret[cbm->currKey]->flag = item->v.v0.flags == 0 ? 1 : item->v.v0.flags; 21 | cbm->ret[cbm->currKey]->cas = item->v.v0.cas; 22 | if (error == LCB_SUCCESS) { 23 | cbm->ret[cbm->currKey]->data = malloc(item->v.v0.nbytes); 24 | memcpy(cbm->ret[cbm->currKey]->data, item->v.v0.bytes, item->v.v0.nbytes); 25 | cbm->ret[cbm->currKey]->size = item->v.v0.nbytes; 26 | } 27 | cbm->currKey += 1; 28 | } 29 | 30 | void arithmetic_callback(lcb_t instance, 31 | const void *cookie, 32 | lcb_error_t error, 33 | const lcb_arithmetic_resp_t *resp) 34 | { 35 | struct libcouchbase_callback *cb; 36 | cb = (struct libcouchbase_callback *)cookie; 37 | cb->error = error; 38 | cb->flag = 1; 39 | if (error == LCB_SUCCESS) { 40 | cb->cas = resp->v.v0.cas; 41 | cb->data = malloc(20*sizeof(char)); 42 | memset(cb->data, 0, 20); 43 | sprintf(cb->data, "%llu", (long long unsigned) resp->v.v0.value); 44 | cb->size = strlen(cb->data); 45 | } 46 | } 47 | 48 | void unlock_callback(lcb_t instance, 49 | const void *cookie, 50 | lcb_error_t error, 51 | const lcb_unlock_resp_t *resp) 52 | { 53 | (void)instance; 54 | struct libcouchbase_callback *cb; 55 | cb = (struct libcouchbase_callback *)cookie; 56 | cb->error = error; 57 | } 58 | 59 | void touch_callback(lcb_t instance, 60 | const void *cookie, 61 | lcb_error_t error, 62 | const lcb_touch_resp_t *resp) 63 | { 64 | (void)instance; 65 | struct libcouchbase_callback_m *cbm; 66 | cbm = (struct libcouchbase_callback_m *)cookie; 67 | cbm->ret[cbm->currKey] = malloc(sizeof(struct libcouchbase_callback)); 68 | cbm->ret[cbm->currKey]->key = malloc(resp->v.v0.nkey); 69 | memcpy(cbm->ret[cbm->currKey]->key, resp->v.v0.key, resp->v.v0.nkey); 70 | cbm->ret[cbm->currKey]->nkey = resp->v.v0.nkey; 71 | cbm->ret[cbm->currKey]->error = error; 72 | cbm->currKey += 1; 73 | } 74 | 75 | void store_callback(lcb_t instance, 76 | const void *cookie, 77 | lcb_storage_t operation, 78 | lcb_error_t error, 79 | const lcb_store_resp_t *item) 80 | { 81 | 82 | (void)instance; (void)operation; 83 | struct libcouchbase_callback *cb; 84 | cb = (struct libcouchbase_callback *)cookie; 85 | cb->error = error; 86 | cb->cas = item->v.v0.cas; 87 | } 88 | 89 | void remove_callback(lcb_t instance, 90 | const void *cookie, 91 | lcb_error_t error, 92 | const lcb_remove_resp_t *resp) 93 | { 94 | (void)instance; 95 | struct libcouchbase_callback *cb; 96 | cb = (struct libcouchbase_callback *)cookie; 97 | cb->error = error; 98 | } 99 | 100 | void http_callback(lcb_http_request_t request, 101 | lcb_t instance, 102 | const void* cookie, 103 | lcb_error_t error, 104 | const lcb_http_resp_t *resp) 105 | { 106 | (void)instance; 107 | struct libcouchbase_callback_http *cbh; 108 | cbh = (struct libcouchbase_callback_http *)cookie; 109 | cbh->ret.error = error; 110 | cbh->status = resp->v.v0.status; 111 | if(error == LCB_SUCCESS) { 112 | cbh->ret.data = malloc(resp->v.v0.nbytes); 113 | cbh->ret.size = resp->v.v0.nbytes; 114 | memcpy(cbh->ret.data, resp->v.v0.bytes, resp->v.v0.nbytes); 115 | } 116 | } 117 | 118 | void n1ql_callback(lcb_t instance, 119 | int cbtype, 120 | const lcb_RESPN1QL *resp) 121 | { 122 | struct libcouchbase_callback_n1ql *cb; 123 | cb = (struct libcouchbase_callback_n1ql *) ((lcb_RESPBASE *)resp)->cookie; 124 | 125 | if (! (resp->rflags & LCB_RESP_F_FINAL)) { 126 | if (cb->currrow == cb->size) { 127 | cb->size *= 2; 128 | cb->ret = realloc(cb->ret, sizeof(struct libcouchbase_callback*) * cb->size); 129 | } 130 | 131 | cb->ret[cb->currrow] = malloc(sizeof(struct libcouchbase_callback)); 132 | cb->ret[cb->currrow]->data = malloc(resp->nrow); 133 | cb->ret[cb->currrow]->size = resp->nrow; 134 | memcpy(cb->ret[cb->currrow]->data, resp->row, resp->nrow); 135 | cb->ret[cb->currrow]->error = LCB_SUCCESS; 136 | cb->currrow++; 137 | } else { 138 | cb->meta->data = malloc(resp->nrow); 139 | cb->meta->size = resp->nrow; 140 | memcpy(cb->meta->data, resp->row, resp->nrow); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /c_src/callbacks.h: -------------------------------------------------------------------------------- 1 | #ifndef CALLBACKS_H 2 | #define CALLBACKS_H 3 | 4 | struct libcouchbase_callback { 5 | lcb_error_t error; 6 | size_t size; 7 | void *data; 8 | void *key; 9 | size_t nkey; 10 | lcb_uint32_t flag; 11 | lcb_cas_t cas; 12 | }; 13 | 14 | struct libcouchbase_callback_http { 15 | lcb_http_status_t status; 16 | struct libcouchbase_callback ret; 17 | }; 18 | 19 | struct libcouchbase_callback_m { 20 | int currKey; 21 | struct libcouchbase_callback** ret; 22 | }; 23 | 24 | struct libcouchbase_callback_n1ql { 25 | int currrow; 26 | int size; 27 | struct libcouchbase_callback** ret; 28 | struct libcouchbase_callback* meta; 29 | }; 30 | 31 | void get_callback(lcb_t instance, 32 | const void *cookie, 33 | lcb_error_t error, 34 | const lcb_get_resp_t *item); 35 | 36 | void arithmetic_callback(lcb_t instance, 37 | const void *cookie, 38 | lcb_error_t error, 39 | const lcb_arithmetic_resp_t *resp); 40 | 41 | void unlock_callback(lcb_t instance, 42 | const void *cookie, 43 | lcb_error_t error, 44 | const lcb_unlock_resp_t *resp); 45 | 46 | void touch_callback(lcb_t instance, 47 | const void *cookie, 48 | lcb_error_t error, 49 | const lcb_touch_resp_t *resp); 50 | 51 | void store_callback(lcb_t instance, 52 | const void *cookie, 53 | lcb_storage_t operation, 54 | lcb_error_t error, 55 | const lcb_store_resp_t *item); 56 | 57 | void remove_callback(lcb_t instance, 58 | const void *cookie, 59 | lcb_error_t error, 60 | const lcb_remove_resp_t *resp); 61 | 62 | void http_callback(lcb_http_request_t request, 63 | lcb_t instance, 64 | const void* cookie, 65 | lcb_error_t error, 66 | const lcb_http_resp_t *resp); 67 | 68 | void n1ql_callback(lcb_t instance, 69 | int cbtype, 70 | const lcb_RESPN1QL *resp); 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /c_src/cb.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "callbacks.h" 7 | #include "cb.h" 8 | 9 | void *cb_connect_args(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) 10 | { 11 | connect_args_t* args = (connect_args_t*)enif_alloc(sizeof(connect_args_t)); 12 | 13 | unsigned arg_length; 14 | if (!enif_get_list_length(env, argv[0], &arg_length)) goto error0; 15 | args->host = (char *) malloc(arg_length + 1); 16 | if (!enif_get_string(env, argv[0], args->host, arg_length + 1, ERL_NIF_LATIN1)) goto error1; 17 | 18 | if (!enif_get_list_length(env, argv[1], &arg_length)) goto error1; 19 | args->user = (char *) malloc(arg_length + 1); 20 | if (!enif_get_string(env, argv[1], args->user, arg_length + 1, ERL_NIF_LATIN1)) goto error2; 21 | 22 | if (!enif_get_list_length(env, argv[2], &arg_length)) goto error2; 23 | args->pass = (char *) malloc(arg_length + 1); 24 | if (!enif_get_string(env, argv[2], args->pass, arg_length + 1, ERL_NIF_LATIN1)) goto error3; 25 | 26 | if (!enif_get_list_length(env, argv[3], &arg_length)) goto error3; 27 | args->bucket = (char *) malloc(arg_length + 1); 28 | if (!enif_get_string(env, argv[3], args->bucket, arg_length + 1, ERL_NIF_LATIN1)) goto error4; 29 | 30 | return (void*)args; 31 | 32 | error4: 33 | free(args->bucket); 34 | error3: 35 | free(args->pass); 36 | error2: 37 | free(args->user); 38 | error1: 39 | free(args->host); 40 | error0: 41 | enif_free(args); 42 | 43 | return NULL; 44 | } 45 | 46 | ERL_NIF_TERM cb_connect(ErlNifEnv* env, handle_t* handle, void* obj) 47 | { 48 | connect_args_t* args = (connect_args_t*)obj; 49 | 50 | lcb_error_t err; 51 | struct lcb_create_st create_options; 52 | struct lcb_create_io_ops_st io_opts; 53 | 54 | io_opts.version = 0; 55 | io_opts.v.v0.type = LCB_IO_OPS_DEFAULT; 56 | io_opts.v.v0.cookie = NULL; 57 | 58 | memset(&create_options, 0, sizeof(create_options)); 59 | err = lcb_create_io_ops(&create_options.v.v0.io, &io_opts); 60 | if (err != LCB_SUCCESS) { 61 | printf("failed create io ops\n"); 62 | fprintf(stderr, "Failed to create IO instance: %s\n", 63 | lcb_strerror(NULL, err)); 64 | return return_lcb_error(env, err); 65 | } 66 | 67 | create_options.v.v0.host = args->host; 68 | create_options.v.v0.user = args->user; 69 | create_options.v.v0.bucket = args->bucket; 70 | create_options.v.v0.passwd = args->pass; 71 | 72 | err = lcb_create(&(handle->instance), &create_options); 73 | 74 | free(args->host); 75 | free(args->user); 76 | free(args->pass); 77 | free(args->bucket); 78 | 79 | if (err != LCB_SUCCESS) { 80 | return enif_make_tuple2(env, enif_make_atom(env, "error"), 81 | enif_make_string(env, lcb_strerror(NULL, err), ERL_NIF_LATIN1)); 82 | } 83 | 84 | (void)lcb_set_get_callback(handle->instance, get_callback); 85 | (void)lcb_set_store_callback(handle->instance, store_callback); 86 | (void)lcb_set_unlock_callback(handle->instance, unlock_callback); 87 | (void)lcb_set_touch_callback(handle->instance, touch_callback); 88 | (void)lcb_set_arithmetic_callback(handle->instance, arithmetic_callback); 89 | (void)lcb_set_remove_callback(handle->instance, remove_callback); 90 | (void)lcb_set_http_complete_callback(handle->instance, http_callback); 91 | 92 | err = lcb_connect(handle->instance); 93 | 94 | if (err != LCB_SUCCESS) { 95 | return return_lcb_error(env, err); 96 | } 97 | 98 | err = lcb_wait(handle->instance); 99 | 100 | if(err != LCB_SUCCESS) { 101 | return return_lcb_error(env, err); 102 | } 103 | 104 | #ifdef LCB_CNTL_DETAILED_ERRCODES 105 | int val = 1; 106 | err = lcb_cntl(handle->instance, LCB_CNTL_SET, LCB_CNTL_DETAILED_ERRCODES, &val); 107 | if(err != LCB_SUCCESS) { 108 | return return_lcb_error(env, err); 109 | } 110 | #endif 111 | 112 | return enif_make_atom(env, "ok"); 113 | } 114 | 115 | void* cb_store_args(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) 116 | { 117 | store_args_t* args = (store_args_t*)enif_alloc(sizeof(store_args_t)); 118 | 119 | ErlNifBinary value_binary; 120 | ErlNifBinary key_binary; 121 | 122 | if (!enif_get_int(env, argv[0], &args->operation)) goto error0; 123 | if (!enif_inspect_iolist_as_binary(env, argv[1], &key_binary)) goto error0; 124 | if (!enif_inspect_iolist_as_binary(env, argv[2], &value_binary)) goto error0; 125 | 126 | args->nkey = key_binary.size; 127 | args->nbytes = value_binary.size; 128 | args->key = (char*)malloc(key_binary.size); 129 | args->bytes = (char*)malloc(value_binary.size); 130 | memcpy(args->bytes, value_binary.data, value_binary.size); 131 | memcpy(args->key, key_binary.data, key_binary.size); 132 | 133 | if (!enif_get_uint(env, argv[3], &args->flags)) goto error1; 134 | if (!enif_get_int(env, argv[4], &args->exp)) goto error1; 135 | if (!enif_get_uint64(env, argv[5], (ErlNifUInt64*)&args->cas)) goto error1; 136 | 137 | return args; 138 | 139 | error1: 140 | free(args->bytes); 141 | free(args->key); 142 | error0: 143 | enif_free(args); 144 | 145 | return NULL; 146 | } 147 | 148 | ERL_NIF_TERM cb_store(ErlNifEnv* env, handle_t* handle, void* obj) 149 | { 150 | store_args_t* args = (store_args_t*)obj; 151 | 152 | struct libcouchbase_callback cb; 153 | 154 | lcb_error_t ret; 155 | 156 | lcb_store_cmd_t cmd; 157 | const lcb_store_cmd_t *commands[1]; 158 | 159 | commands[0] = &cmd; 160 | memset(&cmd, 0, sizeof(cmd)); 161 | cmd.v.v0.operation = args->operation; 162 | cmd.v.v0.key = args->key; 163 | cmd.v.v0.nkey = args->nkey; 164 | cmd.v.v0.bytes = args->bytes; 165 | cmd.v.v0.nbytes = args->nbytes; 166 | cmd.v.v0.flags = args->flags; 167 | cmd.v.v0.exptime = args->exp; 168 | cmd.v.v0.cas = args->cas; 169 | 170 | ret = lcb_store(handle->instance, &cb, 1, commands); 171 | 172 | free(args->key); 173 | free(args->bytes); 174 | 175 | if (ret != LCB_SUCCESS) { 176 | return return_lcb_error(env, ret); 177 | } 178 | 179 | lcb_wait(handle->instance); 180 | 181 | if (cb.error != LCB_SUCCESS) { 182 | return return_lcb_error(env, cb.error); 183 | } 184 | return A_OK(env); 185 | } 186 | 187 | void* cb_mget_args(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) 188 | { 189 | mget_args_t* args = (mget_args_t*)enif_alloc(sizeof(mget_args_t)); 190 | 191 | ERL_NIF_TERM* currKey; 192 | ERL_NIF_TERM tail; 193 | ErlNifBinary key_binary; 194 | 195 | if (!enif_get_list_length(env, argv[0], &args->numkeys)) goto error0; 196 | args->keys = malloc(sizeof(char*) * args->numkeys); 197 | args->nkeys = malloc(sizeof(size_t) * args->numkeys); 198 | currKey = malloc(sizeof(ERL_NIF_TERM)); 199 | tail = argv[0]; 200 | int i = 0; 201 | while(0 != enif_get_list_cell(env, tail, currKey, &tail)) { 202 | if (!enif_inspect_iolist_as_binary(env, *currKey, &key_binary)) goto error1; 203 | args->keys[i] = malloc(sizeof(char) * key_binary.size); 204 | memcpy(args->keys[i], key_binary.data, key_binary.size); 205 | args->nkeys[i] = key_binary.size; 206 | i++; 207 | } 208 | 209 | if (!enif_get_int(env, argv[1], &args->exp)) goto error1; 210 | if (!enif_get_int(env, argv[2], &args->lock)) goto error1; 211 | 212 | free(currKey); 213 | 214 | return (void*)args; 215 | 216 | int f = 0; 217 | 218 | error1: 219 | for(f = 0; f < i; f++) { 220 | free(args->keys[f]); 221 | } 222 | free(args->keys); 223 | free(args->nkeys); 224 | free(currKey); 225 | error0: 226 | enif_free(args); 227 | 228 | return NULL; 229 | } 230 | 231 | ERL_NIF_TERM cb_mget(ErlNifEnv* env, handle_t* handle, void* obj) 232 | { 233 | mget_args_t* args = (mget_args_t*)obj; 234 | 235 | struct libcouchbase_callback_m cb; 236 | 237 | lcb_error_t ret; 238 | 239 | ERL_NIF_TERM* results; 240 | ERL_NIF_TERM returnValue; 241 | ErlNifBinary databin; 242 | ErlNifBinary key_binary; 243 | unsigned int numkeys = args->numkeys; 244 | void** keys = args->keys; 245 | size_t* nkeys = args->nkeys; 246 | int exp = args->exp; 247 | int lock = args->lock; 248 | int i = 0; 249 | 250 | cb.currKey = 0; 251 | cb.ret = malloc(sizeof(struct libcouchbase_callback*) * numkeys); 252 | 253 | 254 | const lcb_get_cmd_t* commands[numkeys]; 255 | i = 0; 256 | for (; i < numkeys; i++) { 257 | lcb_get_cmd_t *get = calloc(1, sizeof(*get)); 258 | get->version = 0; 259 | get->v.v0.key = keys[i]; 260 | get->v.v0.nkey = nkeys[i]; 261 | get->v.v0.exptime = exp; 262 | get->v.v0.lock = lock; 263 | commands[i] = get; 264 | } 265 | 266 | ret = lcb_get(handle->instance, &cb, numkeys, commands); 267 | 268 | if (ret != LCB_SUCCESS) { 269 | return return_lcb_error(env, ret); 270 | } 271 | lcb_wait(handle->instance); 272 | 273 | results = malloc(sizeof(ERL_NIF_TERM) * numkeys); 274 | i = 0; 275 | for(; i < numkeys; i++) { 276 | enif_alloc_binary(cb.ret[i]->nkey, &key_binary); 277 | memcpy(key_binary.data, cb.ret[i]->key, cb.ret[i]->nkey); 278 | if (cb.ret[i]->error == LCB_SUCCESS) { 279 | enif_alloc_binary(cb.ret[i]->size, &databin); 280 | memcpy(databin.data, cb.ret[i]->data, cb.ret[i]->size); 281 | results[i] = enif_make_tuple4(env, 282 | enif_make_uint64(env, cb.ret[i]->cas), 283 | enif_make_int(env, cb.ret[i]->flag), 284 | enif_make_binary(env, &key_binary), 285 | enif_make_binary(env, &databin)); 286 | free(cb.ret[i]->data); 287 | } else { 288 | results[i] = enif_make_tuple2(env, 289 | enif_make_binary(env, &key_binary), 290 | return_lcb_error(env, cb.ret[i]->error)); 291 | } 292 | free(cb.ret[i]->key); 293 | free(cb.ret[i]); 294 | free(keys[i]); 295 | free((lcb_get_cmd_t*) commands[i]); 296 | } 297 | 298 | returnValue = enif_make_list_from_array(env, results, numkeys); 299 | 300 | free(results); 301 | free(cb.ret); 302 | free(keys); 303 | free(nkeys); 304 | 305 | return enif_make_tuple2(env, A_OK(env), returnValue); 306 | } 307 | 308 | void* cb_unlock_args(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) 309 | { 310 | unlock_args_t* args = (unlock_args_t*)enif_alloc(sizeof(unlock_args_t)); 311 | 312 | ErlNifBinary key_binary; 313 | 314 | if (!enif_inspect_iolist_as_binary(env, argv[0], &key_binary)) goto error0; 315 | args->nkey = key_binary.size; 316 | args->key = (char *) malloc(key_binary.size); 317 | memcpy(args->key, key_binary.data, key_binary.size); 318 | 319 | if (!enif_get_uint64(env, argv[1], (ErlNifUInt64*)&args->cas)) goto error1; 320 | 321 | return (void*)args; 322 | 323 | error1: 324 | free(args->key); 325 | error0: 326 | enif_free(args); 327 | 328 | return NULL; 329 | } 330 | 331 | ERL_NIF_TERM cb_unlock(ErlNifEnv* env, handle_t* handle, void* obj) 332 | { 333 | unlock_args_t* args = (unlock_args_t*)obj; 334 | 335 | struct libcouchbase_callback cb; 336 | 337 | lcb_error_t ret; 338 | 339 | lcb_unlock_cmd_t unlock; 340 | memset(&unlock, 0, sizeof(unlock)); 341 | unlock.v.v0.key = args->key; 342 | unlock.v.v0.nkey = args->nkey; 343 | unlock.v.v0.cas = args->cas; 344 | const lcb_unlock_cmd_t* commands[] = { &unlock }; 345 | ret = lcb_unlock(handle->instance, &cb, 1, commands); 346 | 347 | free(args->key); 348 | 349 | if (ret != LCB_SUCCESS) { 350 | return return_lcb_error(env, ret); 351 | } 352 | lcb_wait(handle->instance); 353 | if(cb.error != LCB_SUCCESS) { 354 | return return_lcb_error(env, cb.error); 355 | } 356 | return A_OK(env); 357 | } 358 | 359 | void* cb_mtouch_args(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) 360 | { 361 | mtouch_args_t* args = (mtouch_args_t*)enif_alloc(sizeof(mtouch_args_t)); 362 | 363 | ERL_NIF_TERM* currKey; 364 | ERL_NIF_TERM tail; 365 | ErlNifBinary key_binary; 366 | 367 | if (!enif_get_list_length(env, argv[0], &args->numkeys)) goto error0; 368 | args->keys = malloc(sizeof(char*) * args->numkeys); 369 | args->nkeys = malloc(sizeof(size_t) * args->numkeys); 370 | currKey = malloc(sizeof(ERL_NIF_TERM)); 371 | tail = argv[0]; 372 | int i = 0; 373 | while(0 != enif_get_list_cell(env, tail, currKey, &tail)) { 374 | if (!enif_inspect_iolist_as_binary(env, *currKey, &key_binary)) goto error1; 375 | args->keys[i] = malloc(sizeof(char) * key_binary.size); 376 | memcpy(args->keys[i], key_binary.data, key_binary.size); 377 | args->nkeys[i] = key_binary.size; 378 | i++; 379 | } 380 | 381 | args->exp = malloc(sizeof(int64_t) * args->numkeys); 382 | tail = argv[1]; 383 | int i2 = 0; 384 | while(0 != enif_get_list_cell(env, tail, currKey, &tail)) { 385 | if (!enif_get_long(env, *currKey, &args->exp[i2])) goto error2; 386 | i2++; 387 | } 388 | 389 | free(currKey); 390 | 391 | return (void*)args; 392 | 393 | int f = 0; 394 | 395 | error2: 396 | free(args->exp); 397 | error1: 398 | for(f = 0; f < i; f++) { 399 | free(args->keys[f]); 400 | } 401 | free(args->keys); 402 | free(args->nkeys); 403 | error0: 404 | enif_free(args); 405 | 406 | return NULL; 407 | } 408 | 409 | ERL_NIF_TERM cb_mtouch(ErlNifEnv* env, handle_t* handle, void* obj) 410 | { 411 | mtouch_args_t* args = (mtouch_args_t*)obj; 412 | 413 | struct libcouchbase_callback_m cb; 414 | int i = 0; 415 | lcb_error_t ret; 416 | 417 | ERL_NIF_TERM* results; 418 | ERL_NIF_TERM returnValue; 419 | 420 | ErlNifBinary key_binary; 421 | 422 | cb.currKey = 0; 423 | cb.ret = malloc(sizeof(struct libcouchbase_callback*) * args->numkeys); 424 | 425 | const lcb_touch_cmd_t* commands[args->numkeys]; 426 | i = 0; 427 | for (; i < args->numkeys; i++) { 428 | lcb_touch_cmd_t* touch = calloc(1, sizeof(*touch)); 429 | touch->version = 0; 430 | touch->v.v0.key = args->keys[i]; 431 | touch->v.v0.nkey = args->nkeys[i]; 432 | touch->v.v0.exptime = args->exp[i]; 433 | commands[i] = touch; 434 | } 435 | 436 | ret = lcb_touch(handle->instance, &cb, args->numkeys, commands); 437 | 438 | if (ret != LCB_SUCCESS) { 439 | return return_lcb_error(env, ret); 440 | } 441 | lcb_wait(handle->instance); 442 | 443 | results = malloc(sizeof(ERL_NIF_TERM) * args->numkeys); 444 | i = 0; 445 | for(; i < args->numkeys; i++) { 446 | enif_alloc_binary(cb.ret[i]->nkey, &key_binary); 447 | memcpy(key_binary.data, cb.ret[i]->key, cb.ret[i]->nkey); 448 | ERL_NIF_TERM key = enif_make_binary(env, &key_binary); 449 | if (cb.ret[i]->error == LCB_SUCCESS) { 450 | results[i] = enif_make_tuple2(env, 451 | key, 452 | A_OK(env)); 453 | } else { 454 | results[i] = enif_make_tuple2(env, 455 | key, 456 | return_lcb_error(env, cb.ret[i]->error)); 457 | } 458 | free(cb.ret[i]->key); 459 | free(cb.ret[i]); 460 | free(args->keys[i]); 461 | free((lcb_touch_cmd_t*) commands[i]); 462 | } 463 | returnValue = enif_make_list_from_array(env, results, args->numkeys); 464 | 465 | free(results); 466 | free(cb.ret); 467 | free(args->keys); 468 | free(args->exp); 469 | free(args->nkeys); 470 | 471 | return enif_make_tuple2(env, A_OK(env), returnValue); 472 | } 473 | 474 | void* cb_arithmetic_args(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) 475 | { 476 | arithmetic_args_t* args = (arithmetic_args_t*)enif_alloc(sizeof(arithmetic_args_t)); 477 | 478 | ErlNifBinary key_binary; 479 | 480 | if (!enif_inspect_iolist_as_binary(env, argv[0], &key_binary)) goto error0; 481 | args->key = malloc(sizeof(char) * key_binary.size); 482 | memcpy(args->key, key_binary.data, key_binary.size); 483 | args->nkey = key_binary.size; 484 | if (!enif_get_int64(env, argv[1], (ErlNifSInt64*)&args->delta)) goto error1; 485 | if (!enif_get_uint64(env, argv[2], (ErlNifUInt64 *)&args->exp)) goto error1; 486 | if (!enif_get_int(env, argv[3], &args->create)) goto error1; 487 | if (!enif_get_uint64(env, argv[4], (ErlNifUInt64 *)&args->initial)) goto error1; 488 | 489 | return (void*)args; 490 | 491 | error1: 492 | free(args->key); 493 | error0: 494 | enif_free(args); 495 | 496 | return NULL; 497 | } 498 | 499 | ERL_NIF_TERM cb_arithmetic(ErlNifEnv* env, handle_t* handle, void* obj) 500 | { 501 | arithmetic_args_t* args = (arithmetic_args_t*)obj; 502 | 503 | struct libcouchbase_callback cb; 504 | 505 | lcb_error_t ret; //for checking responses 506 | 507 | lcb_arithmetic_cmd_t arithmetic; 508 | const lcb_arithmetic_cmd_t* commands[1]; 509 | commands[0] = &arithmetic; 510 | memset(&arithmetic, 0, sizeof(arithmetic)); 511 | arithmetic.v.v0.key = args->key; 512 | arithmetic.v.v0.nkey = args->nkey; 513 | arithmetic.v.v0.initial = args->initial; 514 | arithmetic.v.v0.create = args->create; 515 | arithmetic.v.v0.delta = args->delta; 516 | arithmetic.v.v0.exptime = args->exp; 517 | ret = lcb_arithmetic(handle->instance, &cb, 1, commands); 518 | 519 | free(args->key); 520 | if (ret != LCB_SUCCESS) { 521 | return return_lcb_error(env, ret); 522 | } 523 | lcb_wait(handle->instance); 524 | if(cb.error != LCB_SUCCESS) { 525 | return return_lcb_error(env, cb.error); 526 | } 527 | return enif_make_tuple2(env, A_OK(env), return_value(env, &cb)); 528 | } 529 | 530 | void* cb_remove_args(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) 531 | { 532 | remove_args_t* args = (remove_args_t*)enif_alloc(sizeof(remove_args_t)); 533 | 534 | ErlNifBinary key_binary; 535 | 536 | if (!enif_inspect_iolist_as_binary(env, argv[0], &key_binary)) goto error0; 537 | args->key = malloc(sizeof(char) * key_binary.size); 538 | memcpy(args->key, key_binary.data, key_binary.size); 539 | args->nkey = key_binary.size; 540 | 541 | if (!enif_get_int(env, argv[1], &args->cas)) goto error1; 542 | 543 | return (void*)args; 544 | 545 | error1: 546 | free(args->key); 547 | error0: 548 | enif_free(args); 549 | 550 | return NULL; 551 | } 552 | 553 | ERL_NIF_TERM cb_remove(ErlNifEnv* env, handle_t* handle, void* obj) 554 | { 555 | remove_args_t* args = (remove_args_t*)obj; 556 | 557 | struct libcouchbase_callback cb; 558 | 559 | lcb_error_t ret; //for checking responses 560 | 561 | lcb_remove_cmd_t remove; 562 | const lcb_remove_cmd_t* commands[1]; 563 | commands[0] = &remove; 564 | memset(&remove, 0, sizeof(remove)); 565 | remove.v.v0.key = args->key; 566 | remove.v.v0.nkey = args->nkey; 567 | remove.v.v0.cas = args->cas; 568 | 569 | ret = lcb_remove(handle->instance, &cb, 1, commands); 570 | 571 | free(args->key); 572 | if (ret != LCB_SUCCESS) { 573 | return return_lcb_error(env, ret); 574 | } 575 | 576 | lcb_wait(handle->instance); 577 | 578 | if(cb.error != LCB_SUCCESS) { 579 | return return_lcb_error(env, cb.error); 580 | } 581 | 582 | return A_OK(env); 583 | } 584 | 585 | void* cb_http_args(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) 586 | { 587 | http_args_t* args = (http_args_t*)enif_alloc(sizeof(http_args_t)); 588 | ErlNifBinary path; 589 | ErlNifBinary body; 590 | ErlNifBinary content_type; 591 | 592 | if (!enif_inspect_iolist_as_binary(env, argv[0], &path)) goto error0; 593 | args->path = (char *)enif_alloc((path.size + 1) * sizeof(char)); 594 | memset(args->path, 0, path.size + 1); 595 | memcpy(args->path, path.data, path.size); 596 | 597 | if (!enif_inspect_iolist_as_binary(env, argv[1], &body)) goto error1; 598 | args->body = (char *)enif_alloc((body.size + 1) * sizeof(char)); 599 | memset(args->body, 0, body.size + 1); 600 | memcpy(args->body, body.data, body.size); 601 | 602 | if (!enif_inspect_iolist_as_binary(env, argv[2], &content_type)) goto error2; 603 | args->content_type = (char *)enif_alloc((content_type.size + 1) * sizeof(char)); 604 | memset(args->content_type, 0, content_type.size + 1); 605 | memcpy(args->content_type, content_type.data, content_type.size); 606 | 607 | if (!enif_get_int(env, argv[3], (int*)&args->method)) goto error3; 608 | if (!enif_get_int(env, argv[4], (int*)&args->type)) goto error3; 609 | 610 | return (void*)args; 611 | 612 | error3: 613 | enif_free(args->content_type); 614 | error2: 615 | enif_free(args->body); 616 | error1: 617 | enif_free(args->path); 618 | error0: 619 | enif_free(args); 620 | return NULL; 621 | } 622 | 623 | ERL_NIF_TERM cb_http(ErlNifEnv* env, handle_t* handle, void* obj) 624 | { 625 | http_args_t* args = (http_args_t*)obj; 626 | 627 | struct libcouchbase_callback_http cb = {0}; 628 | lcb_error_t ret; 629 | lcb_http_request_t req; 630 | 631 | lcb_http_cmd_t cmd; 632 | cmd.version = 0; 633 | cmd.v.v0.path = args->path; 634 | cmd.v.v0.npath = strlen(args->path); 635 | cmd.v.v0.body = args->body; 636 | cmd.v.v0.nbody = strlen(args->body); 637 | cmd.v.v0.method = args->method; 638 | cmd.v.v0.chunked = 0; // no support for chunking 639 | cmd.v.v0.content_type = args->content_type; 640 | 641 | ret = lcb_make_http_request(handle->instance, &cb, args->type, &cmd, &req); 642 | 643 | if (ret != LCB_SUCCESS) { 644 | return return_lcb_error(env, ret); 645 | } 646 | 647 | lcb_wait(handle->instance); 648 | 649 | enif_free(args->content_type); 650 | enif_free(args->body); 651 | enif_free(args->path); 652 | 653 | if(cb.ret.error != LCB_SUCCESS) { 654 | return return_lcb_error(env, cb.ret.error); 655 | } 656 | 657 | ErlNifBinary value_binary; 658 | enif_alloc_binary(cb.ret.size, &value_binary); 659 | memcpy(value_binary.data, cb.ret.data, cb.ret.size); 660 | free(cb.ret.data); 661 | free(cb.ret.key); 662 | return enif_make_tuple3(env, A_OK(env), enif_make_int(env, cb.status), enif_make_binary(env, &value_binary)); 663 | } 664 | 665 | void* cb_n1ql_args(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) 666 | { 667 | n1ql_args_t* args = (n1ql_args_t*)enif_alloc(sizeof(n1ql_args_t)); 668 | 669 | ERL_NIF_TERM* currParam; 670 | ERL_NIF_TERM tail; 671 | ErlNifBinary query_binary; 672 | 673 | if (!enif_inspect_iolist_as_binary(env, argv[0], &query_binary)) goto error0; 674 | args->query = malloc(sizeof(char) * (query_binary.size + 1)); 675 | memset(args->query, 0, query_binary.size + 1); 676 | memcpy(args->query, query_binary.data, query_binary.size); 677 | args->nquery = query_binary.size; 678 | 679 | if (!enif_get_list_length(env, argv[1], &args->numparams)) goto error1; 680 | args->params = malloc(sizeof(char*) * args->numparams); 681 | args->nparams = malloc(sizeof(size_t) * args->numparams); 682 | currParam = malloc(sizeof(ERL_NIF_TERM)); 683 | tail = argv[1]; 684 | int i = 0; 685 | while(0 != enif_get_list_cell(env, tail, currParam, &tail)) { 686 | ErlNifBinary param_binary; 687 | if (!enif_inspect_iolist_as_binary(env, *currParam, ¶m_binary)) goto error2; 688 | args->params[i] = malloc(sizeof(char) * param_binary.size); 689 | memcpy(args->params[i], param_binary.data, param_binary.size); 690 | args->nparams[i] = param_binary.size; 691 | i++; 692 | } 693 | 694 | if (!enif_get_int(env, argv[2], (int*)&args->prepared)) goto error2; 695 | 696 | free(currParam); 697 | return (void*)args; 698 | 699 | int f = 0; 700 | error2: 701 | for(f = 0; f < i; f++) { 702 | free(args->params[f]); 703 | } 704 | free(args->query); 705 | free(args->params); 706 | free(args->nparams); 707 | free(currParam); 708 | error1: 709 | free(args->query); 710 | enif_free(args); 711 | error0: 712 | enif_free(args); 713 | 714 | return NULL; 715 | } 716 | 717 | ERL_NIF_TERM cb_n1ql(ErlNifEnv* env, handle_t* handle, void* obj) 718 | { 719 | n1ql_args_t* args = (n1ql_args_t*)obj; 720 | lcb_error_t ret; 721 | int f = 0; 722 | lcb_N1QLPARAMS *params = lcb_n1p_new(); 723 | lcb_CMDN1QL cmd = { 0 }; 724 | struct libcouchbase_callback_n1ql cb; 725 | 726 | if (args->prepared) { 727 | cmd.cmdflags |= LCB_CMDN1QL_F_PREPCACHE; 728 | } 729 | 730 | if ((ret = lcb_n1p_setstmtz(params, args->query)) != LCB_SUCCESS) goto error0; 731 | 732 | for(f = 0; f < args->numparams; f++) { 733 | if ((ret = lcb_n1p_posparam(params, args->params[f], args->nparams[f])) != LCB_SUCCESS) goto error0; 734 | } 735 | 736 | cb.currrow = 0; 737 | cb.size = 5; 738 | cb.ret = malloc(sizeof(struct libcouchbase_callback*) * 1); 739 | cb.meta = malloc(sizeof(struct libcouchbase_callback*) * 1); 740 | cmd.callback = n1ql_callback; 741 | 742 | if ((ret = lcb_n1p_mkcmd(params, &cmd)) != LCB_SUCCESS) goto error1; 743 | if ((ret = lcb_n1ql_query(handle->instance, &cb, &cmd)) != LCB_SUCCESS) goto error1; 744 | lcb_n1p_free(params); 745 | lcb_wait(handle->instance); 746 | 747 | for(f = 0; f < args->numparams; f++) { 748 | free(args->params[f]); 749 | } 750 | free(args->query); 751 | free(args->params); 752 | free(args->nparams); 753 | 754 | ERL_NIF_TERM* results; 755 | ErlNifBinary databin; 756 | ERL_NIF_TERM metaValue; 757 | ERL_NIF_TERM returnValue; 758 | results = malloc(sizeof(ERL_NIF_TERM) * cb.currrow); 759 | 760 | // Add meta data section 761 | enif_alloc_binary(cb.meta->size, &databin); 762 | memcpy(databin.data, cb.meta->data, cb.meta->size); 763 | metaValue = enif_make_binary(env, &databin); 764 | free(cb.meta->data); 765 | free(cb.meta); 766 | 767 | int i = 0; 768 | for(; i < cb.currrow; i++) { 769 | if (cb.ret[i]->error == LCB_SUCCESS) { 770 | enif_alloc_binary(cb.ret[i]->size, &databin); 771 | memcpy(databin.data, cb.ret[i]->data, cb.ret[i]->size); 772 | results[i] = enif_make_binary(env, &databin); 773 | } else { 774 | results[i] = enif_make_tuple1(env, 775 | return_lcb_error(env, cb.ret[i]->error)); 776 | } 777 | free(cb.ret[i]->data); 778 | free(cb.ret[i]); 779 | } 780 | free(cb.ret); 781 | 782 | returnValue = enif_make_list_from_array(env, results, cb.currrow); 783 | free(results); 784 | return enif_make_tuple3(env, A_OK(env), metaValue, returnValue); 785 | 786 | error0: 787 | free(args->query); 788 | free(args->params); 789 | free(args->nparams); 790 | lcb_n1p_free(params); 791 | for(f = 0; f < args->numparams; f++) { 792 | free(args->params[f]); 793 | } 794 | error1: 795 | free(cb.meta->data); 796 | free(cb.meta); 797 | free(cb.ret); 798 | free(args->query); 799 | free(args->params); 800 | free(args->nparams); 801 | lcb_n1p_free(params); 802 | for(f = 0; f < args->numparams; f++) { 803 | free(args->params[f]); 804 | } 805 | 806 | return return_lcb_error(env, ret); 807 | } 808 | 809 | ERL_NIF_TERM return_lcb_error(ErlNifEnv* env, int const value) { 810 | switch (value) { 811 | case LCB_SUCCESS: 812 | return enif_make_tuple2(env, A_ERROR(env), enif_make_atom(env, "success")); 813 | case LCB_AUTH_CONTINUE: 814 | return enif_make_tuple2(env, A_ERROR(env), enif_make_atom(env, "auth_continue")); 815 | case LCB_AUTH_ERROR: 816 | return enif_make_tuple2(env, A_ERROR(env), enif_make_atom(env, "auth_error")); 817 | case LCB_DELTA_BADVAL: 818 | return enif_make_tuple2(env, A_ERROR(env), enif_make_atom(env, "delta_badval")); 819 | case LCB_E2BIG: 820 | return enif_make_tuple2(env, A_ERROR(env), enif_make_atom(env, "e2big")); 821 | case LCB_EBUSY: 822 | return enif_make_tuple2(env, A_ERROR(env), enif_make_atom(env, "ebusy")); 823 | case LCB_EINTERNAL: 824 | return enif_make_tuple2(env, A_ERROR(env), enif_make_atom(env, "einternal")); 825 | case LCB_EINVAL: 826 | return enif_make_tuple2(env, A_ERROR(env), enif_make_atom(env, "einval")); 827 | case LCB_ENOMEM: 828 | return enif_make_tuple2(env, A_ERROR(env), enif_make_atom(env, "enomem")); 829 | case LCB_ERANGE: 830 | return enif_make_tuple2(env, A_ERROR(env), enif_make_atom(env, "erange")); 831 | case LCB_ERROR: 832 | return enif_make_tuple2(env, A_ERROR(env), enif_make_atom(env, "error")); 833 | case LCB_ETMPFAIL: 834 | return enif_make_tuple2(env, A_ERROR(env), enif_make_atom(env, "etmpfail")); 835 | case LCB_KEY_EEXISTS: 836 | return enif_make_tuple2(env, A_ERROR(env), enif_make_atom(env, "key_eexists")); 837 | case LCB_KEY_ENOENT: 838 | return enif_make_tuple2(env, A_ERROR(env), enif_make_atom(env, "key_enoent")); 839 | case LCB_NETWORK_ERROR: 840 | return enif_make_tuple2(env, A_ERROR(env), enif_make_atom(env, "network_error")); 841 | case LCB_NOT_MY_VBUCKET: 842 | return enif_make_tuple2(env, A_ERROR(env), enif_make_atom(env, "not_my_vbucket")); 843 | case LCB_NOT_STORED: 844 | return enif_make_tuple2(env, A_ERROR(env), enif_make_atom(env, "not_stored")); 845 | case LCB_NOT_SUPPORTED: 846 | return enif_make_tuple2(env, A_ERROR(env), enif_make_atom(env, "not_supported")); 847 | case LCB_UNKNOWN_COMMAND: 848 | return enif_make_tuple2(env, A_ERROR(env), enif_make_atom(env, "unknown_command")); 849 | case LCB_UNKNOWN_HOST: 850 | return enif_make_tuple2(env, A_ERROR(env), enif_make_atom(env, "unknown_host")); 851 | case LCB_PROTOCOL_ERROR: 852 | return enif_make_tuple2(env, A_ERROR(env), enif_make_atom(env, "protocol_error")); 853 | case LCB_ETIMEDOUT: 854 | return enif_make_tuple2(env, A_ERROR(env), enif_make_atom(env, "etimedout")); 855 | case LCB_CONNECT_ERROR: 856 | return enif_make_tuple2(env, A_ERROR(env), enif_make_atom(env, "connect_error")); 857 | case LCB_BUCKET_ENOENT: 858 | return enif_make_tuple2(env, A_ERROR(env), enif_make_atom(env, "bucket_enoent")); 859 | case LCB_CLIENT_ENOMEM: 860 | return enif_make_tuple2(env, A_ERROR(env), enif_make_atom(env, "client_enomem")); 861 | default: 862 | return enif_make_tuple2(env, A_ERROR(env), enif_make_tuple2(env, enif_make_atom(env, "unknown_error"), enif_make_int(env, value))); 863 | } 864 | } 865 | 866 | ERL_NIF_TERM return_value(ErlNifEnv* env, void * cookie) { 867 | struct libcouchbase_callback *cb; 868 | cb = (struct libcouchbase_callback *)cookie; 869 | ErlNifBinary value_binary; 870 | ERL_NIF_TERM term; 871 | enif_alloc_binary(cb->size, &value_binary); 872 | memcpy(value_binary.data, cb->data, cb->size); 873 | term = enif_make_tuple3(env, enif_make_int(env, cb->cas), 874 | enif_make_int(env, cb->flag), 875 | enif_make_binary(env, &value_binary)); 876 | free(cb->data); 877 | return term; 878 | } 879 | -------------------------------------------------------------------------------- /c_src/cb.h: -------------------------------------------------------------------------------- 1 | #ifndef CB_H 2 | #define CB_H 3 | 4 | #include "erl_nif.h" 5 | #include "cberl.h" 6 | 7 | typedef struct connect_args { 8 | char* host; 9 | char* user; 10 | char* pass; 11 | char* bucket; 12 | } connect_args_t; 13 | 14 | typedef struct store_args { 15 | int operation; 16 | unsigned int nkey; 17 | lcb_uint32_t flags; 18 | int exp; 19 | lcb_cas_t cas; 20 | void * key; 21 | void * bytes; 22 | lcb_size_t nbytes; 23 | } store_args_t; 24 | 25 | typedef struct mget_args { 26 | unsigned int numkeys; 27 | void** keys; 28 | size_t* nkeys; 29 | int exp; 30 | int lock; 31 | } mget_args_t; 32 | 33 | typedef struct unlock_args { 34 | void * key; 35 | unsigned int nkey; 36 | lcb_cas_t cas; 37 | } unlock_args_t; 38 | 39 | typedef struct mtouch_args { 40 | void** keys; 41 | size_t* nkeys; 42 | long *exp; 43 | unsigned int numkeys; 44 | } mtouch_args_t; 45 | 46 | typedef struct arithmetic_args { 47 | void * key; 48 | unsigned int nkey; 49 | int64_t delta; 50 | uint64_t exp; 51 | int create; 52 | uint64_t initial; 53 | } arithmetic_args_t; 54 | 55 | typedef struct remove_args_t { 56 | void * key; 57 | unsigned int nkey; 58 | int cas; 59 | } remove_args_t; 60 | 61 | typedef struct http_args { 62 | char *path; 63 | char *body; 64 | lcb_http_method_t method; 65 | char *content_type; 66 | lcb_http_type_t type; 67 | } http_args_t; 68 | 69 | typedef struct n1ql_args { 70 | char *query; 71 | unsigned int nquery; 72 | void** params; 73 | unsigned int numparams; 74 | size_t* nparams; 75 | unsigned int prepared; 76 | } n1ql_args_t; 77 | 78 | typedef struct n1ql_param { 79 | char *parameter; 80 | char *value; 81 | unsigned int nparameter; 82 | unsigned int nvalue; 83 | } n1ql_param_t; 84 | 85 | void* cb_connect_args(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); 86 | ERL_NIF_TERM cb_connect(ErlNifEnv* env, handle_t* handle, void* obj); 87 | void* cb_store_args(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); 88 | ERL_NIF_TERM cb_store(ErlNifEnv* env, handle_t* handle, void* obj); 89 | void* cb_mget_args(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); 90 | ERL_NIF_TERM cb_mget(ErlNifEnv* env, handle_t* handle, void* obj); 91 | void* cb_unlock_args(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); 92 | ERL_NIF_TERM cb_unlock(ErlNifEnv* env, handle_t* handle, void* obj); 93 | void* cb_mtouch_args(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); 94 | ERL_NIF_TERM cb_mtouch(ErlNifEnv* env, handle_t* handle, void* obj); 95 | void* cb_arithmetic_args(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); 96 | ERL_NIF_TERM cb_arithmetic(ErlNifEnv* env, handle_t* handle, void* obj); 97 | void* cb_remove_args(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); 98 | ERL_NIF_TERM cb_remove(ErlNifEnv* env, handle_t* handle, void* obj); 99 | void* cb_http_args(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); 100 | ERL_NIF_TERM cb_http(ErlNifEnv* env, handle_t* handle, void* obj); 101 | void* cb_n1ql_args(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); 102 | ERL_NIF_TERM cb_n1ql(ErlNifEnv* env, handle_t* handle, void* obj); 103 | 104 | ERL_NIF_TERM return_lcb_error(ErlNifEnv* env, int const value); 105 | ERL_NIF_TERM return_value(ErlNifEnv* env, void * cookie); 106 | 107 | #endif 108 | -------------------------------------------------------------------------------- /c_src/cberl.h: -------------------------------------------------------------------------------- 1 | #ifndef CBERL_H 2 | #define CBERL_H 3 | 4 | #include 5 | #include 6 | #include "queue.h" 7 | #include "erl_nif.h" 8 | 9 | #define A_OK(env) enif_make_atom(env, "ok") 10 | #define A_ERROR(env) enif_make_atom(env, "error") 11 | 12 | #define NIF(name) ERL_NIF_TERM name(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) 13 | 14 | #define assert_badarg(S, Env) if (! S) { return enif_make_badarg(env); } 15 | 16 | typedef struct handle { 17 | ErlNifTid thread; 18 | ErlNifThreadOpts* thread_opts; 19 | queue_t *queue; 20 | ERL_NIF_TERM (*calltable[9])(ErlNifEnv* env, struct handle* handle, void* obj); 21 | void* (*args_calltable[9])(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); 22 | lcb_t instance; 23 | } handle_t; 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /c_src/cberl_nif.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "cberl.h" 4 | #include "cberl_nif.h" 5 | #include "cb.h" 6 | 7 | static ErlNifResourceType* cberl_handle = NULL; 8 | 9 | static void cberl_handle_cleanup(ErlNifEnv* env, void* arg) {} 10 | 11 | static int load(ErlNifEnv* env, void** priv, ERL_NIF_TERM load_info) 12 | { 13 | ErlNifResourceFlags flags = ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER; 14 | cberl_handle = enif_open_resource_type(env, "cberl_nif", 15 | "cberl_handle", 16 | &cberl_handle_cleanup, 17 | flags, 0); 18 | return 0; 19 | } 20 | 21 | static int upgrade(ErlNifEnv* env, void** priv, void** old_priv_data, ERL_NIF_TERM load_info) 22 | { 23 | return load(env, priv, load_info); 24 | } 25 | 26 | NIF(cberl_nif_new) 27 | { 28 | handle_t* handle = enif_alloc_resource(cberl_handle, sizeof(handle_t)); 29 | handle->queue = queue_new(); 30 | 31 | handle->calltable[CMD_CONNECT] = cb_connect; 32 | handle->args_calltable[CMD_CONNECT] = cb_connect_args; 33 | handle->calltable[CMD_STORE] = cb_store; 34 | handle->args_calltable[CMD_STORE] = cb_store_args; 35 | handle->calltable[CMD_MGET] = cb_mget; 36 | handle->args_calltable[CMD_MGET] = cb_mget_args; 37 | handle->calltable[CMD_UNLOCK] = cb_unlock; 38 | handle->args_calltable[CMD_UNLOCK] = cb_unlock_args; 39 | handle->calltable[CMD_MTOUCH] = cb_mtouch; 40 | handle->args_calltable[CMD_MTOUCH] = cb_mtouch_args; 41 | handle->calltable[CMD_ARITHMETIC] = cb_arithmetic; 42 | handle->args_calltable[CMD_ARITHMETIC] = cb_arithmetic_args; 43 | handle->calltable[CMD_REMOVE] = cb_remove; 44 | handle->args_calltable[CMD_REMOVE] = cb_remove_args; 45 | handle->calltable[CMD_HTTP] = cb_http; 46 | handle->args_calltable[CMD_HTTP] = cb_http_args; 47 | handle->calltable[CMD_N1QL] = cb_n1ql; 48 | handle->args_calltable[CMD_N1QL] = cb_n1ql_args; 49 | 50 | handle->thread_opts = enif_thread_opts_create("thread_opts"); 51 | 52 | if (enif_thread_create("", &handle->thread, worker, handle, handle->thread_opts) != 0) { 53 | return enif_make_atom(env, "error"); 54 | } 55 | 56 | return enif_make_tuple2(env, enif_make_atom(env, "ok"), enif_make_resource(env, handle)); 57 | } 58 | 59 | NIF(cberl_nif_control) 60 | { 61 | handle_t* handle; 62 | 63 | assert_badarg(enif_get_resource(env, argv[0], cberl_handle, (void **) &handle), env); 64 | 65 | unsigned int len; 66 | enif_get_atom_length(env, argv[1], &len, ERL_NIF_LATIN1); 67 | int cmd; 68 | enif_get_int(env, argv[1], &cmd); 69 | 70 | if (cmd == -1) { 71 | return enif_make_badarg(env); 72 | } 73 | 74 | ErlNifPid* pid = (ErlNifPid*)enif_alloc(sizeof(ErlNifPid)); 75 | task_t* task = (task_t*)enif_alloc(sizeof(task_t)); 76 | 77 | unsigned arg_length; 78 | if (!enif_get_list_length(env, argv[2], &arg_length)) { 79 | enif_free(pid); 80 | enif_free(task); 81 | return enif_make_badarg(env); 82 | } 83 | 84 | ERL_NIF_TERM nargs = argv[2]; 85 | ERL_NIF_TERM head, tail; 86 | ERL_NIF_TERM* new_argv = (ERL_NIF_TERM*)enif_alloc(sizeof(ERL_NIF_TERM) * arg_length); 87 | int i = 0; 88 | while (enif_get_list_cell(env, nargs, &head, &tail)) { 89 | new_argv[i] = head; 90 | i++; 91 | nargs = tail; 92 | } 93 | 94 | void* args = handle->args_calltable[cmd](env, arg_length, new_argv); 95 | 96 | enif_free(new_argv); 97 | 98 | if(args == NULL) { 99 | enif_free(pid); 100 | enif_free(task); 101 | return enif_make_badarg(env); 102 | } 103 | 104 | enif_self(env, pid); 105 | 106 | task->pid = pid; 107 | task->cmd = cmd; 108 | task->args = args; 109 | 110 | queue_put(handle->queue, task); 111 | 112 | return A_OK(env); 113 | } 114 | 115 | NIF(cberl_nif_destroy) { 116 | handle_t * handle; 117 | void* resp; 118 | assert_badarg(enif_get_resource(env, argv[0], cberl_handle, (void **) &handle), env); 119 | queue_put(handle->queue, NULL); // push NULL into our queue so the thread will join 120 | enif_thread_join(handle->thread, &resp); 121 | queue_destroy(handle->queue); 122 | enif_thread_opts_destroy(handle->thread_opts); 123 | lcb_destroy(handle->instance); 124 | enif_release_resource(handle); 125 | return A_OK(env); 126 | } 127 | 128 | static void* worker(void *obj) 129 | { 130 | handle_t* handle = (handle_t*)obj; 131 | 132 | task_t* task; 133 | ErlNifEnv* env = enif_alloc_env(); 134 | 135 | while ((task = (task_t*)queue_get(handle->queue)) != NULL) { 136 | ERL_NIF_TERM result = handle->calltable[task->cmd](env, handle, task->args); 137 | enif_send(NULL, task->pid, env, result); 138 | enif_free(task->pid); 139 | enif_free(task->args); 140 | enif_free(task); 141 | enif_clear_env(env); 142 | } 143 | 144 | return NULL; 145 | } 146 | 147 | static ErlNifFunc nif_funcs[] = { 148 | {"new", 0, cberl_nif_new}, 149 | {"control", 3, cberl_nif_control}, 150 | {"destroy", 1, cberl_nif_destroy} 151 | }; 152 | 153 | ERL_NIF_INIT(cberl_nif, nif_funcs, load, NULL, upgrade, NULL); 154 | -------------------------------------------------------------------------------- /c_src/cberl_nif.h: -------------------------------------------------------------------------------- 1 | #ifndef CBERL_NIF_H 2 | #define CBERL_NIF_H 3 | 4 | #include "erl_nif.h" 5 | #include "cberl.h" 6 | 7 | // Command enum 8 | #define CMD_CONNECT 0 9 | #define CMD_STORE 1 10 | #define CMD_MGET 2 11 | #define CMD_UNLOCK 3 12 | #define CMD_MTOUCH 4 13 | #define CMD_ARITHMETIC 5 14 | #define CMD_REMOVE 6 15 | #define CMD_HTTP 7 16 | #define CMD_N1QL 8 17 | 18 | typedef struct task { 19 | ErlNifPid* pid; 20 | unsigned int cmd; 21 | void *args; 22 | } task_t; 23 | 24 | static void* worker(void *obj); 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /c_src/queue.c: -------------------------------------------------------------------------------- 1 | #include "erl_nif.h" 2 | #include "queue.h" 3 | #include 4 | 5 | queue_t* queue_new(void) 6 | { 7 | queue_t* queue = (queue_t*)enif_alloc(sizeof(queue_t)); 8 | queue->head = NULL; 9 | queue->tail = NULL; 10 | queue->mutex = enif_mutex_create("queue_mutex"); 11 | queue->cond = enif_cond_create("queue_cond"); 12 | return queue; 13 | } 14 | 15 | void queue_put(queue_t* queue, void *data) 16 | { 17 | queue_item_t* item = (queue_item_t*)enif_alloc(sizeof(queue_item_t)); 18 | item->next = NULL; 19 | item->data = data; 20 | 21 | enif_mutex_lock(queue->mutex); 22 | 23 | if (queue->tail != NULL) { 24 | queue->tail->next = item; 25 | } 26 | 27 | queue->tail = item; 28 | 29 | if (queue->head == NULL) { 30 | queue->head = queue->tail; 31 | } 32 | 33 | enif_cond_signal(queue->cond); 34 | enif_mutex_unlock(queue->mutex); 35 | } 36 | 37 | void* queue_get(queue_t* queue) 38 | { 39 | queue_item_t* item; 40 | 41 | enif_mutex_lock(queue->mutex); 42 | 43 | // Block until theres something in the queue 44 | while (queue->head == NULL) { 45 | enif_cond_wait(queue->cond, queue->mutex); 46 | } 47 | 48 | item = queue->head; 49 | queue->head = queue->head->next; 50 | item->next = NULL; 51 | 52 | if (queue->head == NULL) { 53 | queue->tail = NULL; 54 | } 55 | 56 | enif_mutex_unlock(queue->mutex); 57 | 58 | void* data = item->data; 59 | 60 | enif_free(item); 61 | 62 | return data; 63 | } 64 | 65 | void queue_destroy(queue_t* queue) 66 | { 67 | enif_mutex_destroy(queue->mutex); 68 | enif_cond_destroy(queue->cond); 69 | enif_free(queue); 70 | } -------------------------------------------------------------------------------- /c_src/queue.h: -------------------------------------------------------------------------------- 1 | #ifndef QUEUE_H 2 | #define QUEUE_H 3 | 4 | #include "erl_nif.h" 5 | 6 | typedef struct queue_item { 7 | struct queue_item* next; 8 | void* data; 9 | } queue_item_t; 10 | 11 | typedef struct queue { 12 | queue_item_t *head; 13 | queue_item_t *tail; 14 | ErlNifMutex* mutex; 15 | ErlNifCond* cond; 16 | } queue_t; 17 | 18 | queue_t* queue_new(void); 19 | void queue_put(queue_t* queue, void *data); 20 | void* queue_get(queue_t* queue); 21 | void queue_destroy(queue_t* queue); 22 | 23 | #endif -------------------------------------------------------------------------------- /include/cberl.hrl: -------------------------------------------------------------------------------- 1 | -define('CBE_ADD', 1). 2 | -define('CBE_REPLACE', 2). 3 | -define('CBE_SET', 3). 4 | -define('CBE_APPEND', 4). 5 | -define('CBE_PREPEND', 5). 6 | 7 | -define('CMD_CONNECT', 0). 8 | -define('CMD_STORE', 1). 9 | -define('CMD_MGET', 2). 10 | -define('CMD_UNLOCK', 3). 11 | -define('CMD_MTOUCH', 4). 12 | -define('CMD_ARITHMETIC', 5). 13 | -define('CMD_REMOVE', 6). 14 | -define('CMD_HTTP', 7). 15 | -define('CMD_N1QL', 8). 16 | 17 | -type handle() :: binary(). 18 | 19 | -record(instance, {handle :: handle(), 20 | bucketname :: string(), 21 | transcoder :: module(), 22 | connected :: true | false, 23 | opts :: list()}). 24 | 25 | -type key() :: string(). 26 | -type value() :: string() | list() | integer() | binary(). 27 | -type operation_type() :: add | replace | set | append | prepend. 28 | -type instance() :: #instance{}. 29 | -type http_type() :: view | management | raw. 30 | -type http_method() :: get | post | put | delete. 31 | -------------------------------------------------------------------------------- /rebar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chitika/cberl/400190257bfc290def84491bdbf082507e78e5c5/rebar -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {port_env, [ 2 | {"DRV_LDFLAGS", "-lcouchbase -shared $ERL_LDFLAGS"}, 3 | {"darwin", "DRV_LDFLAGS", "-lcouchbase -bundle -flat_namespace -undefined suppress $ERL_LDFLAGS"}, 4 | {"DRV_CFLAGS", "-Ic_src -g -Wall -fPIC $ERL_CFLAGS"} 5 | ]}. 6 | 7 | {port_specs, [{"priv/cberl_drv.so", ["c_src/*.c"]}]}. 8 | 9 | {deps, [ 10 | {'jiffy', "", {git, "git://github.com/davisp/jiffy.git", {branch, master}}}, 11 | {'poolboy', "", {git, "git://github.com/devinus/poolboy.git", {branch, master}}} 12 | ]}. 13 | 14 | {erl_opts, [debug_info, warnings_as_errors]}. 15 | {eunit_opts, [verbose]}. 16 | {cover_enabled, true}. 17 | -------------------------------------------------------------------------------- /src/cberl.app.src: -------------------------------------------------------------------------------- 1 | {application, cberl, 2 | [ 3 | {description, ""}, 4 | {vsn, "1.0.6"}, 5 | {registered, []}, 6 | {applications, [ 7 | kernel, 8 | stdlib, 9 | poolboy, 10 | jiffy 11 | ]}, 12 | {mod, { cberl_app, []}}, 13 | {env, []} 14 | ]}. 15 | -------------------------------------------------------------------------------- /src/cberl.erl: -------------------------------------------------------------------------------- 1 | %%% @author Ali Yakamercan 2 | %%% @copyright 2012-2013 Chitika Inc. 3 | %%% @version 1.0.5 4 | 5 | -module(cberl). 6 | -vsn("1.0.5"). 7 | -include("cberl.hrl"). 8 | 9 | -export([start_link/2, start_link/3, start_link/5, start_link/6, start_link/7]). 10 | -export([stop/1]). 11 | %% store operations 12 | -export([add/4, add/5, replace/4, replace/5, set/4, set/5, store/7]). 13 | %% update operations 14 | -export([append/3, prepend/3, touch/3, mtouch/3]). 15 | -export([incr/3, incr/4, incr/5, decr/3, decr/4, decr/5]). 16 | -export([arithmetic/6]). 17 | -export([append/4, prepend/4]). 18 | %% retrieval operations 19 | -export([get_and_touch/3, get_and_lock/3, mget/2, get/2, unlock/3, 20 | mget/3, getl/3, http/6, view/4, foldl/3, foldr/3, foreach/2, 21 | n1ql/4, n1ql/5]). 22 | %% removal operations 23 | -export([remove/2, flush/1, flush/2]). 24 | %% design doc opertations 25 | -export([set_design_doc/3, remove_design_doc/2]). 26 | -deprecated({append, 4}). 27 | -deprecated({prepend, 4}). 28 | 29 | %% @equiv start_link(PoolName, NumCon, "localhost:8091", "", "", "") 30 | start_link(PoolName, NumCon) -> 31 | start_link(PoolName, NumCon, "localhost:8091", "", "", ""). 32 | 33 | %% @equiv start_link(PoolName, NumCon, Host, "", "", "") 34 | start_link(PoolName, NumCon, Host) -> 35 | start_link(PoolName, NumCon, Host, "", "", ""). 36 | 37 | %% @equiv start_link(PoolName, NumCon, Host, Username, Password, "") 38 | start_link(PoolName, NumCon, Host, Username, Password) -> 39 | start_link(PoolName, NumCon, Host, Username, Password, ""). 40 | 41 | %% @doc Create an instance of libcouchbase 42 | %% hosts A list of hosts:port separated by ';' to the 43 | %% administration port of the couchbase cluster. (ex: 44 | %% "host1;host2:9000;host3" would try to connect to 45 | %% host1 on port 8091, if that fails it'll connect to 46 | %% host2 on port 9000 etc). 47 | %% Username the username to use 48 | %% Password The password 49 | %% bucket The bucket to connect to 50 | %% @end 51 | %% @equiv start_link(PoolName, NumCon, Host, Username, Password, cberl_transcoder) 52 | start_link(PoolName, NumCon, Host, Username, Password, BucketName) -> 53 | start_link(PoolName, NumCon, Host, Username, Password, BucketName, cberl_transcoder). 54 | 55 | -spec start_link(atom(), integer(), string(), string(), string(), string(), atom()) -> {ok, pid()} | {error, _}. 56 | start_link(PoolName, NumCon, Host, Username, Password, BucketName, Transcoder) -> 57 | SizeArgs = [{size, NumCon}, 58 | {max_overflow, 0}], 59 | PoolArgs = [{name, {local, PoolName}}, 60 | {worker_module, cberl_worker}] ++ SizeArgs, 61 | WorkerArgs = [{host, Host}, 62 | {username, Username}, 63 | {password, Password}, 64 | {bucketname, BucketName}, 65 | {transcoder, Transcoder}], 66 | poolboy:start_link(PoolArgs, WorkerArgs). 67 | 68 | stop(PoolPid) -> 69 | poolboy:stop(PoolPid). 70 | 71 | %%%%%%%%%%%%%%%%%%%%%%%% 72 | %%% STORE OPERATIONS %%% 73 | %%%%%%%%%%%%%%%%%%%%%%%% 74 | 75 | %% @equiv add(PoolPid, Key, Exp, Value, standard) 76 | -spec add(pid(), key(), integer(), value()) -> ok | {error, _}. 77 | add(PoolPid, Key, Exp, Value) -> 78 | add(PoolPid, Key, Exp, Value, standard). 79 | 80 | %% @equiv store(PoolPid, add, Key, Value, TranscoderOpts, Exp, 0) 81 | -spec add(pid(), key(), integer(), value(), atom()) -> ok | {error, _}. 82 | add(PoolPid, Key, Exp, Value, TranscoderOpts) -> 83 | store(PoolPid, add, Key, Value, TranscoderOpts, Exp, 0). 84 | 85 | %% @equiv replace(PoolPid, Key, Exp, Value, standard) 86 | -spec replace(pid(), key(), integer(), value()) -> ok | {error, _}. 87 | replace(PoolPid, Key, Exp, Value) -> 88 | replace(PoolPid, Key, Exp, Value, standard). 89 | 90 | %% @equiv store(PoolPid, replace, "", Key, Value, Exp) 91 | -spec replace(pid(), key(), integer(), value(), atom()) -> ok | {error, _}. 92 | replace(PoolPid, Key, Exp, Value, TranscoderOpts) -> 93 | store(PoolPid, replace, Key, Value, TranscoderOpts, Exp, 0). 94 | 95 | %% @equiv set(PoolPid, Key, Exp, Value, standard) 96 | -spec set(pid(), key(), integer(), value()) -> ok | {error, _}. 97 | set(PoolPid, Key, Exp, Value) -> 98 | set(PoolPid, Key, Exp, Value, standard). 99 | 100 | %% @equiv store(PoolPid, set, "", Key, Value, Exp) 101 | -spec set(pid(), key(), integer(), value(), atom()) -> ok | {error, _}. 102 | set(PoolPid, Key, Exp, Value, TranscoderOpts) -> 103 | store(PoolPid, set, Key, Value, TranscoderOpts, Exp, 0). 104 | 105 | %%%%%%%%%%%%%%%%%%%%%%%%% 106 | %%% UPDATE OPERATIONS %%% 107 | %%%%%%%%%%%%%%%%%%%%%%%%% 108 | 109 | %% @deprecated 110 | %% @equiv append(PoolPid, Key, Value) 111 | %% @doc Deprecated append function which accepts an _unused_ CAS value 112 | -spec append(pid(), integer(), key(), value()) -> ok | {error, _}. 113 | append(PoolPid, _Cas, Key, Value) -> 114 | append(PoolPid, Key, Value). 115 | 116 | -spec append(pid(), key(), value()) -> ok | {error, _}. 117 | append(PoolPid, Key, Value) -> 118 | store(PoolPid, append, Key, Value, none, 0, 0). 119 | 120 | %% @deprecated 121 | %% @equiv prepend(PoolPid, Key, Value) 122 | %% @doc Deprecated prepend function which accepts an _unused_ CAS value 123 | -spec prepend(pid(), integer(), key(), value()) -> ok | {error, _}. 124 | prepend(PoolPid, _Cas, Key, Value) -> 125 | prepend(PoolPid, Key, Value). 126 | 127 | -spec prepend(pid(), key(), value()) -> ok | {error, _}. 128 | prepend(PoolPid, Key, Value) -> 129 | store(PoolPid, prepend, Key, Value, none, 0, 0). 130 | 131 | %% @doc Touch (set expiration time) on the given key 132 | %% PoolPid libcouchbase instance to use 133 | %% Key key to touch 134 | %% ExpTime a new expiration time for the item 135 | -spec touch(pid(), key(), integer()) -> {ok, any()}. 136 | touch(PoolPid, Key, ExpTime) -> 137 | {ok, Return} = mtouch(PoolPid, [Key], [ExpTime]), 138 | {ok, hd(Return)}. 139 | 140 | -spec mtouch(pid(), [key()], integer() | [integer()]) 141 | -> {ok, any()} | {error, any()}. 142 | mtouch(PoolPid, Keys, ExpTime) when is_integer(ExpTime) -> 143 | mtouch(PoolPid, Keys, [ExpTime]); 144 | mtouch(PoolPid, Keys, ExpTimes) -> 145 | ExpTimesE = case length(Keys) - length(ExpTimes) of 146 | R when R > 0 -> 147 | ExpTimes ++ lists:duplicate(R, lists:last(ExpTimes)); 148 | _ -> 149 | ExpTimes 150 | end, 151 | execute(PoolPid, {mtouch, Keys, ExpTimesE}). 152 | 153 | incr(PoolPid, Key, OffSet) -> 154 | arithmetic(PoolPid, Key, OffSet, 0, 0, 0). 155 | 156 | incr(PoolPid, Key, OffSet, Default) -> 157 | arithmetic(PoolPid, Key, OffSet, 0, 1, Default). 158 | 159 | incr(PoolPid, Key, OffSet, Default, Exp) -> 160 | arithmetic(PoolPid, Key, OffSet, Exp, 1, Default). 161 | 162 | decr(PoolPid, Key, OffSet) -> 163 | arithmetic(PoolPid, Key, -OffSet, 0, 0, 0). 164 | 165 | decr(PoolPid, Key, OffSet, Default) -> 166 | arithmetic(PoolPid, Key, -OffSet, 0, 1, Default). 167 | 168 | decr(PoolPid, Key, OffSet, Default, Exp) -> 169 | arithmetic(PoolPid, Key, -OffSet, Exp, 1, Default). 170 | 171 | %%%%%%%%%%%%%%%%%%%%%%%%% 172 | %%% RETRIEVAL METHODS %%% 173 | %%%%%%%%%%%%%%%%%%%%%%%%% 174 | 175 | -spec get_and_touch(pid(), key(), integer()) -> [{ok, integer(), value()} | {error, _}]. 176 | get_and_touch(PoolPid, Key, Exp) -> 177 | mget(PoolPid, [Key], Exp). 178 | 179 | -spec get(pid(), key()) -> {ok, integer(), value()} | {error, _}. 180 | get(PoolPid, Key) -> 181 | case mget(PoolPid, [Key], 0) of 182 | {error, _} = E -> E; 183 | Result -> hd(Result) 184 | end. 185 | 186 | mget(PoolPid, Keys) -> 187 | mget(PoolPid, Keys, 0). 188 | 189 | -spec get_and_lock(pid(), key(), integer()) -> {ok, integer(), value()} | {error, _}. 190 | get_and_lock(PoolPid, Key, Exp) -> 191 | hd(getl(PoolPid, Key, Exp)). 192 | 193 | -spec unlock(pid(), key(), integer()) -> ok | {error, _}. 194 | unlock(PoolPid, Key, Cas) -> 195 | execute(PoolPid, {unlock, Key, Cas}). 196 | 197 | %% @doc main store function takes care of all storing 198 | %% Instance libcouchbase instance to use 199 | %% Op add | replace | set | append | prepend 200 | %% add : Add the item to the cache, but fail if the object exists already 201 | %% replace: Replace the existing object in the cache 202 | %% set : Unconditionally set the object in the cache 203 | %% append/prepend : Append/Prepend this object to the existing object 204 | %% Key the key to set 205 | %% Value the value to set 206 | %% Transcoder to encode the value 207 | %% Exp When the object should expire. The expiration time is 208 | %% either an offset into the future.. OR an absolute 209 | %% timestamp, depending on how large (numerically) the 210 | %% expiration is. if the expiration exceeds 30 days 211 | %% (i.e. 24 * 3600 * 30) then it's an absolute timestamp. 212 | %% pass 0 for infinity 213 | %% CAS 214 | -spec store(pid(), operation_type(), key(), value(), atom(), 215 | integer(), integer()) -> ok | {error, _}. 216 | store(PoolPid, Op, Key, Value, TranscoderOpts, Exp, Cas) -> 217 | execute(PoolPid, {store, Op, Key, Value, 218 | TranscoderOpts, Exp, Cas}). 219 | 220 | %% @doc get the value for the given key 221 | %% Instance libcouchbase instance to use 222 | %% HashKey the key to use for hashing 223 | %% Key the key to get 224 | %% Exp When the object should expire 225 | %% pass a negative number for infinity 226 | -spec mget(pid(), [key()], integer()) -> list(). 227 | mget(PoolPid, Keys, Exp) -> 228 | execute(PoolPid, {mget, Keys, Exp, 0}). 229 | 230 | %% @doc Get an item with a lock that has a timeout 231 | %% Instance libcouchbase instance to use 232 | %% HashKey the key to use for hashing 233 | %% Key the key to get 234 | %% Exp When the lock should expire 235 | -spec getl(pid(), key(), integer()) -> list(). 236 | getl(PoolPid, Key, Exp) -> 237 | execute(PoolPid, {mget, [Key], Exp, 1}). 238 | 239 | %% @doc perform an arithmetic operation on the given key 240 | %% Instance libcouchbase instance to use 241 | %% Key key to perform on 242 | %% Delta The amount to add / subtract 243 | %% Exp When the object should expire 244 | %% Create set to true if you want the object to be created if it 245 | %% doesn't exist. 246 | %% Initial The initial value of the object if we create it 247 | -spec arithmetic(pid(), key(), integer(), integer(), integer(), integer()) -> 248 | ok | {error, _}. 249 | arithmetic(PoolPid, Key, OffSet, Exp, Create, Initial) -> 250 | execute(PoolPid, {arithmetic, Key, OffSet, Exp, Create, Initial}). 251 | 252 | %% @doc remove the value for given key 253 | %% Instance libcouchbase instance to use 254 | %% Key key to remove 255 | -spec remove(pid(), key()) -> ok | {error, _}. 256 | remove(PoolPid, Key) -> 257 | execute(PoolPid, {remove, Key, 0}). 258 | 259 | %% @doc flush all documents from the bucket 260 | %% Instance libcouchbase Instance to use 261 | %% BucketName name of the bucket to flush 262 | -spec flush(pid(), string()) -> ok | {error, _}. 263 | flush(PoolPid, BucketName) -> 264 | FlushMarker = <<"__flush_marker_document__">>, 265 | set(PoolPid, FlushMarker, 0, ""), 266 | Path = string:join(["pools/default/buckets", BucketName, "controller/doFlush"], "/"), 267 | Result = http(PoolPid, Path, "", "application/json", post, management), 268 | handle_flush_result(PoolPid, FlushMarker, Result). 269 | 270 | %% @doc flush all documents from the current bucket 271 | %% Instance libcouchbase Instance to use 272 | -spec flush(pid()) -> ok | {error, _}. 273 | flush(PoolPid) -> 274 | {ok, BucketName} = execute(PoolPid, bucketname), 275 | flush(PoolPid, BucketName). 276 | 277 | handle_flush_result(_, _, {ok, 200, _}) -> ok; 278 | handle_flush_result(PoolPid, FlushMarker, Result={ok, 201, _}) -> 279 | case get(PoolPid, FlushMarker) of 280 | {_, {error, key_enoent}} -> ok; 281 | _ -> 282 | erlang:send_after(1000, self(), check_flush_done), 283 | receive 284 | check_flush_done -> handle_flush_result(PoolPid, FlushMarker, Result) 285 | end 286 | end. 287 | 288 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 289 | %%% VIEWS %%% 290 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 291 | 292 | %% @doc execute a command with the REST API 293 | %% PoolPid pid of connection pool 294 | %% Path HTTP path 295 | %% Body HTTP body (for POST requests) 296 | %% ContentType HTTP content type 297 | %% Method HTTP method 298 | %% Type Couchbase request type 299 | -spec http(pid(), string(), string(), string(), http_method(), http_type()) 300 | -> {ok, binary()} | {error, _}. 301 | http(PoolPid, Path, Body, ContentType, Method, Type) -> 302 | execute(PoolPid, {http, Path, Body, ContentType, http_method(Method), http_type(Type)}). 303 | 304 | %% @doc Query a view 305 | %% PoolPid pid of connection pool 306 | %% DocName design doc name 307 | %% ViewName view name 308 | %% Args arguments and filters (limit etc.) 309 | view(PoolPid, DocName, ViewName, Args) -> 310 | Path = string:join(["_design", DocName, "_view", ViewName], "/"), 311 | Resp = case proplists:get_value(keys, Args) of 312 | undefined -> 313 | http(PoolPid, string:join([Path, query_args(Args)], "?"), "", "application/json", get, view); 314 | Keys -> 315 | http(PoolPid, string:join([Path, query_args(proplists:delete(keys, Args))], "?"), binary_to_list(iolist_to_binary(jiffy:encode({[{keys, Keys}]}))), "application/json", post, view) 316 | end, 317 | decode_query_resp(Resp). 318 | 319 | foldl(Func, Acc, {PoolPid, DocName, ViewName, Args}) -> 320 | case view(PoolPid, DocName, ViewName, Args) of 321 | {ok, {_TotalRows, Rows}} -> 322 | lists:foldl(Func, Acc, Rows); 323 | {error, _} = E -> E 324 | end. 325 | 326 | foldr(Func, Acc, {PoolPid, DocName, ViewName, Args}) -> 327 | case view(PoolPid, DocName, ViewName, Args) of 328 | {ok, {_TotalRows, Rows}} -> 329 | lists:foldr(Func, Acc, Rows); 330 | {error, _} = E -> E 331 | end. 332 | 333 | foreach(Func, {PoolPid, DocName, ViewName, Args}) -> 334 | case view(PoolPid, DocName, ViewName, Args) of 335 | {ok, {_TotalRows, Rows}} -> 336 | lists:foreach(Func, Rows); 337 | {error, _} = E -> E 338 | end. 339 | 340 | n1ql(PoolPid, Query, Params, Prepared) -> 341 | n1ql(PoolPid, Query, Params, Prepared, standard). 342 | 343 | n1ql(PoolPid, Query, Params, Prepared, TranscoderOpts) when is_binary(Query), is_atom(Prepared) -> 344 | %% Checking for binaries to avoid segmentation fault 345 | lists:foreach(fun(Param) -> 346 | case is_binary(Param) of 347 | true -> ok; 348 | _ -> throw(invalid_parameter) 349 | end 350 | end, Params), 351 | PreparedValue = case Prepared of 352 | true -> 1; 353 | false -> 0; 354 | _ -> throw(invalid_flag) 355 | end, 356 | execute(PoolPid, {n1ql, Query, Params, PreparedValue, TranscoderOpts}). 357 | 358 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 359 | %%% DESIGN DOCUMENT MANAGMENT %%% 360 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 361 | 362 | set_design_doc(PoolPid, DocName, DesignDoc) -> 363 | Path = string:join(["_design", DocName], "/"), 364 | Resp = http(PoolPid, Path, binary_to_list(iolist_to_binary(jiffy:encode(DesignDoc))), "application/json", put, view), 365 | decode_update_design_doc_resp(Resp). 366 | 367 | remove_design_doc(PoolPid, DocName) -> 368 | Path = string:join(["_design", DocName], "/"), 369 | Resp = http(PoolPid, Path, "", "application/json", delete, view), 370 | decode_update_design_doc_resp(Resp). 371 | 372 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 373 | %%% INTERNAL FUNCTIONS %%% 374 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 375 | 376 | execute(PoolPid, Cmd) -> 377 | poolboy:transaction(PoolPid, fun(Worker) -> 378 | gen_server:call(Worker, Cmd) 379 | end). 380 | 381 | http_type(view) -> 0; 382 | http_type(management) -> 1; 383 | http_type(raw) -> 2. 384 | 385 | http_method(get) -> 0; 386 | http_method(post) -> 1; 387 | http_method(put) -> 2; 388 | http_method(delete) -> 3. 389 | 390 | query_args(Args) when is_list(Args) -> 391 | string:join([query_arg(A) || A <- Args], "&"). 392 | 393 | decode_query_resp({ok, _, Resp}) -> 394 | case jiffy:decode(Resp) of 395 | {[{<<"total_rows">>, TotalRows}, {<<"rows">>, Rows}]} -> 396 | {ok, {TotalRows, lists:map(fun ({Row}) -> Row end, Rows)}}; 397 | {[{<<"rows">>, Rows}]} -> 398 | {ok, {lists:map(fun ({Row}) -> Row end, Rows)}}; 399 | {[{<<"error">>,Error}, {<<"reason">>, Reason}]} -> 400 | {error, {view_error(Error), Reason}} 401 | end; 402 | decode_query_resp({error, _} = E) -> E. 403 | 404 | decode_update_design_doc_resp({ok, Http_Code, _Resp}) when 200 =< Http_Code andalso Http_Code < 300 -> ok; 405 | decode_update_design_doc_resp({ok, _Http_Code, Resp}) -> 406 | case jiffy:decode(Resp) of 407 | {[{<<"error">>,Error}, {<<"reason">>, Reason}]} -> 408 | {error, {view_error(Error), Reason}}; 409 | _Other -> {error, {unknown_error, Resp}} 410 | end. 411 | 412 | query_arg({descending, true}) -> "descending=true"; 413 | query_arg({descending, false}) -> "descending=false"; 414 | 415 | query_arg({endkey, V}) when is_list(V) -> string:join(["endkey", V], "="); 416 | 417 | query_arg({endkey_docid, V}) when is_list(V) -> string:join(["endkey_docid", V], "="); 418 | 419 | query_arg({full_set, true}) -> "full_set=true"; 420 | query_arg({full_set, false}) -> "full_set=false"; 421 | 422 | query_arg({group, true}) -> "group=true"; 423 | query_arg({group, false}) -> "group=false"; 424 | 425 | query_arg({group_level, V}) when is_integer(V) -> string:join(["group_level", integer_to_list(V)], "="); 426 | 427 | query_arg({inclusive_end, true}) -> "inclusive_end=true"; 428 | query_arg({inclusive_end, false}) -> "inclusive_end=false"; 429 | 430 | query_arg({key, V}) -> string:join(["key", binary_to_list(iolist_to_binary(jiffy:encode(V)))], "="); 431 | 432 | query_arg({keys, V}) when is_list(V) -> string:join(["keys", jiffy:encode(V)], "="); 433 | 434 | query_arg({limit, V}) when is_integer(V) -> string:join(["limit", integer_to_list(V)], "="); 435 | 436 | query_arg({on_error, continue}) -> "on_error=continue"; 437 | query_arg({on_error, stop}) -> "on_error=stop"; 438 | 439 | query_arg({reduce, true}) -> "reduce=true"; 440 | query_arg({reduce, false}) -> "reduce=false"; 441 | 442 | query_arg({skip, V}) when is_integer(V) -> string:join(["skip", integer_to_list(V)], "="); 443 | 444 | query_arg({stale, false}) -> "stale=false"; 445 | query_arg({stale, ok}) -> "stale=ok"; 446 | query_arg({stale, update_after}) -> "stale=update_after"; 447 | 448 | query_arg({startkey, V}) when is_list(V) -> string:join(["startkey", V], "="); 449 | 450 | query_arg({startkey_docid, V}) when is_list(V) -> string:join(["startkey_docid", V], "="). 451 | 452 | view_error(Error) -> list_to_atom(binary_to_list(Error)). 453 | 454 | -------------------------------------------------------------------------------- /src/cberl_app.erl: -------------------------------------------------------------------------------- 1 | -module(cberl_app). 2 | 3 | -behaviour(application). 4 | 5 | %% Application callbacks 6 | -export([start/2, stop/1]). 7 | 8 | %% =================================================================== 9 | %% Application callbacks 10 | %% =================================================================== 11 | 12 | start(_StartType, _StartArgs) -> 13 | cberl_sup:start_link(). 14 | 15 | stop(_State) -> 16 | ok. 17 | -------------------------------------------------------------------------------- /src/cberl_nif.erl: -------------------------------------------------------------------------------- 1 | -module(cberl_nif). 2 | -export([new/0, control/3, destroy/1]). 3 | 4 | -on_load(init/0). 5 | 6 | -define(NIF_STUB, erlang:nif_error(nif_library_not_loaded)). 7 | 8 | init() -> 9 | PrivDir = case code:priv_dir(cberl) of 10 | {error, bad_name} -> 11 | re:replace(code:which(?MODULE), "cberl.*", "cberl/priv",[{return,list}]); 12 | Path -> 13 | Path 14 | end, 15 | erlang:load_nif(filename:join(PrivDir, "cberl_drv"), 0). 16 | 17 | new() -> 18 | ?NIF_STUB. 19 | 20 | control(_, _, _) -> 21 | ?NIF_STUB. 22 | 23 | destroy(_) -> 24 | ?NIF_STUB. 25 | -------------------------------------------------------------------------------- /src/cberl_sup.erl: -------------------------------------------------------------------------------- 1 | 2 | -module(cberl_sup). 3 | 4 | -behaviour(supervisor). 5 | 6 | %% API 7 | -export([start_link/0]). 8 | 9 | %% Supervisor callbacks 10 | -export([init/1]). 11 | 12 | %% Helper macro for declaring children of supervisor 13 | -define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). 14 | 15 | %% =================================================================== 16 | %% API functions 17 | %% =================================================================== 18 | 19 | start_link() -> 20 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 21 | 22 | %% =================================================================== 23 | %% Supervisor callbacks 24 | %% =================================================================== 25 | 26 | init([]) -> 27 | {ok, { {one_for_one, 5, 10}, []} }. 28 | 29 | -------------------------------------------------------------------------------- /src/cberl_transcoder.erl: -------------------------------------------------------------------------------- 1 | -module(cberl_transcoder). 2 | -export([encode_value/2, decode_value/2, flag/1]). 3 | -include("cberl.hrl"). 4 | 5 | -define('CBE_NONE', 0). 6 | -define('CBE_JSON', 16#0002). 7 | -define('CBE_RAW', 16#0004). 8 | -define('CBE_STR', 16#0008). 9 | 10 | -define(STANDARD_FLAG, json). 11 | 12 | -type encoder() :: json | raw | str. 13 | -type encoder_list() :: [encoder()]. 14 | 15 | -spec encode_value(encoder() | encoder_list(), value()) -> value(). 16 | encode_value(Encoders, Value) -> 17 | encode_value1(flag(Encoders), Value). 18 | 19 | -spec encode_value1(integer(), value()) -> value(). 20 | encode_value1(Flag, Value) when Flag band ?'CBE_STR' == ?'CBE_STR' -> 21 | encode_value1(Flag bxor ?'CBE_STR', list_to_binary(Value)); 22 | encode_value1(Flag, Value) when Flag band ?'CBE_JSON' == ?'CBE_JSON' -> 23 | encode_value1(Flag bxor ?'CBE_JSON', jiffy:encode(Value)); 24 | encode_value1(Flag, Value) when Flag band ?'CBE_RAW' == ?'CBE_RAW' -> 25 | encode_value1(Flag bxor ?'CBE_RAW', term_to_binary(Value)); 26 | encode_value1(_, Value) -> 27 | Value. 28 | 29 | -spec decode_value(integer(), value()) -> value(). 30 | decode_value(Flag, Value) when ?'CBE_RAW' band Flag == ?'CBE_RAW' -> 31 | decode_value(Flag bxor ?'CBE_RAW', binary_to_term(Value)); 32 | decode_value(Flag, Value) when ?'CBE_JSON' band Flag == ?'CBE_JSON' -> 33 | decode_value(Flag bxor ?'CBE_JSON', jiffy:decode(Value)); 34 | decode_value(Flag, Value) when ?'CBE_STR' band Flag == ?'CBE_STR' -> 35 | decode_value(Flag bxor ?'CBE_STR', binary_to_list(Value)); 36 | decode_value(_, Value) -> 37 | Value. 38 | 39 | -spec flag(encoder() | encoder_list()) -> integer(). 40 | flag(none) -> ?'CBE_NONE'; 41 | flag(standard) -> flag(?STANDARD_FLAG); 42 | flag(json) -> ?'CBE_JSON'; 43 | flag(raw_binary) -> ?'CBE_RAW'; 44 | flag(str) -> ?'CBE_STR'; 45 | flag(Encoders) when is_list(Encoders) -> 46 | lists:foldr(fun(Encoder, Acc) -> 47 | Acc bor flag(Encoder) 48 | end, 0, Encoders). -------------------------------------------------------------------------------- /src/cberl_worker.erl: -------------------------------------------------------------------------------- 1 | -module(cberl_worker). 2 | -behaviour(poolboy_worker). 3 | -include("cberl.hrl"). 4 | -behaviour(gen_server). 5 | 6 | %% API 7 | -export([start_link/1]). 8 | 9 | %% gen_server callbacks 10 | -export([init/1, 11 | handle_call/3, 12 | handle_cast/2, 13 | handle_info/2, 14 | terminate/2, 15 | code_change/3]). 16 | 17 | %%%=================================================================== 18 | %%% API 19 | %%%=================================================================== 20 | 21 | %%-------------------------------------------------------------------- 22 | %% @doc 23 | %% Starts the server 24 | %% 25 | %% @spec start_link(Args) -> {ok, Pid} | ignore | {error, Error} 26 | %% @end 27 | %%-------------------------------------------------------------------- 28 | start_link(Args) -> 29 | gen_server:start_link(?MODULE, Args, []). 30 | 31 | %%%=================================================================== 32 | %%% gen_server callbacks 33 | %%%=================================================================== 34 | 35 | %%-------------------------------------------------------------------- 36 | %% @private 37 | %% @doc 38 | %% Initializes the server 39 | %% 40 | %% @spec init(Args) -> {ok, State} | 41 | %% {ok, State, Timeout} | 42 | %% ignore | 43 | %% {stop, Reason} 44 | %% @end 45 | %%-------------------------------------------------------------------- 46 | init([{host, Host}, {username, Username}, {password, Password}, 47 | {bucketname, BucketName}, {transcoder, Transcoder}]) -> 48 | process_flag(trap_exit, true), 49 | {ok, Handle} = cberl_nif:new(), 50 | State = #instance{handle = Handle, 51 | transcoder = Transcoder, 52 | bucketname = canonical_bucket_name(BucketName), 53 | opts = [Host, Username, Password, BucketName], 54 | connected = false}, 55 | State2 = case connect(State) of 56 | ok -> State#instance{connected = true}; 57 | {error, _} -> State#instance{connected = false} 58 | end, 59 | {ok, State2}. 60 | 61 | %%-------------------------------------------------------------------- 62 | %% @private 63 | %% @doc 64 | %% Handling call messages 65 | %% 66 | %% @spec handle_call(Request, From, State) -> 67 | %% {reply, Reply, State} | 68 | %% {reply, Reply, State, Timeout} | 69 | %% {noreply, State} | 70 | %% {noreply, State, Timeout} | 71 | %% {stop, Reason, Reply, State} | 72 | %% {stop, Reason, State} 73 | %% @end 74 | %%-------------------------------------------------------------------- 75 | handle_call({mtouch, Keys, ExpTimesE}, _From, State) -> 76 | {Connected, Reply} = case connect(State) of 77 | ok -> {true, mtouch(Keys, ExpTimesE, State)}; 78 | {error, _} -> {false, {error, unavailable}} 79 | end, 80 | {reply, Reply, State#instance{connected = Connected}}; 81 | handle_call({unlock, Key, Cas}, _From, State) -> 82 | {Connected, Reply} = case connect(State) of 83 | ok -> {true, unlock(Key, Cas, State)}; 84 | {error, _} = E -> {false, E} 85 | end, 86 | {reply, Reply, State#instance{connected = Connected}}; 87 | handle_call({store, Op, Key, Value, TranscoderOpts, Exp, Cas}, _From, State) -> 88 | {Connected, Reply} = case connect(State) of 89 | ok -> {true, store(Op, Key, Value, TranscoderOpts, Exp, Cas, State)}; 90 | {error, _} = E -> {false, E} 91 | end, 92 | {reply, Reply, State#instance{connected = Connected}}; 93 | handle_call({mget, Keys, Exp, Lock}, _From, State) -> 94 | {Connected, Reply} = case connect(State) of 95 | ok -> {true, mget(Keys, Exp, Lock, State)}; 96 | {error, _} = E -> {false, E} 97 | end, 98 | {reply, Reply, State#instance{connected = Connected}}; 99 | handle_call({arithmetic, Key, OffSet, Exp, Create, Initial}, _From, State) -> 100 | {Connected, Reply} = case connect(State) of 101 | ok -> {true, arithmetic(Key, OffSet, Exp, Create, Initial, State)}; 102 | {error, _} = E -> {false, E} 103 | end, 104 | {reply, Reply, State#instance{connected = Connected}}; 105 | handle_call({remove, Key, N}, _From, State) -> 106 | {Connected, Reply} = case connect(State) of 107 | ok -> {true, remove(Key, N, State)}; 108 | {error, _} = E -> {false, E} 109 | end, 110 | {reply, Reply, State#instance{connected = Connected}}; 111 | handle_call({http, Path, Body, ContentType, Method, Chunked}, _From, State) -> 112 | {Connected, Reply} = case connect(State) of 113 | ok -> {true, http(Path, Body, ContentType, Method, Chunked, State)}; 114 | {error, _} = E -> {false, E} 115 | end, 116 | {reply, Reply, State#instance{connected = Connected}}; 117 | handle_call({n1ql, Query, Params, Prepared, TranscoderOpts}, _From, State) -> 118 | {Connected, Reply} = case connect(State) of 119 | ok -> {true, n1ql(Query, Params, Prepared, TranscoderOpts, State)}; 120 | {error, _} = E -> {false, E} 121 | end, 122 | {reply, Reply, State#instance{connected = Connected}}; 123 | handle_call(bucketname, _From, State = #instance{bucketname = BucketName}) -> 124 | {reply, {ok, BucketName}, State}; 125 | handle_call(_Request, _From, State) -> 126 | Reply = ok, 127 | {reply, Reply, State}. 128 | 129 | %%-------------------------------------------------------------------- 130 | %% @private 131 | %% @doc 132 | %% Handling cast messages 133 | %% 134 | %% @spec handle_cast(Msg, State) -> {noreply, State} | 135 | %% {noreply, State, Timeout} | 136 | %% {stop, Reason, State} 137 | %% @end 138 | %%-------------------------------------------------------------------- 139 | handle_cast(_Msg, State) -> 140 | {noreply, State}. 141 | 142 | %%-------------------------------------------------------------------- 143 | %% @private 144 | %% @doc 145 | %% Handling all non call/cast messages 146 | %% 147 | %% @spec handle_info(Info, State) -> {noreply, State} | 148 | %% {noreply, State, Timeout} | 149 | %% {stop, Reason, State} 150 | %% @end 151 | %%-------------------------------------------------------------------- 152 | handle_info(_Info, State) -> 153 | {noreply, State}. 154 | 155 | %%-------------------------------------------------------------------- 156 | %% @private 157 | %% @doc 158 | %% This function is called by a gen_server when it is about to 159 | %% terminate. It should be the opposite of Module:init/1 and do any 160 | %% necessary cleaning up. When it returns, the gen_server terminates 161 | %% with Reason. The return value is ignored. 162 | %% 163 | %% @spec terminate(Reason, State) -> void() 164 | %% @end 165 | %%-------------------------------------------------------------------- 166 | terminate(_Reason, _State = #instance{handle = Handle}) -> 167 | cberl_nif:destroy(Handle), 168 | ok. 169 | 170 | %%-------------------------------------------------------------------- 171 | %% @private 172 | %% @doc 173 | %% Convert process state when code is changed 174 | %% 175 | %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} 176 | %% @end 177 | %%-------------------------------------------------------------------- 178 | code_change(_OldVsn, State, _Extra) -> 179 | {ok, State}. 180 | 181 | %%%=================================================================== 182 | %%% Internal functions 183 | %%%=================================================================== 184 | 185 | connect(#instance{connected = true}) -> 186 | ok; 187 | connect(#instance{connected = false, handle = Handle, opts = Opts}) -> 188 | ok = cberl_nif:control(Handle, op(connect), Opts), 189 | receive 190 | ok -> ok; 191 | {error, _} = E -> E 192 | end. 193 | 194 | mtouch(Keys, ExpTimesE, #instance{handle = Handle}) -> 195 | ok = cberl_nif:control(Handle, op(mtouch), [Keys, ExpTimesE]), 196 | receive 197 | Reply -> Reply 198 | end. 199 | 200 | unlock(Key, Cas, #instance{handle = Handle}) -> 201 | cberl_nif:control(Handle, op(unlock), [Key, Cas]), 202 | receive 203 | Reply -> Reply 204 | end. 205 | 206 | store(Op, Key, Value, TranscoderOpts, Exp, Cas, 207 | #instance{handle = Handle, transcoder = Transcoder}) -> 208 | StoreValue = Transcoder:encode_value(TranscoderOpts, Value), 209 | ok = cberl_nif:control(Handle, op(store), [operation_value(Op), Key, StoreValue, 210 | Transcoder:flag(TranscoderOpts), Exp, Cas]), 211 | receive 212 | Reply -> Reply 213 | end. 214 | 215 | mget(Keys, Exp, Lock, #instance{handle = Handle, transcoder = Transcoder}) -> 216 | ok = cberl_nif:control(Handle, op(mget), [Keys, Exp, Lock]), 217 | receive 218 | {error, Error} -> {error, Error}; 219 | {ok, Results} -> 220 | lists:map(fun(Result) -> 221 | case Result of 222 | {Cas, Flag, Key, Value} -> 223 | DecodedValue = Transcoder:decode_value(Flag, Value), 224 | {Key, Cas, DecodedValue}; 225 | {_Key, {error, _Error}} -> 226 | Result 227 | end 228 | end, Results) 229 | end. 230 | 231 | arithmetic(Key, OffSet, Exp, Create, Initial, 232 | #instance{handle = Handle, transcoder = Transcoder}) -> 233 | ok = cberl_nif:control(Handle, op(arithmetic), [Key, OffSet, Exp, Create, Initial]), 234 | receive 235 | {error, Error} -> {error, Error}; 236 | {ok, {Cas, Flag, Value}} -> 237 | DecodedValue = Transcoder:decode_value(Flag, Value), 238 | {ok, Cas, DecodedValue} 239 | end. 240 | 241 | remove(Key, N, #instance{handle = Handle}) -> 242 | ok = cberl_nif:control(Handle, op(remove), [Key, N]), 243 | receive 244 | Reply -> Reply 245 | end. 246 | 247 | http(Path, Body, ContentType, Method, Chunked, #instance{handle = Handle}) -> 248 | ok = cberl_nif:control(Handle, op(http), [Path, Body, ContentType, Method, Chunked]), 249 | receive 250 | Reply -> Reply 251 | end. 252 | 253 | n1ql(Query, Params, Prepared, TranscoderOpts, #instance{handle = Handle, transcoder = Transcoder}) -> 254 | Flag = Transcoder:flag(TranscoderOpts), 255 | ok = cberl_nif:control(Handle, op(n1ql), [Query, Params, Prepared]), 256 | receive 257 | {error, Error} -> {error, Error}; 258 | {ok, MetaBin, Results} -> 259 | Data = lists:map(fun(Result) -> 260 | Transcoder:decode_value(Flag, Result) 261 | end, Results), 262 | Meta = Transcoder:decode_value(Flag, MetaBin), 263 | {ok, Meta, Data} 264 | end. 265 | 266 | -spec operation_value(operation_type()) -> integer(). 267 | operation_value(add) -> ?'CBE_ADD'; 268 | operation_value(replace) -> ?'CBE_REPLACE'; 269 | operation_value(set) -> ?'CBE_SET'; 270 | operation_value(append) -> ?'CBE_APPEND'; 271 | operation_value(prepend) -> ?'CBE_PREPEND'. 272 | 273 | -spec op(atom()) -> integer(). 274 | op(connect) -> ?'CMD_CONNECT'; 275 | op(store) -> ?'CMD_STORE'; 276 | op(mget) -> ?'CMD_MGET'; 277 | op(unlock) -> ?'CMD_UNLOCK'; 278 | op(mtouch) -> ?'CMD_MTOUCH'; 279 | op(arithmetic) -> ?'CMD_ARITHMETIC'; 280 | op(remove) -> ?'CMD_REMOVE'; 281 | op(http) -> ?'CMD_HTTP'; 282 | op(n1ql) -> ?'CMD_N1QL'. 283 | 284 | -spec canonical_bucket_name(string()) -> string(). 285 | canonical_bucket_name(Name) -> 286 | case Name of 287 | [] -> "default"; 288 | BucketName -> BucketName 289 | end. 290 | -------------------------------------------------------------------------------- /test/cberl_tests.erl: -------------------------------------------------------------------------------- 1 | -module(cberl_tests). 2 | -include_lib("eunit/include/eunit.hrl"). 3 | -include_lib("couchbase_connection.hrl"). 4 | 5 | cberl_test_() -> 6 | [{foreach, fun setup/0, fun clean_up/1, 7 | [fun test_set_and_get/1, 8 | fun test_replace_add/1, 9 | fun test_multi_get/1, 10 | fun test_get_and_touch/1, 11 | fun test_append_prepend/1, 12 | fun test_remove/1, 13 | fun test_touch/1, 14 | fun test_lock/1, 15 | fun test_flush/1, 16 | fun test_flush_1/1]}]. 17 | 18 | 19 | %%%=================================================================== 20 | %%% Setup / Teardown 21 | %%%=================================================================== 22 | 23 | setup() -> 24 | cberl:start_link(?POOLNAME, 3, 25 | ?COUCHBASE_HOST, 26 | ?COUCHBASE_USER, 27 | ?COUCHBASE_PASSWORD), 28 | ok. 29 | 30 | clean_up(_) -> 31 | cberl:remove(?POOLNAME, <<"testkey">>), 32 | cberl:remove(?POOLNAME, <<"testkey1">>), 33 | cberl:remove(?POOLNAME, <<"notestkey">>), 34 | cberl:stop(?POOLNAME). 35 | 36 | %%%=================================================================== 37 | %%% Tests 38 | %%%=================================================================== 39 | 40 | test_set_and_get(_) -> 41 | Key = <<"testkey">>, 42 | Value = "testval", 43 | ok = cberl:set(?POOLNAME, Key, 0, Value), 44 | Get1 = cberl:get(?POOLNAME, Key), 45 | ok = cberl:set(?POOLNAME, Key, 0, Value, json), 46 | Get2 = cberl:get(?POOLNAME, Key), 47 | ok = cberl:set(?POOLNAME, Key, 0, Value, raw_binary), 48 | Get3 = cberl:get(?POOLNAME, Key), 49 | [?_assertMatch({Key, _, Value}, Get1), 50 | ?_assertMatch({Key, _, Value}, Get2), 51 | ?_assertMatch({Key, _, Value}, Get3) 52 | ]. 53 | 54 | test_multi_get(_) -> 55 | Value = "testval", 56 | Keys = lists:map(fun(N) -> list_to_binary(integer_to_list(N)) end, lists:seq(1, 1000)), 57 | lists:map(fun(Key) -> ok = cberl:set(?POOLNAME, Key, 0, Value) end, Keys), 58 | [?_assertMatch({<<"1">>,_, "testval"}, lists:nth(1, cberl:mget(?POOLNAME, Keys)))]. 59 | 60 | test_replace_add(_) -> 61 | Key = <<"testkey">>, 62 | Value = "testval", 63 | ok = cberl:set(?POOLNAME, Key, 0, Value), 64 | AddFail = cberl:add(?POOLNAME, Key, 0, Value), 65 | AddPass = cberl:add(?POOLNAME, <<"testkey1">>, 0, Value), 66 | ReplaceFail = cberl:replace(?POOLNAME, <<"notestkey">>, 0, Value), 67 | ok = cberl:replace(?POOLNAME, Key, 0, "testval1"), 68 | Get1 = cberl:get(?POOLNAME, Key), 69 | [?_assertEqual({error, key_eexists}, AddFail), 70 | ?_assertEqual(ok, AddPass), 71 | ?_assertEqual({error, key_enoent}, ReplaceFail), 72 | ?_assertMatch({Key, _, "testval1"}, Get1) 73 | ]. 74 | 75 | test_append_prepend(_) -> 76 | Key = <<"testkey">>, 77 | ok = cberl:set(?POOLNAME, Key, 0, "base", str), 78 | ok = cberl:append(?POOLNAME, Key, "tail"), 79 | Get1 = cberl:get(?POOLNAME, Key), 80 | ok = cberl:prepend(?POOLNAME, Key, "head"), 81 | Get2 = cberl:get(?POOLNAME, Key), 82 | [?_assertMatch({Key, _, "basetail"}, Get1), 83 | ?_assertMatch({Key, _, "headbasetail"}, Get2) 84 | ]. 85 | 86 | test_get_and_touch(_) -> 87 | Key = <<"testkey">>, 88 | Value = "testval", 89 | ok = cberl:set(?POOLNAME, Key, 0, Value), 90 | cberl:get_and_touch(?POOLNAME, Key, 1), 91 | timer:sleep(5000), 92 | [?_assertEqual({Key, {error,key_enoent}}, cberl:get(?POOLNAME, Key))]. 93 | 94 | test_touch(_) -> 95 | Key = <<"testkey">>, 96 | Value = "testval", 97 | ok = cberl:set(?POOLNAME, Key, 0, Value), 98 | {ok, _} = cberl:touch(?POOLNAME, Key, 1), 99 | timer:sleep(5000), 100 | [?_assertEqual({Key, {error,key_enoent}}, cberl:get(?POOLNAME, Key))]. 101 | 102 | test_remove(_) -> 103 | Key = <<"testkey">>, 104 | Value = "testval", 105 | ok = cberl:set(?POOLNAME, Key, 0, Value), 106 | ok = cberl:remove(?POOLNAME, Key), 107 | [?_assertEqual({Key, {error,key_enoent}}, cberl:get(?POOLNAME, Key))]. 108 | 109 | test_lock(_) -> 110 | Key = <<"testkey">>, 111 | Value = "testval", 112 | Value2 = "testval2", 113 | ok = cberl:set(?POOLNAME, Key, 0, Value), 114 | {Key, CAS, _Exp} = cberl:get_and_lock(?POOLNAME, Key, 100000), 115 | fun () -> 116 | [?assertEqual({error,key_eexists}, cberl:set(?POOLNAME, Key, 0, Value2)), 117 | ?assertEqual(ok, cberl:unlock(?POOLNAME, Key, CAS)), 118 | ?assertEqual(ok, cberl:set(?POOLNAME, Key, 0, Value2))] 119 | end. 120 | 121 | test_flush(_) -> 122 | Key = <<"testkey">>, 123 | Value = "testval", 124 | ok = cberl:set(?POOLNAME, Key, 0, Value), 125 | fun() -> 126 | [?assertMatch(ok, cberl:flush(?POOLNAME, "default")), 127 | ?assertMatch({Key, {error, key_enoent}}, cberl:get(?POOLNAME, Key))] 128 | end. 129 | 130 | test_flush_1(_) -> 131 | Key = <<"testkey">>, 132 | Value = "testval", 133 | ok = cberl:set(?POOLNAME, Key, 0, Value), 134 | fun() -> 135 | [?assertMatch(ok, cberl:flush(?POOLNAME)), 136 | ?assertMatch({Key, {error, key_enoent}}, cberl:get(?POOLNAME, Key))] 137 | end. 138 | 139 | -------------------------------------------------------------------------------- /test/cberl_transcoder_tests.erl: -------------------------------------------------------------------------------- 1 | -module(cberl_transcoder_tests). 2 | -include_lib("eunit/include/eunit.hrl"). 3 | 4 | cberl_transcoder_test_() -> 5 | [ 6 | ?_assertEqual("abc", cberl_transcoder:decode_value( 7 | cberl_transcoder:flag(json), cberl_transcoder:encode_value(json, "abc"))), 8 | ?_assertEqual("abc", cberl_transcoder:decode_value( 9 | cberl_transcoder:flag(raw_binary), cberl_transcoder:encode_value(raw_binary, "abc"))), 10 | ?_assertEqual("abc", cberl_transcoder:decode_value( 11 | cberl_transcoder:flag(str), cberl_transcoder:encode_value(str, "abc"))), 12 | ?_assertEqual("abc", cberl_transcoder:decode_value( 13 | cberl_transcoder:flag([json, str]), cberl_transcoder:encode_value([json,str], "abc"))), 14 | ?_assertEqual("abc", cberl_transcoder:decode_value( 15 | cberl_transcoder:flag([raw_binary, str]), cberl_transcoder:encode_value([raw_binary,str], "abc"))) 16 | ]. 17 | -------------------------------------------------------------------------------- /test/cberl_view_tests.erl: -------------------------------------------------------------------------------- 1 | -module(cberl_view_tests). 2 | -include_lib("eunit/include/eunit.hrl"). 3 | -include_lib("couchbase_connection.hrl"). 4 | 5 | cberl_view_test_() -> 6 | [{foreach, fun setup/0, fun clean_up/1, 7 | [fun test_set_design_doc/1, 8 | fun test_remove_design_doc/1, 9 | fun test_query_view/1]}]. 10 | 11 | 12 | %%%=================================================================== 13 | %%% Setup / Teardown 14 | %%%=================================================================== 15 | 16 | setup() -> 17 | cberl:start_link(?POOLNAME, 3, 18 | ?COUCHBASE_HOST, 19 | ?COUCHBASE_USER, 20 | ?COUCHBASE_PASSWORD), 21 | cberl:set_design_doc(?POOLNAME, "test-design-doc", 22 | {[{<<"views">>, 23 | {[{<<"test-view">>, 24 | {[{<<"map">>, <<"function(doc,meta){}">>}]} 25 | }]} 26 | }]}), 27 | ok. 28 | 29 | clean_up(_) -> 30 | cberl:stop(?POOLNAME). 31 | 32 | %%%=================================================================== 33 | %%% Tests 34 | %%%=================================================================== 35 | test_query_view(_) -> 36 | DocName = "test-set-design-doc", 37 | ViewName = "test-view", 38 | [?_assertMatch({ok, {0, []}}, cberl:view(?POOLNAME, DocName, ViewName, []))]. 39 | 40 | test_set_design_doc(_) -> 41 | DocName = "test-set-design-doc", 42 | ViewName = "test-view", 43 | DesignDoc = {[{<<"views">>, 44 | {[{list_to_binary(ViewName), 45 | {[{<<"map">>, <<"function(doc,meta){}">>}]} 46 | }]} 47 | }]}, 48 | fun () -> 49 | [?assertEqual(ok, cberl:set_design_doc(?POOLNAME, DocName, DesignDoc)), 50 | ?assertMatch({ok, _}, cberl:view(?POOLNAME, DocName, ViewName, []))] 51 | end. 52 | 53 | test_remove_design_doc(_) -> 54 | DocName = "test-design-doc", 55 | ViewName = "test-view", 56 | fun () -> 57 | [?assertEqual(ok, cberl:remove_design_doc(?POOLNAME, DocName)), 58 | ?assertMatch({error, _}, cberl:view(?POOLNAME, DocName, ViewName, []))] 59 | end. 60 | -------------------------------------------------------------------------------- /test/couchbase_connection.hrl: -------------------------------------------------------------------------------- 1 | -define(POOLNAME, testpool). 2 | -define(COUCHBASE_HOST, "localhost:8091"). 3 | -define(COUCHBASE_USER, ""). 4 | -define(COUCHBASE_PASSWORD, ""). 5 | --------------------------------------------------------------------------------