%% ------------------------------------------------------------------- %% %% riakc_obj: Container for Riak data and metadata %% %% Copyright (c) 2007-2010 Basho Technologies, Inc. All Rights Reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file %% except in compliance with the License. You may obtain %% a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, %% software distributed under the License is distributed on an %% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY %% KIND, either express or implied. See the License for the %% specific language governing permissions and limitations %% under the License. %% %% ------------------------------------------------------------------- %% @doc riakc_obj is used to wrap bucket/key/value data sent to the %% server on put and received on get. It provides %% accessors for retrieving the data and metadata %% and copes with siblings if multiple values are allowed. -module(riakc_obj). -export([new/2, new/3, new/4, bucket_type/1, bucket/1, only_bucket/1, key/1, vclock/1, value_count/1, select_sibling/2, get_contents/1, get_metadata/1, get_metadatas/1, get_content_type/1, get_content_types/1, get_value/1, get_values/1, update_metadata/2, update_value/2, update_value/3, update_content_type/2, get_update_metadata/1, get_update_content_type/1, get_update_value/1, md_ctype/1, set_vclock/2, get_user_metadata_entry/2, get_user_metadata_entries/1, clear_user_metadata_entries/1, delete_user_metadata_entry/2, set_user_metadata_entry/2, get_secondary_index/2, get_secondary_indexes/1, clear_secondary_indexes/1, delete_secondary_index/2, set_secondary_index/2, add_secondary_index/2, get_links/2, get_all_links/1, clear_links/1, delete_links/2, set_link/2, add_link/2 ]). %% Internal library use only -export([new_obj/4,index_id_to_bin/1]). -include_lib("riak_pb/include/riak_pb_kv_codec.hrl"). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -endif. -type bucket() :: binary() | {binary(), binary()}. %% A bucket name -type key() :: binary() | 'undefined'. %% A key name -type id() :: {bucket(), key()}. -type vclock() :: binary(). %% An opaque vector clock -type content_type() :: string(). %% The media type of a value -type value() :: binary(). %% An opaque value -type contents() :: [{metadata(), value()}]. %% All metadata/value pairs in a `riakc_obj'. -type binary_index_id() :: {binary_index, string()}. -type binary_index_value() :: binary(). -type binary_index() :: {binary_index_id(), [binary_index_value()]}. -type integer_index_id() :: {integer_index, string()}. -type integer_index_value() :: integer(). -type integer_index() :: {integer_index_id(), [integer_index_value()]}. -type secondary_index_id() :: binary_index_id() | integer_index_id(). -type secondary_index_value() :: integer_index_value() | binary_index_value(). -type secondary_index() :: binary_index() | integer_index(). -type metadata_key() :: binary(). -type metadata_value() :: binary(). -type metadata_entry() :: {metadata_key(), metadata_value()}. -ifdef(pre17). -type metadata() :: dict(). %% Value metadata -else. -type metadata() :: dict:dict(metadata_key(), metadata_value()). -endif. -type tag() :: binary(). -type link() :: {tag(), [id()]}. -record(riakc_obj, { bucket :: bucket(), key :: key(), vclock :: vclock(), contents :: contents(), updatemetadata :: metadata(), updatevalue :: value() }). -type riakc_obj() :: #riakc_obj{}. %% The record/type containing the entire Riak object. -export_type([riakc_obj/0, bucket/0, key/0, vclock/0, contents/0, metadata/0, value/0, binary_index_id/0, binary_index/0, integer_index_id/0, integer_index/0, secondary_index/0, metadata_key/0, metadata_value/0, metadata_entry/0]). %% ==================================================================== %% object functions %% ==================================================================== %% @doc Constructor for new riak client objects. -spec new(bucket(), key()) -> riakc_obj(). new(Bucket, Key) -> build_client_object(Bucket, Key, undefined). %% @doc Constructor for new riak client objects with an update value. -spec new(bucket(), key(), value()) -> riakc_obj(). new(Bucket, Key, Value) -> build_client_object(Bucket, Key, Value). %% @doc Constructor for new riak client objects with an update value and content type. -spec new(bucket(), key(), value(), content_type()) -> riakc_obj(). new(Bucket, Key, Value, ContentType) -> case build_client_object(Bucket, Key, Value) of {error, Reason} -> {error, Reason}; O -> update_content_type(O, ContentType) end. %% @doc Build a new riak client object with non-empty key -spec build_client_object(bucket(), key(), undefined | value()) -> riakc_obj() | {error, atom()}. build_client_object(<<>>, K, _) when is_binary(K) -> {error, zero_length_bucket}; build_client_object(B, <<>>, _) when is_binary(B) -> {error, zero_length_key}; build_client_object({T, B0}=B, K, V) when is_binary(T), is_binary(B0), is_binary(K) orelse undefined =:= K -> #riakc_obj{bucket = B, key = K, contents = [], updatevalue = V}; build_client_object(B, K, V) when is_binary(B), is_binary(K) orelse undefined =:= K -> #riakc_obj{bucket = B, key = K, contents = [], updatevalue = V}. %% @doc Return the containing bucket for this riakc_obj. -spec bucket(Object::riakc_obj()) -> bucket(). bucket(O) -> O#riakc_obj.bucket. %% @doc Return the containing bucket for this riakc_obj. -spec only_bucket(Object::riakc_obj()) -> binary(). only_bucket(O) -> case O#riakc_obj.bucket of {_Type, Bucket} -> Bucket; Bucket -> Bucket end. -spec bucket_type(Object::riakc_obj()) -> bucket(). bucket_type(O) -> case O#riakc_obj.bucket of {Type, _Bucket} -> Type; _Bucket -> undefined end. %% @doc Return the key for this riakc_obj. -spec key(Object::riakc_obj()) -> key(). key(O) -> O#riakc_obj.key. %% @doc Return the vector clock for this riakc_obj. -spec vclock(Object::riakc_obj()) -> vclock() | undefined. vclock(O) -> O#riakc_obj.vclock. %% @doc Return the number of values (siblings) of this riakc_obj. -spec value_count(Object::riakc_obj()) -> non_neg_integer(). value_count(#riakc_obj{contents=Contents}) -> length(Contents). %% @doc Select the sibling to use for update - starting from 1. -spec select_sibling(pos_integer(), Object::riakc_obj()) -> riakc_obj(). select_sibling(Index, O) -> {MD,V} = lists:nth(Index, O#riakc_obj.contents), O#riakc_obj{updatemetadata=MD, updatevalue=V}. %% @doc Return the contents (a list of {metadata, value} tuples) for %% this riakc_obj. -spec get_contents(Object::riakc_obj()) -> contents(). get_contents(O) -> O#riakc_obj.contents. %% @doc Assert that this riakc_obj has no siblings and return its %% associated metadata. This function will throw `siblings' if %% the object has siblings (value_count() > 1). %% @throws siblings -spec get_metadata(Object::riakc_obj()) -> metadata(). get_metadata(O=#riakc_obj{}) -> case get_contents(O) of [] -> dict:new(); [{MD,_V}] -> MD; _ -> throw(siblings) end. %% @doc Return a list of the metadata values for this riakc_obj. -spec get_metadatas(Object::riakc_obj()) -> [metadata()]. get_metadatas(#riakc_obj{contents=Contents}) -> [M || {M,_V} <- Contents]. %% @doc Return the content type of the value if there are no siblings. %% @see get_metadata/1 %% @throws siblings -spec get_content_type(Object::riakc_obj()) -> content_type(). get_content_type(Object=#riakc_obj{}) -> UM = get_metadata(Object), md_ctype(UM). %% @doc Return a list of content types for all siblings. -spec get_content_types(Object::riakc_obj()) -> [content_type()]. get_content_types(Object=#riakc_obj{}) -> F = fun({M,_}) -> md_ctype(M) end, [F(C) || C<- get_contents(Object)]. %% @doc Assert that this riakc_obj has no siblings and return its %% associated value. This function will throw `siblings' if the %% object has siblings (value_count() > 1), or `no_value' if the %% object has no value. %% @throws siblings | no_value -spec get_value(Object::riakc_obj()) -> value(). get_value(#riakc_obj{}=O) -> case get_contents(O) of [] -> throw(no_value); [{_MD,V}] -> V; _ -> throw(siblings) end. %% @doc Return a list of object values for this riakc_obj. -spec get_values(Object::riakc_obj()) -> [value()]. get_values(#riakc_obj{contents=Contents}) -> [V || {_,V} <- Contents]. %% @doc Set the updated metadata of an object to M. -spec update_metadata(riakc_obj(), metadata()) -> riakc_obj(). update_metadata(Object=#riakc_obj{}, M) -> Object#riakc_obj{updatemetadata=M}. %% @doc Set the updated content-type of an object to CT. -spec update_content_type(riakc_obj(),content_type()|binary()) -> riakc_obj(). update_content_type(Object=#riakc_obj{}, CT) when is_binary(CT) -> update_content_type(Object, binary_to_list(CT)); update_content_type(Object=#riakc_obj{}, CT) when is_list(CT) -> M1 = get_update_metadata(Object), Object#riakc_obj{updatemetadata=dict:store(?MD_CTYPE, CT, M1)}. %% @doc Set the updated value of an object to V -spec update_value(riakc_obj(), value()) -> riakc_obj(). update_value(Object=#riakc_obj{}, V) -> Object#riakc_obj{updatevalue=V}. %% @doc Set the updated value of an object to V -spec update_value(riakc_obj(), value(), content_type()) -> riakc_obj(). update_value(Object=#riakc_obj{}, V, CT) -> O1 = update_content_type(Object, CT), O1#riakc_obj{updatevalue=V}. %% @doc Return the updated metadata of this riakc_obj. -spec get_update_metadata(Object::riakc_obj()) -> metadata(). get_update_metadata(#riakc_obj{updatemetadata=UM}=Object) -> case UM of undefined -> get_metadata(Object); UM -> UM end. %% @doc Return the content type of the update value -spec get_update_content_type(riakc_obj()) -> content_type(). get_update_content_type(Object=#riakc_obj{}) -> UM = get_update_metadata(Object), md_ctype(UM). %% @doc Return the updated value of this riakc_obj. -spec get_update_value(Object::riakc_obj()) -> value(). get_update_value(#riakc_obj{updatevalue=UV}=Object) -> case UV of undefined -> get_value(Object); UV -> UV end. %% @doc Return the content type from metadata -spec md_ctype(metadata()) -> undefined | content_type(). md_ctype(MetaData) -> case dict:find(?MD_CTYPE, MetaData) of error -> undefined; {ok, Ctype} -> Ctype end. %% @doc Set the vector clock of an object -spec set_vclock(riakc_obj(), vclock()) -> riakc_obj(). set_vclock(Object=#riakc_obj{}, Vclock) -> Object#riakc_obj{vclock=Vclock}. %% @doc Get specific metadata entry -spec get_user_metadata_entry(metadata(), metadata_key()) -> metadata_value() | notfound. get_user_metadata_entry(MD, Key) -> case dict:find(?MD_USERMETA, MD) of {ok, Entries} -> case lists:keyfind(Key, 1, Entries) of false -> notfound; {Key, Value} -> Value end; error -> notfound end. %% @doc Get all metadata entries -spec get_user_metadata_entries(metadata()) -> [metadata_entry()]. get_user_metadata_entries(MD) -> case dict:find(?MD_USERMETA, MD) of {ok, Entries} -> Entries; error -> [] end. %% @doc Clear all metadata entries -spec clear_user_metadata_entries(metadata()) -> metadata(). clear_user_metadata_entries(MD) -> dict:erase(?MD_USERMETA, MD). %% @doc Delete specific metadata entry -spec delete_user_metadata_entry(metadata(), metadata_key()) -> metadata(). delete_user_metadata_entry(MD, Key) -> case dict:find(?MD_USERMETA, MD) of {ok, Entries} -> case [{K, V} || {K, V} <- Entries, K /= Key] of [] -> dict:erase(?MD_USERMETA, MD); NewList -> dict:store(?MD_USERMETA, NewList, MD) end; error -> MD end. %% @doc Set a metadata entry -spec set_user_metadata_entry(metadata(), metadata_entry()) -> metadata(). set_user_metadata_entry(MD, {Key, Value}) -> case dict:find(?MD_USERMETA, MD) of {ok, Entries} -> case [{K, V} || {K, V} <- Entries, K /= Key] of [] -> dict:store(?MD_USERMETA, [{Key, Value}], MD); List -> dict:store(?MD_USERMETA, [{Key, Value} | List], MD) end; error -> dict:store(?MD_USERMETA, [{Key, Value}], MD) end. %% @doc Get value(s) for specific secondary index -spec get_secondary_index(metadata(), secondary_index_id()) -> [secondary_index_value()] | notfound. get_secondary_index(MD, {Type, Name}) -> IndexName = index_id_to_bin({Type, Name}), case dict:find(?MD_INDEX, MD) of {ok, Entries} -> case {Type, [V || {K, V} <- Entries, K == IndexName]} of {_, []} -> notfound; {binary_index, List} -> List; {integer_index, List} -> [list_to_integer(binary_to_list(I)) || I <- List] end; error -> notfound end. %% @doc Get all secondary indexes -spec get_secondary_indexes(metadata()) -> [secondary_index()]. get_secondary_indexes(MD) -> case dict:find(?MD_INDEX, MD) of {ok, Entries} -> dict:to_list(lists:foldl(fun({N, V}, D) -> case bin_to_index_id(N) of {binary_index, Name} -> dict:append({binary_index, Name}, V, D); {integer_index, Name} -> Int = list_to_integer(binary_to_list(V)), dict:append({integer_index, Name}, Int, D) end end, dict:new(), Entries)); error -> [] end. %% @doc Clear all secondary indexes -spec clear_secondary_indexes(metadata()) -> metadata(). clear_secondary_indexes(MD) -> dict:erase(?MD_INDEX, MD). %% @doc Delete specific secondary index -spec delete_secondary_index(metadata(), secondary_index_id()) -> metadata(). delete_secondary_index(MD, IndexId) -> IndexName = index_id_to_bin(IndexId), case dict:find(?MD_INDEX, MD) of {ok, Entries} -> List = [{N, V} || {N, V} <- Entries, N /= IndexName], dict:store(?MD_INDEX, List, MD); error -> MD end. %% @doc Set a secondary index -spec set_secondary_index(metadata(), secondary_index() | [secondary_index()]) -> metadata(). set_secondary_index(MD, []) -> MD; set_secondary_index(MD, {{binary_index, Name}, BinList}) -> set_secondary_index(MD, [{{binary_index, Name}, BinList}]); set_secondary_index(MD, {{integer_index, Name}, IntList}) -> set_secondary_index(MD, [{{integer_index, Name}, IntList}]); set_secondary_index(MD, [{{binary_index, Name}, BinList} | Rest]) -> IndexName = index_id_to_bin({binary_index, Name}), set_secondary_index(MD, [{IndexName, BinList} | Rest]); set_secondary_index(MD, [{{integer_index, Name}, IntList} | Rest]) -> IndexName = index_id_to_bin({integer_index, Name}), set_secondary_index(MD, [{IndexName, [list_to_binary(integer_to_list(I)) || I <- IntList]} | Rest]); set_secondary_index(MD, [{Id, BinList} | Rest]) when is_binary(Id) -> List = [{Id, V} || V <- BinList], case dict:find(?MD_INDEX, MD) of {ok, Entries} -> OtherEntries = [{N, V} || {N, V} <- Entries, N /= Id], NewList = lists:usort(lists:append(OtherEntries, List)), MD2 = dict:store(?MD_INDEX, NewList, MD), set_secondary_index(MD2, Rest); error -> NewList = lists:usort(List), MD2 = dict:store(?MD_INDEX, NewList, MD), set_secondary_index(MD2, Rest) end. %% @doc Add a secondary index -spec add_secondary_index(metadata(), secondary_index() | [secondary_index()]) -> metadata(). add_secondary_index(MD, []) -> MD; add_secondary_index(MD, {{binary_index, Name}, BinList}) -> add_secondary_index(MD, [{{binary_index, Name}, BinList}]); add_secondary_index(MD, {{integer_index, Name}, IntList}) -> add_secondary_index(MD, [{{integer_index, Name}, IntList}]); add_secondary_index(MD, [{{binary_index, Name}, BinList} | Rest]) -> IndexName = index_id_to_bin({binary_index, Name}), add_secondary_index(MD, [{IndexName, BinList} | Rest]); add_secondary_index(MD, [{{integer_index, Name}, IntList} | Rest]) -> IndexName = index_id_to_bin({integer_index, Name}), add_secondary_index(MD, [{IndexName, [list_to_binary(integer_to_list(I)) || I <- IntList]} | Rest]); add_secondary_index(MD, [{Id, BinList} | Rest]) when is_binary(Id) -> List = [{Id, V} || V <- BinList], case dict:find(?MD_INDEX, MD) of {ok, Entries} -> NewList = lists:usort(lists:append(Entries, List)), MD2 = dict:store(?MD_INDEX, NewList, MD), add_secondary_index(MD2, Rest); error -> NewList = lists:usort(List), MD2 = dict:store(?MD_INDEX, NewList, MD), add_secondary_index(MD2, Rest) end. %% @doc Get links for a specific tag -spec get_links(metadata(), tag()) -> [id()] | notfound. get_links(MD, Tag) -> case dict:find(?MD_LINKS, MD) of {ok, Links} -> case [I || {I, T} <- Links, T == Tag] of [] -> notfound; List -> List end; error -> notfound end. %% @doc Get all links -spec get_all_links(metadata()) -> [link()]. get_all_links(MD) -> case dict:find(?MD_LINKS, MD) of {ok, Links} -> dict:to_list(lists:foldl(fun({I, T}, D) -> dict:append(T, I, D) end, dict:new(), Links)); error -> [] end. %% @doc Clear all links -spec clear_links(metadata()) -> metadata(). clear_links(MD) -> dict:erase(?MD_LINKS, MD). %% @doc Delete links for a specific tag -spec delete_links(metadata(), tag()) -> metadata(). delete_links(MD, Tag) -> case dict:find(?MD_LINKS, MD) of {ok, Links} -> List = [{I, T} || {I, T} <- Links, T /= Tag], dict:store(?MD_LINKS, List, MD); error -> MD end. %% @doc Set links for a specific tag -spec set_link(metadata(), link() | [link()]) -> metadata(). set_link(MD, []) -> MD; set_link(MD, Link) when is_tuple(Link) -> set_link(MD, [Link]); set_link(MD, [{T, IdList} | Rest]) -> List = [{I, T} || I <- IdList], case dict:find(?MD_LINKS, MD) of {ok, Links} -> OtherLinks = [{N, Tag} || {N, Tag} <- Links, Tag /= T], NewList = lists:usort(lists:append(OtherLinks, List)), MD2 = dict:store(?MD_LINKS, NewList, MD), set_link(MD2, Rest); error -> NewList = lists:usort(List), MD2 = dict:store(?MD_LINKS, NewList, MD), set_link(MD2, Rest) end. %% @doc Add links for a specific tag -spec add_link(metadata(), secondary_index() | [secondary_index()]) -> metadata(). add_link(MD, []) -> MD; add_link(MD, Link) when is_tuple(Link) -> add_link(MD, [Link]); add_link(MD, [{T, IdList} | Rest]) -> List = [{I, T} || I <- IdList], case dict:find(?MD_LINKS, MD) of {ok, Links} -> NewList = lists:usort(lists:append(Links, List)), MD2 = dict:store(?MD_LINKS, NewList, MD), add_link(MD2, Rest); error -> NewList = lists:usort(List), MD2 = dict:store(?MD_LINKS, NewList, MD), add_link(MD2, Rest) end. %% @doc INTERNAL USE ONLY. Set the contents of riakc_obj to the %% {Metadata, Value} pairs in MVs. Normal clients should use the %% set_update_[value|metadata]() + apply_updates() method for changing %% object contents. %% @private -spec new_obj(bucket(), key(), vclock(), contents()) -> riakc_obj(). new_obj(Bucket, Key, Vclock, Contents) -> #riakc_obj{bucket = Bucket, key = Key, vclock = Vclock, contents = Contents}. %% @doc INTERNAL USE ONLY. Convert binary secondary index name to index id tuple. %% @private -spec bin_to_index_id(binary()) -> secondary_index_id(). bin_to_index_id(Index) -> Str = binary_to_list(Index), case lists:split((length(Str) - 4), Str) of {Name, "_bin"} -> {binary_index, Name}; {Name, "_int"} -> {integer_index, Name} end. %% @doc INTERNAL USE ONLY. Convert index id tuple to binary index name %% @private -spec index_id_to_bin(secondary_index_id()) -> binary(). index_id_to_bin({binary_index, Name}) -> list_to_binary([Name, "_bin"]); index_id_to_bin({integer_index, Name}) -> list_to_binary([Name, "_int"]). %% =================================================================== %% Unit Tests %% =================================================================== -ifdef(TEST). bucket_test() -> O = riakc_obj:new(<<"b">>, <<"k">>), ?assertEqual(<<"b">>, bucket(O)). key_test() -> O = riakc_obj:new(<<"b">>, <<"k">>), ?assertEqual(<<"k">>, key(O)). invalid_key_test() -> ?assertMatch({error, _}, riakc_obj:new(<<"b">>, <<>>)), ?assertMatch({error, _}, riakc_obj:new(<<"b">>, <<>>, <<"v">>)), ?assertMatch({error, _}, riakc_obj:new(<<"b">>, <<>>, <<"v">>, <<"application/x-foo">>)), ?assertMatch({error, _}, riakc_obj:new(<<>>, <<"k">>)), ?assertMatch({error, _}, riakc_obj:new(<<>>, <<"k">>, <<"v">>)), ?assertMatch({error, _}, riakc_obj:new(<<>>, <<"k">>, <<"v">>, <<"application/x-foo">>)), ?assertMatch({error, _}, riakc_obj:new(<<>>, <<>>)), ?assertMatch({error, _}, riakc_obj:new(<<>>, <<>>, <<"v">>)), ?assertMatch({error, _}, riakc_obj:new(<<>>, <<>>, <<"v">>, <<"application/x-foo">>)), ?assertMatch(#riakc_obj{}, riakc_obj:new(<<"b">>, undefined)), ?assertError(function_clause, riakc_obj:new("bucket","key")). vclock_test() -> %% For internal use only O = riakc_obj:new_obj(<<"b">>, <<"k">>, <<"vclock">>, []), ?assertEqual(<<"vclock">>, vclock(O)). newcontent0_test() -> O = riakc_obj:new(<<"b">>, <<"k">>), ?assertEqual(0, value_count(O)), ?assertEqual([], get_metadatas(O)), ?assertEqual([], get_values(O)), ?assertEqual([], get_contents(O)), ?assertEqual(dict:new(), get_metadata(O)), ?assertThrow(no_value, get_value(O)). contents0_test() -> O = riakc_obj:new_obj(<<"b">>, <<"k">>, <<"vclock">>, []), ?assertEqual(0, value_count(O)), ?assertEqual([], get_metadatas(O)), ?assertEqual([], get_values(O)), ?assertEqual([], get_contents(O)), ?assertEqual(dict:new(), get_metadata(O)), ?assertThrow(no_value, get_value(O)). contents1_test() -> M1 = dict:from_list([{?MD_VTAG, "tag1"}]), O = riakc_obj:new_obj(<<"b">>, <<"k">>, <<"vclock">>, [{M1, <<"val1">>}]), ?assertEqual(1, value_count(O)), ?assertEqual([M1], get_metadatas(O)), ?assertEqual([<<"val1">>], get_values(O)), ?assertEqual([{M1,<<"val1">>}], get_contents(O)), ?assertEqual(M1, get_metadata(O)), ?assertEqual(<<"val1">>, get_value(O)). contents2_test() -> M1 = dict:from_list([{?MD_VTAG, "tag1"}]), M2 = dict:from_list([{?MD_VTAG, "tag1"}]), O = riakc_obj:new_obj(<<"b">>, <<"k">>, <<"vclock">>, [{M1, <<"val1">>}, {M2, <<"val2">>}]), ?assertEqual(2, value_count(O)), ?assertEqual([M1, M2], get_metadatas(O)), ?assertEqual([<<"val1">>, <<"val2">>], get_values(O)), ?assertEqual([{M1,<<"val1">>},{M2,<<"val2">>}], get_contents(O)), ?assertThrow(siblings, get_metadata(O)), ?assertThrow(siblings, get_value(O)). update_metadata_test() -> O = riakc_obj:new(<<"b">>, <<"k">>), UM = riakc_obj:get_update_metadata(O), ?assertEqual([], dict:to_list(UM)). update_value_test() -> O = riakc_obj:new(<<"b">>, <<"k">>), ?assertThrow(no_value, get_update_value(O)), O1 = riakc_obj:update_value(O, <<"v">>), ?assertEqual(<<"v">>, get_update_value(O1)), M1 = dict:from_list([{?MD_VTAG, "tag1"}]), O2 = riakc_obj:update_metadata(O1, M1), ?assertEqual(M1, get_update_metadata(O2)). updatevalue_ct_test() -> O = riakc_obj:new(<<"b">>, <<"k">>), ?assertThrow(no_value, get_update_value(O)), O1 = riakc_obj:update_value(O, <<"v">>, "x-application/custom"), ?assertEqual(<<"v">>, get_update_value(O1)), M1 = dict:from_list([{?MD_VTAG, "tag1"}]), O2 = riakc_obj:update_metadata(O1, M1), ?assertEqual(M1, get_update_metadata(O2)), ?assertEqual("x-application/custom", get_update_content_type(O1)). update_content_type_test() -> O = riakc_obj:new(<<"b">>, <<"k">>), undefined = get_update_content_type(O), O1 = update_content_type(O, "application/json"), ?assertEqual("application/json", get_update_content_type(O1)). binary_content_type_test() -> O = riakc_obj:new(<<"b">>, <<"k">>, <<"v">>, <<"application/x-foo">>), ?assertEqual("application/x-foo", get_update_content_type(O)), O1 = update_content_type(O, <<"application/x-bar">>), ?assertEqual("application/x-bar", get_update_content_type(O1)). get_update_data_test() -> MD0 = dict:from_list([{?MD_CTYPE, "text/plain"}]), MD1 = dict:from_list([{?MD_CTYPE, "application/json"}]), O = new_obj(<<"b">>, <<"k">>, <<"">>, [{MD0, <<"v">>}]), %% Create an updated metadata object Oumd = update_metadata(O, MD1), %% Create an updated value object Ouv = update_value(O, <<"valueonly">>), %% Create updated both object Oboth = update_value(Oumd, <<"both">>), %% dbgh:start(), %% dbgh:trace(?MODULE) io:format("O=~p\n", [O]), ?assertEqual(<<"v">>, get_update_value(O)), MD2 = get_update_metadata(O), io:format("MD2=~p\n", [MD2]), ?assertEqual("text/plain", md_ctype(MD2)), MD3 = get_update_metadata(Oumd), ?assertEqual("application/json", md_ctype(MD3)), ?assertEqual(<<"valueonly">>, get_update_value(Ouv)), MD4 = get_update_metadata(Ouv), ?assertEqual("text/plain", md_ctype(MD4)), ?assertEqual(<<"both">>, get_update_value(Oboth)), MD5 = get_update_metadata(Oboth), ?assertEqual("application/json", md_ctype(MD5)). %% get_update_data_sibs_test() -> %% MD0 = dict:from_list([{?MD_CTYPE, "text/plain"}]), %% MD1 = dict:from_list([{?MD_CTYPE, "application/json"}]), %% O = new_obj(<<"b">>, <<"k">>, <<"">>, %% [{MD0, <<"v">>},{MD1, <<"sibling">>}]), %% %% Create an updated metadata object %% Oumd = update_metadata(O, MD1), %% %% Create an updated value object %% Ouv = update_value(O, <<"valueonly">>), %% %% Create updated both object %% Oboth = update_value(Oumd, <<"both">>), %% ?assertThrow({error, siblings}, get_update_data(O)), %% ?assertThrow({error, siblings}, get_update_data(Oumd)), %% ?assertEqual({error, siblings}, get_update_data(Ouv)), %% ?assertEqual({ok, {MD1, <<"both">>}}, get_update_data(Oboth)). select_sibling_test() -> MD0 = dict:from_list([{?MD_CTYPE, "text/plain"}]), MD1 = dict:from_list([{?MD_CTYPE, "application/json"}]), O = new_obj(<<"b">>, <<"k">>, <<"">>, [{MD0, <<"sib_one">>}, {MD1, <<"sib_two">>}]), O1 = select_sibling(1, O), O2 = select_sibling(2, O), ?assertEqual("text/plain", get_update_content_type(O1)), ?assertEqual(<<"sib_one">>, get_update_value(O1)), ?assertEqual("application/json", get_update_content_type(O2)), ?assertEqual(<<"sib_two">>, get_update_value(O2)). user_metadata_utilities_test() -> MD0 = dict:new(), ?assertEqual(dict:to_list(MD0), dict:to_list(delete_user_metadata_entry(MD0, <<"None">>))), MD1 = set_user_metadata_entry(MD0,{<<"Key1">>, <<"Value0">>}), ?assertEqual([{<<"Key1">>, <<"Value0">>}], get_user_metadata_entries(MD1)), MD2 = set_user_metadata_entry(MD1,{<<"Key1">>, <<"Value1">>}), ?assertEqual([{<<"Key1">>, <<"Value1">>}], get_user_metadata_entries(MD2)), ?assertEqual(notfound, get_user_metadata_entry(MD2, <<"WrongKey">>)), MD3 = set_user_metadata_entry(MD2,{<<"Key2">>, <<"Value2">>}), ?assertEqual(<<"Value2">>, get_user_metadata_entry(MD3, <<"Key2">>)), ?assertEqual(2, length(get_user_metadata_entries(MD3))), MD4 = delete_user_metadata_entry(MD3, <<"Key1">>), ?assertEqual([{<<"Key2">>, <<"Value2">>}], get_user_metadata_entries(MD4)), MD5 = clear_user_metadata_entries(MD4), ?assertEqual([], get_user_metadata_entries(MD5)), MD6 = delete_user_metadata_entry(MD1, <<"Key1">>), ?assertEqual([], get_user_metadata_entries(MD6)), ?assertEqual(notfound, get_user_metadata_entry(MD6, <<"Key1">>)). link_utilities_test() -> MD0 = dict:new(), ?assertEqual(notfound, get_links(MD0, <<"Tag1">>)), ?assertEqual([], get_all_links(MD0)), MD1 = set_link(MD0, [{<<"Tag1">>, [{<<"B">>,<<"K1">>},{<<"B">>,<<"K2">>}]}]), ?assertEqual([{<<"B">>,<<"K1">>},{<<"B">>,<<"K2">>}], lists:sort(get_links(MD1,<<"Tag1">>))), MD2 = add_link(MD1, [{<<"Tag1">>, [{<<"B">>,<<"K1">>},{<<"B">>,<<"K3">>}]}]), ?assertEqual([{<<"B">>,<<"K1">>},{<<"B">>,<<"K2">>},{<<"B">>,<<"K3">>}], lists:sort(get_links(MD2,<<"Tag1">>))), MD3 = set_link(MD2, [{<<"Tag1">>, [{<<"B">>,<<"K4">>}]}]), ?assertEqual([{<<"B">>,<<"K4">>}], lists:sort(get_links(MD3,<<"Tag1">>))), ?assertEqual([{<<"Tag1">>,[{<<"B">>,<<"K4">>}]}], get_all_links(MD3)), MD4 = set_link(MD3, [{<<"Tag2">>, [{<<"B">>,<<"K1">>}]}]), ?assertEqual([{<<"B">>,<<"K1">>}], lists:sort(get_links(MD4,<<"Tag2">>))), MD5 = delete_links(MD4,<<"Tag1">>), ?assertEqual([{<<"Tag2">>,[{<<"B">>,<<"K1">>}]}], get_all_links(MD5)), MD6 = clear_links(MD5), ?assertEqual([], get_all_links(MD6)). secondary_index_utilities_test() -> MD0 = dict:new(), ?assertEqual([], get_secondary_indexes(MD0)), ?assertEqual(notfound, get_secondary_index(MD0, {binary_index,"none"})), ?assertEqual(notfound, get_secondary_index(MD0, {integer_index,"none"})), MD1 = set_secondary_index(MD0, [{{integer_index,"idx"}, [12,4,56]}]), ?assertEqual([4,12,56], lists:sort(get_secondary_index(MD1,{integer_index,"idx"}))), MD2 = add_secondary_index(MD1, [{{integer_index,"idx"}, [4,15,34]}]), ?assertEqual([4,12,15,34,56], lists:sort(get_secondary_index(MD2,{integer_index,"idx"}))), MD3 = set_secondary_index(MD2, {{integer_index,"idx"}, [7]}), ?assertEqual([7], lists:sort(get_secondary_index(MD3,{integer_index,"idx"}))), MD4 = set_secondary_index(MD3, [{{binary_index,"idx"}, [<<"12">>,<<"4">>,<<"56">>]}]), ?assertEqual([<<"12">>,<<"4">>,<<"56">>], lists:sort(get_secondary_index(MD4,{binary_index,"idx"}))), MD5 = add_secondary_index(MD4, [{{binary_index,"idx"}, [<<"4">>,<<"15">>,<<"34">>]}]), ?assertEqual([<<"12">>,<<"15">>,<<"34">>,<<"4">>,<<"56">>], lists:sort(get_secondary_index(MD5,{binary_index,"idx"}))), MD6 = set_secondary_index(MD5, {{binary_index,"idx"}, [<<"7">>]}), ?assertEqual([<<"7">>], lists:sort(get_secondary_index(MD6,{binary_index,"idx"}))), ?assertEqual(2, length(get_secondary_indexes(MD6))), ?assertEqual(notfound, get_secondary_index(MD6,{binary_index,"error"})), MD7 = delete_secondary_index(MD6,{binary_index,"idx"}), ?assertEqual([{{integer_index,"idx"},[7]}], get_secondary_indexes(MD7)), MD8 = clear_secondary_indexes(MD7), ?assertEqual([], get_secondary_indexes(MD8)), MD9 = delete_secondary_index(MD8,{binary_index,"none"}), ?assertEqual([], get_secondary_indexes(MD9)), MD10 = add_secondary_index(MD9, [{{integer_index,"idx2"}, [23,4,34]}]), ?assertEqual([4,23,34], lists:sort(get_secondary_index(MD10,{integer_index,"idx2"}))), MD11 = dict:new(), ?assertEqual([], get_secondary_indexes(MD11)), ?assertEqual(notfound, get_secondary_index(MD11, {binary_index,"none"})), ?assertEqual(notfound, get_secondary_index(MD11, {integer_index,"none"})), MD12 = set_secondary_index(MD11, [{{integer_index,"itest"}, [12,4,56]},{{binary_index,"btest"}, [<<"test1">>]}]), ?assertEqual([4,12,56], lists:sort(get_secondary_index(MD12,{integer_index,"itest"}))), ?assertEqual([<<"test1">>], lists:sort(get_secondary_index(MD12,{binary_index,"btest"}))), MD13 = add_secondary_index(MD12, [{{integer_index,"itest"}, [4,15,34]},{{binary_index,"btest"}, [<<"test2">>]}]), ?assertEqual([4,12,15,34,56], lists:sort(get_secondary_index(MD13,{integer_index,"itest"}))), ?assertEqual([<<"test1">>,<<"test2">>], lists:sort(get_secondary_index(MD13,{binary_index,"btest"}))). -endif.