/* * 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.SortedDocValues; import org.apache.lucene.index.SortedSetDocValues; import org.apache.lucene.util.packed.PackedInts; import org.elasticsearch.index.fielddata.FieldData; import org.elasticsearch.search.MultiValueMode; import org.elasticsearch.test.ESTestCase; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Random; import java.util.Set; import static org.hamcrest.Matchers.equalTo; public class MultiOrdinalsTests extends ESTestCase { protected Ordinals creationMultiOrdinals(OrdinalsBuilder builder) { return builder.build(); } public void testRandomValues() throws IOException { Random random = random(); 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); SortedSetDocValues 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()) { assertTrue(singleOrds.advanceExact(docId)); assertThat((long) singleOrds.ordValue(), equalTo(docOrds.get(0))); assertTrue(docs.advanceExact(docId)); for (Long ord : docOrds) { assertThat(docs.nextOrd(), equalTo(ord)); } assertEquals(SortedSetDocValues.NO_MORE_ORDS, docs.nextOrd()); } for (int i = docId + 1; i < ordAndId.id; i++) { assertFalse(singleOrds.advanceExact(i)); assertFalse(docs.advanceExact(i)); } 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; } } 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); SortedSetDocValues docs = ordinals.ordinals(); assertEquals(docs, ordinalPlan); } 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); SortedSetDocValues docs = ordinals.ordinals(); assertEquals(docs, ordinalPlan); } private void assertEquals(SortedSetDocValues docs, long[][] ordinalPlan) throws IOException { 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]; assertEquals(ords.length > 0, docs.advanceExact(doc)); if (ords.length > 0) { for (long ord : ords) { assertThat(docs.nextOrd(), equalTo(ord)); } assertThat(docs.nextOrd(), equalTo(SortedSetDocValues.NO_MORE_ORDS)); } } } }