com.tesora.dve.db.mysql.RedistTupleBuilder.java Source code

Java tutorial

Introduction

Here is the source code for com.tesora.dve.db.mysql.RedistTupleBuilder.java

Source

package com.tesora.dve.db.mysql;

/*
 * #%L
 * Tesora Inc.
 * Database Virtualization Engine
 * %%
 * Copyright (C) 2011 - 2014 Tesora Inc.
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */

import com.tesora.dve.common.catalog.CatalogDAO;
import com.tesora.dve.common.catalog.DistributionModel;
import com.tesora.dve.concurrent.*;
import com.tesora.dve.db.mysql.common.DBTypeBasedUtils;
import com.tesora.dve.db.mysql.portal.protocol.FlowControl;
import com.tesora.dve.db.mysql.portal.protocol.ValveFlowControlSet;
import com.tesora.dve.distribution.BroadcastDistributionModel;
import com.tesora.dve.distribution.KeyValue;
import com.tesora.dve.queryplan.QueryStepMultiTupleRedistOperation;
import com.tesora.dve.queryplan.TableHints;
import com.tesora.dve.worker.MysqlRedistTupleForwarder;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.CharsetUtil;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.log4j.Logger;

import com.tesora.dve.db.mysql.libmy.*;
import com.tesora.dve.common.catalog.PersistentTable;
import com.tesora.dve.exceptions.PEException;
import com.tesora.dve.resultset.ColumnSet;
import com.tesora.dve.server.messaging.SQLCommand;
import com.tesora.dve.worker.WorkerGroup;
import com.tesora.dve.worker.WorkerGroup.MappingSolution;

public class RedistTupleBuilder
        implements RedistTargetSite.InsertPolicy, RedistTargetSite.InsertWatcher, FlowControl {
    static final Logger logger = Logger.getLogger(RedistTupleBuilder.class);
    static final String SIMPLE_CLASSNAME = RedistTupleBuilder.class.getSimpleName();

    static private AtomicInteger nextId = new AtomicInteger();
    private int thisId = nextId.incrementAndGet();

    RedistTargetSet targetSet;

    ValveFlowControlSet upstreamSet = new ValveFlowControlSet();

    class CountHolder {
        int sourceRows = 0;
        int updatedRows = 0;
        long sourceBytesTotal = 0;
    }

    final PEDefaultPromise<CountHolder> completionPromise = new PEDefaultPromise<>();

    CountHolder counts = new CountHolder();

    boolean lastPacketSent = false;
    boolean failedRedist = false;

    final PersistentTable targetTable;
    final WorkerGroup targetWG;
    final CatalogDAO catalogDAO;
    final DistributionModel distModel;

    final int maximumRowCount;
    final int maxDataSize;
    final SQLCommand insertOptions;
    boolean insertIgnore = false;

    TableHints tableHints;
    MysqlRedistTupleForwarder.MaximumAutoIncr maxAutoIncr = null;
    private ColumnSet rowSetMetadata;

    public RedistTupleBuilder(CatalogDAO catalogDAO, DistributionModel distModel, SQLCommand insertOptions,
            PersistentTable targetTable, int maximumRowCount, int maxDataSize, WorkerGroup targetWG)
            throws PEException {
        this.distModel = distModel;
        this.catalogDAO = catalogDAO;
        this.insertOptions = insertOptions;
        this.targetTable = targetTable;
        this.targetWG = targetWG;
        this.maximumRowCount = maximumRowCount;
        this.maxDataSize = maxDataSize;

        this.targetSet = new RedistTargetSet(targetWG, this, this, this);
    }

    public void processSourceRow(KeyValue distValue,
            List<MysqlRedistTupleForwarder.ColumnValueInspector> columnInspectorList, MyBinaryResultRow binRow)
            throws PEException {
        //this get's called by MysqlRedistTupleForwarder when we receive a new row from a source query.
        try {
            innerHandleRow(distValue, columnInspectorList, binRow);
        } catch (Exception e) {
            this.failure(e);
        }
    }

    private void innerHandleRow(KeyValue distValue,
            List<MysqlRedistTupleForwarder.ColumnValueInspector> columnInspectorList, MyBinaryResultRow binRow)
            throws PEException {
        if (failedRedist)
            return;//drop our source rows quickly if redist has already failed.

        long[] autoIncrBlocks = null;

        if (tableHints.tableHasAutoIncs()) {
            //TODO: this call fetches a single autoinc, which might block on hibernate if the background pre-fetch is slow, stalling the netty thread.  sgossard
            autoIncrBlocks = tableHints.buildBlocks(catalogDAO, 1 /*rowcount*/); //gets an autoinc for one row.
        }

        if (tableHints.usesExistingAutoIncs() && maxAutoIncr == null)
            maxAutoIncr = new MysqlRedistTupleForwarder.MaximumAutoIncr();

        MappingSolution mappingSolution;
        if (BroadcastDistributionModel.SINGLETON.equals(distModel) && !tableHints.isUsingAutoIncColumn()) {
            //broadcast and not using an autoinc.
            mappingSolution = MappingSolution.AllWorkersSerialized;
        } else {
            //either using an autoinc, or not broadcast.

            KeyValue dv = new KeyValue(distValue);

            //picks apart the row, looking for distribution keys or autoinc fields.
            for (int i = 0; i < columnInspectorList.size(); ++i) {
                MysqlRedistTupleForwarder.ColumnValueInspector dvm = columnInspectorList.get(i);
                dvm.inspectValue(binRow, i, dv, maxAutoIncr);
            }

            // don't need exec state here - values are fully materialized
            mappingSolution = distModel.mapKeyForInsert(catalogDAO, targetWG.getGroup(), dv);
        }

        counts.sourceRows += 1;
        counts.sourceBytesTotal += binRow.sizeInBytes();

        //********************
        Long actualAutoInc = autoIncrBlocks == null ? null : autoIncrBlocks[0];
        if (actualAutoInc != null) {
            //we have an autoinc, append it to the binrow
            DecodedMeta autoIncFunc = new DecodedMeta(
                    DBTypeBasedUtils.getMysqlTypeFunc(MyFieldType.FIELD_TYPE_LONGLONG));
            binRow = binRow.append(autoIncFunc, actualAutoInc);
        }

        boolean triggeredFlush = targetSet.sendInsert(mappingSolution, binRow);
        //********************

        //TODO: this should really be called BEFORE flushes, to ensure we don't lose a tracked autoinc on a failure. -sgossard
        if (triggeredFlush)
            updateAutoIncIfNeeded();
    }

    private void updateAutoIncIfNeeded() {
        if (maxAutoIncr != null && maxAutoIncr.isSet() && tableHints.isUsingAutoIncColumn()) {
            //TODO: this call records the maximum autoinc via hibernate, and could stall the netty thread.  sgossard
            tableHints.recordMaximalAutoInc(catalogDAO, maxAutoIncr.getMaxValue());
            maxAutoIncr = null;
        }
    }

    public void setProcessingComplete() throws PEException {
        //Called when upstream forwarder has seen stream EOFs from all source streams, so all rows have been forwarded.

        //unpause all source streams so they are ready for re-use.
        resumeSourceStreams();

        lastPacketSent = true;

        updateAutoIncIfNeeded();

        targetSet.flush();

        testRedistributionComplete();
    }

    public void failure(Exception e) {
        failedRedist = true;
        completionPromise.failure(e);
    }

    public int getUpdateCount() throws Exception {
        if (logger.isDebugEnabled())
            logger.debug("redist # " + thisId + " , about to call completionPromise.sync(): " + completionPromise);
        CountHolder holder = SynchronousListener.sync(completionPromise);
        return holder.updatedRows;
    }

    public int getSourceRowCount() throws Exception {
        CountHolder holder = SynchronousListener.sync(completionPromise);
        return holder.sourceRows;
    }

    public long getSourceRowBytesTotal() throws Exception {
        CountHolder holder = SynchronousListener.sync(completionPromise);
        return holder.sourceBytesTotal;
    }

    @Override
    public int getMaximumRowsToBuffer() {
        return maximumRowCount;
    }

    @Override
    public long getMaximumBytesToBuffer() {
        return maxDataSize;
    }

    @Override
    public int getColumnsPerTuple() {
        return targetTable.getNumberOfColumns();
    }

    public SQLCommand buildInsertStatement(int tupleCount) throws PEException {
        return QueryStepMultiTupleRedistOperation.getTableInsertStatement(/*
                                                                           * PerHostConnectionManager.INSTANCE.lookupConnection(targetWG
                                                                           * .getCommectionId())
                                                                           */CharsetUtil.UTF_8, targetTable,
                insertOptions, rowSetMetadata, tupleCount, insertIgnore);
    }

    public void setRowSetMetadata(TableHints tableHints, ColumnSet resultColumnMetadata) {
        this.tableHints = tableHints;
        this.rowSetMetadata = resultColumnMetadata;
    }

    public void setInsertIgnore(boolean insertIgnore) {
        this.insertIgnore = insertIgnore;
    }

    @Override
    public String toString() {
        return SIMPLE_CLASSNAME + "{" + thisId + "}";
    }

    public void sourceActive(ChannelHandlerContext ctx) {
        upstreamSet.register(ctx);
    }

    @Override
    public void pauseSourceStreams() {
        if (!(lastPacketSent || failedRedist)) //don't pause source sockets if we are finished.
            upstreamSet.pauseSourceStreams();
    }

    @Override
    public void resumeSourceStreams() {
        upstreamSet.resumeSourceStreams();
    }

    @Override
    public void insertOK(RedistTargetSite siteCtx, MyOKResponse okPacket) {
        testRedistributionComplete();
    }

    @Override
    public void insertFailed(RedistTargetSite site, MyErrorResponse errorPacket) {
        this.failure(errorPacket.asException());
    }

    @Override
    public void insertFailed(RedistTargetSite site, Exception e) {
        this.failure(e);
    }

    private void testRedistributionComplete() {
        boolean anySiteHasPending = targetSet.hasPendingRows();

        boolean isProcessingComplete = lastPacketSent && !anySiteHasPending;
        if (isProcessingComplete) {

            counts.updatedRows = (int) targetSet.getUpdatedRowCount();

            if (logger.isDebugEnabled()) {
                String formatted = String.format(
                        "redist # %s, redistribution of %s complete - source=%s, updated=%s, sourceBytes=%s",
                        thisId, targetTable.displayName(), counts.sourceRows, counts.updatedRows,
                        counts.sourceBytesTotal);
                logger.debug(formatted);
            }
            try {
                targetSet.close();
                completionPromise.trySuccess(counts);
            } catch (Exception e) {
                completionPromise.failure(e);
            }

        }
    }

}