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

com.infomaximum.database.domainobject.Transaction Maven / Gradle / Ivy

The newest version!
package com.infomaximum.database.domainobject;

import com.google.common.collect.Range;
import com.google.common.collect.RangeSet;
import com.google.common.collect.TreeRangeSet;
import com.infomaximum.database.DataCommand;
import com.infomaximum.database.domainobject.filter.EmptyFilter;
import com.infomaximum.database.domainobject.iterator.IteratorEntity;
import com.infomaximum.database.exception.ClosedObjectException;
import com.infomaximum.database.exception.DatabaseException;
import com.infomaximum.database.exception.ForeignDependencyException;
import com.infomaximum.database.provider.*;
import com.infomaximum.database.schema.*;
import com.infomaximum.database.utils.HashIndexUtils;
import com.infomaximum.database.utils.PrefixIndexUtils;
import com.infomaximum.database.utils.RangeIndexUtils;
import com.infomaximum.database.utils.TypeConvert;
import com.infomaximum.database.utils.key.FieldKey;
import com.infomaximum.database.utils.key.HashIndexKey;
import com.infomaximum.database.utils.key.IntervalIndexKey;
import com.infomaximum.database.utils.key.RangeIndexKey;

import java.io.Serializable;
import java.util.*;

public class Transaction extends DataEnumerable implements AutoCloseable {

    private DBTransaction transaction = null;
    private DataCommand dataCommand = null;
    private boolean closed = false;
    private boolean foreignFieldEnabled = true;
    private final Map deletingObjects = new HashMap<>();

    protected Transaction(DBProvider dbProvider, Boolean reloadSchema) {
        super(dbProvider, reloadSchema);
    }

    public boolean isForeignFieldEnabled() {
        return foreignFieldEnabled;
    }

    public void setForeignFieldEnabled(boolean value) {
        this.foreignFieldEnabled = value;
    }

    public DBTransaction getDBTransaction() throws DatabaseException {
        ensureTransaction();
        return transaction;
    }

    public DataCommand getDataCommand() throws DatabaseException {
        ensureTransaction();
        dataCommand = new DataCommand(transaction, schema.getDbSchema());
        return dataCommand;
    }

    public  T create(final Class clazz) throws DatabaseException {
        ensureTransaction();

        StructEntity entity = Schema.getEntity(clazz);

        long id = transaction.nextId(entity.getColumnFamily());

        T domainObject = buildDomainObject(DomainObject.getConstructor(clazz), id, Collections.emptyList());
        domainObject._setAsJustCreated();

        //Принудительно указываем, что все поля отредактированы - иначе для не инициализированных полей не правильно построятся индексы
        for (Field field: entity.getFields()) {
            domainObject.set(field.getNumber(), null);
        }

        return domainObject;
    }

    public  void save(final T object) throws DatabaseException {
        Value[] newValues = object.getNewValues();
        if (newValues == null) {
            return;
        }

        ensureTransaction();

        final String columnFamily = object.getStructEntity().getColumnFamily();
        final Value[] loadedValues = object.getLoadedValues();

        // update hash-indexed values
        for (HashIndex index: object.getStructEntity().getHashIndexes()) {
            if (anyChanged(index.sortedFields, newValues)) {
                tryLoadFields(columnFamily, object, index.sortedFields, loadedValues);
                updateIndexedValue(index, object, loadedValues, newValues, transaction);
            }
        }

        // update prefix-indexed values
        for (PrefixIndex index: object.getStructEntity().getPrefixIndexes()) {
            if (anyChanged(index.sortedFields, newValues)) {
                tryLoadFields(columnFamily, object, index.sortedFields, loadedValues);
                updateIndexedValue(index, object, loadedValues, newValues, transaction);
            }
        }

        // update interval-indexed values
        for (IntervalIndex index: object.getStructEntity().getIntervalIndexes()) {
            if (anyChanged(index.sortedFields, newValues)) {
                tryLoadFields(columnFamily, object, index.sortedFields, loadedValues);
                updateIndexedValue(index, object, loadedValues, newValues, transaction);
            }
        }

        // update range-indexed values
        for (RangeIndex index: object.getStructEntity().getRangeIndexes()) {
            if (anyChanged(index.sortedFields, newValues)) {
                tryLoadFields(columnFamily, object, index.sortedFields, loadedValues);
                updateIndexedValue(index, object, loadedValues, newValues, transaction);
            }
        }

        // update self-object
        if (object._isJustCreated()) {
            transaction.put(columnFamily, new FieldKey(object.getId()).pack(), TypeConvert.EMPTY_BYTE_ARRAY);
        }
        for (int i = 0; i < newValues.length; ++i) {
            Value newValue = newValues[i];
            if (newValue == null) {
                continue;
            }

            Field field = object.getStructEntity().getFields()[i];
            Object value = newValue.getValue();

            validateUpdatingValue(object, field, value);
            if (object._isJustCreated() && value == null) {
                continue;
            }

            byte[] key = new FieldKey(object.getId(), field.getNameBytes()).pack();
            byte[] bValue = TypeConvert.pack(field.getType(), value, field.getConverter());
            transaction.put(columnFamily, key, bValue);
        }

        object._flushNewValues();
    }

    public  void remove(final T obj) throws DatabaseException {
        ensureTransaction();

        validateForeignValues(obj);

        String columnFamily = obj.getStructEntity().getColumnFamily();
        deletingObjects.computeIfAbsent(columnFamily, s -> new Objects(obj.getStructEntity())).add(obj);
    }

    private void deleteObjects() throws DatabaseException {
        for (Map.Entry entry : deletingObjects.entrySet()) {
            String columnFamily = entry.getKey();
            StructEntity entity = entry.getValue().entity;
            Value[] loadedValues = new Value[entity.getFields().length];

            for (Range range : entry.getValue().ids.asRanges()) {
                for (long objId = range.lowerEndpoint(); objId < range.upperEndpoint(); ++objId) {
                    Arrays.fill(loadedValues, null);

                    // delete hash-indexed values
                    for (HashIndex index : entity.getHashIndexes()) {
                        tryLoadFields(columnFamily, objId, index.sortedFields, loadedValues);
                        removeIndexedValue(index, objId, loadedValues, transaction);
                    }

                    // delete prefix-indexed values
                    for (PrefixIndex index : entity.getPrefixIndexes()) {
                        tryLoadFields(columnFamily, objId, index.sortedFields, loadedValues);
                        removeIndexedValue(index, objId, loadedValues, transaction);
                    }

                    // delete interval-indexed values
                    for (IntervalIndex index : entity.getIntervalIndexes()) {
                        tryLoadFields(columnFamily, objId, index.sortedFields, loadedValues);
                        removeIndexedValue(index, objId, loadedValues, transaction);
                    }

                    // delete range-indexed values
                    for (RangeIndex index : entity.getRangeIndexes()) {
                        tryLoadFields(columnFamily, objId, index.sortedFields, loadedValues);
                        removeIndexedValue(index, objId, loadedValues, transaction);
                    }
                }

                // delete self-object
                transaction.singleDeleteRange(columnFamily,
                        FieldKey.buildKeyPrefix(range.lowerEndpoint()),
                        FieldKey.buildKeyPrefix(range.upperEndpoint())
                );
            }
        }
    }

    @Override
    public boolean isMarkedForDeletion(StructEntity entity, long objId) {
        Objects objs = deletingObjects.get(entity.getColumnFamily());
        return objs != null && objs.ids.contains(objId);
    }

    public  void removeAll(Class objClass) throws DatabaseException {
        ensureTransaction();

        StructEntity entity = Schema.getEntity(objClass);

        validateForeignValues(entity);

        Objects objects = deletingObjects.computeIfAbsent(entity.getColumnFamily(), s -> new Objects(entity));
        try (IteratorEntity i = find(objClass, EmptyFilter.INSTANCE, Collections.emptySet())) {
            while (i.hasNext()) {
                objects.add(i.next());
            }
        }
    }

    @Override
    public DBIterator createIterator(String columnFamily) throws DatabaseException {
        ensureTransaction();

        return transaction.createIterator(columnFamily);
    }

    public void commit() throws DatabaseException {
        if (transaction != null) {
            deleteObjects();
            transaction.commit();
        }
        close();
    }

    public boolean isClosed() {
        return closed;
    }

    @Override
    public void close() throws DatabaseException {
        closed = true;
        try (DBTransaction t = transaction) {
            transaction = null;
        }
    }

    private void ensureTransaction() throws DatabaseException {
        if (closed) {
            throw new ClosedObjectException(this.getClass());
        }

        if (transaction == null) {
            transaction = getDbProvider().beginTransaction();
        }
    }

    private void tryLoadFields(String columnFamily, DomainObject obj, List fields, Value[] loadedValues) throws DatabaseException {
        if (!obj._isJustCreated()) {
            tryLoadFields(columnFamily, obj.getId(), fields, loadedValues);
        }
    }

    private void tryLoadFields(String columnFamily, long objId, List fields, Value[] loadedValues) throws DatabaseException {
        for (Field field: fields) {
            tryLoadField(columnFamily, objId, field, loadedValues);
        }
    }

    private void tryLoadField(String columnFamily, long id, Field field, Value[] loadedValues) throws DatabaseException {
        if (loadedValues[field.getNumber()] != null) {
            return;
        }

        final byte[] key = new FieldKey(id, field.getNameBytes()).pack();
        final byte[] value = transaction.getValue(columnFamily, key);
        loadedValues[field.getNumber()] = Value.of(TypeConvert.unpack(field.getType(), value, field.getConverter()));
    }

    private static void updateIndexedValue(HashIndex index, DomainObject obj, Value[] prevValues, Value[] newValues, DBTransaction transaction) throws DatabaseException {
        final HashIndexKey indexKey = new HashIndexKey(obj.getId(), index);

        if (!obj._isJustCreated()) {
            // Remove old value-index
            HashIndexUtils.setHashValues(index.sortedFields, prevValues, indexKey.getFieldValues());
            transaction.delete(index.columnFamily, indexKey.pack());
        }

        // Add new value-index
        setHashValues(index.sortedFields, prevValues, newValues, indexKey.getFieldValues());
        transaction.put(index.columnFamily, indexKey.pack(), TypeConvert.EMPTY_BYTE_ARRAY);
    }

    private static void removeIndexedValue(HashIndex index, long id, Value[] values, DBTransaction transaction) throws DatabaseException {
        final HashIndexKey indexKey = new HashIndexKey(id, index);

        HashIndexUtils.setHashValues(index.sortedFields, values, indexKey.getFieldValues());
        transaction.singleDelete(index.columnFamily, indexKey.pack());
    }

    private static void updateIndexedValue(PrefixIndex index, DomainObject obj, Value[] prevValues, Value[] newValues, DBTransaction transaction) throws DatabaseException {
        List deletingLexemes = new ArrayList<>();
        List insertingLexemes = new ArrayList<>();
        PrefixIndexUtils.diffIndexedLexemes(index.sortedFields, prevValues, newValues, deletingLexemes, insertingLexemes);

        if (!obj._isJustCreated()) {
            PrefixIndexUtils.removeIndexedLexemes(index, obj.getId(), deletingLexemes, transaction);
        }
        PrefixIndexUtils.insertIndexedLexemes(index, obj.getId(), insertingLexemes, transaction);
    }

    private static void removeIndexedValue(PrefixIndex index, long id, Value[] values, DBTransaction transaction) throws DatabaseException {
        SortedSet lexemes = PrefixIndexUtils.buildSortedSet();
        for (Field field : index.sortedFields) {
            PrefixIndexUtils.splitIndexingTextIntoLexemes((String) values[field.getNumber()].getValue(), lexemes);
        }

        PrefixIndexUtils.removeIndexedLexemes(index, id, lexemes, transaction);
    }

    private static void updateIndexedValue(IntervalIndex index, DomainObject obj, Value[] prevValues, Value[] newValues, DBTransaction transaction) throws DatabaseException {
        final List hashedFields = index.getHashedFields();
        final Field indexedField = index.getIndexedField();
        final IntervalIndexKey indexKey = new IntervalIndexKey(obj.getId(), new long[hashedFields.size()], index);

        if (!obj._isJustCreated()) {
            // Remove old value-index
            HashIndexUtils.setHashValues(hashedFields, prevValues, indexKey.getHashedValues());
            indexKey.setIndexedValue(prevValues[indexedField.getNumber()].getValue());
            transaction.delete(index.columnFamily, indexKey.pack());
        }

        // Add new value-index
        setHashValues(hashedFields, prevValues, newValues, indexKey.getHashedValues());
        indexKey.setIndexedValue(getValue(indexedField, prevValues, newValues));
        transaction.put(index.columnFamily, indexKey.pack(), TypeConvert.EMPTY_BYTE_ARRAY);
    }

    private static void removeIndexedValue(IntervalIndex index, long id, Value[] values, DBTransaction transaction) throws DatabaseException {
        final List hashedFields = index.getHashedFields();
        final IntervalIndexKey indexKey = new IntervalIndexKey(id, new long[hashedFields.size()], index);

        HashIndexUtils.setHashValues(hashedFields, values, indexKey.getHashedValues());
        indexKey.setIndexedValue(values[index.getIndexedField().getNumber()].getValue());

        transaction.singleDelete(index.columnFamily, indexKey.pack());
    }

    private static void updateIndexedValue(RangeIndex index, DomainObject obj, Value[] prevValues, Value[] newValues, DBTransaction transaction) throws DatabaseException {
        final List hashedFields = index.getHashedFields();
        final RangeIndexKey indexKey = new RangeIndexKey(obj.getId(), new long[hashedFields.size()], index);

        if (!obj._isJustCreated()) {
            // Remove old value-index
            HashIndexUtils.setHashValues(hashedFields, prevValues, indexKey.getHashedValues());
            RangeIndexUtils.removeIndexedRange(index, indexKey,
                    prevValues[index.getBeginIndexedField().getNumber()].getValue(),
                    prevValues[index.getEndIndexedField().getNumber()].getValue(),
                    transaction, transaction::delete);
        }

        // Add new value-index
        setHashValues(hashedFields, prevValues, newValues, indexKey.getHashedValues());
        RangeIndexUtils.insertIndexedRange(index, indexKey,
                getValue(index.getBeginIndexedField(), prevValues, newValues),
                getValue(index.getEndIndexedField(), prevValues, newValues),
                transaction);
    }

    private static void removeIndexedValue(RangeIndex index, long id, Value[] values, DBTransaction transaction) throws DatabaseException {
        final List hashedFields = index.getHashedFields();
        final RangeIndexKey indexKey = new RangeIndexKey(id, new long[hashedFields.size()], index);

        HashIndexUtils.setHashValues(hashedFields, values, indexKey.getHashedValues());
        RangeIndexUtils.removeIndexedRange(index, indexKey,
                values[index.getBeginIndexedField().getNumber()].getValue(),
                values[index.getEndIndexedField().getNumber()].getValue(),
                transaction, transaction::singleDelete);
    }

    private static void setHashValues(List fields, Value[] prevValues, Value[] newValues, long[] destination) {
        for (int i = 0; i < fields.size(); ++i) {
            Field field = fields.get(i);
            Object value = getValue(field, prevValues, newValues);
            destination[i] = HashIndexUtils.buildHash(field.getType(), value, field.getConverter());
        }
    }

    private static Object getValue(Field field, Value[] prevValues, Value[] newValues) {
        Value value = newValues[field.getNumber()];
        if (value == null) {
            value = prevValues[field.getNumber()];
        }
        return value.getValue();
    }

    private static boolean anyChanged(List fields, Value[] newValues) {
        for (Field field: fields) {
            if (newValues[field.getNumber()] != null) {
                return true;
            }
        }
        return false;
    }

    private void validateUpdatingValue(DomainObject obj, Field field, Object value) throws DatabaseException {
        if (value == null) {
            return;
        }

        if (!foreignFieldEnabled || !field.isForeign()) {
            return;
        }

        long fkeyIdValue = (Long) value;
        if (transaction.getValue(field.getForeignDependency().getColumnFamily(), new FieldKey(fkeyIdValue).pack()) == null ||
                isMarkedForDeletion(field.getForeignDependency(), fkeyIdValue)) {
            throw new ForeignDependencyException(obj.getId(), obj.getStructEntity().getObjectClass(), field, fkeyIdValue);
        }
    }

    private void validateForeignValues(DomainObject obj) throws DatabaseException {
        if (!foreignFieldEnabled) {
            return;
        }

        List references = obj.getStructEntity().getReferencingForeignFields();
        if (references.isEmpty()) {
            return;
        }

        for (StructEntity.Reference ref : references) {
            KeyPattern keyPattern = HashIndexKey.buildKeyPattern(ref.fieldIndex, obj.getId());
            try (DBIterator i = transaction.createIterator(ref.fieldIndex.columnFamily)) {
                KeyValue keyValue = i.seek(keyPattern);
                if (keyValue != null) {
                    long referencingId = HashIndexKey.unpackId(keyValue.getKey());
                    if (!isMarkedForDeletion(Schema.getEntity(ref.objClass), referencingId)) {
                        throw new ForeignDependencyException(obj.getId(), obj.getStructEntity().getObjectClass(), referencingId, ref.objClass);
                    }
                }
            }
        }
    }

    private void validateForeignValues(StructEntity entity) throws DatabaseException {
        if (!foreignFieldEnabled) {
            return;
        }

        List references = entity.getReferencingForeignFields();
        if (references.isEmpty()) {
            return;
        }

        for (StructEntity.Reference ref : references) {
            if (ref.objClass.equals(entity.getObjectClass())) {
                continue;
            }

            Objects objs = deletingObjects.get(Schema.getEntity(ref.objClass).getColumnFamily());
            KeyPattern keyPattern = HashIndexKey.buildKeyPatternForLastKey(ref.fieldIndex);
            keyPattern.setForBackward(true);
            try (DBIterator i = transaction.createIterator(ref.fieldIndex.columnFamily)) {
                for (KeyValue keyValue = i.seek(keyPattern); keyValue != null; keyValue = i.step(DBIterator.StepDirection.BACKWARD)) {
                    if (HashIndexKey.unpackFirstIndexedValue(keyValue.getKey()) == 0) {
                        break;
                    }
                    long referencingId = HashIndexKey.unpackId(keyValue.getKey());
                    if (objs != null && objs.ids.contains(referencingId)) {
                        continue;
                    }

                    long objId = HashIndexKey.unpackFirstIndexedValue(keyValue.getKey());
                    throw new ForeignDependencyException(objId, entity.getObjectClass(), referencingId, ref.objClass);
                }
            }
        }
    }

    private static class Objects {

        final StructEntity entity;
        final RangeSet ids = TreeRangeSet.create();

        Objects(StructEntity entity) {
            this.entity = entity;
        }

        void add(DomainObject obj) {
            ids.add(Range.closedOpen(obj.getId(), obj.getId() + 1));
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy