Search in sources :

Example 61 with MetaDataException

use of com.apple.foundationdb.record.metadata.MetaDataException in project fdb-record-layer by FoundationDB.

the class TextIndexMaintainerFactory method getIndexValidator.

/**
 * Validates that the index provided is valid for text indexes. This means that
 * the index must:
 *
 * <ul>
 *     <li>Not be a unique index.</li>
 *     <li>Not include a {@link com.apple.foundationdb.record.metadata.expressions.VersionKeyExpression#VERSION} expression in its root expression.</li>
 *     <li>Have a key expression whose first column is of type <code>string</code> (possibly with grouping columns
 *         before the tokenized text column) and is not repeated.</li> <!--Maybe we should support FanType.Concatenate?-->
 *     <li>Specify a valid tokenizer and tokenizer version through the index options (possibly using the defaults).</li>
 *     <li>Not define a value expression.</li>
 * </ul>
 *
 * @param index the index to validate
 * @return a validator to run against the index
 * @throws KeyExpression.InvalidExpressionException if the expression does not contain a string as its first ungrouped column
 * @throws com.apple.foundationdb.record.metadata.MetaDataException if the tokenizer is not defined, if the tokenizer version
 *                                                    is out of range, or if the index is marked as unique
 */
@Nonnull
@Override
public IndexValidator getIndexValidator(Index index) {
    return new IndexValidator(index) {

        @Override
        public void validate(@Nonnull MetaDataValidator metaDataValidator) {
            super.validate(metaDataValidator);
            validateNotVersion();
            validateNotUnique();
            // TODO: allow value expressions for covering text indexes
            validateNoValue();
            // Validate that the tokenizer exists and that the version is in a valid range.
            TextTokenizer tokenizer = TextIndexMaintainer.getTokenizer(index);
            int tokenizerVersion = TextIndexMaintainer.getIndexTokenizerVersion(index);
            tokenizer.validateVersion(tokenizerVersion);
        }

        @Override
        public void validateIndexForRecordType(@Nonnull RecordType recordType, @Nonnull MetaDataValidator metaDataValidator) {
            final List<Descriptors.FieldDescriptor> fields = metaDataValidator.validateIndexForRecordType(index, recordType);
            int textFieldPosition = TextIndexMaintainer.textFieldPosition(index.getRootExpression());
            if (textFieldPosition > fields.size()) {
                throw new KeyExpression.InvalidExpressionException("text index does not have text field after grouped fields");
            } else {
                Descriptors.FieldDescriptor textFieldDescriptor = fields.get(textFieldPosition);
                if (!textFieldDescriptor.getType().equals(Descriptors.FieldDescriptor.Type.STRING)) {
                    throw new KeyExpression.InvalidExpressionException(String.format("text index has non-string type %s as text field", textFieldDescriptor.getLiteJavaType()));
                }
                if (textFieldDescriptor.isRepeated()) {
                    throw new KeyExpression.InvalidExpressionException("text index does not allow a repeated field for text body");
                }
            }
        }

        /**
         * Validate any options that have changed. There are several options unique to text indexes which
         * may change without requiring the index be rebuilt. They are:
         *
         * <ul>
         *     <li>{@link IndexOptions#TEXT_TOKENIZER_VERSION_OPTION} which can be increased (but not decreased)</li>
         *     <li>{@link IndexOptions#TEXT_ADD_AGGRESSIVE_CONFLICT_RANGES_OPTION} which only affects what conflict ranges
         *          are added at index update time and thus has no impact on the on-disk representation</li>
         *     <li>{@link IndexOptions#TEXT_OMIT_POSITIONS_OPTION} which changes whether the position lists are included
         *          in index entries</li>
         * </ul>
         *
         * <p>
         * Note that the {@link IndexOptions#TEXT_TOKENIZER_NAME_OPTION} is <em>not</em> allowed to change
         * (without rebuilding the index).
         * </p>
         *
         * @param oldIndex an older version of this index
         * @param changedOptions the set of changed options
         */
        @Override
        protected void validateChangedOptions(@Nonnull Index oldIndex, @Nonnull Set<String> changedOptions) {
            for (String changedOption : changedOptions) {
                switch(changedOption) {
                    case IndexOptions.TEXT_ADD_AGGRESSIVE_CONFLICT_RANGES_OPTION:
                    case IndexOptions.TEXT_OMIT_POSITIONS_OPTION:
                        // without breaking compatibility.
                        break;
                    case IndexOptions.TEXT_TOKENIZER_NAME_OPTION:
                        String oldTokenizerName = TextIndexMaintainer.getTokenizer(oldIndex).getName();
                        String newTokenizerName = TextIndexMaintainer.getTokenizer(index).getName();
                        if (!oldTokenizerName.equals(newTokenizerName)) {
                            throw new MetaDataException("text tokenizer changed", LogMessageKeys.INDEX_NAME, index.getName());
                        }
                        break;
                    case IndexOptions.TEXT_TOKENIZER_VERSION_OPTION:
                        // The tokenizer version should always go up.
                        int oldTokenizerVersion = TextIndexMaintainer.getIndexTokenizerVersion(oldIndex);
                        int newTokenizerVersion = TextIndexMaintainer.getIndexTokenizerVersion(index);
                        if (oldTokenizerVersion > newTokenizerVersion) {
                            throw new MetaDataException("text tokenizer version downgraded", LogMessageKeys.INDEX_NAME, index.getName(), LogMessageKeys.OLD_VERSION, oldTokenizerVersion, LogMessageKeys.NEW_VERSION, newTokenizerVersion);
                        }
                        break;
                    default:
                        // Changed options that are not text options will be handled by super class
                        if (TEXT_OPTIONS.contains(changedOption)) {
                            throw new MetaDataException("index option changed", LogMessageKeys.INDEX_NAME, index.getName(), LogMessageKeys.INDEX_OPTION, changedOption, LogMessageKeys.OLD_OPTION, oldIndex.getOption(changedOption), LogMessageKeys.NEW_OPTION, index.getOption(changedOption));
                        }
                }
            }
            changedOptions.removeAll(TEXT_OPTIONS);
            super.validateChangedOptions(oldIndex, changedOptions);
        }
    };
}
Also used : IndexValidator(com.apple.foundationdb.record.metadata.IndexValidator) ImmutableSet(com.google.common.collect.ImmutableSet) Set(java.util.Set) Nonnull(javax.annotation.Nonnull) Index(com.apple.foundationdb.record.metadata.Index) MetaDataException(com.apple.foundationdb.record.metadata.MetaDataException) TextTokenizer(com.apple.foundationdb.record.provider.common.text.TextTokenizer) RecordType(com.apple.foundationdb.record.metadata.RecordType) MetaDataValidator(com.apple.foundationdb.record.metadata.MetaDataValidator) Descriptors(com.google.protobuf.Descriptors) Nonnull(javax.annotation.Nonnull)

Aggregations

MetaDataException (com.apple.foundationdb.record.metadata.MetaDataException)61 Test (org.junit.jupiter.api.Test)33 RecordMetaData (com.apple.foundationdb.record.RecordMetaData)29 ParameterizedTest (org.junit.jupiter.params.ParameterizedTest)28 MetaDataProtoTest (com.apple.foundationdb.record.metadata.MetaDataProtoTest)25 Nonnull (javax.annotation.Nonnull)24 Index (com.apple.foundationdb.record.metadata.Index)22 Descriptors (com.google.protobuf.Descriptors)14 Tuple (com.apple.foundationdb.tuple.Tuple)13 List (java.util.List)12 ScanProperties (com.apple.foundationdb.record.ScanProperties)11 TupleRange (com.apple.foundationdb.record.TupleRange)11 KeyExpression (com.apple.foundationdb.record.metadata.expressions.KeyExpression)11 IndexEntry (com.apple.foundationdb.record.IndexEntry)10 IndexTypes (com.apple.foundationdb.record.metadata.IndexTypes)10 DescriptorProtos (com.google.protobuf.DescriptorProtos)10 TestRecords1Proto (com.apple.foundationdb.record.TestRecords1Proto)9 Message (com.google.protobuf.Message)9 Collectors (java.util.stream.Collectors)9 IndexScanType (com.apple.foundationdb.record.IndexScanType)8