Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 139 additions & 17 deletions lib/couchie.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ defmodule Couchie do
## Examples

# open connection named "default_connection" to the default bucket, which should be used for testing only
Couchie.open(:default_connection)
{ok, <0.XX.0>} #=> successful connection to default bucket on localhost
iex> {:ok, _} = Couchie.open(:default_connection)
iex> :ok
:ok

# if your bucket is password protected:
Couchie.open(:secret, 10, 'localhost:8091', 'bucket_name', 'bucket_pasword')
Expand All @@ -38,9 +39,7 @@ defmodule Couchie do
end

def open(name, size, host) do
IO.puts "Opening #{name}, #{size}, #{host}"
open(name, size, host, '', '', '')
IO.puts "Opened #{name}, #{size}, #{host}"
end

def open(name, size, host, bucket) do # assume the bucket user and pass are the same as bucket name
Expand All @@ -52,14 +51,18 @@ defmodule Couchie do
end

def open(name, size, host, bucket, username, pass) do #currently usernames are set to bucket names in this interface.
IO.puts "Opening #{name}, #{size}, #{host}, #{username}, #{pass}, #{bucket} "
:cberl.start_link(name, size, host, username, pass, bucket, Couchie.Transcoder)
end

@doc """
Shutdown the connection to a particular bucket

Couchie.close(:connection)
## Examples

iex> Couchie.open(:connection)
iex> Couchie.close(:connection)
:ok

"""
def close(pool) do
:cberl.stop(pool)
Expand All @@ -70,8 +73,11 @@ defmodule Couchie do
First parameter is the connection you passed into Couchie.open()

## Examples

iex> Couchie.open(:default)
iex> Couchie.set(:default, "key", "document data")
:ok

Couchie.set(:default, "key", "document data")
"""
def set(connection, key, document) do
Couchie.set(connection, key, document, 0)
Expand All @@ -85,19 +91,79 @@ defmodule Couchie do

## Example

Couchie.set(:default, "key", "document data", 0)
iex> Couchie.open(:default)
iex> Couchie.set(:default, "key", "document data", 0)
:ok

"""
def set(connection, key, document, expiration) do
:cberl.set(connection, key, expiration, document) # NOTE: cberl parameter order is different!
end

@doc """
Create document if it doesn't exist, or replace it if it does.
First parameter is the connection you passed into Couchie.open()
If you want to verify the document hasn't been updated since the last read,
use the cas property from the last read.

## Example

iex> Couchie.open(:default)
iex> Couchie.set(:default, "key", "document data", 0, 12345)
{:error, :key_eexists}

"""
def set(connection, key, document, expiration, cas) do
:cberl.set(connection, key, expiration, document, :standard, cas)
end


@doc """
Increment

## Example

iex> Couchie.open(:default)
iex> Couchie.set(:default, "test_increment", 1)
iex> {:ok, _, "2"} = Couchie.incr(:default, "test_increment")
iex> :ok
:ok

"""
def incr(connection, key, offset \\ 1, exp \\ 0) do
:cberl.incr(connection, key, offset, exp)
end


@doc """
Decrement

## Example

iex> Couchie.open(:default)
iex> Couchie.set(:default, "test_decrement", 1)
iex> {:ok, _, "0"} = Couchie.decr(:default, "test_decrement")
iex> :ok
:ok

"""
def decr(connection, key, offset \\ 1, exp \\ 0) do
:cberl.decr(connection, key, offset, exp)
end




@doc """
Get document. Keys should be binary.
## Example

iex> Couchie.open(:default)
iex> Couchie.set(:default, "test_key", %{:blah => "blah"})
iex> {"test_key", _cas , res} = Couchie.get(:default, "test_key")
iex> res
%{"blah" => "blah"}

Couchie.get(:connection, "test_key")
#=> {"test_key" 1234567890, "value"} # The middle figure is the CAS for this document.
"""
def get(connection, key) do
:cberl.get(connection, key)
Expand All @@ -114,12 +180,12 @@ defmodule Couchie do
end

@doc """
Delete document. Key should be binary.
Remove document. Key should be binary.
## Example

Couchie.delete(:connection, "test_key")
Couchie.remove(:connection, "test_key")
"""
def delete(connection, key) do
def remove(connection, key) do
:cberl.remove(connection, key)
end

Expand All @@ -139,7 +205,7 @@ defmodule Couchie do

Couchie.delete(:connection, "test_key")
"""
def query(connection, doc, view, args) do
def view(connection, doc, view, args) do
:cberl.view(connection, doc, view, args)
end

Expand Down Expand Up @@ -179,18 +245,74 @@ defmodule Couchie do
{ view.name,
{ view
|> Map.take([:map, :reduce])
|> Enum.filter(fn {k, v} -> !is_nil(v) end) # only put fields that are not nil
|> Enum.filter(fn {_k, v} -> !is_nil(v) end) # only put fields that are not nil
}
}
end


@doc """


## Example

iex> Couchie.open(:default)
iex> {:ok, _results, _meta} = Couchie.query(:default, "select * from default limit 1")
iex> :ok
:ok

"""
def query(connection, query) do
query = "statement=#{query}" |> to_char_list
case :cberl.http(connection, '', query, 'application/x-www-form-urlencoded; charset=UTF-8', :post, :n1ql) do
{:ok, 200, result} ->
results = Poison.decode!(result)
{:ok, results["results"], Map.delete(results, "results")}
err ->
err
end
end


@doc """
Delete view.
## Example

Couchie.delete_view(:connection, "design-doc-id")
Couchie.remove_view(:connection, "design-doc-id")
"""
def delete_view(connection, doc_name) do
def remove_view(connection, doc_name) do
:cberl.remove_design_doc(connection, doc_name)
end

@doc """
Merges the couchbase document with a given map, to simplify cases where you are updating a few properties

If "safe" is specified then CAS is used to verify it hasn't been changed while modifying.

If doc has changed {:error, :key_eexists} is returned.

## Example

iex> Couchie.open(:default)
...> Couchie.set(:default, "somekey", %{"key1" => 1, "key2" => 2})
...> Couchie.merge(:default, "somekey", %{"key2" => "changed", "key3" => 3})
...> {"somekey", _cas, doc} = Couchie.get(:default, "somekey")
...> doc
%{"key1" => 1, "key2" => "changed", "key3" => 3}

"""
def merge(connection, key, doc, safe \\ false) do
case Couchie.get(connection, key) do
{^key, cas , old_doc} ->
cas_to_use = case safe do
true ->
cas
false ->
0
end
Couchie.set(connection, key, Map.merge(old_doc, doc), 0, cas_to_use)
_ ->
{:error, :key_enoent}
end
end
end
66 changes: 49 additions & 17 deletions lib/transcoder.ex
Original file line number Diff line number Diff line change
@@ -1,38 +1,70 @@
defmodule Couchie.Transcoder do
use Bitwise

@json_flag 0x2
@raw_flag 0x4
@str_flag 0x8
#When editing documents in the administrator flags are changed to 0x01.
#We're going to treat it as JSON data. Better ideas welcome.
@gui_legacy 0x01

@json_flag 0x02 <<< 24
@json_flag_legacy 0x02

@raw_flag 0x03 <<< 24
@raw_flag_legacy 0x04

@str_flag 0x04 <<< 24
@str_flag_legacy 0x08

@flag_mask 0x01 ||| 0x02 <<< 24 ||| 0x02 ||| 0x03 <<< 24 ||| 0x04 ||| 0x04 <<< 24 ||| 0x08
#API defined by cberl_transcoder.erl

# Encoder

def encode_value(encoders, value),
do: do_encode_value(flag(encoders), value)
def encode_value(encoders, value) do
do_encode_value(flag(encoders), value)
end

def do_encode_value(flag, value) when (flag &&& @str_flag) === @str_flag,
do: do_encode_value(flag ^^^ @str_flag, value)
def do_encode_value(flag, value) when (flag &&& @flag_mask) === @str_flag do
do_encode_value(flag ^^^ @str_flag, value)
end

def do_encode_value(flag, value) when (flag &&& @json_flag) === @json_flag,
do: do_encode_value(flag ^^^ @json_flag, Poison.encode!(value))
def do_encode_value(flag, value) when (flag &&& @flag_mask) === @json_flag do
do_encode_value(flag ^^^ @json_flag, Poison.encode!(value))
end

def do_encode_value(flag, value) when (flag &&& @raw_flag) === @raw_flag,
do: do_encode_value(flag ^^^ @raw_flag, :erlang.term_to_binary(value))
def do_encode_value(flag, value) when (flag &&& @flag_mask) === @raw_flag do
do_encode_value(flag ^^^ @raw_flag, :erlang.term_to_binary(value))
end

def do_encode_value(_, value), do: value

# Decoder

def decode_value(flag, value) when (@raw_flag &&& flag) === @raw_flag,
do: decode_value(flag ^^^ @raw_flag, :erlang.binary_to_term(value))
def decode_value(flag, value) when (flag &&& @flag_mask) === @raw_flag do
decode_value(flag ^^^ @raw_flag, :erlang.binary_to_term(value))
end
def decode_value(flag, value) when (flag &&& @flag_mask) === @raw_flag_legacy do
decode_value(flag ^^^ @raw_flag_legacy, :erlang.binary_to_term(value))
end

def decode_value(flag, value) when (@json_flag &&& flag) === @json_flag,
do: decode_value(flag ^^^ @json_flag, Poison.decode!(value, keys: :atoms))

def decode_value(flag, value) when (@str_flag &&& flag) === @str_flag,
do: decode_value(flag ^^^ @str_flag, value)
def decode_value(flag, value) when (flag &&& @flag_mask) === @json_flag do
decode_value(flag ^^^ @json_flag, Poison.decode!(value))
end
def decode_value(flag, value) when (flag &&& @flag_mask) === @json_flag_legacy do
decode_value(flag ^^^ @json_flag_legacy, Poison.decode!(value))
end
def decode_value(flag, value) when (flag &&& @flag_mask) === @gui_legacy do
decode_value(flag ^^^ @json_flag_legacy, Poison.decode!(value))
end


def decode_value(flag, value) when (flag &&& @flag_mask) === @str_flag do
decode_value(flag ^^^ @str_flag, value)
end
def decode_value(flag, value) when (flag &&& @flag_mask) === @str_flag_legacy do
decode_value(flag ^^^ @str_flag_legacy, value)
end


def decode_value(_, value), do: value

Expand Down
6 changes: 3 additions & 3 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ defmodule Couchie.Mixfile do
@doc "Project Details"
def project do
[ app: :couchie,
elixir: "~> 1.0.2",
version: "0.0.6",
elixir: ">= 1.0.2",
version: "0.0.7",
deps: deps ]
end

Expand All @@ -24,7 +24,7 @@ defmodule Couchie.Mixfile do
# {:erlmc, "0.1", git: "https://github.com/n1rvana/erlmc.git"}
defp deps do
[
{:cberl, github: "chitika/cberl"}, #chitika is authoritative source
{:cberl, github: "muut/cberl"}, #chitika is authoritative source
{:poison, ">= 1.2.0"}
]
end
Expand Down
5 changes: 2 additions & 3 deletions test/couchie_test.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
defmodule CouchieTest do
use ExUnit.Case

test "the truth" do
assert true
end
doctest Couchie

end