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
1 change: 1 addition & 0 deletions extra/nouveau/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ spotless {
java {
importOrder()
removeUnusedImports()
forbidWildcardImports()
cleanthat()
palantirJavaFormat()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// 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.

package org.apache.couchdb.nouveau.api;

import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;

public record BulkUpdateRequest(@JsonProperty List<DocumentUpdate> updates) {}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.PositiveOrZero;

public final class DocumentDeleteRequest {
public final class DocumentDeleteRequest extends DocumentRequest {

@PositiveOrZero
private final long matchSeq;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// 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.

package org.apache.couchdb.nouveau.api;

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "@type")
@JsonSubTypes({
@JsonSubTypes.Type(value = DocumentDeleteRequest.class, name = "delete"),
@JsonSubTypes.Type(value = DocumentUpdateRequest.class, name = "update"),
})
public abstract class DocumentRequest {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// 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.

package org.apache.couchdb.nouveau.api;

import com.fasterxml.jackson.annotation.JsonProperty;

public record DocumentUpdate(@JsonProperty("doc_id") String docId, @JsonProperty("update") DocumentRequest request) {}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import jakarta.validation.constraints.PositiveOrZero;
import java.util.Collection;

public final class DocumentUpdateRequest {
public final class DocumentUpdateRequest extends DocumentRequest {

@PositiveOrZero
private final long matchSeq;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
import com.codahale.metrics.health.HealthCheck;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import org.apache.couchdb.nouveau.api.BulkUpdateRequest;
import org.apache.couchdb.nouveau.api.DocumentUpdate;
import org.apache.couchdb.nouveau.api.DocumentUpdateRequest;
import org.apache.couchdb.nouveau.api.IndexDefinition;
import org.apache.couchdb.nouveau.api.SearchRequest;
Expand All @@ -41,10 +44,10 @@ protected Result check() throws Exception {

indexResource.createIndex(name, new IndexDefinition(IndexDefinition.LATEST_LUCENE_VERSION, "standard", null));
try {
final DocumentUpdateRequest documentUpdateRequest =
new DocumentUpdateRequest(0, 1, null, Collections.emptyList());
indexResource.updateDoc(name, "foo", documentUpdateRequest);

indexResource.update(
name,
new BulkUpdateRequest(List.of(new DocumentUpdate(
"foo", new DocumentUpdateRequest(0, 1, null, Collections.emptyList())))));
final SearchRequest searchRequest = new SearchRequest();
searchRequest.setQuery("_id:foo");
searchRequest.setMinUpdateSeq(1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import org.apache.couchdb.nouveau.api.BulkUpdateRequest;
import org.apache.couchdb.nouveau.api.DocumentDeleteRequest;
import org.apache.couchdb.nouveau.api.DocumentUpdateRequest;
import org.apache.couchdb.nouveau.api.IndexDefinition;
Expand Down Expand Up @@ -67,6 +68,7 @@ public Ok createIndex(@PathParam("name") String name, @NotNull @Valid IndexDefin
return Ok.INSTANCE;
}

@Deprecated(since = "3.5.2", forRemoval = true)
@DELETE
@Path("/doc/{docId}")
public Ok deleteDoc(
Expand Down Expand Up @@ -120,6 +122,7 @@ public SearchResults searchIndex(@PathParam("name") String name, @NotNull @Valid
});
}

@Deprecated(since = "3.5.2", forRemoval = true)
@PUT
@Path("/doc/{docId}")
public Ok updateDoc(
Expand All @@ -132,4 +135,20 @@ public Ok updateDoc(
return Ok.INSTANCE;
});
}

@POST
@Path("/update")
public Ok update(@PathParam("name") String name, @NotNull @Valid BulkUpdateRequest request) throws Exception {
return indexManager.with(name, (index) -> {
for (var update : request.updates()) {
if (update.request() instanceof DocumentUpdateRequest) {
index.update(update.docId(), (DocumentUpdateRequest) update.request());
}
if (update.request() instanceof DocumentDeleteRequest) {
index.delete(update.docId(), (DocumentDeleteRequest) update.request());
}
}
return Ok.INSTANCE;
});
}
}
2 changes: 1 addition & 1 deletion mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ NODE_ENV = 'production'

[tools]
elixir = "1.19.5-otp-26"
erlang = '26'
erlang = "28.3.3"
java = '21'
nodejs = '24'
python = '3'
89 changes: 89 additions & 0 deletions src/nouveau/src/nouveau_api.erl
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,22 @@
jaxrs_error/2
]).

%% batch api functions
-export([
update/2,
make_update/5,
make_delete/3,
make_purge/3
]).

-define(JSON_CONTENT_TYPE, {"Content-Type", "application/json"}).

-deprecated([
{purge_doc, 4, "replaced by updates/2"},
{update_doc, 6, "replaced by updates/2"},
{delete_doc, 4, "replaced by updates/2"}
]).

analyze(Text, Analyzer) when
is_binary(Text), is_binary(Analyzer)
->
Expand Down Expand Up @@ -172,6 +186,78 @@ update_doc(#index{} = Index, DocId, MatchSeq, UpdateSeq, Partition, Fields) when
send_error(Reason)
end.

update(#index{} = Index, Updates) when is_list(Updates) ->
Resp = send_if_enabled(
update_path(Index),
[?JSON_CONTENT_TYPE],
<<"POST">>,
jiffy:encode(#{updates => Updates})
),
case Resp of
{ok, 200, _, _} ->
ok;
{ok, StatusCode, _, RespBody} ->
{error, jaxrs_error(StatusCode, RespBody)};
{error, Reason} ->
send_error(Reason)
end.

make_delete(DocId, MatchSeq, UpdateSeq) when
is_binary(DocId),
is_integer(MatchSeq),
MatchSeq >= 0,
is_integer(UpdateSeq),
UpdateSeq > 0
->
#{
doc_id => DocId,
update => #{
'@type' => delete,
doc_id => DocId,
match_seq => MatchSeq,
seq => UpdateSeq,
delete => true
}
}.

make_purge(DocId, MatchSeq, PurgeSeq) when
is_binary(DocId),
is_integer(MatchSeq),
MatchSeq >= 0,
is_integer(PurgeSeq),
PurgeSeq > 0
->
#{
doc_id => DocId,
update => #{
'@type' => delete,
doc_id => DocId,
match_seq => MatchSeq,
seq => PurgeSeq,
purge => true
}
}.

make_update(DocId, MatchSeq, UpdateSeq, Partition, Fields) when
is_binary(DocId),
is_integer(MatchSeq),
MatchSeq >= 0,
is_integer(UpdateSeq),
UpdateSeq > 0,
(is_binary(Partition) orelse Partition == null),
is_list(Fields)
->
#{
doc_id => DocId,
update => #{
'@type' => update,
match_seq => MatchSeq,
seq => UpdateSeq,
partition => Partition,
fields => Fields
}
}.

search(#index{} = Index, QueryArgs) ->
Resp = send_if_enabled(
search_path(Index), [?JSON_CONTENT_TYPE], <<"POST">>, jiffy:encode(QueryArgs)
Expand Down Expand Up @@ -245,6 +331,9 @@ doc_path(#index{} = Index, DocId) ->
search_path(#index{} = Index) ->
[index_path(Index), <<"/search">>].

update_path(#index{} = Index) ->
[index_path(Index), <<"/update">>].

jaxrs_error(400, Body) ->
{bad_request, message(Body)};
jaxrs_error(404, Body) ->
Expand Down
Loading