com.torodb.backend.AbstractReadInterface.java Source code

Java tutorial

Introduction

Here is the source code for com.torodb.backend.AbstractReadInterface.java

Source

/*
 * ToroDB
 * Copyright  2014 8Kdata Technology (www.8kdata.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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/>.
 */

package com.torodb.backend;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.torodb.backend.ErrorHandler.Context;
import com.torodb.backend.d2r.ResultSetDocPartResult;
import com.torodb.backend.tables.MetaDocPartTable.DocPartTableFields;
import com.torodb.core.TableRef;
import com.torodb.core.TableRefFactory;
import com.torodb.core.cursors.Cursor;
import com.torodb.core.cursors.EmptyCursor;
import com.torodb.core.cursors.IteratorCursor;
import com.torodb.core.d2r.DocPartResult;
import com.torodb.core.transaction.metainf.MetaCollection;
import com.torodb.core.transaction.metainf.MetaDatabase;
import com.torodb.core.transaction.metainf.MetaDocPart;
import com.torodb.core.transaction.metainf.MetaField;
import com.torodb.kvdocument.values.KvValue;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.jooq.DSLContext;
import org.jooq.lambda.Seq;
import org.jooq.lambda.Unchecked;
import org.jooq.lambda.tuple.Tuple2;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.annotation.Nonnull;
import javax.inject.Provider;
import javax.inject.Singleton;

/**
 *
 */
@Singleton
@SuppressFBWarnings("SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING")
public abstract class AbstractReadInterface implements ReadInterface {

    private final MetaDataReadInterface metaDataReadInterface;
    private final DataTypeProvider dataTypeProvider;
    private final ErrorHandler errorHandler;
    private final SqlHelper sqlHelper;
    private final TableRefFactory tableRefFactory;

    public AbstractReadInterface(MetaDataReadInterface metaDataReadInterface, DataTypeProvider dataTypeProvider,
            ErrorHandler errorHandler, SqlHelper sqlHelper, TableRefFactory tableRefFactory) {
        this.metaDataReadInterface = metaDataReadInterface;
        this.dataTypeProvider = dataTypeProvider;
        this.errorHandler = errorHandler;
        this.sqlHelper = sqlHelper;
        this.tableRefFactory = tableRefFactory;
    }

    @Override
    @SuppressFBWarnings(value = { "OBL_UNSATISFIED_OBLIGATION",
            "ODR_OPEN_DATABASE_RESOURCE" }, justification = "ResultSet is wrapped in a Cursor<Integer>. It's iterated and closed in caller code")
    public Cursor<Integer> getCollectionDidsWithFieldEqualsTo(DSLContext dsl, MetaDatabase metaDatabase,
            MetaCollection metaCol, MetaDocPart metaDocPart, MetaField metaField, KvValue<?> value)
            throws SQLException {
        assert metaDatabase.getMetaCollectionByIdentifier(metaCol.getIdentifier()) != null;
        assert metaCol.getMetaDocPartByIdentifier(metaDocPart.getIdentifier()) != null;
        assert metaDocPart.getMetaFieldByIdentifier(metaField.getIdentifier()) != null;

        String statement = getReadCollectionDidsWithFieldEqualsToStatement(metaDatabase.getIdentifier(),
                metaDocPart.getIdentifier(), metaField.getIdentifier());
        Connection connection = dsl.configuration().connectionProvider().acquire();
        try {
            PreparedStatement preparedStatement = connection.prepareStatement(statement);
            sqlHelper.setPreparedStatementValue(preparedStatement, 1, metaField.getType(), value);
            return new DefaultDidCursor(errorHandler, preparedStatement.executeQuery());
        } finally {
            dsl.configuration().connectionProvider().release(connection);
        }
    }

    protected abstract String getReadCollectionDidsWithFieldEqualsToStatement(String schemaName,
            String rootTableName, String columnName);

    @Override
    @SuppressFBWarnings(value = { "OBL_UNSATISFIED_OBLIGATION",
            "ODR_OPEN_DATABASE_RESOURCE" }, justification = "ResultSet is wrapped in a Cursor<Integer>. It's iterated and closed in caller code")
    public Cursor<Integer> getCollectionDidsWithFieldsIn(DSLContext dsl, MetaDatabase metaDatabase,
            MetaCollection metaCol, MetaDocPart metaDocPart, Multimap<MetaField, KvValue<?>> valuesMultimap)
            throws SQLException {
        assert metaDatabase.getMetaCollectionByIdentifier(metaCol.getIdentifier()) != null;
        assert metaCol.getMetaDocPartByIdentifier(metaDocPart.getIdentifier()) != null;
        assert valuesMultimap.keySet().stream()
                .allMatch(metafield -> metaDocPart.getMetaFieldByIdentifier(metafield.getIdentifier()) != null);

        if (valuesMultimap.size() > 500) {
            @SuppressWarnings("checkstyle:LineLength")
            Stream<Entry<Long, List<Tuple2<Entry<MetaField, KvValue<?>>, Long>>>> valuesEntriesBatchStream = Seq
                    .seq(valuesMultimap.entries().stream()).zipWithIndex().groupBy(t -> t.v2 / 500).entrySet()
                    .stream();
            Stream<Stream<Entry<MetaField, KvValue<?>>>> valuesEntryBatchStreamOfStream = valuesEntriesBatchStream
                    .map(e -> e.getValue().stream().map(se -> se.v1));
            Stream<Multimap<MetaField, KvValue<?>>> valuesMultimapBatchStream = valuesEntryBatchStreamOfStream
                    .map(e -> toValuesMultimap(e));
            Stream<Cursor<Integer>> didCursorStream = valuesMultimapBatchStream
                    .map(Unchecked.function(valuesMultimapBatch -> getCollectionDidsWithFieldsInBatch(dsl,
                            metaDatabase, metaCol, metaDocPart, valuesMultimapBatch)));
            Stream<Integer> didStream = didCursorStream.flatMap(cursor -> cursor.getRemaining().stream());

            return new IteratorCursor<>(didStream.iterator());
        }

        return getCollectionDidsWithFieldsInBatch(dsl, metaDatabase, metaCol, metaDocPart, valuesMultimap);
    }

    private Multimap<MetaField, KvValue<?>> toValuesMultimap(
            Stream<Entry<MetaField, KvValue<?>>> valueEntryStream) {
        Multimap<MetaField, KvValue<?>> valuesMultimap = ArrayListMultimap.create();

        valueEntryStream.forEach(e -> valuesMultimap.put(e.getKey(), e.getValue()));

        return valuesMultimap;
    }

    @SuppressFBWarnings(value = { "OBL_UNSATISFIED_OBLIGATION",
            "ODR_OPEN_DATABASE_RESOURCE" }, justification = "ResultSet is wrapped in a Cursor<Integer>. It's iterated and closed in caller code")
    private Cursor<Integer> getCollectionDidsWithFieldsInBatch(DSLContext dsl, MetaDatabase metaDatabase,
            MetaCollection metaCol, MetaDocPart metaDocPart, Multimap<MetaField, KvValue<?>> valuesMultimap)
            throws SQLException {
        @SuppressWarnings("checkstyle:LineLength")
        Provider<Stream<Map.Entry<MetaField, Collection<KvValue<?>>>>> valuesMultimapSortedStreamProvider = () -> valuesMultimap
                .asMap().entrySet().stream()
                .sorted((e1, e2) -> e1.getKey().getIdentifier().compareTo(e2.getKey().getIdentifier()));
        String statement = getReadCollectionDidsWithFieldInStatement(metaDatabase.getIdentifier(),
                metaDocPart.getIdentifier(), valuesMultimapSortedStreamProvider.get()
                        .map(e -> new Tuple2<String, Integer>(e.getKey().getIdentifier(), e.getValue().size())));
        Connection connection = dsl.configuration().connectionProvider().acquire();
        try {
            PreparedStatement preparedStatement = connection.prepareStatement(statement);
            int parameterIndex = 1;
            Iterator<Map.Entry<MetaField, Collection<KvValue<?>>>> valuesMultimapSortedIterator = valuesMultimapSortedStreamProvider
                    .get().iterator();
            while (valuesMultimapSortedIterator.hasNext()) {
                Map.Entry<MetaField, Collection<KvValue<?>>> valuesMultimapEntry = valuesMultimapSortedIterator
                        .next();
                for (KvValue<?> value : valuesMultimapEntry.getValue()) {
                    sqlHelper.setPreparedStatementValue(preparedStatement, parameterIndex,
                            valuesMultimapEntry.getKey().getType(), value);
                    parameterIndex++;
                }
            }
            return new DefaultDidCursor(errorHandler, preparedStatement.executeQuery());
        } finally {
            dsl.configuration().connectionProvider().release(connection);
        }
    }

    protected abstract String getReadCollectionDidsWithFieldInStatement(String schemaName, String rootTableName,
            Stream<Tuple2<String, Integer>> valuesCountList);

    @Override
    @SuppressFBWarnings(value = { "OBL_UNSATISFIED_OBLIGATION",
            "ODR_OPEN_DATABASE_RESOURCE" }, justification = "ResultSet is wrapped in a Cursor<Tuple2<Integer, KVValue<?>>>. It's "
                    + "iterated and closed in caller code")
    public Cursor<Tuple2<Integer, KvValue<?>>> getCollectionDidsAndProjectionWithFieldsIn(DSLContext dsl,
            MetaDatabase metaDatabase, MetaCollection metaCol, MetaDocPart metaDocPart,
            Multimap<MetaField, KvValue<?>> valuesMultimap) throws SQLException {
        assert metaDatabase.getMetaCollectionByIdentifier(metaCol.getIdentifier()) != null;
        assert metaCol.getMetaDocPartByIdentifier(metaDocPart.getIdentifier()) != null;
        assert valuesMultimap.keySet().stream()
                .allMatch(metafield -> metaDocPart.getMetaFieldByIdentifier(metafield.getIdentifier()) != null);

        Stream<Tuple2<MetaField, Collection<KvValue<?>>>> valuesBatchStream = valuesMultimap.asMap().entrySet()
                .stream().map(e -> new Tuple2<MetaField, Collection<KvValue<?>>>(e.getKey(), e.getValue()));
        if (valuesMultimap.asMap().entrySet().stream().anyMatch(e -> e.getValue().size() > 500)) {
            valuesBatchStream = valuesBatchStream.flatMap(e -> Seq.seq(e.v2.stream()).zipWithIndex()
                    .groupBy(t -> t.v2 / 500).entrySet().stream().map(se -> toValuesMap(e.v1, se)));
        }
        Stream<Cursor<Tuple2<Integer, KvValue<?>>>> didProjectionCursorStream = valuesBatchStream
                .map(Unchecked.function(mapBatch -> getCollectionDidsAndProjectionWithFieldsInBatch(dsl,
                        metaDatabase, metaCol, metaDocPart, mapBatch.v1, mapBatch.v2)));
        Stream<Tuple2<Integer, KvValue<?>>> didProjectionStream = didProjectionCursorStream
                .flatMap(cursor -> cursor.getRemaining().stream());

        return new IteratorCursor<>(didProjectionStream.iterator());
    }

    @SuppressWarnings("rawtypes")
    private Tuple2<MetaField, Collection<KvValue<?>>> toValuesMap(MetaField metaField,
            Entry<Long, List<Tuple2<KvValue<?>, Long>>> groupedValuesMap) {
        List<KvValue> collect = groupedValuesMap.getValue().stream().map(e -> (KvValue) e.v1)
                .collect(Collectors.toList());

        return new Tuple2<MetaField, Collection<KvValue<?>>>(metaField,
                collect.stream().map(e -> (KvValue<?>) e).collect(Collectors.toList()));
    }

    @SuppressFBWarnings(value = { "OBL_UNSATISFIED_OBLIGATION",
            "ODR_OPEN_DATABASE_RESOURCE" }, justification = "ResultSet is wrapped in a Cursor<Tuple2<Integer, KVValue<?>>>. "
                    + "It's iterated and closed in caller code")
    private Cursor<Tuple2<Integer, KvValue<?>>> getCollectionDidsAndProjectionWithFieldsInBatch(DSLContext dsl,
            MetaDatabase metaDatabase, MetaCollection metaCol, MetaDocPart metaDocPart, MetaField metaField,
            Collection<KvValue<?>> values) throws SQLException {
        String statement = getReadCollectionDidsAndProjectionWithFieldInStatement(metaDatabase.getIdentifier(),
                metaDocPart.getIdentifier(), metaField.getIdentifier(), values.size());
        Connection connection = dsl.configuration().connectionProvider().acquire();
        try {
            PreparedStatement preparedStatement = connection.prepareStatement(statement);
            int parameterIndex = 1;
            for (KvValue<?> value : values) {
                sqlHelper.setPreparedStatementValue(preparedStatement, parameterIndex, metaField.getType(), value);
                parameterIndex++;
            }
            return new AbstractCursor<Tuple2<Integer, KvValue<?>>>(errorHandler, preparedStatement.executeQuery()) {
                @Override
                protected Tuple2<Integer, KvValue<?>> read(ResultSet resultSet) throws SQLException {
                    return new Tuple2<>(resultSet.getInt(1), sqlHelper.getResultSetKvValue(metaField.getType(),
                            dataTypeProvider.getDataType(metaField.getType()), resultSet, 2));
                }
            };
        } finally {
            dsl.configuration().connectionProvider().release(connection);
        }
    }

    protected abstract String getReadCollectionDidsAndProjectionWithFieldInStatement(String schemaName,
            String rootTableName, String columnName, int valuesCount);

    @Override
    @SuppressFBWarnings(value = { "OBL_UNSATISFIED_OBLIGATION",
            "ODR_OPEN_DATABASE_RESOURCE" }, justification = "ResultSet is wrapped in a Cursor<Integer>. It's iterated and closed in caller code")
    public Cursor<Integer> getAllCollectionDids(DSLContext dsl, MetaDatabase metaDatabase,
            MetaCollection metaCollection) throws SQLException {

        MetaDocPart rootDocPart = metaCollection.getMetaDocPartByTableRef(tableRefFactory.createRoot());
        if (rootDocPart == null) {
            return new EmptyCursor<>();
        }

        String statement = getReadAllCollectionDidsStatement(metaDatabase.getIdentifier(),
                rootDocPart.getIdentifier());
        Connection connection = dsl.configuration().connectionProvider().acquire();
        try {
            PreparedStatement preparedStatement = connection.prepareStatement(statement);
            return new DefaultDidCursor(errorHandler, preparedStatement.executeQuery());
        } finally {
            dsl.configuration().connectionProvider().release(connection);
        }
    }

    protected abstract String getReadAllCollectionDidsStatement(String schemaName, String rootTableName);

    @Override
    public long countAll(@Nonnull DSLContext dsl, @Nonnull MetaDatabase database,
            @Nonnull MetaCollection collection) {
        MetaDocPart rootDocPart = collection.getMetaDocPartByTableRef(tableRefFactory.createRoot());
        if (rootDocPart == null) {
            return 0;
        }
        String statement = getReadCountAllStatement(database.getIdentifier(), rootDocPart.getIdentifier());
        return sqlHelper.executeStatementWithResult(dsl, statement, Context.FETCH).get(0).into(Long.class);
    }

    protected abstract String getReadCountAllStatement(String schema, String rootTableName);

    @Nonnull
    @Override
    public List<DocPartResult> getCollectionResultSets(@Nonnull DSLContext dsl, @Nonnull MetaDatabase metaDatabase,
            @Nonnull MetaCollection metaCollection, @Nonnull Cursor<Integer> didCursor, int maxSize)
            throws SQLException {
        Collection<Integer> dids = didCursor.getNextBatch(maxSize);
        return getCollectionResultSets(dsl, metaDatabase, metaCollection, dids);
    }

    @Override
    @SuppressFBWarnings(value = { "OBL_UNSATISFIED_OBLIGATION",
            "ODR_OPEN_DATABASE_RESOURCE" }, justification = "ResultSet is wrapped in a DocPartResult. It's iterated and closed in caller code")
    public List<DocPartResult> getCollectionResultSets(DSLContext dsl, MetaDatabase metaDatabase,
            MetaCollection metaCollection, Collection<Integer> dids) throws SQLException {
        ArrayList<DocPartResult> result = new ArrayList<>();
        Connection connection = dsl.configuration().connectionProvider().acquire();
        try {
            Iterator<? extends MetaDocPart> metaDocPartIterator = metaCollection.streamContainedMetaDocParts()
                    .sorted(TableRefComparator.MetaDocPart.DESC).iterator();
            while (metaDocPartIterator.hasNext()) {
                MetaDocPart metaDocPart = metaDocPartIterator.next();
                String statament = getDocPartStatament(metaDatabase, metaDocPart, dids);

                PreparedStatement preparedStatement = connection.prepareStatement(statament);
                result.add(new ResultSetDocPartResult(metaDataReadInterface, dataTypeProvider, errorHandler,
                        metaDocPart, preparedStatement.executeQuery(), sqlHelper));
            }
        } finally {
            dsl.configuration().connectionProvider().release(connection);
        }
        return result;
    }

    protected abstract String getDocPartStatament(MetaDatabase metaDatabase, MetaDocPart metaDocPart,
            Collection<Integer> dids);

    @Override
    public int getLastRowIdUsed(DSLContext dsl, MetaDatabase metaDatabase, MetaCollection metaCollection,
            MetaDocPart metaDocPart) {

        String statement = getLastRowIdUsedStatement(metaDatabase, metaDocPart);

        Connection connection = dsl.configuration().connectionProvider().acquire();
        try (PreparedStatement preparedStatement = connection.prepareStatement(statement)) {
            try (ResultSet rs = preparedStatement.executeQuery()) {
                rs.next();
                int maxId = rs.getInt(1);
                if (rs.wasNull()) {
                    return -1;
                }
                return maxId;
            }
        } catch (SQLException ex) {
            throw errorHandler.handleException(Context.FETCH, ex);
        } finally {
            dsl.configuration().connectionProvider().release(connection);
        }
    }

    protected abstract String getLastRowIdUsedStatement(MetaDatabase metaDatabase, MetaDocPart metaDocPart);

    protected String getPrimaryKeyColumnIdentifier(TableRef tableRef) {
        if (tableRef.isRoot()) {
            return DocPartTableFields.DID.fieldName;
        }
        return DocPartTableFields.RID.fieldName;
    }
}