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

com.eventsourcing.postgresql.index.NavigableIndex Maven / Gradle / Ivy

/**
 * Copyright (c) 2016, All Contributors (see CONTRIBUTORS file)
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
package com.eventsourcing.postgresql.index;

import com.eventsourcing.Entity;
import com.eventsourcing.EntityHandle;
import com.eventsourcing.index.Attribute;
import com.eventsourcing.layout.Layout;
import com.eventsourcing.layout.TypeHandler;
import com.eventsourcing.postgresql.PostgreSQLSerialization;
import com.eventsourcing.postgresql.PostgreSQLStatementIterator;
import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.TypeResolver;
import com.google.common.io.BaseEncoding;
import com.googlecode.cqengine.index.support.SortedKeyStatisticsAttributeIndex;
import com.googlecode.cqengine.quantizer.Quantizer;
import com.googlecode.cqengine.query.Query;
import com.googlecode.cqengine.query.option.QueryOptions;
import com.googlecode.cqengine.query.simple.*;
import com.googlecode.cqengine.resultset.ResultSet;
import com.googlecode.cqengine.resultset.closeable.CloseableResultSet;
import com.googlecode.cqengine.resultset.filter.QuantizedResultSet;
import lombok.Getter;
import lombok.SneakyThrows;

import javax.sql.DataSource;
import java.security.MessageDigest;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.HashSet;
import java.util.UUID;

import static com.eventsourcing.postgresql.PostgreSQLSerialization.getParameter;
import static com.eventsourcing.postgresql.PostgreSQLSerialization.setValue;

public class NavigableIndex , O extends Entity> extends PostgreSQLAttributeIndex
        implements SortedKeyStatisticsAttributeIndex> {

    protected static final int INDEX_RETRIEVAL_COST = 40;

    @Getter
    private final DataSource dataSource;
    @Getter
    private final Layout layout;
    @Getter
    private final TypeHandler attributeTypeHandler;
    @Getter
    private String tableName;

    @Override protected boolean isUnique() {
        return false;
    }

    public static , O extends Entity> NavigableIndex onAttribute(DataSource dataSource,
                                                                                               Attribute attribute) {
        return new NavigableIndex<>(dataSource, (Attribute) serializableComparable(attribute));
    }

    public static , O extends Entity> NavigableIndex
           withQuantizerOnAttribute(DataSource dataSource, Quantizer quantizer, Attribute attribute) {
        return new NavigableIndex(dataSource, (Attribute) serializableComparable(attribute)) {
            @Override public boolean isQuantized() {
                return true;
            }

            @Override protected A getQuantizedValue(A attributeValue) {
                return quantizer.getQuantizedValue(attributeValue);
            }

            @Override
            public ResultSet> retrieve(Query> query, QueryOptions queryOptions) {
                ResultSet> rs = super.retrieve(query, queryOptions);
                return new QuantizedResultSet<>(rs, query, queryOptions);
            }
        };
    }

    @SneakyThrows
    protected NavigableIndex(DataSource dataSource, Attribute attribute) {
        super(attribute, new HashSet>() {{
            add(Equal.class);
            add(LessThan.class);
            add(GreaterThan.class);
            add(Between.class);
            add(Has.class);
        }});
        this.dataSource = dataSource;
        layout = Layout.forClass(attribute.getEffectiveObjectType());
        TypeResolver typeResolver = new TypeResolver();
        ResolvedType resolvedType = typeResolver.resolve(attribute.getAttributeType());
        attributeTypeHandler = TypeHandler.lookup(resolvedType);
        init();
    }

    @SneakyThrows
    private void init() {
        try(Connection connection = dataSource.getConnection()) {
            MessageDigest digest = MessageDigest.getInstance("SHA-1");
            digest.update(layout.getHash());
            digest.update(attribute.getAttributeName().getBytes());
            String encodedHash = BaseEncoding.base16().encode(digest.digest());
            tableName = "index_v1_" + encodedHash + "_navigable";
            String attributeType = PostgreSQLSerialization.getMappedType(connection, attributeTypeHandler);
            String create = "CREATE TABLE IF NOT EXISTS " + tableName + " (" +
                    "\"key\" " + attributeType + ",\n" +
                    "\"object\" UUID," +
                    "PRIMARY KEY(\"key\", \"object\")" +
                    ")";
            try (PreparedStatement s = connection.prepareStatement(create)) {
                s.executeUpdate();
            }
            String indexKey = "CREATE INDEX IF NOT EXISTS " + tableName + "_key_idx ON " + tableName + " (\"key\")";
            try (PreparedStatement s = connection.prepareStatement(indexKey)) {
                s.executeUpdate();
            }
            String indexObj = "CREATE INDEX IF NOT EXISTS " + tableName + "_obj_idx ON " + tableName + " (\"object\")";
            try (PreparedStatement s = connection.prepareStatement(indexObj)) {
                s.executeUpdate();
            }
            String indexComment = layout.getName() + "." + attribute.getAttributeName() + " EQ LT GT BT";
            String comment = "COMMENT ON TABLE " + tableName + " IS '" + indexComment + "'";
            try (PreparedStatement s = connection.prepareStatement(comment)) {
                s.executeUpdate();
            }

        }
    }

    @SneakyThrows
    @Override public ResultSet> retrieve(Query> query, QueryOptions queryOptions) {
        Class queryClass = query.getClass();
        if (queryClass.equals(LessThan.class)) {
            final LessThan, A> lessThan = (LessThan, A>) query;
            Connection connection = getDataSource().getConnection();

            String op = lessThan.isValueInclusive() || isQuantized() ? "<=" : "<";

            int size = 0;
            A value = getQuantizedValue(((LessThan, A>) query).getValue());
            try(PreparedStatement counter = connection
                    .prepareStatement("SELECT count(object) FROM " + getTableName() + " WHERE key " + op + " " +
                                      getParameter
                            (connection, getAttributeTypeHandler(), null))) {
                setValue(connection, counter, 1, value, getAttributeTypeHandler());
                try (java.sql.ResultSet resultSet = counter.executeQuery()) {
                    resultSet.next();
                    size = resultSet.getInt(1);
                }
            }

            PreparedStatement s = connection
                    .prepareStatement("SELECT object FROM " + getTableName() + " WHERE key " + op + " " +
                                              getParameter(connection, getAttributeTypeHandler(), null));
            setValue(connection, s, 1, value, getAttributeTypeHandler());

            PostgreSQLStatementIterator> iterator = new PostgreSQLStatementIterator>
                    (s, connection, isMutable()) {
                @SneakyThrows
                @Override public EntityHandle fetchNext() {
                    UUID uuid = UUID.fromString(resultSet.getString(1));
                    return keyObjectStore.get(uuid);
                }
            };


            int finalSize = size;
            ResultSet> rs = new MatchingResultSet<>(iterator, lessThan, queryOptions, finalSize);
            return new CloseableResultSet<>(rs, query, queryOptions);
        }
        if (queryClass.equals(GreaterThan.class)) {
            final GreaterThan, A> greaterThan = (GreaterThan, A>) query;
            Connection connection = getDataSource().getConnection();

            String op = greaterThan.isValueInclusive() || isQuantized() ? ">=" : ">";

            int size = 0;
            A value = getQuantizedValue(((GreaterThan, A>) query).getValue());

            try(PreparedStatement counter = connection
                    .prepareStatement("SELECT count(object) FROM " + getTableName() + " WHERE key " + op + " " +
                                              getParameter
                                                      (connection, getAttributeTypeHandler(), null))) {
                setValue(connection, counter, 1, value,
                         getAttributeTypeHandler());
                try (java.sql.ResultSet resultSet = counter.executeQuery()) {
                    resultSet.next();
                    size = resultSet.getInt(1);
                }
            }

            PreparedStatement s = connection
                    .prepareStatement("SELECT object FROM " + getTableName() + " WHERE key " + op + " " +
                                              getParameter(connection, getAttributeTypeHandler(), null));
            setValue(connection, s, 1, value, getAttributeTypeHandler());

            PostgreSQLStatementIterator> iterator = new PostgreSQLStatementIterator>
                    (s, connection, isMutable()) {
                @SneakyThrows
                @Override public EntityHandle fetchNext() {
                    UUID uuid = UUID.fromString(resultSet.getString(1));
                    return keyObjectStore.get(uuid);
                }
            };


            int finalSize = size;
            ResultSet> rs = new MatchingResultSet<>(iterator, greaterThan, queryOptions, finalSize);
            return new CloseableResultSet<>(rs, query, queryOptions);
        }
        if (queryClass.equals(Between.class)) {
            final Between, A> between = (Between, A>) query;
            Connection connection = getDataSource().getConnection();

            String lowerOp = between.isLowerInclusive() || isQuantized() ? ">=" : ">";
            String upperOp = between.isUpperInclusive() || isQuantized() ? "<=" : "<";

            int size = 0;
            A lowerValue = getQuantizedValue(((Between, A>) query).getLowerValue());
            A upperValue = getQuantizedValue(((Between, A>) query).getUpperValue());

            String parameter = getParameter(connection, getAttributeTypeHandler(), null);
            try(PreparedStatement counter = connection
                    .prepareStatement("SELECT count(object) FROM " + getTableName() + " WHERE " +
                                              "key " + lowerOp + " " + parameter + " AND " +
                                              "key " + upperOp + " " + parameter
                    )) {
                setValue(connection, counter, 1, lowerValue, getAttributeTypeHandler());
                setValue(connection, counter, 2, upperValue, getAttributeTypeHandler());

                try (java.sql.ResultSet resultSet = counter.executeQuery()) {
                    resultSet.next();
                    size = resultSet.getInt(1);
                }
            }

            PreparedStatement s = connection
                    .prepareStatement("SELECT object FROM " + getTableName() + " WHERE " +
                                              "key " + lowerOp + " " + parameter + " AND " +
                                              "key " + upperOp + " " + parameter);
            setValue(connection, s, 1, lowerValue, getAttributeTypeHandler());
            setValue(connection, s, 2, upperValue, getAttributeTypeHandler());

            PostgreSQLStatementIterator> iterator = new PostgreSQLStatementIterator>
                    (s, connection, isMutable()) {
                @SneakyThrows
                @Override public EntityHandle fetchNext() {
                    UUID uuid = UUID.fromString(resultSet.getString(1));
                    return keyObjectStore.get(uuid);
                }
            };


            int finalSize = size;
            ResultSet> rs = new MatchingResultSet, A>>
                    (iterator, between, queryOptions, finalSize) {
                @Override public int getMergeCost() {
                    return finalSize;
                }
            };
            return new CloseableResultSet<>(rs, query, queryOptions);
        }
        return super.retrieve(query, queryOptions);
    }


    @Override protected int indexRetrievalCost() {
        return INDEX_RETRIEVAL_COST;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy