You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by va...@apache.org on 2022/02/11 22:55:56 UTC

[couchdb] branch 3.x updated (732c3fd -> c6da1c6)

This is an automated email from the ASF dual-hosted git repository.

vatamane pushed a change to branch 3.x
in repository https://gitbox.apache.org/repos/asf/couchdb.git.


    from 732c3fd  Merge pull request #3927 from apache/index_sig
     new f3d4c9f  Add get_collator_version/0 libicu NIF function
     new 81fe821  Return the opaque collator version from _node/*/_versions
     new 24d6582  Track libicu collator versions in the view header
     new c6da1c6  View collation upgrade tests

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 rel/overlay/etc/default.ini                        |  10 +
 src/chttpd/src/chttpd_node.erl                     |  13 +-
 .../priv/couch_ejson_compare/couch_ejson_compare.c |  19 +-
 src/couch/src/couch_ejson_compare.erl              |   6 +-
 src/couch/src/couch_util.erl                       |   9 +
 src/couch/test/eunit/couch_ejson_compare_tests.erl |   9 +
 src/couch/test/eunit/couchdb_views_tests.erl       | 395 ++++++++++++++++++++-
 ...couch => 15a5cb17365a99cd9ddc7327c82bbd0d.view} | Bin 12479 -> 12388 bytes
 .../fixtures/1f2c24bc334d701c2048f85e7438eef1.view | Bin 0 -> 4230 bytes
 .../eunit/fixtures/{test.couch => colltest1.couch} | Bin 28878 -> 24768 bytes
 .../eunit/fixtures/{test.couch => db321.couch}     | Bin 28878 -> 28864 bytes
 src/couch_mrview/include/couch_mrview.hrl          |   4 +-
 src/couch_mrview/src/couch_mrview_index.erl        |  51 ++-
 src/couch_mrview/src/couch_mrview_util.erl         | 112 +++++-
 src/fabric/src/fabric_group_info.erl               |   5 +
 src/smoosh/src/smoosh_server.erl                   |  15 +
 test/elixir/test/design_docs_test.exs              |  15 +-
 17 files changed, 608 insertions(+), 55 deletions(-)
 copy src/couch/test/eunit/fixtures/{db_non_partitioned.couch => 15a5cb17365a99cd9ddc7327c82bbd0d.view} (91%)
 create mode 100644 src/couch/test/eunit/fixtures/1f2c24bc334d701c2048f85e7438eef1.view
 copy src/couch/test/eunit/fixtures/{test.couch => colltest1.couch} (75%)
 copy src/couch/test/eunit/fixtures/{test.couch => db321.couch} (87%)

[couchdb] 03/04: Track libicu collator versions in the view header

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

vatamane pushed a commit to branch 3.x
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit 24d6582dfd341b7c22fcabd8fb85217c923f7bb2
Author: Nick Vatamaniuc <va...@apache.org>
AuthorDate: Fri Feb 4 00:11:51 2022 -0500

    Track libicu collator versions in the view header
    
    Previously, libicu collator versions were not tracked, and during major OS
    version upgrades, it was possible to experience apparent data loss due to
    collation order changes between libicu library versions. The view order
    inconsistency would last until the view is compacted.
    
    This commit introduces a view info map in the header which records the list of
    libicu collator versions used by that view. The collator versions list is
    checked and updated every time a view is opened.
    
    The new view info map is re-using a previously removed view header field from
    2.x views. The upgrade logic from 2.x to 3.x ignores that header field, and
    this allows for transparent downgrading back to 3.2.1, and then upgrading back
    to 3.2.1+ versions, all while keeping the same view signature.
    
    If there is no collator version recorded in the view header, the first time the
    view is opened, the header will be upgraded to record the current libicu
    version. It's possible to avoid immediately writting the upgraded header and
    instead delaying till the next view data update with this setting:
    
    ```
    [view_upgrade]
    commit_on_header_upgrade = false
    ```
    
    By default it's toggled to `true`, meaning the view header will be written
    immediately.
    
    The list of collator version is returned in the _design/*/_info response. This
    allows users to easily track the condition when the view is built or opened
    with more than one libicu collator versions.
    
    Views which have more than one collator versions are submitted for
    re-compaction to the "upgrade_views" channel. This behavior is triggered both
    on update (which is the typical smoosh trigger mechanism), and when opened.
    Triggering on open is inteded to be used with read-only views, which may not be
    updated after libicu upgrades, and so would perpetually emit inconsistent data.
    
    Automatic re-compaction may be disabled with a config setting:
    
    ```
    [view_upgrade]
    compact_on_collator_upgrade = false
    ```
    
    The default value is `true`.
---
 rel/overlay/etc/default.ini                 |  10 +++
 src/couch_mrview/include/couch_mrview.hrl   |   4 +-
 src/couch_mrview/src/couch_mrview_index.erl |  51 +++++++++++--
 src/couch_mrview/src/couch_mrview_util.erl  | 112 +++++++++++++++++++++++-----
 src/fabric/src/fabric_group_info.erl        |   5 ++
 src/smoosh/src/smoosh_server.erl            |  15 ++++
 6 files changed, 171 insertions(+), 26 deletions(-)

diff --git a/rel/overlay/etc/default.ini b/rel/overlay/etc/default.ini
index 93aa1ca..3c15ae9 100644
--- a/rel/overlay/etc/default.ini
+++ b/rel/overlay/etc/default.ini
@@ -718,3 +718,13 @@ partitioned||* = true
 additional_port = false
 bind_address = 127.0.0.1
 port = {{prometheus_port}}
+
+[view_upgrade]
+; When enabled, views with more than one collator versions will be submitted
+; for auto-compaction to smoosh's "upgrade_views" channel.
+;compact_on_collator_upgrade = true
+
+; Eagerly commit views which been upgraded from older header formats. A reason
+; to disable this setting could be if the views need an upgrade but located on
+; read-only file system.
+;commit_on_header_upgrade = true
diff --git a/src/couch_mrview/include/couch_mrview.hrl b/src/couch_mrview/include/couch_mrview.hrl
index bb0ab0b..b31463c 100644
--- a/src/couch_mrview/include/couch_mrview.hrl
+++ b/src/couch_mrview/include/couch_mrview.hrl
@@ -29,7 +29,8 @@
     doc_acc,
     doc_queue,
     write_queue,
-    qserver=nil
+    qserver=nil,
+    view_info=#{}
 }).
 
 
@@ -49,6 +50,7 @@
     seq=0,
     purge_seq=0,
     id_btree_state=nil,
+    view_info=#{}, % replaces log btree in versions < 3.x
     view_states=nil
 }).
 
diff --git a/src/couch_mrview/src/couch_mrview_index.erl b/src/couch_mrview/src/couch_mrview_index.erl
index a024d35..1bfdb28 100644
--- a/src/couch_mrview/src/couch_mrview_index.erl
+++ b/src/couch_mrview/src/couch_mrview_index.erl
@@ -63,7 +63,8 @@ get(info, State) ->
         language = Lang,
         update_seq = UpdateSeq,
         purge_seq = PurgeSeq,
-        views = Views
+        views = Views,
+        view_info = ViewInfo
     } = State,
     {ok, FileSize} = couch_file:bytes(Fd),
     {ok, ExternalSize} = couch_mrview_util:calculate_external_size(Views),
@@ -72,7 +73,8 @@ get(info, State) ->
 
     UpdateOptions0 = get(update_options, State),
     UpdateOptions = [atom_to_binary(O, latin1) || O <- UpdateOptions0],
-
+    CollVsTups = couch_mrview_util:get_collator_versions(ViewInfo),
+    CollVsBins = [couch_util:version_to_binary(V) || V <- CollVsTups],
     {ok, [
         {signature, list_to_binary(couch_index_util:hexsig(Sig))},
         {language, Lang},
@@ -84,7 +86,8 @@ get(info, State) ->
             ]}},
         {update_seq, UpdateSeq},
         {purge_seq, PurgeSeq},
-        {update_options, UpdateOptions}
+        {update_options, UpdateOptions},
+        {collator_versions, CollVsBins}
     ]};
 get(Other, _) ->
     throw({unknown_index_property, Other}).
@@ -123,15 +126,15 @@ open(Db, State0) ->
                 % upgrade code for <= 2.x
                 {ok, {OldSig, Header}} ->
                     % Matching view signatures.
-                    NewSt = couch_mrview_util:init_state(Db, Fd, State, Header),
-                    ok = commit(NewSt),
+                    NewSt = init_and_upgrade_state(Db, Fd, State, Header),
                     ensure_local_purge_doc(Db, NewSt),
                     {ok, NewSt};
                 % end of upgrade code for <= 2.x
                 {ok, {Sig, Header}} ->
                     % Matching view signatures.
-                    NewSt = couch_mrview_util:init_state(Db, Fd, State, Header),
+                    NewSt = init_and_upgrade_state(Db, Fd, State, Header),
                     ensure_local_purge_doc(Db, NewSt),
+                    check_collator_versions(DbName, NewSt),
                     {ok, NewSt};
                 {ok, {WrongSig, _}} ->
                     couch_log:error(
@@ -321,3 +324,39 @@ update_local_purge_doc(Db, State, PSeq) ->
                 BaseDoc
         end,
     couch_db:update_doc(Db, Doc, []).
+
+init_and_upgrade_state(Db, Fd, State, Header) ->
+    {Commit, #mrst{} = Mrst} = couch_mrview_util:init_state(Db, Fd, State, Header),
+    case Commit of
+        true ->
+            case couch_mrview_util:commit_on_header_upgrade() of
+                true ->
+                    LogMsg = "~p : Index ~s ~s was upgraded",
+                    DbName = couch_db:name(Db),
+                    IdxName = State#mrst.idx_name,
+                    couch_log:warning(LogMsg, [?MODULE, DbName, IdxName]),
+                    ok = commit(Mrst),
+                    Mrst;
+                false ->
+                    Mrst
+            end;
+        false ->
+            Mrst
+    end.
+
+% Check if there are multiple collator versions used to build this view
+check_collator_versions(DbName, #mrst{} = Mrst) ->
+    case couch_mrview_util:compact_on_collator_upgrade() of
+        true ->
+            #mrst{view_info = ViewInfo, idx_name = IdxName} = Mrst,
+            Vers = couch_mrview_util:get_collator_versions(ViewInfo),
+            case length(Vers) >= 2 of
+                true ->
+                    Event = {index_collator_upgrade, IdxName},
+                    couch_event:notify(DbName, Event);
+                false ->
+                    ok
+            end;
+        false ->
+            ok
+    end.
diff --git a/src/couch_mrview/src/couch_mrview_util.erl b/src/couch_mrview/src/couch_mrview_util.erl
index b7220f7..9e3d292 100644
--- a/src/couch_mrview/src/couch_mrview_util.erl
+++ b/src/couch_mrview/src/couch_mrview_util.erl
@@ -32,6 +32,9 @@
 -export([get_view_keys/1, get_view_queries/1]).
 -export([set_view_type/3]).
 -export([set_extra/3, get_extra/2, get_extra/3]).
+-export([get_collator_versions/1]).
+-export([compact_on_collator_upgrade/0]).
+-export([commit_on_header_upgrade/0]).
 
 -define(MOD, couch_mrview_index).
 -define(GET_VIEW_RETRY_COUNT, 1).
@@ -285,6 +288,7 @@ init_state(Db, Fd, #mrst{views = Views} = State, nil) ->
         seq = 0,
         purge_seq = PurgeSeq,
         id_btree_state = nil,
+        view_info = update_collator_versions(#{}),
         view_states = [make_view_state(#mrview{}) || _ <- Views]
     },
     init_state(Db, Fd, State, Header);
@@ -293,12 +297,14 @@ init_state(Db, Fd, State, Header) ->
         language = Lang,
         views = Views
     } = State,
-    #mrheader{
+
+    {ShouldCommit, #mrheader{
         seq = Seq,
         purge_seq = PurgeSeq,
         id_btree_state = IdBtreeState,
+        view_info = ViewInfo,
         view_states = ViewStates
-    } = maybe_update_header(Header),
+    }} = maybe_update_header(Header),
 
     IdBtOpts = [
         {compression, couch_compress:get_compression_method()}
@@ -308,14 +314,15 @@ init_state(Db, Fd, State, Header) ->
     OpenViewFun = fun(St, View) -> open_view(Db, Fd, Lang, St, View) end,
     Views2 = lists:zipwith(OpenViewFun, ViewStates, Views),
 
-    State#mrst{
+    {ShouldCommit, State#mrst{
         fd = Fd,
         fd_monitor = erlang:monitor(process, Fd),
         update_seq = Seq,
         purge_seq = PurgeSeq,
         id_btree = IdBtree,
-        views = Views2
-    }.
+        views = Views2,
+        view_info = ViewInfo
+    }}.
 
 open_view(_Db, Fd, Lang, ViewState, View) ->
     ReduceFun = make_reduce_fun(Lang, View#mrview.reduce_funs),
@@ -764,14 +771,16 @@ make_header(State) ->
         update_seq = Seq,
         purge_seq = PurgeSeq,
         id_btree = IdBtree,
-        views = Views
+        views = Views,
+        view_info = ViewInfo
     } = State,
 
     #mrheader{
         seq = Seq,
         purge_seq = PurgeSeq,
         id_btree_state = get_btree_state(IdBtree),
-        view_states = [make_view_state(V) || V <- Views]
+        view_info = ViewInfo,
+        view_states = [make_disk_view_state(V) || V <- Views]
     }.
 
 index_file(DbName, Sig) ->
@@ -811,7 +820,8 @@ delete_file(FName) ->
 reset_index(Db, Fd, #mrst{sig = Sig} = State) ->
     ok = couch_file:truncate(Fd, 0),
     ok = couch_file:write_header(Fd, {Sig, nil}),
-    init_state(Db, Fd, reset_state(State), nil).
+    {_Commit, NewSt} = init_state(Db, Fd, reset_state(State), nil),
+    NewSt.
 
 reset_state(State) ->
     State#mrst{
@@ -819,7 +829,8 @@ reset_state(State) ->
         qserver = nil,
         update_seq = 0,
         id_btree = nil,
-        views = [View#mrview{btree = nil} || View <- State#mrst.views]
+        views = [View#mrview{btree = nil} || View <- State#mrst.views],
+        view_info = #{}
     }.
 
 all_docs_key_opts(#mrargs{extra = Extra} = Args) ->
@@ -1070,18 +1081,41 @@ old_view_format(View, SI, KSI) ->
         View#mrview.options
     }.
 
-maybe_update_header(#mrheader{} = Header) ->
-    Header;
-maybe_update_header(Header) when tuple_size(Header) == 6 ->
-    #mrheader{
-        seq = element(2, Header),
-        purge_seq = element(3, Header),
-        id_btree_state = element(4, Header),
-        view_states = [make_view_state(S) || S <- element(6, Header)]
-    }.
+maybe_update_header(#mrheader{view_info = Info} = Header) when is_map(Info) ->
+    % Latest (3.2.1+) version. The size of the record is the same as
+    % the <2.3.1 version. The main difference is that the LogBt field
+    % is now a map. This trick allows for easy downgrading back to
+    % version 3.2.1 and then upgrading back to 3.2.1+ if needed.
+    {false, Header#mrheader{
+        view_info = update_collator_versions(Info),
+        view_states = [make_view_state(S) || S <- Header#mrheader.view_states]
+    }};
+maybe_update_header({mrheader, Seq, PSeq, IDBt, ViewStates}) ->
+    % Versions >2.3.1 and =<3.2.1 (no view info map)
+    {true, #mrheader{
+        seq = Seq,
+        purge_seq = PSeq,
+        id_btree_state = IDBt,
+        view_info = update_collator_versions(#{}),
+        view_states = [make_view_state(S) || S <- ViewStates]
+    }};
+maybe_update_header({mrheader, Seq, PSeq, IDBt, _LogBt, ViewStates}) ->
+    % Versions <2.3.1.
+    {true, #mrheader{
+        seq = Seq,
+        purge_seq = PSeq,
+        id_btree_state = IDBt,
+        view_info = update_collator_versions(#{}),
+        view_states = [make_view_state(S) || S <- ViewStates]
+    }}.
 
 %% End of <= 2.x upgrade code.
 
+% Used for creating a new view states or reading (upgrading) from
+% disk. On disk, the state will be a 5 tuple with nil values in
+% positions 2 and 3 to allow downgrading between current version and
+% =<3.2.1 views.
+%
 make_view_state(#mrview{} = View) ->
     BTState = get_btree_state(View#mrview.btree),
     {
@@ -1089,11 +1123,35 @@ make_view_state(#mrview{} = View) ->
         View#mrview.update_seq,
         View#mrview.purge_seq
     };
-make_view_state({BTState, _SeqBTState, _KSeqBTState, UpdateSeq, PurgeSeq}) ->
+make_view_state({BTState, UpdateSeq, PurgeSeq}) ->
+    % Versions >2.x and =<3.2.1
+    {BTState, UpdateSeq, PurgeSeq};
+make_view_state({BTState, _SeqBTOrNil, _KSeqBTOrNil, UpdateSeq, PurgeSeq}) ->
+    % Current disk version and version 2.x views
     {BTState, UpdateSeq, PurgeSeq};
 make_view_state(nil) ->
     {nil, 0, 0}.
 
+% Used by make_header/1 before committing to disk. The two added nil
+% values in position 2 and 3 make the state on disk look like a 2.x
+% view, where those fields used to be SeqBTState and KSeqBTState,
+% respectively. This is to allow easy downgrading between current
+% version and >2.x and =<3.2.1 views.
+%
+make_disk_view_state(#mrview{} = View) ->
+    BTState = get_btree_state(View#mrview.btree),
+    {
+        BTState,
+        nil,
+        nil,
+        View#mrview.update_seq,
+        View#mrview.purge_seq
+    };
+make_disk_view_state({BTState, UpdateSeq, PurgeSeq}) ->
+    {BTState, nil, nil, UpdateSeq, PurgeSeq};
+make_disk_view_state(nil) ->
+    {nil, nil, nil, 0, 0}.
+
 get_key_btree_state(ViewState) ->
     element(1, ViewState).
 
@@ -1216,3 +1274,19 @@ kv_external_size(KVList, Reduction) ->
         ?term_size(Reduction),
         KVList
     ).
+
+update_collator_versions(#{} = ViewInfo) ->
+    Versions = maps:get(ucol_vs, ViewInfo, []),
+    Ver = tuple_to_list(couch_ejson_compare:get_collator_version()),
+    ViewInfo#{ucol_vs => lists:usort([Ver | Versions])}.
+
+get_collator_versions(#{ucol_vs := Versions}) when is_list(Versions) ->
+    Versions;
+get_collator_versions(#{}) ->
+    [].
+
+compact_on_collator_upgrade() ->
+    config:get_boolean("view_upgrade", "compact_on_collator_upgrade", true).
+
+commit_on_header_upgrade() ->
+    config:get_boolean("view_upgrade", "commit_on_header_upgrade", true).
diff --git a/src/fabric/src/fabric_group_info.erl b/src/fabric/src/fabric_group_info.erl
index c7d7293..ff875aa 100644
--- a/src/fabric/src/fabric_group_info.erl
+++ b/src/fabric/src/fabric_group_info.erl
@@ -135,6 +135,11 @@ merge_results(Info) ->
                 [{update_seq, lists:sum(X)} | Acc];
             (purge_seq, X, Acc) ->
                 [{purge_seq, lists:sum(X)} | Acc];
+            (collator_versions, X, Acc) ->
+                % Concatenate (undo orddict:append/3), then
+                % sort and remove duplicates.
+                Vs = lists:usort(lists:flatmap(fun(V) -> V end, X)),
+                [{collator_versions, Vs} | Acc];
             (_, _, Acc) ->
                 Acc
         end,
diff --git a/src/smoosh/src/smoosh_server.erl b/src/smoosh/src/smoosh_server.erl
index 0526625..5529e93 100644
--- a/src/smoosh/src/smoosh_server.erl
+++ b/src/smoosh/src/smoosh_server.erl
@@ -100,6 +100,9 @@ handle_db_event(DbName, updated, St) ->
 handle_db_event(DbName, {index_commit, IdxName}, St) ->
     smoosh_server:enqueue({DbName, IdxName}),
     {ok, St};
+handle_db_event(DbName, {index_collator_upgrade, IdxName}, St) ->
+    smoosh_server:enqueue({DbName, IdxName}),
+    {ok, St};
 handle_db_event(DbName, {schema_updated, DDocId}, St) ->
     smoosh_server:enqueue({schema, DbName, DDocId}),
     {ok, St};
@@ -480,6 +483,9 @@ get_priority(Channel) ->
     smoosh_utils:get(Channel, "priority", "ratio").
 
 needs_upgrade(Props) ->
+    db_needs_upgrade(Props) orelse view_needs_upgrade(Props).
+
+db_needs_upgrade(Props) ->
     DiskVersion = couch_util:get_value(disk_format_version, Props),
     case couch_util:get_value(engine, Props) of
         couch_bt_engine ->
@@ -488,6 +494,15 @@ needs_upgrade(Props) ->
             false
     end.
 
+view_needs_upgrade(Props) ->
+    case couch_util:get_value(collator_versions, Props) of
+        undefined ->
+            false;
+        Versions when is_list(Versions) ->
+            Enabled = couch_mrview_util:compact_on_collator_upgrade(),
+            Enabled andalso length(Versions) >= 2
+    end.
+
 -ifdef(TEST).
 -include_lib("eunit/include/eunit.hrl").
 

[couchdb] 04/04: View collation upgrade tests

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

vatamane pushed a commit to branch 3.x
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit c6da1c6dcea55d9a782e4111b2764e114154adb7
Author: Nick Vatamaniuc <va...@apache.org>
AuthorDate: Fri Feb 4 00:59:41 2022 -0500

    View collation upgrade tests
    
    Checking a few scenarios:
     * _design/*/_info returns collation versions
     * A 2.x view can upgraded to have a view info map
     * A 3.2.1 view can be upgraded to have a view info map
     * A view with multiple collator versions:
       - Can be opened and tagged with the current libicu version
       - Can be queried, written to and then queried again
       - Will be submitted for compaction when open and on updates
       - After compaction the view will have only one collator version
    
    Includes two new fixture views:
     * Version 3.2.1 view without the view info map field
     * A view with a bogus (old) libicu version "1.1.1.1"
    
    The bogus libicu version view is generated by overriding the collation
    version API to return `[1, 1, 1, 1]` [1]. And it can be re-created
    with this snippet [2]
    
    [1]
    ```
    update_collator_versions(#{} = ViewInfo) ->
         Versions = maps:get(ucol_vs, ViewInfo, []),
    -    Ver = tuple_to_list(couch_ejson_compare:get_collator_version()),
    +    % Ver = tuple_to_list(couch_ejson_compare:get_collator_version()),
    +    Ver = [1, 1, 1, 1],
    ```
    
    [2] `http` is the `httpie` command line HTTP client
    ```
    http delete $DB/colltest1 && http put $DB/colltest1'?q=1' && http put $DB/colltest1/_design/colltest1ddoc views:='{"colltest1view":{"map":"function(doc){emit(doc.id, null);}"}}' && http put $DB/colltest1/d1 a=b && http put $DB/colltest1/d2 c=d
    ```
---
 src/couch/test/eunit/couchdb_views_tests.erl       | 395 ++++++++++++++++++++-
 .../fixtures/15a5cb17365a99cd9ddc7327c82bbd0d.view | Bin 0 -> 12388 bytes
 .../fixtures/1f2c24bc334d701c2048f85e7438eef1.view | Bin 0 -> 4230 bytes
 src/couch/test/eunit/fixtures/colltest1.couch      | Bin 0 -> 24768 bytes
 src/couch/test/eunit/fixtures/db321.couch          | Bin 0 -> 28864 bytes
 test/elixir/test/design_docs_test.exs              |  15 +-
 6 files changed, 392 insertions(+), 18 deletions(-)

diff --git a/src/couch/test/eunit/couchdb_views_tests.erl b/src/couch/test/eunit/couchdb_views_tests.erl
index f4d51bd..183a5e6 100644
--- a/src/couch/test/eunit/couchdb_views_tests.erl
+++ b/src/couch/test/eunit/couchdb_views_tests.erl
@@ -19,6 +19,7 @@
 -define(DELAY, 100).
 -define(TIMEOUT, 1000).
 -define(WAIT_DELAY_COUNT, 40).
+-define(OLD_COLLATOR_VERSION, [1, 1, 1, 1]).
 
 setup() ->
     DbName = ?tempdb(),
@@ -38,25 +39,45 @@ setup_with_docs() ->
     create_design_doc(DbName, <<"_design/foo">>, <<"bar">>),
     DbName.
 
-setup_legacy() ->
-    DbName = <<"test">>,
-    DbFileName = "test.couch",
-    OldDbFilePath = filename:join([?FIXTURESDIR, DbFileName]),
+% See src/couch/test/eunit/fixtures for fixture files
+%
+setup_legacy_2x() ->
+    % see src/couch/test/eunit/fixtures folder
+    DbName = "test",
     OldViewName = "6cf2c2f766f87b618edf6630b00f8736.view",
-    FixtureViewFilePath = filename:join([?FIXTURESDIR, OldViewName]),
     NewViewName = "a1c5929f912aca32f13446122cc6ce50.view",
+    setup_legacy(DbName, OldViewName, NewViewName).
+
+setup_legacy_3_2_1() ->
+    DbName = "db321",
+    ViewName = "15a5cb17365a99cd9ddc7327c82bbd0d.view",
+    % View signature stays the same
+    setup_legacy(DbName, ViewName, ViewName).
+
+setup_collator_test1() ->
+    DbName = "colltest1",
+    ViewName = "1f2c24bc334d701c2048f85e7438eef1.view",
+    % View signature stays the same
+    setup_legacy(DbName, ViewName, ViewName).
+
+setup_legacy(DbName, OldViewName, NewViewName) when
+    is_list(DbName), is_list(OldViewName), is_list(NewViewName)
+->
+    DbFileName = DbName ++ ".couch",
+    OldDbFilePath = filename:join([?FIXTURESDIR, DbFileName]),
+    FixtureViewFilePath = filename:join([?FIXTURESDIR, OldViewName]),
 
     DbDir = config:get("couchdb", "database_dir"),
     ViewDir = config:get("couchdb", "view_index_dir"),
     OldViewFilePath = filename:join([
         ViewDir,
-        ".test_design",
+        "." ++ DbName ++ "_design",
         "mrview",
         OldViewName
     ]),
     NewViewFilePath = filename:join([
         ViewDir,
-        ".test_design",
+        "." ++ DbName ++ "_design",
         "mrview",
         NewViewName
     ]),
@@ -76,7 +97,7 @@ setup_legacy() ->
 
     {ok, _} = file:copy(FixtureViewFilePath, OldViewFilePath),
 
-    {DbName, Files}.
+    {?l2b(DbName), Files}.
 
 teardown({DbName, _}) ->
     teardown(DbName);
@@ -161,24 +182,88 @@ backup_restore_test_() ->
         }
     }.
 
-upgrade_test_() ->
+upgrade_2x_test_() ->
     {
-        "Upgrade tests",
+        "Upgrade 2x tests",
         {
             setup,
             fun test_util:start_couch/0,
             fun test_util:stop_couch/1,
             {
                 foreach,
-                fun setup_legacy/0,
+                fun setup_legacy_2x/0,
                 fun teardown_legacy/1,
                 [
-                    fun should_upgrade_legacy_view_files/1
+                    fun should_upgrade_legacy_2x_view_files/1
                 ]
             }
         }
     }.
 
+upgrade_3_2_1_test_() ->
+    {
+        "Upgrade 3.2.1 tests",
+        {
+            foreach,
+            fun() ->
+                Ctx = test_util:start_couch(),
+                DbFiles = setup_legacy_3_2_1(),
+                {Ctx, DbFiles}
+            end,
+            fun({Ctx, DbFiles}) ->
+                teardown_legacy(DbFiles),
+                test_util:stop_couch(Ctx)
+            end,
+            [
+                fun should_upgrade_legacy_3_2_1_view_files/1,
+                fun can_disable_auto_commit_on_view_upgrade/1
+            ]
+        }
+    }.
+
+multiple_view_collators_test_() ->
+    {
+        "Test views with multiple collators",
+        {
+            foreach,
+            fun() ->
+                Ctx = test_util:start_couch(),
+                DbFiles = setup_collator_test1(),
+                {Ctx, DbFiles}
+            end,
+            fun({Ctx, DbFiles}) ->
+                teardown_legacy(DbFiles),
+                test_util:stop_couch(Ctx)
+            end,
+            [
+                fun can_read_views_with_old_collators/1,
+                fun can_update_views_with_old_collators/1
+            ]
+        }
+    }.
+
+autocompact_view_to_upgrade_collators_test_() ->
+    {
+        "Auto compactions triggered to update collators",
+        {
+            foreach,
+            fun() ->
+                Ctx = test_util:start_couch([smoosh]),
+                DbFiles = setup_collator_test1(),
+                {Ctx, DbFiles}
+            end,
+            fun({Ctx, DbFiles}) ->
+                teardown_legacy(DbFiles),
+                test_util:stop_couch(Ctx)
+            end,
+            [
+                fun view_collator_auto_upgrade_on_open/1,
+                fun view_collator_auto_upgrade_on_update/1,
+                fun view_collator_auto_upgrade_can_be_disabled/1
+            ]
+        }
+    }.
+
 should_not_remember_docs_in_index_after_backup_restore(DbName) ->
     ?_test(begin
         %% COUCHDB-640
@@ -201,7 +286,7 @@ should_not_remember_docs_in_index_after_backup_restore(DbName) ->
         ?assertNot(has_doc("doc666", Rows1))
     end).
 
-should_upgrade_legacy_view_files({DbName, Files}) ->
+should_upgrade_legacy_2x_view_files({DbName, Files}) ->
     ?_test(begin
         [_NewDbFilePath, OldViewFilePath, NewViewFilePath] = Files,
         ok = config:set("query_server_config", "commit_freq", "0", false),
@@ -231,11 +316,258 @@ should_upgrade_legacy_view_files({DbName, Files}) ->
         % ensure new header
 
         % have to wait for awhile to upgrade the index
-        timer:sleep(2000),
+        wait_mrheader_record(NewViewFilePath, 3000),
         NewHeader = read_header(NewViewFilePath),
         ?assertMatch(#mrheader{}, NewHeader),
+
+        % assert that 2.x header was upgraded with a view_info map
+        ViewInfo = NewHeader#mrheader.view_info,
+        ?assert(is_map(ViewInfo)),
+        Ver = tuple_to_list(couch_ejson_compare:get_collator_version()),
+        ?assertMatch(#{ucol_vs := [Ver]}, ViewInfo),
+
         NewViewStatus = hd(NewHeader#mrheader.view_states),
-        ?assertEqual(3, tuple_size(NewViewStatus))
+        ?assertEqual(5, tuple_size(NewViewStatus))
+    end).
+
+should_upgrade_legacy_3_2_1_view_files({_, {DbName, Files}}) ->
+    ?_test(begin
+        [_NewDbFilePath, OldViewFilePath, NewViewFilePath] = Files,
+        ok = config:set("query_server_config", "commit_freq", "0", false),
+
+        % preliminary assert that we expect view signature and view files names
+        % to stay exactly the same
+        ?assertEqual(OldViewFilePath, NewViewFilePath),
+
+        % ensure old header
+        OldHeader = read_header(OldViewFilePath),
+        ?assertEqual(5, tuple_size(OldHeader)),
+        ?assertMatch(mrheader, element(1, OldHeader)),
+
+        % query view for expected results
+        Rows0 = query_view(DbName, "ddoc321", "view321"),
+        ?assertEqual(2, length(Rows0)),
+
+        % have to wait for a while to write to the index
+        % with [view_upgrade] commit_on_header_upgrade should happen after open
+        wait_mrheader_record(NewViewFilePath, 3000),
+        NewHeader = read_header(NewViewFilePath),
+        ?assertMatch(#mrheader{}, NewHeader),
+
+        % assert that 3.2.1 header was upgraded with a view_info map
+        ViewInfo = NewHeader#mrheader.view_info,
+        ?assert(is_map(ViewInfo)),
+        Ver = tuple_to_list(couch_ejson_compare:get_collator_version()),
+        ?assertMatch(#{ucol_vs := [Ver]}, ViewInfo),
+
+        NewViewStatus = hd(NewHeader#mrheader.view_states),
+        ?assertEqual(5, tuple_size(NewViewStatus)),
+
+        NewSig = get_signature(DbName, "ddoc321"),
+        OldSig = filename:basename(OldViewFilePath, ".view"),
+        ?assertEqual(OldSig, ?b2l(NewSig))
+    end).
+
+can_disable_auto_commit_on_view_upgrade({_, {DbName, Files}}) ->
+    ?_test(begin
+        [_NewDbFilePath, OldViewFilePath, NewViewFilePath] = Files,
+        ok = config:set("query_server_config", "commit_freq", "0", false),
+        ok = config:set(
+            "view_upgrade",
+            "commit_on_header_upgrade",
+            "false",
+            false
+        ),
+
+        % preliminary assert that we expect view signature and view files names
+        % to stay exactly the same
+        ?assertEqual(OldViewFilePath, NewViewFilePath),
+
+        % ensure old header
+        OldHeader = read_header(OldViewFilePath),
+        ?assertEqual(5, tuple_size(OldHeader)),
+        ?assertMatch(mrheader, element(1, OldHeader)),
+
+        % query view for expected results
+        Rows0 = query_view(DbName, "ddoc321", "view321"),
+        ?assertEqual(2, length(Rows0)),
+
+        % ensure old header is still there after a query as we intend not to
+        % auto-commit after header open
+        AfterQueryHeader = read_header(NewViewFilePath),
+        ?assertEqual(5, tuple_size(AfterQueryHeader)),
+        ?assertMatch(mrheader, element(1, AfterQueryHeader)),
+
+        % add 3 new documents
+        create_docs(DbName),
+
+        % query view for expected results
+        Rows1 = query_view(DbName, "ddoc321", "view321"),
+        ?assertEqual(5, length(Rows1)),
+
+        % ensure old file is still there
+        ?assert(filelib:is_regular(OldViewFilePath)),
+
+        % ensure new header
+
+        % have to wait for awhile to write to the index
+        wait_mrheader_record(NewViewFilePath, 3000),
+        NewHeader = read_header(NewViewFilePath),
+        ?assertMatch(#mrheader{}, NewHeader),
+
+        % assert that 3.2.1 header was upgraded with a view_info map
+        ViewInfo = NewHeader#mrheader.view_info,
+        ?assert(is_map(ViewInfo)),
+        Ver = tuple_to_list(couch_ejson_compare:get_collator_version()),
+        ?assertMatch(#{ucol_vs := [Ver]}, ViewInfo),
+
+        NewViewStatus = hd(NewHeader#mrheader.view_states),
+        ?assertEqual(5, tuple_size(NewViewStatus)),
+
+        NewSig = get_signature(DbName, "ddoc321"),
+        OldSig = filename:basename(OldViewFilePath, ".view"),
+        ?assertEqual(OldSig, ?b2l(NewSig))
+    end).
+
+can_read_views_with_old_collators({_, {DbName, Files}}) ->
+    ?_test(begin
+        [_NewDbFilePath, ViewFilePath, ViewFilePath] = Files,
+
+        % check that there is an old (bogus) collator version
+        Header1 = read_header(ViewFilePath),
+        ViewInfo1 = Header1#mrheader.view_info,
+        ?assert(is_map(ViewInfo1)),
+        ?assertMatch(#{ucol_vs := [?OLD_COLLATOR_VERSION]}, ViewInfo1),
+
+        % view query works with the old collator version
+        Rows0 = query_view(DbName, "colltest1ddoc", "colltest1view"),
+        ?assertEqual(2, length(Rows0))
+    end).
+
+can_update_views_with_old_collators({_, {DbName, Files}}) ->
+    ?_test(begin
+        [_NewDbFilePath, ViewFilePath, ViewFilePath] = Files,
+        ok = config:set("query_server_config", "commit_freq", "0", false),
+
+        % check that there is an old (bogus) collator version
+        Header1 = read_header(ViewFilePath),
+        ViewInfo1 = Header1#mrheader.view_info,
+        ?assert(is_map(ViewInfo1)),
+        ?assertMatch(#{ucol_vs := [?OLD_COLLATOR_VERSION]}, ViewInfo1),
+
+        create_docs(DbName),
+        Rows1 = query_view(DbName, "colltest1ddoc", "colltest1view"),
+        ?assertEqual(5, length(Rows1)),
+
+        % ensure old view file is still there
+        ?assert(filelib:is_regular(ViewFilePath)),
+
+        % should have two collator versions
+        CurVer = tuple_to_list(couch_ejson_compare:get_collator_version()),
+        ExpVersions = [?OLD_COLLATOR_VERSION, CurVer],
+        ok = wait_collator_versions(ExpVersions, ViewFilePath, 3000),
+        Header2 = read_header(ViewFilePath),
+        ViewInfo2 = Header2#mrheader.view_info,
+        ?assertMatch(#{ucol_vs := ExpVersions}, ViewInfo2)
+    end).
+
+view_collator_auto_upgrade_on_open({_, {DbName, Files}}) ->
+    ?_test(begin
+        [_NewDbFilePath, ViewFilePath, ViewFilePath] = Files,
+
+        % quick sanity check the test setup
+        Header1 = read_header(ViewFilePath),
+        ViewInfo1 = Header1#mrheader.view_info,
+        ?assertMatch(#{ucol_vs := [?OLD_COLLATOR_VERSION]}, ViewInfo1),
+
+        % make sure smoosh is active
+        smoosh:resume(),
+
+        % query the view
+        Rows = query_view(DbName, "colltest1ddoc", "colltest1view"),
+        ?assertEqual(2, length(Rows)),
+
+        CurVer = tuple_to_list(couch_ejson_compare:get_collator_version()),
+        wait_collator_versions([CurVer], ViewFilePath, 3000),
+        Header2 = read_header(ViewFilePath),
+        ViewInfo2 = Header2#mrheader.view_info,
+        ?assertMatch(#{ucol_vs := [CurVer]}, ViewInfo2),
+
+        % query the view again
+        ?assertEqual(Rows, query_view(DbName, "colltest1ddoc", "colltest1view"))
+    end).
+
+view_collator_auto_upgrade_on_update({_, {DbName, Files}}) ->
+    ?_test(begin
+        [_NewDbFilePath, ViewFilePath, ViewFilePath] = Files,
+        ok = config:set("query_server_config", "commit_freq", "0", false),
+
+        % quick sanity check the test setup
+        Header1 = read_header(ViewFilePath),
+        ViewInfo1 = Header1#mrheader.view_info,
+        ?assertMatch(#{ucol_vs := [?OLD_COLLATOR_VERSION]}, ViewInfo1),
+
+        % stop smoosh so the open/read trigger doesn't fire
+        application:stop(smoosh),
+
+        % open the view so after smoosh starts it won't trigger
+        % the open auto-update event
+        Rows0 = query_view(DbName, "colltest1ddoc", "colltest1view"),
+        ?assertEqual(2, length(Rows0)),
+
+        % update the db
+        create_docs(DbName),
+
+        % start smoosh
+        application:start(smoosh),
+        smoosh:resume(),
+
+        % query the view to trigger an index commit event
+        Rows1 = query_view(DbName, "colltest1ddoc", "colltest1view"),
+        ?assertEqual(5, length(Rows1)),
+
+        CurVer = tuple_to_list(couch_ejson_compare:get_collator_version()),
+        wait_collator_versions([CurVer], ViewFilePath, 3000),
+        Header2 = read_header(ViewFilePath),
+        ViewInfo2 = Header2#mrheader.view_info,
+        ?assertMatch(#{ucol_vs := [CurVer]}, ViewInfo2)
+    end).
+
+view_collator_auto_upgrade_can_be_disabled({_, {DbName, Files}}) ->
+    ?_test(begin
+        [_NewDbFilePath, ViewFilePath, ViewFilePath] = Files,
+        ok = config:set("query_server_config", "commit_freq", "0", false),
+        ok = config:set(
+            "view_upgrade",
+            "compact_on_collator_upgrade",
+            "false",
+            false
+        ),
+
+        % quick sanity check the test setup
+        Header1 = read_header(ViewFilePath),
+        ViewInfo1 = Header1#mrheader.view_info,
+        ?assertMatch(#{ucol_vs := [?OLD_COLLATOR_VERSION]}, ViewInfo1),
+
+        % activate smoosh
+        smoosh:resume(),
+
+        % query the view
+        Rows0 = query_view(DbName, "colltest1ddoc", "colltest1view"),
+        ?assertEqual(2, length(Rows0)),
+
+        % update the db and query again to trigger an index commit
+        create_docs(DbName),
+        Rows1 = query_view(DbName, "colltest1ddoc", "colltest1view"),
+        ?assertEqual(5, length(Rows1)),
+
+        % View header doesn't change
+        CurVer = tuple_to_list(couch_ejson_compare:get_collator_version()),
+        ExpVersions = [?OLD_COLLATOR_VERSION, CurVer],
+        wait_collator_versions(ExpVersions, ViewFilePath, 3000),
+        Header2 = read_header(ViewFilePath),
+        ViewInfo2 = Header2#mrheader.view_info,
+        ?assertMatch(#{ucol_vs := ExpVersions}, ViewInfo2)
     end).
 
 should_have_two_indexes_alive_before_deletion({DbName, _}) ->
@@ -571,6 +903,14 @@ query_view(DbName, DDoc, View, Stale) ->
     {Props} = jiffy:decode(Body),
     couch_util:get_value(<<"rows">>, Props, []).
 
+get_signature(DbName, DDoc) ->
+    Url = db_url(DbName) ++ "/_design/" ++ DDoc ++ "/_info",
+    {ok, Code, _Headers, Body} = test_request:get(Url),
+    ?assertEqual(200, Code),
+    MapBody = jiffy:decode(Body, [return_maps]),
+    #{<<"view_index">> := #{<<"signature">> := Sig}} = MapBody,
+    Sig.
+
 check_rows_value(Rows, Value) ->
     lists:foreach(
         fun({Row}) ->
@@ -750,3 +1090,28 @@ copy_tree([File | Rest], Src, Dst) ->
     FullDst = filename:join(Dst, File),
     ok = copy_tree(FullSrc, FullDst),
     copy_tree(Rest, Src, Dst).
+
+wait_mrheader_record(File, TimeoutMSec) ->
+    WaitFun = fun() ->
+        try read_header(File) of
+            #mrheader{} -> ok;
+            _Other -> wait
+        catch
+            _:_ -> wait
+        end
+    end,
+    test_util:wait(WaitFun, TimeoutMSec).
+
+wait_collator_versions(Vers, File, TimeoutMSec) ->
+    WaitFun = fun() ->
+        try read_header(File) of
+            #mrheader{view_info = #{ucol_vs := Vers}} ->
+                ok;
+            _Other ->
+                wait
+        catch
+            _:_ ->
+                wait
+        end
+    end,
+    test_util:wait(WaitFun, TimeoutMSec).
diff --git a/src/couch/test/eunit/fixtures/15a5cb17365a99cd9ddc7327c82bbd0d.view b/src/couch/test/eunit/fixtures/15a5cb17365a99cd9ddc7327c82bbd0d.view
new file mode 100644
index 0000000..33cbd40
Binary files /dev/null and b/src/couch/test/eunit/fixtures/15a5cb17365a99cd9ddc7327c82bbd0d.view differ
diff --git a/src/couch/test/eunit/fixtures/1f2c24bc334d701c2048f85e7438eef1.view b/src/couch/test/eunit/fixtures/1f2c24bc334d701c2048f85e7438eef1.view
new file mode 100644
index 0000000..25d19d7
Binary files /dev/null and b/src/couch/test/eunit/fixtures/1f2c24bc334d701c2048f85e7438eef1.view differ
diff --git a/src/couch/test/eunit/fixtures/colltest1.couch b/src/couch/test/eunit/fixtures/colltest1.couch
new file mode 100644
index 0000000..d42a066
Binary files /dev/null and b/src/couch/test/eunit/fixtures/colltest1.couch differ
diff --git a/src/couch/test/eunit/fixtures/db321.couch b/src/couch/test/eunit/fixtures/db321.couch
new file mode 100644
index 0000000..7db9cac
Binary files /dev/null and b/src/couch/test/eunit/fixtures/db321.couch differ
diff --git a/test/elixir/test/design_docs_test.exs b/test/elixir/test/design_docs_test.exs
index 85d2dd1..58058b0 100644
--- a/test/elixir/test/design_docs_test.exs
+++ b/test/elixir/test/design_docs_test.exs
@@ -267,9 +267,18 @@ defmodule DesignDocsTest do
     for _x <- 0..1 do
       resp = Couch.get("/#{db_name}/_design/test/_info")
       assert resp.body["name"] == "test"
-      assert resp.body["view_index"]["sizes"]["file"] == prev_view_size
-      assert resp.body["view_index"]["compact_running"] == false
-      assert resp.body["view_index"]["signature"] == prev_view_sig
+      assert is_map(resp.body["view_index"])
+      view_index = resp.body["view_index"]
+      assert view_index["sizes"]["file"] == prev_view_size
+      assert view_index["compact_running"] == false
+      assert view_index["signature"] == prev_view_sig
+
+      # check collator_versions result
+      assert is_list(view_index["collator_versions"])
+      collator_versions = view_index["collator_versions"]
+      assert length(collator_versions) == 1
+      version = hd(collator_versions)
+      assert is_binary(version)
     end
   end
 

[couchdb] 01/04: Add get_collator_version/0 libicu NIF function

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

vatamane pushed a commit to branch 3.x
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit f3d4c9f36c03c680ade9d0d8925394eed2d4ac08
Author: Nick Vatamaniuc <va...@apache.org>
AuthorDate: Thu Feb 3 23:36:47 2022 -0500

    Add get_collator_version/0 libicu NIF function
    
    get_collator_version/0 calls ucol_getVersion() C API [1]. It returns
    an opaque sequence of 4 bytes which encodes both the base UCA version
    and any tailorings which may affect collation order.
    
    [1] https://unicode-org.github.io/icu-docs/apidoc/dev/icu4c/ucol_8h.html#a0f98dd01ba7a64069ade6f0fda13528d
---
 .../priv/couch_ejson_compare/couch_ejson_compare.c    | 19 ++++++++++++++++++-
 src/couch/src/couch_ejson_compare.erl                 |  6 +++++-
 src/couch/test/eunit/couch_ejson_compare_tests.erl    |  9 +++++++++
 3 files changed, 32 insertions(+), 2 deletions(-)

diff --git a/src/couch/priv/couch_ejson_compare/couch_ejson_compare.c b/src/couch/priv/couch_ejson_compare/couch_ejson_compare.c
index 49a3a53..a4e9d1c 100644
--- a/src/couch/priv/couch_ejson_compare/couch_ejson_compare.c
+++ b/src/couch/priv/couch_ejson_compare/couch_ejson_compare.c
@@ -65,6 +65,7 @@ static ERL_NIF_TERM less_json_nif(ErlNifEnv*, int, const ERL_NIF_TERM []);
 static ERL_NIF_TERM compare_strings_nif(ErlNifEnv*, int, const ERL_NIF_TERM []);
 static ERL_NIF_TERM get_icu_version(ErlNifEnv*, int, const ERL_NIF_TERM []);
 static ERL_NIF_TERM get_uca_version(ErlNifEnv*, int, const ERL_NIF_TERM []);
+static ERL_NIF_TERM get_collator_version(ErlNifEnv*, int, const ERL_NIF_TERM []);
 static int on_load(ErlNifEnv*, void**, ERL_NIF_TERM);
 static void on_unload(ErlNifEnv*, void*);
 static __inline int less_json(int, ctx_t*, ERL_NIF_TERM, ERL_NIF_TERM);
@@ -203,6 +204,21 @@ get_uca_version(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
     return enif_make_tuple_from_array(env, tup, U_MAX_VERSION_LENGTH);
 }
 
+ERL_NIF_TERM
+get_collator_version(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+    UVersionInfo ver = {0};
+    ERL_NIF_TERM tup[U_MAX_VERSION_LENGTH] = {0};
+    int i;
+
+    ucol_getVersion(get_collator(), ver);
+
+    for (i = 0; i < U_MAX_VERSION_LENGTH; i++) {
+        tup[i] = enif_make_int(env, ver[i]);
+    }
+
+    return enif_make_tuple_from_array(env, tup, U_MAX_VERSION_LENGTH);
+}
 
 int
 less_json(int depth, ctx_t* ctx, ERL_NIF_TERM a, ERL_NIF_TERM b)
@@ -571,7 +587,8 @@ static ErlNifFunc nif_functions[] = {
     {"less_nif", 2, less_json_nif},
     {"compare_strings_nif", 2, compare_strings_nif},
     {"get_icu_version", 0, get_icu_version},
-    {"get_uca_version", 0, get_uca_version}
+    {"get_uca_version", 0, get_uca_version},
+    {"get_collator_version", 0, get_collator_version}
 };
 
 
diff --git a/src/couch/src/couch_ejson_compare.erl b/src/couch/src/couch_ejson_compare.erl
index 628bc2f..669f413 100644
--- a/src/couch/src/couch_ejson_compare.erl
+++ b/src/couch/src/couch_ejson_compare.erl
@@ -17,7 +17,8 @@
     less_json_ids/2,
     less_json/2,
     get_icu_version/0,
-    get_uca_version/0
+    get_uca_version/0,
+    get_collator_version/0
 ]).
 
 % For testing
@@ -63,6 +64,9 @@ get_icu_version() ->
 get_uca_version() ->
     erlang:nif_error(get_uca_version).
 
+get_collator_version() ->
+    erlang:nif_error(get_collator_version).
+
 less_nif(A, B) ->
     erlang:nif_error(less_nif_load_error, [A, B]).
 
diff --git a/src/couch/test/eunit/couch_ejson_compare_tests.erl b/src/couch/test/eunit/couch_ejson_compare_tests.erl
index f74e40b..ae4a5ff 100644
--- a/src/couch/test/eunit/couch_ejson_compare_tests.erl
+++ b/src/couch/test/eunit/couch_ejson_compare_tests.erl
@@ -185,6 +185,15 @@ get_uca_version_test() ->
     ?assert(is_integer(V3) andalso V3 >= 0),
     ?assert(is_integer(V4) andalso V4 >= 0).
 
+get_collator_version_test() ->
+    Ver = couch_ejson_compare:get_collator_version(),
+    ?assertMatch({_, _, _, _}, Ver),
+    {V1, V2, V3, V4} = Ver,
+    ?assert(is_integer(V1) andalso V1 > 0),
+    ?assert(is_integer(V2) andalso V2 >= 0),
+    ?assert(is_integer(V3) andalso V3 >= 0),
+    ?assert(is_integer(V4) andalso V4 >= 0).
+
 max_depth_error_list_test() ->
     % NIF can handle terms with depth <= 9
     Nested9 = nest_list(<<"val">>, 9),

[couchdb] 02/04: Return the opaque collator version from _node/*/_versions

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

vatamane pushed a commit to branch 3.x
in repository https://gitbox.apache.org/repos/asf/couchdb.git

commit 81fe821496259718c91bb9554dbbb7d6a8988712
Author: Nick Vatamaniuc <va...@apache.org>
AuthorDate: Thu Feb 3 23:46:08 2022 -0500

    Return the opaque collator version from _node/*/_versions
    
    We already return the collation algorithm version and the libicu library version,
    but since we're tracking the opaque collator versions in the view, it is
    beneficial to show that to the user as well.
    
    Thanks to Will Young for the idea [1]
    
    [1] https://lists.apache.org/thread/rqfwrt4kszz79l3wxxtg6zwygz6my8p2
---
 src/chttpd/src/chttpd_node.erl | 13 ++++---------
 src/couch/src/couch_util.erl   |  9 +++++++++
 2 files changed, 13 insertions(+), 9 deletions(-)

diff --git a/src/chttpd/src/chttpd_node.erl b/src/chttpd/src/chttpd_node.erl
index 63a7fb1..cc3370a 100644
--- a/src/chttpd/src/chttpd_node.erl
+++ b/src/chttpd/src/chttpd_node.erl
@@ -41,12 +41,14 @@ handle_node_req(#httpd{path_parts = [A, <<"_local">> | Rest]} = Req) ->
 handle_node_req(#httpd{method = 'GET', path_parts = [_, _Node, <<"_versions">>]} = Req) ->
     IcuVer = couch_ejson_compare:get_icu_version(),
     UcaVer = couch_ejson_compare:get_uca_version(),
+    ColVer = couch_ejson_compare:get_collator_version(),
     send_json(Req, 200, #{
         erlang_version => ?l2b(?COUCHDB_ERLANG_VERSION),
         collation_driver => #{
             name => <<"libicu">>,
-            library_version => version_tuple_to_str(IcuVer),
-            collation_algorithm_version => version_tuple_to_str(UcaVer)
+            library_version => couch_util:version_to_binary(IcuVer),
+            collation_algorithm_version => couch_util:version_to_binary(UcaVer),
+            collator_version => couch_util:version_to_binary(ColVer)
         },
         javascript_engine => #{
             name => <<"spidermonkey">>,
@@ -381,10 +383,3 @@ run_queues() ->
             [DCQ | SQs] = lists:reverse(statistics(run_queue_lengths)),
             {lists:sum(SQs), DCQ}
     end.
-
-version_tuple_to_str(Version) when is_tuple(Version) ->
-    List1 = tuple_to_list(Version),
-    IsZero = fun(N) -> N == 0 end,
-    List2 = lists:reverse(lists:dropwhile(IsZero, lists:reverse(List1))),
-    List3 = [erlang:integer_to_list(N) || N <- List2],
-    ?l2b(lists:join(".", List3)).
diff --git a/src/couch/src/couch_util.erl b/src/couch/src/couch_util.erl
index b7a6ad3..f942b70 100644
--- a/src/couch/src/couch_util.erl
+++ b/src/couch/src/couch_util.erl
@@ -42,6 +42,7 @@
 -export([set_mqd_off_heap/1]).
 -export([set_process_priority/2]).
 -export([hmac/3]).
+-export([version_to_binary/1]).
 
 -include_lib("couch/include/couch_db.hrl").
 
@@ -805,3 +806,11 @@ hmac(Alg, Key, Message) ->
 
 % -ifdef(OTP_RELEASE)
 -endif.
+
+version_to_binary(Ver) when is_tuple(Ver) ->
+    version_to_binary(tuple_to_list(Ver));
+version_to_binary(Ver) when is_list(Ver) ->
+    IsZero = fun(N) -> N == 0 end,
+    Ver1 = lists:reverse(lists:dropwhile(IsZero, lists:reverse(Ver))),
+    Ver2 = [erlang:integer_to_list(N) || N <- Ver1],
+    ?l2b(lists:join(".", Ver2)).