/* * ToroDB * Copyright © 2014 8Kdata Technology (www.8kdata.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.torodb.core.transaction.metainf; import com.google.common.base.Preconditions; import com.google.common.collect.HashBasedTable; import com.google.common.collect.Table; import com.torodb.core.TableRef; import org.jooq.lambda.Seq; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; /** * */ public class ImmutableMetaIndex implements MetaIndex { private final String name; private final boolean unique; private final List<ImmutableMetaIndexField> fieldsByPosition; private final Map<TableRef, List<ImmutableMetaIndexField>> fieldsByTableRefAndPosition; private final Table<TableRef, String, ImmutableMetaIndexField> fieldsByTableRefAndName; public ImmutableMetaIndex(String name, boolean unique) { this(name, unique, Collections.emptyList()); } public ImmutableMetaIndex(String name, boolean unique, Iterable<ImmutableMetaIndexField> fields) { this.name = name; this.unique = unique; fieldsByTableRefAndName = HashBasedTable.create(); fieldsByPosition = new ArrayList<>(fieldsByTableRefAndName.size()); fieldsByTableRefAndPosition = new HashMap<>(fieldsByTableRefAndName.size()); for (ImmutableMetaIndexField field : fields) { fieldsByPosition.add(field); fieldsByTableRefAndPosition.computeIfAbsent(field.getTableRef(), tableRef -> new ArrayList<>()).add(field); fieldsByTableRefAndName.put(field.getTableRef(), field.getName(), field); } } public ImmutableMetaIndex(String name, boolean unique, List<ImmutableMetaIndexField> fieldsByPosition) { this.name = name; this.unique = unique; this.fieldsByPosition = fieldsByPosition; this.fieldsByTableRefAndPosition = new HashMap<>(); this.fieldsByTableRefAndName = HashBasedTable.create(); for (ImmutableMetaIndexField field : fieldsByPosition) { fieldsByTableRefAndPosition.computeIfAbsent(field.getTableRef(), tableRef -> new ArrayList<>()).add(field); fieldsByTableRefAndName.put(field.getTableRef(), field.getName(), field); } } @Override public String getName() { return name; } @Override public boolean isUnique() { return unique; } @Override public int size() { return fieldsByPosition.size(); } @Override public Iterator<ImmutableMetaIndexField> iteratorFields() { return fieldsByPosition.iterator(); } @Override public Iterator<? extends ImmutableMetaIndexField> iteratorMetaIndexFieldByTableRef( TableRef tableRef) { return fieldsByTableRefAndPosition.computeIfAbsent(tableRef, t -> new ArrayList<>()) .iterator(); } @Override public Stream<TableRef> streamTableRefs() { return fieldsByTableRefAndName.rowKeySet().stream(); } @Override public ImmutableMetaIndexField getMetaIndexFieldByTableRefAndName( TableRef tableRef, String name) { return fieldsByTableRefAndName.get(tableRef, name); } @Override public ImmutableMetaIndexField getMetaIndexFieldByTableRefAndPosition(TableRef tableRef, int position) { return fieldsByTableRefAndPosition.computeIfAbsent(tableRef, t -> new ArrayList<>()).get( position); } @Override public ImmutableMetaIndexField getMetaIndexFieldByPosition(int position) { return fieldsByPosition.get(position); } @Override public boolean isCompatible(MetaDocPart docPart) { return isCompatible(docPart, iteratorMetaIndexFieldByTableRef(docPart.getTableRef())); } protected boolean isCompatible(MetaDocPart docPart, Iterator<? extends MetaIndexField> indexFieldIterator) { if (!indexFieldIterator.hasNext()) { return false; } while (indexFieldIterator.hasNext()) { MetaIndexField indexField = indexFieldIterator.next(); if (!indexField.isCompatible(docPart)) { return false; } } return !indexFieldIterator.hasNext(); } @Override public boolean isCompatible(MetaDocPart docPart, MetaDocPartIndex docPartIndex) { return isCompatible(docPart, docPartIndex, iteratorMetaIndexFieldByTableRef(docPart.getTableRef())); } protected boolean isCompatible(MetaDocPart docPart, MetaDocPartIndex docPartIndex, Iterator<? extends MetaIndexField> indexFieldIterator) { if (unique != docPartIndex.isUnique()) { return false; } if (!indexFieldIterator.hasNext()) { return false; } Iterator<? extends MetaDocPartIndexColumn> fieldIndexIterator = docPartIndex.iteratorColumns(); while (indexFieldIterator.hasNext() && fieldIndexIterator.hasNext()) { MetaIndexField indexField = indexFieldIterator.next(); MetaDocPartIndexColumn indexColumn = fieldIndexIterator.next(); if (!indexField.isCompatible(docPart, indexColumn)) { return false; } } return !indexFieldIterator.hasNext() && !fieldIndexIterator.hasNext(); } @Override public boolean isMatch(MetaDocPart docPart, List<String> identifiers, MetaDocPartIndex docPartIndex) { return isMatch(docPart, identifiers, docPartIndex, iteratorMetaIndexFieldByTableRef(docPart.getTableRef()), false); } protected boolean isMatch(MetaDocPart docPart, List<String> identifiers, MetaDocPartIndex docPartIndex, Iterator<? extends MetaIndexField> indexFieldIterator, boolean isSubMatch) { if (isUnique() != docPartIndex.isUnique()) { return false; } if (!indexFieldIterator.hasNext()) { return false; } if (isSubMatch && identifiers.isEmpty()) { return true; } Iterator<? extends MetaDocPartIndexColumn> fieldIndexIterator = docPartIndex.iteratorColumns(); Iterator<String> identifiersIterator = identifiers.iterator(); while (indexFieldIterator.hasNext() && (isSubMatch || fieldIndexIterator.hasNext()) && identifiersIterator.hasNext()) { MetaIndexField indexField = indexFieldIterator.next(); MetaDocPartIndexColumn indexColumn; if (isSubMatch) { if (fieldIndexIterator.hasNext()) { indexColumn = fieldIndexIterator.next(); } else { indexColumn = null; } } else { indexColumn = fieldIndexIterator.next(); } String identifier = identifiersIterator.next(); if (indexColumn == null) { if (!isSubMatch) { return false; } } else { if (!indexField.isMatch(docPart, identifier, indexColumn)) { return false; } } } return (isSubMatch || !indexFieldIterator.hasNext() && !fieldIndexIterator.hasNext()) && !identifiersIterator.hasNext(); } @Override public boolean isMatch(MetaIndex index) { return isMatch(index, iteratorFields()); } protected boolean isMatch(MetaIndex index, Iterator<? extends MetaIndexField> iteratorFields) { if (getName().equals(index.getName())) { return true; } return index.isUnique() == isUnique() && index.size() == size() && Seq.seq(iteratorFields) .allMatch(indexField -> { MetaIndexField otherIndexField = index.getMetaIndexFieldByPosition(indexField .getPosition()); return otherIndexField != null && indexField.isMatch(otherIndexField); }); } @Override public boolean isSubMatch(MetaDocPart docPart, List<String> identifiersSublist, MetaDocPartIndex docPartIndex) { return isMatch(docPart, identifiersSublist, docPartIndex, iteratorMetaIndexFieldByTableRef(docPart.getTableRef()), true); } @Override public Iterator<List<String>> iteratorMetaDocPartIndexesIdentifiers(MetaDocPart docPart) { return iteratorMetaDocPartIndexesIdentifiers(docPart, iteratorMetaIndexFieldByTableRef(docPart .getTableRef())); } protected Iterator<List<String>> iteratorMetaDocPartIndexesIdentifiers(MetaDocPart docPart, Iterator<? extends MetaIndexField> indexFieldIterator) { List<List<String>> docPartIndexesIdentifiers = new ArrayList<>((int) Math.pow(2, size())); while (indexFieldIterator.hasNext()) { MetaIndexField indexField = indexFieldIterator.next(); cartesianAppend(docPartIndexesIdentifiers, docPart.streamMetaFieldByName(indexField.getName()) .collect(Collectors.toList())); } return docPartIndexesIdentifiers.iterator(); } private void cartesianAppend(List<List<String>> docPartIndexesIdentifiers, List<MetaField> fields) { if (fields.isEmpty()) { return; } if (docPartIndexesIdentifiers.isEmpty()) { for (MetaField field : fields) { List<String> docPartIndexIdentifiers = new ArrayList<>(); docPartIndexIdentifiers.add(field.getIdentifier()); docPartIndexesIdentifiers.add(docPartIndexIdentifiers); } } else { List<List<String>> newDocPartIndexesIdentifiers = new ArrayList<>(docPartIndexesIdentifiers.size() * fields.size()); for (List<String> docPartIndexIdentifiers : docPartIndexesIdentifiers) { Iterator<MetaField> fieldsIterator = fields.iterator(); MetaField field = fieldsIterator.next(); while (fieldsIterator.hasNext()) { MetaField nextField = fieldsIterator.next(); List<String> docPartIndexIdentifiersCopy = new ArrayList<>(docPartIndexIdentifiers); docPartIndexIdentifiersCopy.add(nextField.getIdentifier()); newDocPartIndexesIdentifiers.add(docPartIndexIdentifiersCopy); } docPartIndexIdentifiers.add(field.getIdentifier()); newDocPartIndexesIdentifiers.add(docPartIndexIdentifiers); } docPartIndexesIdentifiers.clear(); docPartIndexesIdentifiers.addAll(newDocPartIndexesIdentifiers); } } @Override public String toString() { return defautToString(); } public static class Builder { private boolean built = false; private final String name; private final boolean unique; private final List<ImmutableMetaIndexField> fieldsByPosition; public Builder(String name, boolean unique) { this.name = name; this.unique = unique; fieldsByPosition = new ArrayList<>(); } public Builder(String name, boolean unique, int expectedFields) { this.name = name; this.unique = unique; fieldsByPosition = new ArrayList<>(expectedFields); } public Builder(ImmutableMetaIndex other) { this.name = other.name; this.unique = other.isUnique(); fieldsByPosition = new ArrayList<>(other.fieldsByPosition); } public Builder add(ImmutableMetaIndexField field) { Preconditions.checkState(!built, "This builder has already been built"); fieldsByPosition.add(field); return this; } public Builder remove(MetaIndexField field) { Preconditions.checkState(!built, "This builder has already been built"); fieldsByPosition.remove(field.getPosition()); return this; } public ImmutableMetaIndex build() { Preconditions.checkState(!built, "This builder has already been built"); built = true; return new ImmutableMetaIndex(name, unique, fieldsByPosition); } } }