summaryrefslogtreecommitdiff
path: root/src/couch/src/couch_compress.erl
blob: cfcc2a4815378c56473c56fabba8391b41690a0e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
% Licensed 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.

-module(couch_compress).

-export([compress/2, decompress/1, is_compressed/2]).
-export([get_compression_method/0]).
-export([uncompressed_size/1]).

-include_lib("couch/include/couch_db.hrl").

% binaries compressed with snappy have their first byte set to this value
-define(SNAPPY_PREFIX, 1).
% Term prefixes documented at:
%      http://www.erlang.org/doc/apps/erts/erl_ext_dist.html
-define(TERM_PREFIX, 131).
-define(COMPRESSED_TERM_PREFIX, 131, 80).


get_compression_method() ->
    case config:get("couchdb", "file_compression") of
    undefined ->
        ?DEFAULT_COMPRESSION;
    Method1 ->
        case string:tokens(Method1, "_") of
        [Method] ->
            list_to_existing_atom(Method);
        [Method, Level] ->
            {list_to_existing_atom(Method), list_to_integer(Level)}
        end
    end.


compress(<<?SNAPPY_PREFIX, _/binary>> = Bin, snappy) ->
    Bin;
compress(<<?SNAPPY_PREFIX, _/binary>> = Bin, Method) ->
    compress(decompress(Bin), Method);
compress(<<?COMPRESSED_TERM_PREFIX, _/binary>> = Bin, {deflate, _Level}) ->
    Bin;
compress(<<?TERM_PREFIX, _/binary>> = Bin, Method) ->
    compress(decompress(Bin), Method);
compress(Term, none) ->
    ?term_to_bin(Term);
compress(Term, {deflate, Level}) ->
    term_to_binary(Term, [{minor_version, 1}, {compressed, Level}]);
compress(Term, snappy) ->
    Bin = ?term_to_bin(Term),
    try
        {ok, CompressedBin} = snappy:compress(Bin),
        <<?SNAPPY_PREFIX, CompressedBin/binary>>
    catch exit:snappy_nif_not_loaded ->
        Bin
    end.


decompress(<<?SNAPPY_PREFIX, Rest/binary>>) ->
    {ok, TermBin} = snappy:decompress(Rest),
    binary_to_term(TermBin);
decompress(<<?TERM_PREFIX, _/binary>> = Bin) ->
    binary_to_term(Bin);
decompress(_) ->
    error(invalid_compression).


is_compressed(<<?SNAPPY_PREFIX, _/binary>>, Method) ->
    Method =:= snappy;
is_compressed(<<?COMPRESSED_TERM_PREFIX, _/binary>>, {deflate, _Level}) ->
    true;
is_compressed(<<?COMPRESSED_TERM_PREFIX, _/binary>>, _Method) ->
    false;
is_compressed(<<?TERM_PREFIX, _/binary>>, Method) ->
    Method =:= none;
is_compressed(Term, _Method) when not is_binary(Term) ->
    false;
is_compressed(_, _) ->
    error(invalid_compression).


uncompressed_size(<<?SNAPPY_PREFIX, Rest/binary>>) ->
    {ok, Size} = snappy:uncompressed_length(Rest),
    Size;
uncompressed_size(<<?COMPRESSED_TERM_PREFIX, Size:32, _/binary>> = _Bin) ->
    % See http://erlang.org/doc/apps/erts/erl_ext_dist.html
    % The uncompressed binary would be encoded with <<131, Rest/binary>>
    % so need to add 1 for 131
    Size + 1;
uncompressed_size(<<?TERM_PREFIX, _/binary>> = Bin) ->
    byte_size(Bin);
uncompressed_size(_) ->
    error(invalid_compression).