diff --git a/docs/modules/gigamap/pages/indexing/bitmap/index.adoc b/docs/modules/gigamap/pages/indexing/bitmap/index.adoc index 8813fae6..f3afa45a 100644 --- a/docs/modules/gigamap/pages/indexing/bitmap/index.adoc +++ b/docs/modules/gigamap/pages/indexing/bitmap/index.adoc @@ -52,6 +52,12 @@ Here's a list of all predefined indexers used for low cardinality: |IndexerLocalDateTime |java.time.LocalDateTime +|IndexerInstant +|java.time.Instant + +|IndexerZonedDateTime +|java.time.ZonedDateTime + |IndexerYearMonth |java.time.YearMonth diff --git a/gigamap/gigamap/src/main/java/org/eclipse/store/gigamap/types/IndexerInstant.java b/gigamap/gigamap/src/main/java/org/eclipse/store/gigamap/types/IndexerInstant.java new file mode 100644 index 00000000..57d335f2 --- /dev/null +++ b/gigamap/gigamap/src/main/java/org/eclipse/store/gigamap/types/IndexerInstant.java @@ -0,0 +1,479 @@ +package org.eclipse.store.gigamap.types; + +/*- + * #%L + * EclipseStore GigaMap + * %% + * Copyright (C) 2023 - 2025 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.chrono.IsoChronology; +import java.util.function.IntPredicate; + +/** + * Indexing logic for {@link Instant} keys. + *

+ * The Instant is decomposed into UTC date-time components (year, month, day, hour, minute, second) + * for indexing. This enables component-level queries ({@code isYear()}, {@code isMonth()}, etc.) + * as well as range queries ({@code before()}, {@code after()}, {@code between()}). + *

+ * Nanosecond precision is not included in the index. Two Instants that differ only in their + * nanosecond component will be treated as the same key. + * + * @param the entity type + * + * @see IndexerDateTime + */ +public interface IndexerInstant extends IndexerDateTime +{ + + /** + * Abstract base class for an {@link Instant} key {@link Indexer}. + * + * @param the entity type + */ + public abstract class Abstract extends HashingCompositeIndexer.AbstractSingleValueFixedSize implements IndexerInstant + { + private final static int YEAR_INDEX = 0; + private final static int MONTH_INDEX = 1; + private final static int DAY_INDEX = 2; + private final static int HOUR_INDEX = 3; + private final static int MINUTE_INDEX = 4; + private final static int SECOND_INDEX = 5; + + protected Abstract() + { + super(); + } + + protected abstract Instant getInstant(E entity); + + @Override + protected Instant getValue(final E entity) + { + return this.getInstant(entity); + } + + @Override + protected int compositeSize() + { + return 6; + } + + @Override + protected void fillCarrier(final Instant value, final Object[] carrier) + { + final OffsetDateTime utc = value.atOffset(ZoneOffset.UTC); + carrier[YEAR_INDEX] = utc.getYear(); + carrier[MONTH_INDEX] = utc.getMonthValue(); + carrier[DAY_INDEX] = utc.getDayOfMonth(); + carrier[HOUR_INDEX] = utc.getHour(); + carrier[MINUTE_INDEX] = utc.getMinute(); + carrier[SECOND_INDEX] = utc.getSecond(); + } + + @Override + public final Condition is(final Instant other) + { + return other == null + ? this.isNull() + : this.isValue(other) + ; + } + + @Override + public final Condition isDateTime( + final int year, final int month, final int day, + final int hour, final int minute, final int second + ) + { + Validators.validateMonth(month); + Validators.validateDay(day); + Validators.validateHour(hour); + Validators.validateMinute(minute); + Validators.validateSecond(second); + + return this.isValue( + OffsetDateTime.of(year, month, day, hour, minute, second, 0, ZoneOffset.UTC).toInstant() + ); + } + + @Override + public Condition isYear(final IntPredicate yearPredicate) + { + return this.is(new FieldPredicate(YEAR_INDEX, yearPredicate)); + } + + @Override + public Condition isMonth(final IntPredicate monthPredicate) + { + return this.is(new FieldPredicate(MONTH_INDEX, monthPredicate)); + } + + @Override + public Condition isDay(final IntPredicate dayPredicate) + { + return this.is(new FieldPredicate(DAY_INDEX, dayPredicate)); + } + + @Override + public Condition isHour(final IntPredicate hourPredicate) + { + return this.is(new FieldPredicate(HOUR_INDEX, hourPredicate)); + } + + @Override + public Condition isMinute(final IntPredicate minutePredicate) + { + return this.is(new FieldPredicate(MINUTE_INDEX, minutePredicate)); + } + + @Override + public Condition isSecond(final IntPredicate secondPredicate) + { + return this.is(new FieldPredicate(SECOND_INDEX, secondPredicate)); + } + + @SuppressWarnings("unchecked") + @Override + public final Condition isDate(final int year, final int month, final int day) + { + Validators.validateMonth(month); + Validators.validateDay(day); + + return (Condition)this.isYear(year).and(this.isMonth(month)).and(this.isDay(day)); + } + + @Override + public final Condition isYear(final int year) + { + return this.is(new EqualsFieldPredicate(YEAR_INDEX, year)); + } + + @Override + public final Condition isMonth(final int month) + { + Validators.validateMonth(month); + + return this.is(new EqualsFieldPredicate(MONTH_INDEX, month)); + } + + @Override + public final Condition isDay(final int day) + { + Validators.validateDay(day); + + return this.is(new EqualsFieldPredicate(DAY_INDEX, day)); + } + + @Override + public final Condition isLeapYear() + { + return this.is(new FieldPredicate(YEAR_INDEX, IsoChronology.INSTANCE::isLeapYear)); + } + + @SuppressWarnings("unchecked") + @Override + public final Condition isTime(final int hour, final int minute, final int second) + { + Validators.validateHour(hour); + Validators.validateMinute(minute); + Validators.validateSecond(second); + + return (Condition)this.isHour(hour).and(this.isMinute(minute)).and(this.isSecond(second)); + } + + @Override + public final Condition isHour(final int hour) + { + Validators.validateHour(hour); + + return this.is(new EqualsFieldPredicate(HOUR_INDEX, hour)); + } + + @Override + public final Condition isMinute(final int minute) + { + Validators.validateMinute(minute); + + return this.is(new EqualsFieldPredicate(MINUTE_INDEX, minute)); + } + + @Override + public final Condition isSecond(final int second) + { + Validators.validateSecond(second); + + return this.is(new EqualsFieldPredicate(SECOND_INDEX, second)); + } + + @SuppressWarnings("unchecked") + @Override + public final Condition before(final Instant boundExclusive) + { + if(boundExclusive == null) + { + throw new IllegalArgumentException("boundExclusive cannot be null"); + } + + final OffsetDateTime bound = boundExclusive.atOffset(ZoneOffset.UTC); + + return (Condition)this.is( + new FieldPredicate(YEAR_INDEX, year -> year < bound.getYear()) + ).or( + this.is( + new EqualsUntilPredicate(YEAR_INDEX, boundExclusive) + ).and( + this.is(new FieldPredicate(MONTH_INDEX, month -> month < bound.getMonthValue())) + ) + ).or( + this.is( + new EqualsUntilPredicate(MONTH_INDEX, boundExclusive) + ).and( + this.is(new FieldPredicate(DAY_INDEX, day -> day < bound.getDayOfMonth())) + ) + ).or( + this.is( + new EqualsUntilPredicate(DAY_INDEX, boundExclusive) + ).and( + this.is(new FieldPredicate(HOUR_INDEX, hour -> hour < bound.getHour())) + ) + ).or( + this.is( + new EqualsUntilPredicate(HOUR_INDEX, boundExclusive) + ).and( + this.is(new FieldPredicate(MINUTE_INDEX, minute -> minute < bound.getMinute())) + ) + ).or( + this.is( + new EqualsUntilPredicate(MINUTE_INDEX, boundExclusive) + ).and( + this.is(new FieldPredicate(SECOND_INDEX, second -> second < bound.getSecond())) + ) + ); + } + + @SuppressWarnings("unchecked") + @Override + public final Condition beforeEqual(final Instant boundInclusive) + { + if(boundInclusive == null) + { + throw new IllegalArgumentException("boundInclusive cannot be null"); + } + + return (Condition)this.is(boundInclusive).or(this.before(boundInclusive)); + } + + @SuppressWarnings("unchecked") + @Override + public final Condition after(final Instant boundExclusive) + { + if(boundExclusive == null) + { + throw new IllegalArgumentException("boundExclusive cannot be null"); + } + + final OffsetDateTime bound = boundExclusive.atOffset(ZoneOffset.UTC); + + return (Condition)this.is( + new FieldPredicate(YEAR_INDEX, year -> year > bound.getYear()) + ).or( + this.is( + new EqualsUntilPredicate(YEAR_INDEX, boundExclusive) + ).and( + this.is(new FieldPredicate(MONTH_INDEX, month -> month > bound.getMonthValue())) + ) + ).or( + this.is( + new EqualsUntilPredicate(MONTH_INDEX, boundExclusive) + ).and( + this.is(new FieldPredicate(DAY_INDEX, day -> day > bound.getDayOfMonth())) + ) + ).or( + this.is( + new EqualsUntilPredicate(DAY_INDEX, boundExclusive) + ).and( + this.is(new FieldPredicate(HOUR_INDEX, hour -> hour > bound.getHour())) + ) + ).or( + this.is( + new EqualsUntilPredicate(HOUR_INDEX, boundExclusive) + ).and( + this.is(new FieldPredicate(MINUTE_INDEX, minute -> minute > bound.getMinute())) + ) + ).or( + this.is( + new EqualsUntilPredicate(MINUTE_INDEX, boundExclusive) + ).and( + this.is(new FieldPredicate(SECOND_INDEX, second -> second > bound.getSecond())) + ) + ); + } + + @SuppressWarnings("unchecked") + @Override + public final Condition afterEqual(final Instant boundInclusive) + { + if(boundInclusive == null) + { + throw new IllegalArgumentException("boundInclusive cannot be null"); + } + + return (Condition)this.is(boundInclusive).or(this.after(boundInclusive)); + } + + @SuppressWarnings("unchecked") + @Override + public final Condition between(final Instant startInclusive, final Instant endInclusive) + { + if(startInclusive == null) + { + throw new IllegalArgumentException("startInclusive cannot be null"); + } + if(endInclusive == null) + { + throw new IllegalArgumentException("endInclusive cannot be null"); + } + + return (Condition)this.afterEqual(startInclusive).and(this.beforeEqual(endInclusive)); + } + + + static class FieldPredicate implements CompositePredicate + { + final int subKeyPosition; + final IntPredicate predicate; + + FieldPredicate(final int subKeyPosition, final IntPredicate predicate) + { + this.subKeyPosition = subKeyPosition; + this.predicate = predicate; + } + + @Override + public boolean setSubKeyPosition(final int subKeyPosition) + { + return subKeyPosition == this.subKeyPosition; + } + + @Override + public boolean test(final Object[] keys) + { + return this.test(this.subKeyPosition, keys[this.subKeyPosition]); + } + + @Override + public boolean test(final int subKeyPosition, final Object subKey) + { + return subKeyPosition == this.subKeyPosition && subKey instanceof Integer && this.predicate.test((Integer)subKey); + } + + } + + + static class EqualsFieldPredicate implements CompositePredicate + { + final int subKeyPosition; + final int value; + + EqualsFieldPredicate(final int subKeyPosition, final int value) + { + this.subKeyPosition = subKeyPosition; + this.value = value; + } + + @Override + public boolean setSubKeyPosition(final int subKeyPosition) + { + return subKeyPosition == this.subKeyPosition; + } + + @Override + public boolean test(final Object[] keys) + { + return this.test(this.subKeyPosition, keys[this.subKeyPosition]); + } + + @Override + public boolean test(final int subKeyPosition, final Object subKey) + { + return subKeyPosition == this.subKeyPosition && subKey instanceof Integer && this.value == (Integer)subKey; + } + + } + + + static class EqualsUntilPredicate implements CompositePredicate + { + final int maxSubKeyPosition; + final Instant other; + + EqualsUntilPredicate(final int maxSubKeyPosition, final Instant other) + { + this.maxSubKeyPosition = maxSubKeyPosition; + this.other = other; + } + + @Override + public boolean setSubKeyPosition(final int subKeyPosition) + { + return subKeyPosition <= this.maxSubKeyPosition; + } + + @Override + public boolean test(final Object[] keys) + { + for(int i = 0; i < this.maxSubKeyPosition; i++) + { + if(!this.test(i, keys[i])) + { + return false; + } + } + return true; + } + + @Override + public boolean test(final int subKeyPosition, final Object subKey) + { + return subKeyPosition <= this.maxSubKeyPosition && subKey instanceof Integer && get(this.other, subKeyPosition) == (Integer)subKey; + } + + static int get(final Instant instant, final int subKeyPosition) + { + final OffsetDateTime utc = instant.atOffset(ZoneOffset.UTC); + switch(subKeyPosition) + { + case YEAR_INDEX: + return utc.getYear(); + case MONTH_INDEX: + return utc.getMonthValue(); + case DAY_INDEX: + return utc.getDayOfMonth(); + case HOUR_INDEX: + return utc.getHour(); + case MINUTE_INDEX: + return utc.getMinute(); + case SECOND_INDEX: + return utc.getSecond(); + default: + throw new IllegalArgumentException("Invalid subKeyPosition: " + subKeyPosition); + } + } + + } + + } + +} diff --git a/gigamap/gigamap/src/main/java/org/eclipse/store/gigamap/types/IndexerZonedDateTime.java b/gigamap/gigamap/src/main/java/org/eclipse/store/gigamap/types/IndexerZonedDateTime.java new file mode 100644 index 00000000..c4571337 --- /dev/null +++ b/gigamap/gigamap/src/main/java/org/eclipse/store/gigamap/types/IndexerZonedDateTime.java @@ -0,0 +1,487 @@ +package org.eclipse.store.gigamap.types; + +/*- + * #%L + * EclipseStore GigaMap + * %% + * Copyright (C) 2023 - 2025 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.chrono.IsoChronology; +import java.util.function.IntPredicate; + +/** + * Indexing logic for {@link ZonedDateTime} keys. + *

+ * The ZonedDateTime is converted to UTC and decomposed into date-time components + * (year, month, day, hour, minute, second) for indexing. This enables component-level queries + * ({@code isYear()}, {@code isMonth()}, etc.) as well as range queries + * ({@code before()}, {@code after()}, {@code between()}). + *

+ * All queries operate on the absolute point in time (UTC), not on the local wall-clock time + * of the original zone. + *

+ * Nanosecond precision is not included in the index. Two ZonedDateTimes that differ only in their + * nanosecond component will be treated as the same key. + * + * @param the entity type + * + * @see IndexerDateTime + * @see IndexerInstant + */ +public interface IndexerZonedDateTime extends IndexerDateTime +{ + + /** + * Abstract base class for a {@link ZonedDateTime} key {@link Indexer}. + * + * @param the entity type + */ + public abstract class Abstract extends HashingCompositeIndexer.AbstractSingleValueFixedSize implements IndexerZonedDateTime + { + private final static int YEAR_INDEX = 0; + private final static int MONTH_INDEX = 1; + private final static int DAY_INDEX = 2; + private final static int HOUR_INDEX = 3; + private final static int MINUTE_INDEX = 4; + private final static int SECOND_INDEX = 5; + + protected Abstract() + { + super(); + } + + protected abstract ZonedDateTime getZonedDateTime(E entity); + + @Override + protected ZonedDateTime getValue(final E entity) + { + return this.getZonedDateTime(entity); + } + + @Override + protected int compositeSize() + { + return 6; + } + + @Override + protected void fillCarrier(final ZonedDateTime value, final Object[] carrier) + { + final OffsetDateTime utc = value.toInstant().atOffset(ZoneOffset.UTC); + carrier[YEAR_INDEX] = utc.getYear(); + carrier[MONTH_INDEX] = utc.getMonthValue(); + carrier[DAY_INDEX] = utc.getDayOfMonth(); + carrier[HOUR_INDEX] = utc.getHour(); + carrier[MINUTE_INDEX] = utc.getMinute(); + carrier[SECOND_INDEX] = utc.getSecond(); + } + + @Override + public final Condition is(final ZonedDateTime other) + { + return other == null + ? this.isNull() + : this.isValue(other) + ; + } + + @Override + public final Condition isDateTime( + final int year, final int month, final int day, + final int hour, final int minute, final int second + ) + { + Validators.validateMonth(month); + Validators.validateDay(day); + Validators.validateHour(hour); + Validators.validateMinute(minute); + Validators.validateSecond(second); + + return this.isValue( + OffsetDateTime.of(year, month, day, hour, minute, second, 0, ZoneOffset.UTC).toZonedDateTime() + ); + } + + @Override + public Condition isYear(final IntPredicate yearPredicate) + { + return this.is(new FieldPredicate(YEAR_INDEX, yearPredicate)); + } + + @Override + public Condition isMonth(final IntPredicate monthPredicate) + { + return this.is(new FieldPredicate(MONTH_INDEX, monthPredicate)); + } + + @Override + public Condition isDay(final IntPredicate dayPredicate) + { + return this.is(new FieldPredicate(DAY_INDEX, dayPredicate)); + } + + @Override + public Condition isHour(final IntPredicate hourPredicate) + { + return this.is(new FieldPredicate(HOUR_INDEX, hourPredicate)); + } + + @Override + public Condition isMinute(final IntPredicate minutePredicate) + { + return this.is(new FieldPredicate(MINUTE_INDEX, minutePredicate)); + } + + @Override + public Condition isSecond(final IntPredicate secondPredicate) + { + return this.is(new FieldPredicate(SECOND_INDEX, secondPredicate)); + } + + @SuppressWarnings("unchecked") + @Override + public final Condition isDate(final int year, final int month, final int day) + { + Validators.validateMonth(month); + Validators.validateDay(day); + + return (Condition)this.isYear(year).and(this.isMonth(month)).and(this.isDay(day)); + } + + @Override + public final Condition isYear(final int year) + { + return this.is(new EqualsFieldPredicate(YEAR_INDEX, year)); + } + + @Override + public final Condition isMonth(final int month) + { + Validators.validateMonth(month); + + return this.is(new EqualsFieldPredicate(MONTH_INDEX, month)); + } + + @Override + public final Condition isDay(final int day) + { + Validators.validateDay(day); + + return this.is(new EqualsFieldPredicate(DAY_INDEX, day)); + } + + @Override + public final Condition isLeapYear() + { + return this.is(new FieldPredicate(YEAR_INDEX, IsoChronology.INSTANCE::isLeapYear)); + } + + @SuppressWarnings("unchecked") + @Override + public final Condition isTime(final int hour, final int minute, final int second) + { + Validators.validateHour(hour); + Validators.validateMinute(minute); + Validators.validateSecond(second); + + return (Condition)this.isHour(hour).and(this.isMinute(minute)).and(this.isSecond(second)); + } + + @Override + public final Condition isHour(final int hour) + { + Validators.validateHour(hour); + + return this.is(new EqualsFieldPredicate(HOUR_INDEX, hour)); + } + + @Override + public final Condition isMinute(final int minute) + { + Validators.validateMinute(minute); + + return this.is(new EqualsFieldPredicate(MINUTE_INDEX, minute)); + } + + @Override + public final Condition isSecond(final int second) + { + Validators.validateSecond(second); + + return this.is(new EqualsFieldPredicate(SECOND_INDEX, second)); + } + + @SuppressWarnings("unchecked") + @Override + public final Condition before(final ZonedDateTime boundExclusive) + { + if(boundExclusive == null) + { + throw new IllegalArgumentException("boundExclusive cannot be null"); + } + + final Instant boundInstant = boundExclusive.toInstant(); + final OffsetDateTime bound = boundInstant.atOffset(ZoneOffset.UTC); + + return (Condition)this.is( + new FieldPredicate(YEAR_INDEX, year -> year < bound.getYear()) + ).or( + this.is( + new EqualsUntilPredicate(YEAR_INDEX, boundInstant) + ).and( + this.is(new FieldPredicate(MONTH_INDEX, month -> month < bound.getMonthValue())) + ) + ).or( + this.is( + new EqualsUntilPredicate(MONTH_INDEX, boundInstant) + ).and( + this.is(new FieldPredicate(DAY_INDEX, day -> day < bound.getDayOfMonth())) + ) + ).or( + this.is( + new EqualsUntilPredicate(DAY_INDEX, boundInstant) + ).and( + this.is(new FieldPredicate(HOUR_INDEX, hour -> hour < bound.getHour())) + ) + ).or( + this.is( + new EqualsUntilPredicate(HOUR_INDEX, boundInstant) + ).and( + this.is(new FieldPredicate(MINUTE_INDEX, minute -> minute < bound.getMinute())) + ) + ).or( + this.is( + new EqualsUntilPredicate(MINUTE_INDEX, boundInstant) + ).and( + this.is(new FieldPredicate(SECOND_INDEX, second -> second < bound.getSecond())) + ) + ); + } + + @SuppressWarnings("unchecked") + @Override + public final Condition beforeEqual(final ZonedDateTime boundInclusive) + { + if(boundInclusive == null) + { + throw new IllegalArgumentException("boundInclusive cannot be null"); + } + + return (Condition)this.is(boundInclusive).or(this.before(boundInclusive)); + } + + @SuppressWarnings("unchecked") + @Override + public final Condition after(final ZonedDateTime boundExclusive) + { + if(boundExclusive == null) + { + throw new IllegalArgumentException("boundExclusive cannot be null"); + } + + final Instant boundInstant = boundExclusive.toInstant(); + final OffsetDateTime bound = boundInstant.atOffset(ZoneOffset.UTC); + + return (Condition)this.is( + new FieldPredicate(YEAR_INDEX, year -> year > bound.getYear()) + ).or( + this.is( + new EqualsUntilPredicate(YEAR_INDEX, boundInstant) + ).and( + this.is(new FieldPredicate(MONTH_INDEX, month -> month > bound.getMonthValue())) + ) + ).or( + this.is( + new EqualsUntilPredicate(MONTH_INDEX, boundInstant) + ).and( + this.is(new FieldPredicate(DAY_INDEX, day -> day > bound.getDayOfMonth())) + ) + ).or( + this.is( + new EqualsUntilPredicate(DAY_INDEX, boundInstant) + ).and( + this.is(new FieldPredicate(HOUR_INDEX, hour -> hour > bound.getHour())) + ) + ).or( + this.is( + new EqualsUntilPredicate(HOUR_INDEX, boundInstant) + ).and( + this.is(new FieldPredicate(MINUTE_INDEX, minute -> minute > bound.getMinute())) + ) + ).or( + this.is( + new EqualsUntilPredicate(MINUTE_INDEX, boundInstant) + ).and( + this.is(new FieldPredicate(SECOND_INDEX, second -> second > bound.getSecond())) + ) + ); + } + + @SuppressWarnings("unchecked") + @Override + public final Condition afterEqual(final ZonedDateTime boundInclusive) + { + if(boundInclusive == null) + { + throw new IllegalArgumentException("boundInclusive cannot be null"); + } + + return (Condition)this.is(boundInclusive).or(this.after(boundInclusive)); + } + + @SuppressWarnings("unchecked") + @Override + public final Condition between(final ZonedDateTime startInclusive, final ZonedDateTime endInclusive) + { + if(startInclusive == null) + { + throw new IllegalArgumentException("startInclusive cannot be null"); + } + if(endInclusive == null) + { + throw new IllegalArgumentException("endInclusive cannot be null"); + } + + return (Condition)this.afterEqual(startInclusive).and(this.beforeEqual(endInclusive)); + } + + + static class FieldPredicate implements CompositePredicate + { + final int subKeyPosition; + final IntPredicate predicate; + + FieldPredicate(final int subKeyPosition, final IntPredicate predicate) + { + this.subKeyPosition = subKeyPosition; + this.predicate = predicate; + } + + @Override + public boolean setSubKeyPosition(final int subKeyPosition) + { + return subKeyPosition == this.subKeyPosition; + } + + @Override + public boolean test(final Object[] keys) + { + return this.test(this.subKeyPosition, keys[this.subKeyPosition]); + } + + @Override + public boolean test(final int subKeyPosition, final Object subKey) + { + return subKeyPosition == this.subKeyPosition && subKey instanceof Integer && this.predicate.test((Integer)subKey); + } + + } + + + static class EqualsFieldPredicate implements CompositePredicate + { + final int subKeyPosition; + final int value; + + EqualsFieldPredicate(final int subKeyPosition, final int value) + { + this.subKeyPosition = subKeyPosition; + this.value = value; + } + + @Override + public boolean setSubKeyPosition(final int subKeyPosition) + { + return subKeyPosition == this.subKeyPosition; + } + + @Override + public boolean test(final Object[] keys) + { + return this.test(this.subKeyPosition, keys[this.subKeyPosition]); + } + + @Override + public boolean test(final int subKeyPosition, final Object subKey) + { + return subKeyPosition == this.subKeyPosition && subKey instanceof Integer && this.value == (Integer)subKey; + } + + } + + + static class EqualsUntilPredicate implements CompositePredicate + { + final int maxSubKeyPosition; + final Instant other; + + EqualsUntilPredicate(final int maxSubKeyPosition, final Instant other) + { + this.maxSubKeyPosition = maxSubKeyPosition; + this.other = other; + } + + @Override + public boolean setSubKeyPosition(final int subKeyPosition) + { + return subKeyPosition <= this.maxSubKeyPosition; + } + + @Override + public boolean test(final Object[] keys) + { + for(int i = 0; i < this.maxSubKeyPosition; i++) + { + if(!this.test(i, keys[i])) + { + return false; + } + } + return true; + } + + @Override + public boolean test(final int subKeyPosition, final Object subKey) + { + return subKeyPosition <= this.maxSubKeyPosition && subKey instanceof Integer && get(this.other, subKeyPosition) == (Integer)subKey; + } + + static int get(final Instant instant, final int subKeyPosition) + { + final OffsetDateTime utc = instant.atOffset(ZoneOffset.UTC); + switch(subKeyPosition) + { + case YEAR_INDEX: + return utc.getYear(); + case MONTH_INDEX: + return utc.getMonthValue(); + case DAY_INDEX: + return utc.getDayOfMonth(); + case HOUR_INDEX: + return utc.getHour(); + case MINUTE_INDEX: + return utc.getMinute(); + case SECOND_INDEX: + return utc.getSecond(); + default: + throw new IllegalArgumentException("Invalid subKeyPosition: " + subKeyPosition); + } + } + + } + + } + +} diff --git a/gigamap/gigamap/src/test/java/org/eclipse/store/gigamap/indexer/InstantIndexTest.java b/gigamap/gigamap/src/test/java/org/eclipse/store/gigamap/indexer/InstantIndexTest.java new file mode 100644 index 00000000..045ef854 --- /dev/null +++ b/gigamap/gigamap/src/test/java/org/eclipse/store/gigamap/indexer/InstantIndexTest.java @@ -0,0 +1,434 @@ +package org.eclipse.store.gigamap.indexer; + +/*- + * #%L + * EclipseStore GigaMap + * %% + * Copyright (C) 2023 - 2025 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import org.eclipse.store.gigamap.types.BitmapIndices; +import org.eclipse.store.gigamap.types.GigaMap; +import org.eclipse.store.gigamap.types.IndexerInstant; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Path; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.*; + +public class InstantIndexTest +{ + @TempDir + Path tempDir; + + private InstantPersonIndex instantPersonIndex = new InstantPersonIndex(); + + @Test + void nullTests() + { + GigaMap map = prepageGigaMap(); + + assertEquals(1, map.query(instantPersonIndex.is((Instant) null)).count()); + + assertThrows(IllegalArgumentException.class, () -> map.query(instantPersonIndex.before(null)).count()); + assertThrows(IllegalArgumentException.class, () -> map.query(instantPersonIndex.beforeEqual(null)).count()); + assertThrows(IllegalArgumentException.class, () -> map.query(instantPersonIndex.after(null)).count()); + assertThrows(IllegalArgumentException.class, () -> map.query(instantPersonIndex.afterEqual(null)).count()); + + assertThrows(IllegalArgumentException.class, () -> map.query(instantPersonIndex.between(null, null)).count()); + assertThrows(IllegalArgumentException.class, () -> map.query(instantPersonIndex.between(toInstant(2021, 1, 1, 12, 0, 0), null)).count()); + assertThrows(IllegalArgumentException.class, () -> map.query(instantPersonIndex.between(null, toInstant(2021, 1, 1, 12, 0, 0))).count()); + + } + + @Test + void between() + { + GigaMap map = prepageGigaMap(); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(map, tempDir)) { + long count = map.query(instantPersonIndex.between(toInstant(2021, 1, 1, 12, 0, 0), toInstant(2021, 1, 1, 14, 0, 0))).count(); + assertEquals(3, count); + long count1 = map.query(instantPersonIndex.between(toInstant(2021, 1, 1, 12, 0, 0), toInstant(2021, 1, 1, 14, 0, 0))).count(); + assertEquals(3, count1); + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + GigaMap newMap = (GigaMap) manager.root(); + long count = newMap.query(instantPersonIndex.between(toInstant(2021, 1, 1, 12, 0, 0), toInstant(2021, 1, 1, 14, 0, 0))).count(); + assertEquals(3, count); + long count1 = newMap.query(instantPersonIndex.between(toInstant(2021, 1, 1, 12, 0, 0), toInstant(2021, 1, 1, 14, 0, 0))).count(); + assertEquals(3, count1); + long count2 = newMap.query(instantPersonIndex.between(toInstant(2021, 1, 1, 13, 0, 0), toInstant(2021, 1, 1, 14, 0, 0))).count(); + assertEquals(2, count2); + } + } + + @Test + void after() + { + GigaMap map = prepageGigaMap(); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(map, tempDir)) { + long count = map.query(instantPersonIndex.after(toInstant(2021, 1, 1, 12, 0, 0))).count(); + assertEquals(9, count); + long count1 = map.query(instantPersonIndex.after(toInstant(2021, 1, 1, 13, 0, 0))).count(); + assertEquals(8, count1); + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + GigaMap newMap = (GigaMap) manager.root(); + long count = newMap.query(instantPersonIndex.after(toInstant(2021, 1, 1, 12, 0, 0))).count(); + assertEquals(9, count); + long count1 = newMap.query(instantPersonIndex.after(toInstant(2021, 1, 1, 13, 0, 0))).count(); + assertEquals(8, count1); + } + } + + @Test + void before() + { + GigaMap map = prepageGigaMap(); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(map, tempDir)) { + long count = map.query(instantPersonIndex.before(toInstant(2021, 1, 1, 12, 0, 1))).count(); + assertEquals(1, count); + long count1 = map.query(instantPersonIndex.before(toInstant(2021, 1, 1, 13, 0, 0))).count(); + assertEquals(1, count1); + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + GigaMap newMap = (GigaMap) manager.root(); + long count = newMap.query(instantPersonIndex.before(toInstant(2021, 1, 1, 12, 0, 1))).count(); + assertEquals(1, count); + long count1 = newMap.query(instantPersonIndex.before(toInstant(2021, 1, 1, 13, 0, 0))).count(); + assertEquals(1, count1); + } + } + + @Test + void isSecond() + { + GigaMap map = prepageGigaMap(); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(map, tempDir)) { + long count = map.query(instantPersonIndex.isSecond(0)).count(); + assertEquals(10, count); + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + GigaMap newMap = (GigaMap) manager.root(); + long count = newMap.query(instantPersonIndex.isSecond(0)).count(); + assertEquals(10, count); + } + } + + @Test + void isMinute() + { + GigaMap map = prepageGigaMap(); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(map, tempDir)) { + long count = map.query(instantPersonIndex.isMinute(0)).count(); + assertEquals(10, count); + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + GigaMap newMap = (GigaMap) manager.root(); + long count = newMap.query(instantPersonIndex.isMinute(0)).count(); + assertEquals(10, count); + } + } + + @Test + void isHour() + { + GigaMap map = prepageGigaMap(); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(map, tempDir)) { + long count = map.query(instantPersonIndex.isHour(12)).count(); + assertEquals(1, count); + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + GigaMap newMap = (GigaMap) manager.root(); + long count = newMap.query(instantPersonIndex.isHour(12)).count(); + assertEquals(1, count); + } + } + + @Test + void isTime() + { + GigaMap map = prepageGigaMap(); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(map, tempDir)) { + long count = map.query(instantPersonIndex.isTime(12, 0, 0)).count(); + assertEquals(1, count); + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + GigaMap newMap = (GigaMap) manager.root(); + long count = newMap.query(instantPersonIndex.isTime(12, 0, 0)).count(); + assertEquals(1, count); + } + } + + @Test + void isDay() + { + GigaMap map = prepageGigaMap(); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(map, tempDir)) { + long count = map.query(instantPersonIndex.isDay(1)).count(); + assertEquals(10, count); + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + GigaMap newMap = (GigaMap) manager.root(); + long count = newMap.query(instantPersonIndex.isDay(1)).count(); + assertEquals(10, count); + } + } + + @Test + void isMonth() + { + GigaMap map = prepageGigaMap(); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(map, tempDir)) { + long count = map.query(instantPersonIndex.isMonth(1)).count(); + assertEquals(10, count); + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + GigaMap newMap = (GigaMap) manager.root(); + long count = newMap.query(instantPersonIndex.isMonth(1)).count(); + assertEquals(10, count); + } + } + + @Test + void isYear() + { + GigaMap map = prepageGigaMap(); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(map, tempDir)) { + long count = map.query(instantPersonIndex.isYear(2021)).count(); + assertEquals(10, count); + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + GigaMap newMap = (GigaMap) manager.root(); + long count = newMap.query(instantPersonIndex.isYear(2021)).count(); + assertEquals(10, count); + } + } + + @Test + void isDate() + { + GigaMap map = prepageGigaMap(); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(map, tempDir)) { + long count = map.query(instantPersonIndex.isDate(2021, 1, 1)).count(); + assertEquals(10, count); + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + GigaMap newMap = (GigaMap) manager.root(); + long count = newMap.query(instantPersonIndex.isDate(2021, 1, 1)).count(); + assertEquals(10, count); + } + } + + @Test + void isDateTime() + { + GigaMap map = prepageGigaMap(); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(map, tempDir)) { + long count = map.query(instantPersonIndex.isDateTime(2021, 1, 1, 12, 0, 0)).count(); + assertEquals(1, count); + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + GigaMap newMap = (GigaMap) manager.root(); + long count = newMap.query(instantPersonIndex.isDateTime(2021, 1, 1, 12, 0, 0)).count(); + assertEquals(1, count); + } + + } + + @Test + void instantPersonTest() + { + GigaMap map = GigaMap.New(); + + BitmapIndices bitmap = map.index().bitmap(); + bitmap.add(instantPersonIndex); + + //generate some data + InstantPerson person1 = new InstantPerson("Alice", toInstant(2000, 1, 1, 0, 0, 0)); + InstantPerson person2 = new InstantPerson("Bob", toInstant(1990, 1, 1, 0, 0, 0)); + InstantPerson person3 = new InstantPerson("Charlie", toInstant(1980, 1, 1, 0, 0, 0)); + map.addAll(person1, person2, person3); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(map, tempDir)) { + map.query(instantPersonIndex.is(toInstant(2000, 1, 1, 0, 0, 0))).forEach(person -> assertEquals(toInstant(2000, 1, 1, 0, 0, 0), person.getTimestamp())); + map.query(instantPersonIndex.is(toInstant(1990, 1, 1, 0, 0, 0))).forEach(person -> assertEquals(toInstant(1990, 1, 1, 0, 0, 0), person.getTimestamp())); + + map.query(instantPersonIndex.is(toInstant(2000, 1, 1, 0, 0, 0))).and(instantPersonIndex.is(toInstant(1990, 1, 1, 0, 0, 0))) + .forEach(person -> fail("Should not be reached")); + + List personList = map.query(instantPersonIndex.is(toInstant(2000, 1, 1, 0, 0, 0))).or(instantPersonIndex.is(toInstant(1990, 1, 1, 0, 0, 0))) + .stream().collect(Collectors.toList()); + assertEquals(2, personList.size()); + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + GigaMap newMap = (GigaMap) manager.root(); + newMap.query(instantPersonIndex.is(toInstant(2000, 1, 1, 0, 0, 0))).forEach(person -> assertEquals(toInstant(2000, 1, 1, 0, 0, 0), person.getTimestamp())); + newMap.query(instantPersonIndex.is(toInstant(1990, 1, 1, 0, 0, 0))).forEach(person -> assertEquals(toInstant(1990, 1, 1, 0, 0, 0), person.getTimestamp())); + + newMap.query(instantPersonIndex.is(toInstant(2000, 1, 1, 0, 0, 0))).and(instantPersonIndex.is(toInstant(1990, 1, 1, 0, 0, 0))) + .forEach(person -> fail("Should not be reached")); + + List personList = newMap.query(instantPersonIndex.is(toInstant(2000, 1, 1, 0, 0, 0))).or(instantPersonIndex.is(toInstant(1990, 1, 1, 0, 0, 0))) + .stream().collect(Collectors.toList()); + assertEquals(2, personList.size()); + } + } + + @Test + void beforeEqualTest() + { + GigaMap map = GigaMap.New(); + + BitmapIndices bitmap = map.index().bitmap(); + bitmap.add(instantPersonIndex); + + //generate some data + InstantPerson person1 = new InstantPerson("Alice", toInstant(2000, 1, 1, 0, 0, 0)); + InstantPerson person2 = new InstantPerson("Bob", toInstant(1990, 1, 1, 0, 0, 0)); + InstantPerson person3 = new InstantPerson("Charlie", toInstant(1980, 1, 1, 0, 0, 0)); + map.addAll(person1, person2, person3); + + List list = map.query(instantPersonIndex.beforeEqual(toInstant(1990, 1, 1, 0, 0, 0))).toList(); + assertEquals(2, list.size()); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(map, tempDir)) { + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + GigaMap newMap = (GigaMap) manager.root(); + List list1 = newMap.query(instantPersonIndex.beforeEqual(toInstant(1990, 1, 1, 0, 0, 0))).toList(); + assertEquals(2, list1.size()); + list1.forEach(person -> assertNotEquals("Alice", person.name)); + } + } + + @Test + void afterEqualTest() + { + GigaMap map = GigaMap.New(); + + BitmapIndices bitmap = map.index().bitmap(); + bitmap.add(instantPersonIndex); + + //generate some data + InstantPerson person1 = new InstantPerson("Alice", toInstant(2000, 1, 1, 0, 0, 0)); + InstantPerson person2 = new InstantPerson("Bob", toInstant(1990, 1, 1, 0, 0, 0)); + InstantPerson person3 = new InstantPerson("Charlie", toInstant(1980, 1, 1, 0, 0, 0)); + map.addAll(person1, person2, person3); + + List list = map.query(instantPersonIndex.afterEqual(toInstant(1990, 1, 1, 0, 0, 0))).toList(); + assertEquals(2, list.size()); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(map, tempDir)) { + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + GigaMap newMap = (GigaMap) manager.root(); + List list1 = newMap.query(instantPersonIndex.afterEqual(toInstant(1990, 1, 1, 0, 0, 0))).toList(); + assertEquals(2, list1.size()); + list1.forEach(person -> assertNotEquals("Charlie", person.name)); + } + } + + private GigaMap prepageGigaMap() + { + GigaMap map = GigaMap.New(); + + BitmapIndices bitmap = map.index().bitmap(); + bitmap.add(instantPersonIndex); + + InstantPerson person1 = new InstantPerson("Alice", toInstant(2021, 1, 1, 12, 0, 0)); + InstantPerson person2 = new InstantPerson("Bob", toInstant(2021, 1, 1, 13, 0, 0)); + InstantPerson person3 = new InstantPerson("Charlie", toInstant(2021, 1, 1, 14, 0, 0)); + InstantPerson person4 = new InstantPerson("David", toInstant(2021, 1, 1, 15, 0, 0)); + InstantPerson person5 = new InstantPerson("Eve", toInstant(2021, 1, 1, 16, 0, 0)); + InstantPerson person6 = new InstantPerson("Frank", toInstant(2021, 1, 1, 17, 0, 0)); + InstantPerson person7 = new InstantPerson("Grace", toInstant(2021, 1, 1, 18, 0, 0)); + InstantPerson person8 = new InstantPerson("Hank", toInstant(2021, 1, 1, 19, 0, 0)); + InstantPerson person9 = new InstantPerson("Ivy", toInstant(2021, 1, 1, 20, 0, 0)); + InstantPerson person10 = new InstantPerson("Jack", toInstant(2021, 1, 1, 21, 0, 0)); + InstantPerson person11 = new InstantPerson("Karl", null); + + map.addAll(person1, person2, person3, person4, person5, person6, person7, person8, person9, person10, person11); + + return map; + } + + private static Instant toInstant(int year, int month, int day, int hour, int minute, int second) + { + return LocalDateTime.of(year, month, day, hour, minute, second).toInstant(ZoneOffset.UTC); + } + + private static class InstantPersonIndex extends IndexerInstant.Abstract + { + + @Override + protected Instant getInstant(InstantPerson entity) + { + return entity.getTimestamp(); + } + } + + private static class InstantPerson + { + private final String name; + private final Instant timestamp; + + public InstantPerson(String name, Instant timestamp) + { + this.name = name; + this.timestamp = timestamp; + } + + public String name() + { + return this.name; + } + + public Instant getTimestamp() + { + return this.timestamp; + } + } +} diff --git a/gigamap/gigamap/src/test/java/org/eclipse/store/gigamap/indexer/ZonedDateTimeIndexTest.java b/gigamap/gigamap/src/test/java/org/eclipse/store/gigamap/indexer/ZonedDateTimeIndexTest.java new file mode 100644 index 00000000..e9c16b80 --- /dev/null +++ b/gigamap/gigamap/src/test/java/org/eclipse/store/gigamap/indexer/ZonedDateTimeIndexTest.java @@ -0,0 +1,458 @@ +package org.eclipse.store.gigamap.indexer; + +/*- + * #%L + * EclipseStore GigaMap + * %% + * Copyright (C) 2023 - 2025 MicroStream Software + * %% + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * #L% + */ + +import org.eclipse.store.gigamap.types.BitmapIndices; +import org.eclipse.store.gigamap.types.GigaMap; +import org.eclipse.store.gigamap.types.IndexerZonedDateTime; +import org.eclipse.store.storage.embedded.types.EmbeddedStorage; +import org.eclipse.store.storage.embedded.types.EmbeddedStorageManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.*; + +public class ZonedDateTimeIndexTest +{ + @TempDir + Path tempDir; + + private ZonedDateTimePersonIndex zonedDateTimePersonIndex = new ZonedDateTimePersonIndex(); + + @Test + void nullTests() + { + GigaMap map = prepageGigaMap(); + + assertEquals(1, map.query(zonedDateTimePersonIndex.is((ZonedDateTime) null)).count()); + + assertThrows(IllegalArgumentException.class, () -> map.query(zonedDateTimePersonIndex.before(null)).count()); + assertThrows(IllegalArgumentException.class, () -> map.query(zonedDateTimePersonIndex.beforeEqual(null)).count()); + assertThrows(IllegalArgumentException.class, () -> map.query(zonedDateTimePersonIndex.after(null)).count()); + assertThrows(IllegalArgumentException.class, () -> map.query(zonedDateTimePersonIndex.afterEqual(null)).count()); + + assertThrows(IllegalArgumentException.class, () -> map.query(zonedDateTimePersonIndex.between(null, null)).count()); + assertThrows(IllegalArgumentException.class, () -> map.query(zonedDateTimePersonIndex.between(toUTC(2021, 1, 1, 12, 0, 0), null)).count()); + assertThrows(IllegalArgumentException.class, () -> map.query(zonedDateTimePersonIndex.between(null, toUTC(2021, 1, 1, 12, 0, 0))).count()); + + } + + @Test + void between() + { + GigaMap map = prepageGigaMap(); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(map, tempDir)) { + long count = map.query(zonedDateTimePersonIndex.between(toUTC(2021, 1, 1, 12, 0, 0), toUTC(2021, 1, 1, 14, 0, 0))).count(); + assertEquals(3, count); + long count1 = map.query(zonedDateTimePersonIndex.between(toUTC(2021, 1, 1, 12, 0, 0), toUTC(2021, 1, 1, 14, 0, 0))).count(); + assertEquals(3, count1); + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + GigaMap newMap = (GigaMap) manager.root(); + long count = newMap.query(zonedDateTimePersonIndex.between(toUTC(2021, 1, 1, 12, 0, 0), toUTC(2021, 1, 1, 14, 0, 0))).count(); + assertEquals(3, count); + long count1 = newMap.query(zonedDateTimePersonIndex.between(toUTC(2021, 1, 1, 12, 0, 0), toUTC(2021, 1, 1, 14, 0, 0))).count(); + assertEquals(3, count1); + long count2 = newMap.query(zonedDateTimePersonIndex.between(toUTC(2021, 1, 1, 13, 0, 0), toUTC(2021, 1, 1, 14, 0, 0))).count(); + assertEquals(2, count2); + } + } + + @Test + void after() + { + GigaMap map = prepageGigaMap(); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(map, tempDir)) { + long count = map.query(zonedDateTimePersonIndex.after(toUTC(2021, 1, 1, 12, 0, 0))).count(); + assertEquals(9, count); + long count1 = map.query(zonedDateTimePersonIndex.after(toUTC(2021, 1, 1, 13, 0, 0))).count(); + assertEquals(8, count1); + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + GigaMap newMap = (GigaMap) manager.root(); + long count = newMap.query(zonedDateTimePersonIndex.after(toUTC(2021, 1, 1, 12, 0, 0))).count(); + assertEquals(9, count); + long count1 = newMap.query(zonedDateTimePersonIndex.after(toUTC(2021, 1, 1, 13, 0, 0))).count(); + assertEquals(8, count1); + } + } + + @Test + void before() + { + GigaMap map = prepageGigaMap(); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(map, tempDir)) { + long count = map.query(zonedDateTimePersonIndex.before(toUTC(2021, 1, 1, 12, 0, 1))).count(); + assertEquals(1, count); + long count1 = map.query(zonedDateTimePersonIndex.before(toUTC(2021, 1, 1, 13, 0, 0))).count(); + assertEquals(1, count1); + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + GigaMap newMap = (GigaMap) manager.root(); + long count = newMap.query(zonedDateTimePersonIndex.before(toUTC(2021, 1, 1, 12, 0, 1))).count(); + assertEquals(1, count); + long count1 = newMap.query(zonedDateTimePersonIndex.before(toUTC(2021, 1, 1, 13, 0, 0))).count(); + assertEquals(1, count1); + } + } + + @Test + void isSecond() + { + GigaMap map = prepageGigaMap(); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(map, tempDir)) { + long count = map.query(zonedDateTimePersonIndex.isSecond(0)).count(); + assertEquals(10, count); + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + GigaMap newMap = (GigaMap) manager.root(); + long count = newMap.query(zonedDateTimePersonIndex.isSecond(0)).count(); + assertEquals(10, count); + } + } + + @Test + void isMinute() + { + GigaMap map = prepageGigaMap(); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(map, tempDir)) { + long count = map.query(zonedDateTimePersonIndex.isMinute(0)).count(); + assertEquals(10, count); + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + GigaMap newMap = (GigaMap) manager.root(); + long count = newMap.query(zonedDateTimePersonIndex.isMinute(0)).count(); + assertEquals(10, count); + } + } + + @Test + void isHour() + { + GigaMap map = prepageGigaMap(); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(map, tempDir)) { + long count = map.query(zonedDateTimePersonIndex.isHour(12)).count(); + assertEquals(1, count); + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + GigaMap newMap = (GigaMap) manager.root(); + long count = newMap.query(zonedDateTimePersonIndex.isHour(12)).count(); + assertEquals(1, count); + } + } + + @Test + void isTime() + { + GigaMap map = prepageGigaMap(); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(map, tempDir)) { + long count = map.query(zonedDateTimePersonIndex.isTime(12, 0, 0)).count(); + assertEquals(1, count); + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + GigaMap newMap = (GigaMap) manager.root(); + long count = newMap.query(zonedDateTimePersonIndex.isTime(12, 0, 0)).count(); + assertEquals(1, count); + } + } + + @Test + void isDay() + { + GigaMap map = prepageGigaMap(); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(map, tempDir)) { + long count = map.query(zonedDateTimePersonIndex.isDay(1)).count(); + assertEquals(10, count); + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + GigaMap newMap = (GigaMap) manager.root(); + long count = newMap.query(zonedDateTimePersonIndex.isDay(1)).count(); + assertEquals(10, count); + } + } + + @Test + void isMonth() + { + GigaMap map = prepageGigaMap(); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(map, tempDir)) { + long count = map.query(zonedDateTimePersonIndex.isMonth(1)).count(); + assertEquals(10, count); + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + GigaMap newMap = (GigaMap) manager.root(); + long count = newMap.query(zonedDateTimePersonIndex.isMonth(1)).count(); + assertEquals(10, count); + } + } + + @Test + void isYear() + { + GigaMap map = prepageGigaMap(); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(map, tempDir)) { + long count = map.query(zonedDateTimePersonIndex.isYear(2021)).count(); + assertEquals(10, count); + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + GigaMap newMap = (GigaMap) manager.root(); + long count = newMap.query(zonedDateTimePersonIndex.isYear(2021)).count(); + assertEquals(10, count); + } + } + + @Test + void isDate() + { + GigaMap map = prepageGigaMap(); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(map, tempDir)) { + long count = map.query(zonedDateTimePersonIndex.isDate(2021, 1, 1)).count(); + assertEquals(10, count); + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + GigaMap newMap = (GigaMap) manager.root(); + long count = newMap.query(zonedDateTimePersonIndex.isDate(2021, 1, 1)).count(); + assertEquals(10, count); + } + } + + @Test + void isDateTime() + { + GigaMap map = prepageGigaMap(); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(map, tempDir)) { + long count = map.query(zonedDateTimePersonIndex.isDateTime(2021, 1, 1, 12, 0, 0)).count(); + assertEquals(1, count); + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + GigaMap newMap = (GigaMap) manager.root(); + long count = newMap.query(zonedDateTimePersonIndex.isDateTime(2021, 1, 1, 12, 0, 0)).count(); + assertEquals(1, count); + } + + } + + @Test + void zonedDateTimePersonTest() + { + GigaMap map = GigaMap.New(); + + BitmapIndices bitmap = map.index().bitmap(); + bitmap.add(zonedDateTimePersonIndex); + + //generate some data + ZonedDateTimePerson person1 = new ZonedDateTimePerson("Alice", toUTC(2000, 1, 1, 0, 0, 0)); + ZonedDateTimePerson person2 = new ZonedDateTimePerson("Bob", toUTC(1990, 1, 1, 0, 0, 0)); + ZonedDateTimePerson person3 = new ZonedDateTimePerson("Charlie", toUTC(1980, 1, 1, 0, 0, 0)); + map.addAll(person1, person2, person3); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(map, tempDir)) { + map.query(zonedDateTimePersonIndex.is(toUTC(2000, 1, 1, 0, 0, 0))).forEach(person -> assertEquals(toUTC(2000, 1, 1, 0, 0, 0), person.getTimestamp())); + map.query(zonedDateTimePersonIndex.is(toUTC(1990, 1, 1, 0, 0, 0))).forEach(person -> assertEquals(toUTC(1990, 1, 1, 0, 0, 0), person.getTimestamp())); + + map.query(zonedDateTimePersonIndex.is(toUTC(2000, 1, 1, 0, 0, 0))).and(zonedDateTimePersonIndex.is(toUTC(1990, 1, 1, 0, 0, 0))) + .forEach(person -> fail("Should not be reached")); + + List personList = map.query(zonedDateTimePersonIndex.is(toUTC(2000, 1, 1, 0, 0, 0))).or(zonedDateTimePersonIndex.is(toUTC(1990, 1, 1, 0, 0, 0))) + .stream().collect(Collectors.toList()); + assertEquals(2, personList.size()); + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + GigaMap newMap = (GigaMap) manager.root(); + newMap.query(zonedDateTimePersonIndex.is(toUTC(2000, 1, 1, 0, 0, 0))).forEach(person -> assertEquals(toUTC(2000, 1, 1, 0, 0, 0), person.getTimestamp())); + newMap.query(zonedDateTimePersonIndex.is(toUTC(1990, 1, 1, 0, 0, 0))).forEach(person -> assertEquals(toUTC(1990, 1, 1, 0, 0, 0), person.getTimestamp())); + + newMap.query(zonedDateTimePersonIndex.is(toUTC(2000, 1, 1, 0, 0, 0))).and(zonedDateTimePersonIndex.is(toUTC(1990, 1, 1, 0, 0, 0))) + .forEach(person -> fail("Should not be reached")); + + List personList = newMap.query(zonedDateTimePersonIndex.is(toUTC(2000, 1, 1, 0, 0, 0))).or(zonedDateTimePersonIndex.is(toUTC(1990, 1, 1, 0, 0, 0))) + .stream().collect(Collectors.toList()); + assertEquals(2, personList.size()); + } + } + + @Test + void beforeEqualTest() + { + GigaMap map = GigaMap.New(); + + BitmapIndices bitmap = map.index().bitmap(); + bitmap.add(zonedDateTimePersonIndex); + + //generate some data + ZonedDateTimePerson person1 = new ZonedDateTimePerson("Alice", toUTC(2000, 1, 1, 0, 0, 0)); + ZonedDateTimePerson person2 = new ZonedDateTimePerson("Bob", toUTC(1990, 1, 1, 0, 0, 0)); + ZonedDateTimePerson person3 = new ZonedDateTimePerson("Charlie", toUTC(1980, 1, 1, 0, 0, 0)); + map.addAll(person1, person2, person3); + + List list = map.query(zonedDateTimePersonIndex.beforeEqual(toUTC(1990, 1, 1, 0, 0, 0))).toList(); + assertEquals(2, list.size()); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(map, tempDir)) { + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + GigaMap newMap = (GigaMap) manager.root(); + List list1 = newMap.query(zonedDateTimePersonIndex.beforeEqual(toUTC(1990, 1, 1, 0, 0, 0))).toList(); + assertEquals(2, list1.size()); + list1.forEach(person -> assertNotEquals("Alice", person.name)); + } + } + + @Test + void afterEqualTest() + { + GigaMap map = GigaMap.New(); + + BitmapIndices bitmap = map.index().bitmap(); + bitmap.add(zonedDateTimePersonIndex); + + //generate some data + ZonedDateTimePerson person1 = new ZonedDateTimePerson("Alice", toUTC(2000, 1, 1, 0, 0, 0)); + ZonedDateTimePerson person2 = new ZonedDateTimePerson("Bob", toUTC(1990, 1, 1, 0, 0, 0)); + ZonedDateTimePerson person3 = new ZonedDateTimePerson("Charlie", toUTC(1980, 1, 1, 0, 0, 0)); + map.addAll(person1, person2, person3); + + List list = map.query(zonedDateTimePersonIndex.afterEqual(toUTC(1990, 1, 1, 0, 0, 0))).toList(); + assertEquals(2, list.size()); + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(map, tempDir)) { + } + + try (EmbeddedStorageManager manager = EmbeddedStorage.start(tempDir)) { + GigaMap newMap = (GigaMap) manager.root(); + List list1 = newMap.query(zonedDateTimePersonIndex.afterEqual(toUTC(1990, 1, 1, 0, 0, 0))).toList(); + assertEquals(2, list1.size()); + list1.forEach(person -> assertNotEquals("Charlie", person.name)); + } + } + + @Test + void differentTimeZonesTest() + { + GigaMap map = GigaMap.New(); + + BitmapIndices bitmap = map.index().bitmap(); + bitmap.add(zonedDateTimePersonIndex); + + // Same absolute point in time, different zones: 2021-01-01T12:00 UTC == 2021-01-01T13:00 +01:00 + ZonedDateTimePerson person1 = new ZonedDateTimePerson("Alice", ZonedDateTime.of(2021, 1, 1, 12, 0, 0, 0, ZoneOffset.UTC)); + ZonedDateTimePerson person2 = new ZonedDateTimePerson("Bob", ZonedDateTime.of(2021, 1, 1, 13, 0, 0, 0, ZoneId.of("+01:00"))); + ZonedDateTimePerson person3 = new ZonedDateTimePerson("Charlie", ZonedDateTime.of(2021, 1, 1, 14, 0, 0, 0, ZoneOffset.UTC)); + map.addAll(person1, person2, person3); + + // Alice and Bob represent the same instant in UTC, so isHour(12) should match both + long count = map.query(zonedDateTimePersonIndex.isHour(12)).count(); + assertEquals(2, count); + + // Only Charlie is at 14:00 UTC + long count2 = map.query(zonedDateTimePersonIndex.isHour(14)).count(); + assertEquals(1, count2); + } + + private GigaMap prepageGigaMap() + { + GigaMap map = GigaMap.New(); + + BitmapIndices bitmap = map.index().bitmap(); + bitmap.add(zonedDateTimePersonIndex); + + ZonedDateTimePerson person1 = new ZonedDateTimePerson("Alice", toUTC(2021, 1, 1, 12, 0, 0)); + ZonedDateTimePerson person2 = new ZonedDateTimePerson("Bob", toUTC(2021, 1, 1, 13, 0, 0)); + ZonedDateTimePerson person3 = new ZonedDateTimePerson("Charlie", toUTC(2021, 1, 1, 14, 0, 0)); + ZonedDateTimePerson person4 = new ZonedDateTimePerson("David", toUTC(2021, 1, 1, 15, 0, 0)); + ZonedDateTimePerson person5 = new ZonedDateTimePerson("Eve", toUTC(2021, 1, 1, 16, 0, 0)); + ZonedDateTimePerson person6 = new ZonedDateTimePerson("Frank", toUTC(2021, 1, 1, 17, 0, 0)); + ZonedDateTimePerson person7 = new ZonedDateTimePerson("Grace", toUTC(2021, 1, 1, 18, 0, 0)); + ZonedDateTimePerson person8 = new ZonedDateTimePerson("Hank", toUTC(2021, 1, 1, 19, 0, 0)); + ZonedDateTimePerson person9 = new ZonedDateTimePerson("Ivy", toUTC(2021, 1, 1, 20, 0, 0)); + ZonedDateTimePerson person10 = new ZonedDateTimePerson("Jack", toUTC(2021, 1, 1, 21, 0, 0)); + ZonedDateTimePerson person11 = new ZonedDateTimePerson("Karl", null); + + map.addAll(person1, person2, person3, person4, person5, person6, person7, person8, person9, person10, person11); + + return map; + } + + private static ZonedDateTime toUTC(int year, int month, int day, int hour, int minute, int second) + { + return ZonedDateTime.of(year, month, day, hour, minute, second, 0, ZoneOffset.UTC); + } + + private static class ZonedDateTimePersonIndex extends IndexerZonedDateTime.Abstract + { + + @Override + protected ZonedDateTime getZonedDateTime(ZonedDateTimePerson entity) + { + return entity.getTimestamp(); + } + } + + private static class ZonedDateTimePerson + { + private final String name; + private final ZonedDateTime timestamp; + + public ZonedDateTimePerson(String name, ZonedDateTime timestamp) + { + this.name = name; + this.timestamp = timestamp; + } + + public String name() + { + return this.name; + } + + public ZonedDateTime getTimestamp() + { + return this.timestamp; + } + } +}