io.r2dbc.postgresql.PostgresqlCopyIn Maven / Gradle / Ivy
/*
* Copyright 2017 the original author or authors.
*
* 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
*
* https://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 io.r2dbc.postgresql;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.CompositeByteBuf;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.ReferenceCounted;
import io.r2dbc.postgresql.api.CopyInBuilder;
import io.r2dbc.postgresql.client.Client;
import io.r2dbc.postgresql.message.backend.ErrorResponse;
import io.r2dbc.postgresql.message.backend.ReadyForQuery;
import io.r2dbc.postgresql.message.frontend.CopyData;
import io.r2dbc.postgresql.message.frontend.CopyDone;
import io.r2dbc.postgresql.message.frontend.CopyFail;
import io.r2dbc.postgresql.message.frontend.FrontendMessage;
import io.r2dbc.postgresql.message.frontend.Query;
import io.r2dbc.postgresql.util.Assert;
import io.r2dbc.postgresql.util.Operators;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Sinks;
import reactor.util.annotation.Nullable;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import static io.r2dbc.postgresql.PostgresqlResult.toResult;
/**
* An implementation for {@link CopyData} PostgreSQL queries.
*/
final class PostgresqlCopyIn {
private final ConnectionResources context;
PostgresqlCopyIn(ConnectionResources resources) {
this.context = Assert.requireNonNull(resources, "resources must not be null");
}
Mono copy(String sql, Publisher extends Publisher> stdin) {
ExceptionFactory exceptionFactory = ExceptionFactory.withSql(sql);
AtomicReference toReleaseOnError = new AtomicReference<>();
return Flux.from(stdin)
.concatMap(data -> {
CompositeByteBuf composite = this.context.getClient().getByteBufAllocator().compositeBuffer();
return Flux.from(data)
.reduce(composite, (l, r) -> {
return l.addComponent(true, r);
})
.map(CopyData::new)
.doOnNext(toReleaseOnError::set)
.doOnDiscard(ReferenceCounted.class, ReferenceCountUtil::release);
}).concatWithValues(CopyDone.INSTANCE).startWith(new Query(sql))
.as(messages -> copyIn(exceptionFactory, messages))
.doFinally(signalType -> {
CopyData copyData = toReleaseOnError.get();
if (copyData != null) {
if (copyData.refCnt() > 0) {
copyData.release();
}
}
});
}
private Mono copyIn(ExceptionFactory exceptionFactory, Flux copyDataMessages) {
Client client = this.context.getClient();
AtomicBoolean stop = new AtomicBoolean();
Sinks.Many sink = Sinks.many().unicast().onBackpressureBuffer();
Flux requestMessages = sink.asFlux().mergeWith(copyDataMessages
.doOnComplete(sink::tryEmitComplete)
.filter(it -> !stop.get())
.onErrorResume(e -> {
copyFail(sink, stop, "Copy operation failed: " + e.getMessage());
return Mono.empty();
}));
return client.exchange(backendMessage -> backendMessage instanceof ReadyForQuery, requestMessages)
.doOnNext(it -> {
if (it instanceof ErrorResponse) {
stop.set(true);
sink.tryEmitComplete();
}
})
.doOnComplete(() -> {
stop.set(true);
sink.tryEmitComplete();
})
.doOnError((e) -> {
copyFail(sink, stop, "Copy operation failed: " + e.getMessage());
})
.doOnCancel(() -> {
copyFail(sink, stop, "Copy operation failed: Cancelled");
})
.doOnDiscard(ReferenceCounted.class, ReferenceCountUtil::release)
.as(Operators::discardOnCancel)
.doOnCancel(() -> {
copyFail(sink, stop, "Copy operation failed: Cancelled");
})
.as(messages -> toResult(this.context, messages, exceptionFactory).getRowsUpdated());
}
private void copyFail(Sinks.Many sink, AtomicBoolean stop, String e) {
sink.tryEmitNext(new CopyFail(e));
sink.tryEmitComplete();
stop.set(true);
}
@Override
public String toString() {
return "PostgresqlCopyIn{" +
"context=" + this.context +
'}';
}
static final class Builder implements CopyInBuilder {
private final ConnectionResources resources;
private final String sql;
@Nullable
private Publisher extends Publisher> stdin;
Builder(ConnectionResources resources, String sql) {
this.resources = resources;
this.sql = sql;
}
@Override
public CopyInBuilder fromMany(Publisher extends Publisher> stdin) {
this.stdin = Assert.requireNonNull(stdin, "stdin must not be null");
return this;
}
@Override
public Mono build() {
if (this.stdin == null) {
throw new IllegalArgumentException("No stdin configured for COPY IN");
}
return new PostgresqlCopyIn(this.resources).copy(this.sql, this.stdin);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy