All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dev.miku.r2dbc.mysql.MySqlResult Maven / Gradle / Ivy

/*
 * Copyright 2018-2020 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
 *
 *      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 dev.miku.r2dbc.mysql;

import dev.miku.r2dbc.mysql.codec.Codecs;
import dev.miku.r2dbc.mysql.message.FieldValue;
import dev.miku.r2dbc.mysql.message.server.DefinitionMetadataMessage;
import dev.miku.r2dbc.mysql.message.server.EofMessage;
import dev.miku.r2dbc.mysql.message.server.OkMessage;
import dev.miku.r2dbc.mysql.message.server.RowMessage;
import dev.miku.r2dbc.mysql.message.server.ServerMessage;
import dev.miku.r2dbc.mysql.message.server.SyntheticMetadataMessage;
import dev.miku.r2dbc.mysql.util.OperatorUtils;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.ReferenceCounted;
import io.r2dbc.spi.Result;
import io.r2dbc.spi.Row;
import io.r2dbc.spi.RowMetadata;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoProcessor;
import reactor.core.publisher.SynchronousSink;
import reactor.util.annotation.Nullable;

import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;

import static dev.miku.r2dbc.mysql.util.AssertUtils.requireNonNull;

/**
 * An implementation of {@link Result} representing the results of a query against the MySQL database.
 */
public final class MySqlResult implements Result {

    private static final Function ROWS_UPDATED = message -> (int) message.getAffectedRows();

    private static final Consumer RELEASE = ReferenceCounted::release;

    private final boolean isBinary;

    private final Codecs codecs;

    private final ConnectionContext context;

    @Nullable
    private final String generatedKeyName;

    private final AtomicReference> messages;

    private final MonoProcessor okProcessor = MonoProcessor.create();

    private MySqlRowMetadata rowMetadata;

    /**
     * @param isBinary rows is binary.
     * @param messages must include complete signal.
     */
    MySqlResult(boolean isBinary, Codecs codecs, ConnectionContext context, @Nullable String generatedKeyName, Flux messages) {
        this.isBinary = isBinary;
        this.codecs = requireNonNull(codecs, "codecs must not be null");
        this.context = requireNonNull(context, "context must not be null");
        this.generatedKeyName = generatedKeyName;
        this.messages = new AtomicReference<>(requireNonNull(messages, "messages must not be null"));
    }

    @Override
    public Mono getRowsUpdated() {
        return affects().map(ROWS_UPDATED);
    }

    @Override
    public  Publisher map(BiFunction f) {
        requireNonNull(f, "mapping function must not be null");

        if (generatedKeyName == null) {
            return results().handle((message, sink) -> handleResult(message, sink, f));
        } else {
            return affects().map(message -> {
                InsertSyntheticRow row = new InsertSyntheticRow(codecs, generatedKeyName, message.getLastInsertId());
                return f.apply(row, row);
            });
        }
    }

    private Mono affects() {
        return this.okProcessor.doOnSubscribe(s -> {
            Flux messages = this.messages.getAndSet(null);

            if (messages == null) {
                // Has subscribed, `okProcessor` will be set or cancel.
                return;
            }

            messages.subscribe(message -> {
                if (message instanceof OkMessage) {
                    // No need check terminal because of OkMessage no need release.
                    this.okProcessor.onNext(((OkMessage) message));
                } else if (message instanceof EofMessage) {
                    // Metadata EOF message will be not receive in here.
                    // EOF message, means it is SELECT statement.
                    this.okProcessor.onComplete();
                } else {
                    ReferenceCountUtil.safeRelease(message);
                }
            }, this.okProcessor::onError, this.okProcessor::onComplete);
        });
    }

    private Flux results() {
        return Flux.defer(() -> {
            Flux messages = this.messages.getAndSet(null);

            if (messages == null) {
                return Flux.error(new IllegalStateException("Source has been released"));
            }

            // Result mode, no need ok message.
            this.okProcessor.onComplete();

            return OperatorUtils.discardOnCancel(messages).doOnDiscard(ReferenceCounted.class, RELEASE);
        });
    }

    private  void handleResult(ServerMessage message, SynchronousSink sink, BiFunction f) {
        if (message instanceof SyntheticMetadataMessage) {
            DefinitionMetadataMessage[] metadataMessages = ((SyntheticMetadataMessage) message).unwrap();
            if (metadataMessages.length == 0) {
                return;
            }
            this.rowMetadata = MySqlRowMetadata.create(metadataMessages);
        } else if (message instanceof RowMessage) {
            processRow((RowMessage) message, sink, f);
        } else {
            ReferenceCountUtil.safeRelease(message);
        }
    }

    private  void processRow(RowMessage message, SynchronousSink sink, BiFunction f) {
        MySqlRowMetadata rowMetadata = this.rowMetadata;

        if (rowMetadata == null) {
            ReferenceCountUtil.safeRelease(message);
            sink.error(new IllegalStateException("No MySqlRowMetadata available"));
            return;
        }

        FieldValue[] fields;
        T t;

        try {
            fields = message.decode(isBinary, rowMetadata.unwrap());
        } finally {
            // Release row messages' reader.
            ReferenceCountUtil.safeRelease(message);
        }

        try {
            // Can NOT just sink.next(f.apply(...)) because of finally release
            t = f.apply(new MySqlRow(fields, rowMetadata, codecs, isBinary, context), rowMetadata);
        } finally {
            // Release decoded field values.
            for (FieldValue field : fields) {
                ReferenceCountUtil.safeRelease(field);
            }
        }

        sink.next(t);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy