be.nbb.jackcess.JackcessStatement.java Source code

Java tutorial

Introduction

Here is the source code for be.nbb.jackcess.JackcessStatement.java

Source

/*
 * Copyright 2013 National Bank of Belgium
 * 
 * Licensed under the EUPL, Version 1.1 or - as soon they will be approved 
 * by the European Commission - subsequent versions of the EUPL (the "Licence");
 * You may not use this work except in compliance with the Licence.
 * You may obtain a copy of the Licence at:
 * 
 * http://ec.europa.eu/idabc/eupl
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the Licence is distributed on an "AS IS" basis,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the Licence for the specific language governing permissions and 
 * limitations under the Licence.
 */
package be.nbb.jackcess;

import be.nbb.xdb.DbBasicSelect;
import be.nbb.xdb.DbRawDataUtil;
import com.google.common.base.Function;
import com.google.common.base.Stopwatch;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Range;
import com.google.common.collect.Sets;
import com.healthmarketscience.jackcess.Column;
import com.healthmarketscience.jackcess.Database;
import com.healthmarketscience.jackcess.RowId;
import com.healthmarketscience.jackcess.Table;
import ec.tstoolkit.utilities.CheckedIterator;
import java.io.Closeable;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static be.nbb.jackcess.JackcessColumnComparator.BY_COLUMN_INDEX;
import com.google.common.collect.Iterables;
import com.google.common.collect.Ordering;

/**
 *
 * @author Philippe Charles
 */
public final class JackcessStatement implements Closeable {

    private static final Logger LOGGER = LoggerFactory.getLogger(JackcessStatement.class);

    private final Database database;
    private final Range<RowId> range;

    public JackcessStatement(@Nonnull Database database, @Nullable Range<RowId> range) {
        this.database = database;
        this.range = range != null ? range : Range.<RowId>all();
    }

    @Nonnull
    public JackcessResultSet executeQuery(@Nonnull DbBasicSelect query) throws IOException {
        Table table = database.getTable(query.getTableName());

        List<Column> selectColumns = getAllByName(table, query.getSelectColumns());
        List<Column> orderColumns = getAllByName(table, query.getOrderColumns());
        SortedSet<Column> dataColumns = mergeAndSortByInternalIndex(selectColumns, orderColumns);
        SortedMap<Column, String> filter = getFilter(table, query.getFilterItems());

        LOGGER.debug("Query : '{}'", query);

        Stopwatch sw = Stopwatch.createStarted();
        CheckedIterator<Object[], IOException> rows = new Adapter(
                CursorFacade.range(table, range).withFilter(filter), dataColumns);
        LOGGER.debug("Iterator done in {}ms", sw.stop().elapsed(TimeUnit.MILLISECONDS));

        ToIndex toIndex = new ToIndex(dataColumns);

        if (query.isDistinct()) {
            sw.start();
            rows = DbRawDataUtil.distinct(rows, selectColumns, toIndex, ToDataType.INSTANCE,
                    new Aggregator(dataColumns.size() + 1));
            LOGGER.debug("Distinct done in {}ms", sw.stop().elapsed(TimeUnit.MILLISECONDS));
        }

        if (DbRawDataUtil.isSortRequired(query.isDistinct(), selectColumns, orderColumns)) {
            sw.start();
            rows = DbRawDataUtil.sort(rows, orderColumns, toIndex, ToDataType.INSTANCE);
            LOGGER.debug("Sort done in {}ms", sw.stop().elapsed(TimeUnit.MILLISECONDS));
        }

        return new JackcessResultSet(selectColumns, DbRawDataUtil.createIndexes(selectColumns, toIndex), rows);
    }

    @Override
    public void close() throws IOException {
    }

    //<editor-fold defaultstate="collapsed" desc="Implementation details">
    private static List<Column> getAllByName(Table table, Collection<String> columnNames) {
        List<Column> result = Lists.newArrayListWithExpectedSize(columnNames.size());
        for (String o : columnNames) {
            result.add(table.getColumn(o));
        }
        return result;
    }

    private static SortedSet<Column> mergeAndSortByInternalIndex(Iterable<Column>... list) {
        SortedSet<Column> result = Sets.newTreeSet(BY_COLUMN_INDEX);
        for (Iterable<Column> o : list) {
            Iterables.addAll(result, o);
        }
        return result;
    }

    private static SortedMap<Column, String> getFilter(Table table, Map<String, String> filterItems) {
        SortedMap<Column, String> result = Maps.newTreeMap(BY_COLUMN_INDEX);
        for (Map.Entry<String, String> o : filterItems.entrySet()) {
            result.put(table.getColumn(o.getKey()), o.getValue());
        }
        return result;
    }

    private static final class ToIndex implements DbRawDataUtil.ToIntFunction<Column> {

        private final int[] index;

        public ToIndex(SortedSet<Column> dataColumns) {
            Column max = dataColumns.comparator().equals(BY_COLUMN_INDEX) ? dataColumns.last()
                    : Ordering.from(BY_COLUMN_INDEX).max(dataColumns);
            this.index = new int[max.getColumnIndex() + 1];
            int i = 0;
            for (Column o : dataColumns) {
                index[o.getColumnIndex()] = i++;
            }
        }

        @Override
        public int applyAsInt(Column value) {
            return index[value.getColumnIndex()];
        }
    }

    private static final class ToDataType implements Function<Column, DbRawDataUtil.SuperDataType> {

        private static final ToDataType INSTANCE = new ToDataType();

        @Override
        public DbRawDataUtil.SuperDataType apply(Column column) {
            switch (column.getType()) {
            case BYTE:
            case INT:
            case LONG:
            case DOUBLE:
            case BOOLEAN:
            case FLOAT:
            case SHORT_DATE_TIME:
            case TEXT:
            case MONEY:
            case MEMO:
            case NUMERIC:
            case GUID:
                return DbRawDataUtil.SuperDataType.COMPARABLE;
            case OLE:
            case BINARY:
            case UNKNOWN_0D:
            case UNKNOWN_11:
                return DbRawDataUtil.SuperDataType.BYTE_ARRAY;
            default:
                return DbRawDataUtil.SuperDataType.OTHER;
            }
        }
    }

    private static final class Aggregator implements DbRawDataUtil.BiConsumer<Object[], Object[]> {

        private final int lastPosIdx;

        public Aggregator(int lastPosIdx) {
            this.lastPosIdx = lastPosIdx;
        }

        @Override
        public void accept(Object[] t, Object[] u) {
            t[lastPosIdx] = u[lastPosIdx];
        }
    }

    private static final class Adapter extends CheckedIterator<Object[], IOException> {

        private final CursorFacade cursor;
        private final Column[] dataColumns;

        public Adapter(CursorFacade cursor, SortedSet<Column> dataColumns) {
            this.cursor = cursor;
            this.dataColumns = Iterables.toArray(dataColumns, Column.class);
        }

        @Override
        public boolean hasNext() throws IOException {
            return cursor.moveToNextRow();
        }

        @Override
        public Object[] next() throws IOException {
            Object[] result = new Object[dataColumns.length + 2];
            for (int i = 0; i < result.length - 2; i++) {
                result[i] = cursor.getCurrentRowValue(dataColumns[i]);
            }
            result[dataColumns.length] = result[dataColumns.length + 1] = cursor.getRowId();
            return result;
        }
    }
    //</editor-fold>
}