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

com.apple.foundationdb.record.RecordMetaData Maven / Gradle / Ivy

There is a newer version: 2.8.110.0
Show newest version
/*
 * RecordMetaData.java
 *
 * This source file is part of the FoundationDB open source project
 *
 * Copyright 2015-2018 Apple Inc. and the FoundationDB project authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.apple.foundationdb.record;

import com.apple.foundationdb.annotation.API;
import com.apple.foundationdb.record.metadata.FormerIndex;
import com.apple.foundationdb.record.metadata.Index;
import com.apple.foundationdb.record.metadata.JoinedRecordType;
import com.apple.foundationdb.record.metadata.MetaDataException;
import com.apple.foundationdb.record.metadata.RecordType;
import com.apple.foundationdb.record.metadata.SyntheticRecordType;
import com.apple.foundationdb.record.metadata.expressions.KeyExpression;
import com.apple.foundationdb.record.metadata.expressions.LiteralKeyExpression;
import com.google.protobuf.Descriptors;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 * Meta-data for Record Layer record stores.
 *
 * Records are represented using Protobuf {@link com.google.protobuf.Message}s.
 * Each message {@link com.google.protobuf.Descriptors.Descriptor} corresponds to a {@link RecordType}.
 * All message types in the database come from a single {@link com.google.protobuf.Descriptors.FileDescriptor}.
 * The Protobuf file must also define a union message type (conventionally named {@code RecordTypeUnion}) with fields for
 * each of the possible record types.
 * When serializing, the record is put in the corresponding field and the whole saved as a byte string.
 * Then when deserializing, the returned record can be of any of the allowed types.
 *
 * Meta-data can also define any number of secondary {@link Index}es for record types.
 *
 * @see RecordMetaDataBuilder
 */
@API(API.Status.MAINTAINED)
public class RecordMetaData implements RecordMetaDataProvider {
    @Nonnull
    private final Descriptors.FileDescriptor recordsDescriptor;
    @Nonnull
    private final Descriptors.Descriptor unionDescriptor;
    @Nonnull
    private final Map unionFields;
    @Nonnull
    private final Map recordTypes;
    @Nonnull
    private final Map> syntheticRecordTypes;
    @Nonnull
    private final Map indexes;
    @Nonnull
    private Map universalIndexes;
    @Nonnull
    private final List formerIndexes;
    private final boolean splitLongRecords;
    private final boolean storeRecordVersions;
    private final int version;
    private final long subspaceKeyCounter;
    private final boolean usesSubspaceKeyCounter;
    @Nullable
    private final KeyExpression recordCountKey;
    private final boolean usesLocalRecordsDescriptor;

    private static final Descriptors.FileDescriptor[] defaultExcludedDependencies = new Descriptors.FileDescriptor[] {
            RecordMetaDataProto.getDescriptor(), RecordMetaDataOptionsProto.getDescriptor(), TupleFieldsProto.getDescriptor()
    };

    @SuppressWarnings("squid:S00107") // There is a Builder.
    protected RecordMetaData(@Nonnull Descriptors.FileDescriptor recordsDescriptor,
                             @Nonnull Descriptors.Descriptor unionDescriptor,
                             @Nonnull Map unionFields,
                             @Nonnull Map recordTypes,
                             @Nonnull Map> syntheticRecordTypes,
                             @Nonnull Map indexes,
                             @Nonnull Map universalIndexes,
                             @Nonnull List formerIndexes,
                             boolean splitLongRecords,
                             boolean storeRecordVersions,
                             int version,
                             long subspaceKeyCounter,
                             boolean usesSubspaceKeyCounter,
                             @Nullable KeyExpression recordCountKey,
                             boolean usesLocalRecordsDescriptor) {
        this.recordsDescriptor = recordsDescriptor;
        this.unionDescriptor = unionDescriptor;
        this.unionFields = unionFields;
        this.recordTypes = recordTypes;
        this.syntheticRecordTypes = syntheticRecordTypes;
        this.indexes = indexes;
        this.universalIndexes = universalIndexes;
        this.formerIndexes = formerIndexes;
        this.splitLongRecords = splitLongRecords;
        this.storeRecordVersions = storeRecordVersions;
        this.version = version;
        this.subspaceKeyCounter = subspaceKeyCounter;
        this.usesSubspaceKeyCounter = usesSubspaceKeyCounter;
        this.recordCountKey = recordCountKey;
        this.usesLocalRecordsDescriptor = usesLocalRecordsDescriptor;
    }

    /**
     * Creates an instance of {@link RecordMetaDataBuilder}.
     * @return a new builder
     */
    @Nonnull
    public static RecordMetaDataBuilder newBuilder() {
        return new RecordMetaDataBuilder();
    }

    @Nonnull
    public Descriptors.FileDescriptor getRecordsDescriptor() {
        return recordsDescriptor;
    }

    @Nonnull
    public Descriptors.Descriptor getUnionDescriptor() {
        return unionDescriptor;
    }

    @Nonnull
    public Descriptors.FieldDescriptor getUnionFieldForRecordType(@Nonnull RecordType recordType) {
        final Descriptors.FieldDescriptor unionField = unionFields.get(recordType.getDescriptor());
        if (unionField == null) {
            throw new MetaDataException("Record type " + recordType.getName() + " is not in the union");
        }
        return unionField;
    }

    @Nonnull
    public Map getRecordTypes() {
        return recordTypes;
    }

    @Nonnull
    public RecordType getRecordType(@Nonnull String name) {
        RecordType recordType = recordTypes.get(name);
        if (recordType == null) {
            throw new MetaDataException("Unknown record type " + name);
        }
        return recordType;
    }

    @Nonnull
    public RecordType getRecordTypeForDescriptor(@Nonnull Descriptors.Descriptor descriptor) {
        RecordType recordType = getRecordType(descriptor.getName());
        if (recordType.getDescriptor() != descriptor) {
            throw new MetaDataException("descriptor did not match record type");
        }
        return recordType;
    }

    @Nonnull
    @API(API.Status.EXPERIMENTAL)
    @SuppressWarnings("squid:S1452")
    public Map> getSyntheticRecordTypes() {
        return syntheticRecordTypes;
    }

    @Nonnull
    @API(API.Status.EXPERIMENTAL)
    @SuppressWarnings("squid:S1452")
    public SyntheticRecordType getSyntheticRecordType(@Nonnull String name) {
        SyntheticRecordType recordType = syntheticRecordTypes.get(name);
        if (recordType == null) {
            throw new MetaDataException("Unknown synthetic record type " + name);
        }
        return recordType;
    }

    @Nonnull
    @API(API.Status.EXPERIMENTAL)
    @SuppressWarnings("squid:S1452")
    public SyntheticRecordType getSyntheticRecordTypeFromRecordTypeKey(@Nonnull Object recordTypeKey) {
        for (SyntheticRecordType recordType : getRecordMetaData().getSyntheticRecordTypes().values()) {
            if (recordType.getRecordTypeKey().equals(recordTypeKey)) {
                return recordType;
            }
        }
        throw new MetaDataException("Unknown synthetic record type " + recordTypeKey);
    }

    /**
     * Get a record type or synthetic record type by name as used in an index.
     * @param name the name of the record type
     * @return the possibly synthetic record type
     */
    public RecordType getIndexableRecordType(@Nonnull String name) {
        RecordType recordType = recordTypes.get(name);
        if (recordType == null) {
            recordType = syntheticRecordTypes.get(name);
        }
        if (recordType == null) {
            throw new MetaDataException("Unknown record type " + name);
        }
        return recordType;
    }

    @Nonnull
    public Index getIndex(@Nonnull String indexName) {
        Index index = indexes.get(indexName);
        if (null == index) {
            throw new MetaDataException("Index " + indexName + " not defined");
        }
        return index;
    }

    public boolean hasIndex(@Nonnull String indexName) {
        return indexes.get(indexName) != null;
    }

    @Nonnull
    public List getAllIndexes() {
        return new ArrayList<>(indexes.values());
    }

    @Nonnull
    public Index getUniversalIndex(@Nonnull String indexName) {
        Index index = universalIndexes.get(indexName);
        if (null == index) {
            throw new MetaDataException("Index " + indexName + " not defined");
        }
        return index;
    }

    public boolean hasUniversalIndex(@Nonnull String indexName) {
        return universalIndexes.get(indexName) != null;
    }

    @Nonnull
    public List getUniversalIndexes() {
        return new ArrayList<>(universalIndexes.values());
    }

    public List getFormerIndexes() {
        return formerIndexes;
    }

    public boolean isSplitLongRecords() {
        return splitLongRecords;
    }

    public boolean isStoreRecordVersions() {
        return storeRecordVersions;
    }

    public int getVersion() {
        return version;
    }

    /**
     * Get value of the counter used for index subspace keys if the counter-based assignment is used.
     * @return the value of the counter
     * @see RecordMetaDataBuilder#enableCounterBasedSubspaceKeys()
     */
    public long getSubspaceKeyCounter() {
        return subspaceKeyCounter;
    }

    /**
     * Checks if counter-based subspace key assignment is used.
     * @return {@code true} if the subspace key counter is used
     * @see RecordMetaDataBuilder#enableCounterBasedSubspaceKeys()
     */
    public boolean usesSubspaceKeyCounter() {
        return usesSubspaceKeyCounter;
    }

    public List getFormerIndexesSince(int version) {
        List result = new ArrayList<>();
        for (FormerIndex formerIndex : formerIndexes) {
            // An index that was added *and* removed before this version does not require any action.
            if (formerIndex.getRemovedVersion() > version && formerIndex.getAddedVersion() <= version) {
                result.add(formerIndex);
            }
        }
        return result;
    }

    public Map> getIndexesSince(int version) {
        Map> result = new HashMap<>();
        for (RecordType recordType : recordTypes.values()) {
            for (Index index : recordType.getIndexes()) {
                if (index.getLastModifiedVersion() > version) {
                    result.put(index, Collections.singletonList(recordType));
                }
            }
            for (Index index : recordType.getMultiTypeIndexes()) {
                if (index.getLastModifiedVersion() > version) {
                    if (!result.containsKey(index)) {
                        result.put(index, new ArrayList<>());
                    }
                    result.get(index).add(recordType);
                }
            }
        }
        for (Index index : universalIndexes.values()) {
            if (index.getLastModifiedVersion() > version) {
                result.put(index, null);
            }
        }
        for (SyntheticRecordType recordType : syntheticRecordTypes.values()) {
            for (Index index : recordType.getIndexes()) {
                if (index.getLastModifiedVersion() > version) {
                    result.put(index, Collections.singletonList(recordType));
                }
            }
            for (Index index : recordType.getMultiTypeIndexes()) {
                if (index.getLastModifiedVersion() > version) {
                    if (!result.containsKey(index)) {
                        result.put(index, new ArrayList<>());
                    }
                    result.get(index).add(recordType);
                }
            }
        }
        return result;
    }

    @Nonnull
    public Collection recordTypesForIndex(@Nonnull Index index) {
        if (getUniversalIndexes().contains(index)) {
            return getRecordTypes().values();
        }
        List result = new ArrayList<>();
        for (RecordType recordType : getRecordTypes().values()) {
            if (recordType.getIndexes().contains(index)) {
                return Collections.singletonList(recordType);
            } else if (recordType.getMultiTypeIndexes().contains(index)) {
                result.add(recordType);
            }
        }
        for (SyntheticRecordType recordType : getSyntheticRecordTypes().values()) {
            if (recordType.getIndexes().contains(index)) {
                return Collections.singletonList(recordType);
            } else if (recordType.getMultiTypeIndexes().contains(index)) {
                result.add(recordType);
            }
        }
        return result;
    }

    @Nullable
    @API(API.Status.DEPRECATED)
    public KeyExpression getRecordCountKey() {
        return recordCountKey;
    }

    /**
     * Determine whether every record type in this meta-data has {@link RecordType#primaryKeyHasRecordTypePrefix}.
     *
     * If so, records are strictly partitioned by record type.
     * @return {@code true} if every record type has a record type prefix on the primary key
     */
    public boolean primaryKeyHasRecordTypePrefix() {
        return recordTypes.values().stream().allMatch(RecordType::primaryKeyHasRecordTypePrefix);
    }

    /**
     * Get this RecordMetaData instance.
     *
     * @return this RecordMetaData instance
     */
    @Nonnull
    @Override
    public RecordMetaData getRecordMetaData() {
        return this;
    }

    @Nonnull
    public static RecordMetaData build(@Nonnull Descriptors.FileDescriptor descriptor) {
        return RecordMetaData.newBuilder().setRecords(descriptor).getRecordMetaData();
    }

    /**
     * Factory method to deserialize a record meta-data proto. It assumes that the proto contains all of the dependencies
     * and does not process extension options.
     * @param proto the serialized proto message of the {@code RecordMetaData}
     * @return the {@code RecordMetaData} object
     */
    @Nonnull
    public static RecordMetaData build(@Nonnull RecordMetaDataProto.MetaData proto) {
        return RecordMetaData.newBuilder().setRecords(proto).getRecordMetaData();
    }

    private static void getDependencies(@Nonnull Descriptors.FileDescriptor fileDescriptor,
                                        @Nonnull Map allDependencies,
                                        @Nullable Map excludedDependencies) {
        for (Descriptors.FileDescriptor dependency : fileDescriptor.getDependencies()) {
            if (excludedDependencies != null && excludedDependencies.containsKey(dependency.getName())) {
                // Just pass.
                continue;
            } else if (!allDependencies.containsKey(dependency.getName())) {
                allDependencies.put(dependency.getName(), dependency);
                getDependencies(dependency, allDependencies, excludedDependencies);
            } else if (!allDependencies.get(dependency.getName()).equals(dependency)) {
                throw new MetaDataException(String.format("Dependency mismatch found for file %s", dependency.getName()));
            }
        }
    }

    /**
     * Serializes the record meta-data to a {@code MetaData} proto message. By default, it includes all of the
     * dependencies except {@code TupleFieldsProto}, {@code RecordMetaDataOptionsProto} and {@code RecordMetaDataProto}.
     *
     * 

* Note that if this record meta-data object was created with a * {@linkplain RecordMetaDataBuilder#setLocalFileDescriptor(Descriptors.FileDescriptor) local file descriptor}, * then serializing the meta-data to a proto message is disallowed. This is because setting a local file descriptor * can change the meta-data in ways that would change its serialization, but it also will not update the meta-data * version. This means that if the meta-data were then saved to disk, existing clients would not be informed that * the meta-data had been updated. *

* * @throws KeyExpression.SerializationException on any serialization failures * @throws MetaDataException if this {@code RecordMetaData} was initialized with a * {@linkplain RecordMetaDataBuilder#setLocalFileDescriptor(Descriptors.FileDescriptor) local file descriptor} * @return the serialized MetaData proto message */ @Nonnull public RecordMetaDataProto.MetaData toProto() { return toProto(defaultExcludedDependencies); } /** * Serializes the record meta-data to a MetaData proto message. This operates like * {@link #toProto()} except that any dependency in the excluded list is not included in the * serialized proto message. If the list is set to {@code null}, then all dependencies will be serialized * to the proto message including those that are execluded by default. * * @param excludedDependencies a list of dependencies not to include in the serialized proto * @return the serialized MetaData proto message * @throws KeyExpression.SerializationException on any serialization failures * @throws MetaDataException if this {@code RecordMetaData} was initialized with a * {@linkplain RecordMetaDataBuilder#setLocalFileDescriptor(Descriptors.FileDescriptor) local file descriptor} * @see #toProto() */ @Nonnull @SuppressWarnings("deprecation") public RecordMetaDataProto.MetaData toProto(@Nullable Descriptors.FileDescriptor[] excludedDependencies) throws KeyExpression.SerializationException { if (usesLocalRecordsDescriptor) { throw new MetaDataException("cannot serialize meta-data with a local records descriptor to proto"); } RecordMetaDataProto.MetaData.Builder builder = RecordMetaDataProto.MetaData.newBuilder(); // Set the root records. builder.setRecords(recordsDescriptor.toProto()); // Convert the exclusion list to a map Map excludeMap = null; if (excludedDependencies != null) { excludeMap = new HashMap<>(excludedDependencies.length); for (Descriptors.FileDescriptor dependency : excludedDependencies) { excludeMap.put(dependency.getName(), dependency); } } // Add in the rest of dependencies. Map allDependencies = new TreeMap<>(); getDependencies(recordsDescriptor, allDependencies, excludeMap); for (Descriptors.FileDescriptor dependency : allDependencies.values()) { builder.addDependencies(dependency.toProto()); } // Create builders for each index so that we can then add associated record types (etc.). Map indexBuilders = new TreeMap<>(); for (Map.Entry entry : indexes.entrySet()) { indexBuilders.put(entry.getKey(), entry.getValue().toProto().toBuilder()); } for (RecordType recordType : getRecordTypes().values()) { // Add this record type to each appropriate index. for (Index index : recordType.getIndexes()) { indexBuilders.get(index.getName()).addRecordType(recordType.getName()); } for (Index index : recordType.getMultiTypeIndexes()) { indexBuilders.get(index.getName()).addRecordType(recordType.getName()); } RecordMetaDataProto.RecordType.Builder typeBuilder = builder.addRecordTypesBuilder() .setName(recordType.getName()) .setPrimaryKey(recordType.getPrimaryKey().toKeyExpression()); if (recordType.getSinceVersion() != null) { typeBuilder.setSinceVersion(recordType.getSinceVersion()); } if (recordType.hasExplicitRecordTypeKey()) { typeBuilder.setExplicitKey(LiteralKeyExpression.toProtoValue(recordType.getExplicitRecordTypeKey())); } } indexBuilders.values().forEach(builder::addIndexes); // Add in the former indexes. for (FormerIndex formerIndex : getFormerIndexes()) { builder.addFormerIndexes(formerIndex.toProto()); } // Add in the final options. builder.setSplitLongRecords(splitLongRecords); builder.setStoreRecordVersions(storeRecordVersions); builder.setVersion(version); if (usesSubspaceKeyCounter()) { builder.setSubspaceKeyCounter(subspaceKeyCounter); builder.setUsesSubspaceKeyCounter(true); } if (recordCountKey != null) { builder.setRecordCountKey(recordCountKey.toKeyExpression()); } for (SyntheticRecordType syntheticRecordType : syntheticRecordTypes.values()) { if (syntheticRecordType instanceof JoinedRecordType) { builder.addJoinedRecordTypes(((JoinedRecordType)syntheticRecordType).toProto()); } } return builder.build(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy