/* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch licenses this file to you 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 org.elasticsearch.index.fielddata.ordinals; import org.apache.lucene.index.RandomAccessOrds; import org.apache.lucene.index.SortedDocValues; import org.apache.lucene.util.packed.PackedInts; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.fielddata.FieldData; import org.elasticsearch.search.MultiValueMode; import org.elasticsearch.test.ESTestCase; import org.junit.Test; import java.io.IOException; import java.util.*; import static org.hamcrest.Matchers.equalTo; /** */ public class MultiOrdinalsTests extends ESTestCase { protected final Ordinals creationMultiOrdinals(OrdinalsBuilder builder) { return this.creationMultiOrdinals(builder, Settings.builder()); } protected Ordinals creationMultiOrdinals(OrdinalsBuilder builder, Settings.Builder settings) { return builder.build(settings.build()); } @Test public void testRandomValues() throws IOException { Random random = getRandom(); int numDocs = 100 + random.nextInt(1000); int numOrdinals = 1 + random.nextInt(200); int numValues = 100 + random.nextInt(100000); OrdinalsBuilder builder = new OrdinalsBuilder(numDocs); Set<OrdAndId> ordsAndIdSet = new HashSet<>(); for (int i = 0; i < numValues; i++) { ordsAndIdSet.add(new OrdAndId(random.nextInt(numOrdinals), random.nextInt(numDocs))); } List<OrdAndId> ordsAndIds = new ArrayList<>(ordsAndIdSet); Collections.sort(ordsAndIds, new Comparator<OrdAndId>() { @Override public int compare(OrdAndId o1, OrdAndId o2) { if (o1.ord < o2.ord) { return -1; } if (o1.ord == o2.ord) { if (o1.id < o2.id) { return -1; } if (o1.id > o2.id) { return 1; } return 0; } return 1; } }); long lastOrd = -1; for (OrdAndId ordAndId : ordsAndIds) { if (lastOrd != ordAndId.ord) { lastOrd = ordAndId.ord; builder.nextOrdinal(); } ordAndId.ord = builder.currentOrdinal(); // remap the ordinals in case we have gaps? builder.addDoc(ordAndId.id); } Collections.sort(ordsAndIds, new Comparator<OrdAndId>() { @Override public int compare(OrdAndId o1, OrdAndId o2) { if (o1.id < o2.id) { return -1; } if (o1.id == o2.id) { if (o1.ord < o2.ord) { return -1; } if (o1.ord > o2.ord) { return 1; } return 0; } return 1; } }); Ordinals ords = creationMultiOrdinals(builder); RandomAccessOrds docs = ords.ordinals(); final SortedDocValues singleOrds = MultiValueMode.MIN.select(docs); int docId = ordsAndIds.get(0).id; List<Long> docOrds = new ArrayList<>(); for (OrdAndId ordAndId : ordsAndIds) { if (docId == ordAndId.id) { docOrds.add(ordAndId.ord); } else { if (!docOrds.isEmpty()) { assertThat((long) singleOrds.getOrd(docId), equalTo(docOrds.get(0))); docs.setDocument(docId); final int numOrds = docs.cardinality(); assertThat(numOrds, equalTo(docOrds.size())); for (int i = 0; i < numOrds; i++) { assertThat(docs.nextOrd(), equalTo(docOrds.get(i))); } final long[] array = new long[docOrds.size()]; for (int i = 0; i < array.length; i++) { array[i] = docOrds.get(i); } assertIter(docs, docId, array); } for (int i = docId + 1; i < ordAndId.id; i++) { assertThat((long) singleOrds.getOrd(i), equalTo(RandomAccessOrds.NO_MORE_ORDS)); } docId = ordAndId.id; docOrds.clear(); docOrds.add(ordAndId.ord); } } } public static class OrdAndId { long ord; final int id; public OrdAndId(long ord, int id) { this.ord = ord; this.id = id; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + id; result = prime * result + (int) ord; return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } OrdAndId other = (OrdAndId) obj; if (id != other.id) { return false; } if (ord != other.ord) { return false; } return true; } } @Test public void testOrdinals() throws Exception { int maxDoc = 7; long maxOrds = 32; OrdinalsBuilder builder = new OrdinalsBuilder(maxDoc); builder.nextOrdinal(); // 0 builder.addDoc(1).addDoc(4).addDoc(5).addDoc(6); builder.nextOrdinal(); // 1 builder.addDoc(0).addDoc(5).addDoc(6); builder.nextOrdinal(); // 3 builder.addDoc(2).addDoc(4).addDoc(5).addDoc(6); builder.nextOrdinal(); // 3 builder.addDoc(0).addDoc(4).addDoc(5).addDoc(6); builder.nextOrdinal(); // 4 builder.addDoc(4).addDoc(5).addDoc(6); builder.nextOrdinal(); // 5 builder.addDoc(4).addDoc(5).addDoc(6); while (builder.getValueCount() < maxOrds) { builder.nextOrdinal(); builder.addDoc(5).addDoc(6); } long[][] ordinalPlan = new long[][]{ {1, 3}, {0}, {2}, {}, {0, 2, 3, 4, 5}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31} }; Ordinals ordinals = creationMultiOrdinals(builder); RandomAccessOrds docs = ordinals.ordinals(); assertEquals(docs, ordinalPlan); } protected static void assertIter(RandomAccessOrds docs, int docId, long... expectedOrdinals) { docs.setDocument(docId); assertThat(docs.cardinality(), equalTo(expectedOrdinals.length)); for (long expectedOrdinal : expectedOrdinals) { assertThat(docs.nextOrd(), equalTo(expectedOrdinal)); } } @Test public void testMultiValuesDocsWithOverlappingStorageArrays() throws Exception { int maxDoc = 7; long maxOrds = 15; OrdinalsBuilder builder = new OrdinalsBuilder(maxDoc); for (int i = 0; i < maxOrds; i++) { builder.nextOrdinal(); if (i < 10) { builder.addDoc(0); } builder.addDoc(1); if (i == 0) { builder.addDoc(2); } if (i < 5) { builder.addDoc(3); } if (i < 6) { builder.addDoc(4); } if (i == 1) { builder.addDoc(5); } if (i < 10) { builder.addDoc(6); } } long[][] ordinalPlan = new long[][]{ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, {0,1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}, {0}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4, 5}, {1}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} }; Ordinals ordinals = new MultiOrdinals(builder, PackedInts.FASTEST); RandomAccessOrds docs = ordinals.ordinals(); assertEquals(docs, ordinalPlan); } private void assertEquals(RandomAccessOrds docs, long[][] ordinalPlan) { long maxOrd = 0; for (int doc = 0; doc < ordinalPlan.length; ++doc) { if (ordinalPlan[doc].length > 0) { maxOrd = Math.max(maxOrd, 1 + ordinalPlan[doc][ordinalPlan[doc].length - 1]); } } assertThat(docs.getValueCount(), equalTo(maxOrd)); assertThat(FieldData.isMultiValued(docs), equalTo(true)); for (int doc = 0; doc < ordinalPlan.length; ++doc) { long[] ords = ordinalPlan[doc]; docs.setDocument(doc); assertThat(docs.cardinality(), equalTo(ords.length)); for (int i = 0; i < ords.length; ++i) { assertThat(docs.ordAt(i), equalTo(ords[i])); } } } }