From 3e6500882600afe2caa53740960484077f247c42 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Mon, 7 Sep 2026 18:12:03 +0800 Subject: [PATCH 1/2] Add boundary tests for equal-size bucket outlier sampling --- .../UDTFEqualSizeBucketOutlierSampleTest.java | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/udf/builtin/UDTFEqualSizeBucketOutlierSampleTest.java diff --git a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/udf/builtin/UDTFEqualSizeBucketOutlierSampleTest.java b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/udf/builtin/UDTFEqualSizeBucketOutlierSampleTest.java new file mode 100644 index 000000000000..72fe3532b423 --- /dev/null +++ b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/udf/builtin/UDTFEqualSizeBucketOutlierSampleTest.java @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.iotdb.commons.udf.builtin; + +import org.apache.iotdb.udf.api.access.Row; +import org.apache.iotdb.udf.api.access.RowWindow; +import org.apache.iotdb.udf.api.collector.PointCollector; +import org.apache.iotdb.udf.api.customizer.config.UDTFConfigurations; +import org.apache.iotdb.udf.api.customizer.parameter.UDFParameterValidator; +import org.apache.iotdb.udf.api.customizer.parameter.UDFParameters; +import org.apache.iotdb.udf.api.customizer.strategy.SlidingSizeWindowAccessStrategy; +import org.apache.iotdb.udf.api.type.Type; + +import org.junit.Test; +import org.mockito.InOrder; + +import java.time.ZoneOffset; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +public class UDTFEqualSizeBucketOutlierSampleTest { + + private static final long[] VALUES = { + 1, -1, 3, 11, 9, 1531604122307244742L, -8581625725655917595L, -7162825364312197604L, 0, 1 + }; + + @Test + public void testAvgWithLargeLongValues() throws Exception { + assertSamples("avg", new long[][] {{7, -8581625725655917595L}, {8, -7162825364312197604L}}); + } + + @Test + public void testStendisWithLargeLongValues() throws Exception { + assertSamples("stendis", new long[][] {{7, -8581625725655917595L}, {8, -7162825364312197604L}}); + } + + @Test + public void testCosWithLargeLongValues() throws Exception { + // Integer overflow used to select times 4 and 5 instead of the large outliers at 6 and 7. + assertSamples("cos", new long[][] {{6, 1531604122307244742L}, {7, -8581625725655917595L}}); + } + + @Test + public void testPrenextdisWithLargeLongValues() throws Exception { + assertSamples( + "prenextdis", new long[][] {{6, 1531604122307244742L}, {7, -8581625725655917595L}}); + } + + private void assertSamples(String type, long[][] expected) throws Exception { + assertSamples(type, VALUES, expected); + } + + @Test + public void testStendisWithOverflowingEndpointProduct() throws Exception { + assertSamples( + "stendis", + new long[] { + 4000000000000000000L, + 5000000000000000000L, + 4000000000000000000L, + 2000000000000000000L, + 4000000000000000000L + }, + new long[][] {{2, 5000000000000000000L}, {4, 2000000000000000000L}}); + } + + @Test + public void testPrenextdisWithLongMinValue() throws Exception { + assertSamples( + "prenextdis", + new long[] {0, Long.MIN_VALUE, 0, 5, 0}, + new long[][] {{2, Long.MIN_VALUE}, {3, 0}}); + } + + private void assertSamples(String type, long[] values, long[][] expected) throws Exception { + Map attributes = new HashMap<>(); + attributes.put("proportion", "0.1"); + attributes.put("type", type); + attributes.put("number", "2"); + UDFParameters parameters = + new UDFParameters( + Collections.singletonList("root.sg.d1.s1"), + Collections.singletonList(Type.INT64), + attributes); + UDTFEqualSizeBucketOutlierSample function = new UDTFEqualSizeBucketOutlierSample(); + UDTFConfigurations configurations = new UDTFConfigurations(ZoneOffset.UTC); + function.validate(new UDFParameterValidator(parameters)); + function.beforeStart(parameters, configurations); + assertEquals(Type.INT64, configurations.getOutputDataType()); + assertEquals( + 20, ((SlidingSizeWindowAccessStrategy) configurations.getAccessStrategy()).getWindowSize()); + + // The input points form the final, partially filled bucket. + RowWindow window = mock(RowWindow.class); + when(window.windowSize()).thenReturn(values.length); + for (int i = 0; i < values.length; i++) { + Row row = mock(Row.class); + when(row.getTime()).thenReturn(i + 1L); + when(row.getLong(0)).thenReturn(values[i]); + when(window.getRow(i)).thenReturn(row); + } + + PointCollector collector = mock(PointCollector.class); + function.transform(window, collector); + function.terminate(collector); + + InOrder order = inOrder(collector); + for (long[] point : expected) { + order.verify(collector).putLong(point[0], point[1]); + } + verifyNoMoreInteractions(collector); + } +} From 037ea0355506492c339516715d9b27a0d54d9826 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Mon, 7 Sep 2026 19:04:19 +0800 Subject: [PATCH 2/2] Tests --- .../process/fill/linear/LinearFillTest.java | 60 ++++++++++ .../DateBinFunctionColumnTransformerTest.java | 80 +++++++++++++ .../QueryTimeoutRuntimeExceptionTest.java | 64 ++++++++++ .../PipeRealtimeDataRegionSourceTest.java | 58 +++++++++ .../process/gapfill/GapFillBoundaryTest.java | 112 ++++++++++++++++++ .../optimization/LimitOffsetPushDownTest.java | 83 +++++++++++++ .../plan/parameter/SeriesScanOptionsTest.java | 37 ++++++ ...FillStartAndEndTimeExtractVisitorTest.java | 62 ++++++++++ .../parser/DateExpressionBoundaryTest.java | 64 ++++++++++ .../SlidingTimeWindowBoundaryTest.java | 104 ++++++++++++++++ .../TsFileOverlapRepairBoundaryTest.java | 87 ++++++++++++++ .../udf/builtin/UDTFTimeDifferenceTest.java | 79 ++++++++++++ 12 files changed, 890 insertions(+) create mode 100644 iotdb-core/calc-commons/src/test/java/org/apache/iotdb/calc/execution/operator/process/fill/linear/LinearFillTest.java create mode 100644 iotdb-core/calc-commons/src/test/java/org/apache/iotdb/calc/transformation/dag/column/unary/scalar/DateBinFunctionColumnTransformerTest.java create mode 100644 iotdb-core/datanode/src/test/java/org/apache/iotdb/db/exception/query/QueryTimeoutRuntimeExceptionTest.java create mode 100644 iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/execution/operator/process/gapfill/GapFillBoundaryTest.java create mode 100644 iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/planner/plan/parameter/SeriesScanOptionsTest.java create mode 100644 iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/GapFillStartAndEndTimeExtractVisitorTest.java create mode 100644 iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/DateExpressionBoundaryTest.java create mode 100644 iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/transformation/dag/intermediate/SlidingTimeWindowBoundaryTest.java create mode 100644 iotdb-core/datanode/src/test/java/org/apache/iotdb/db/tools/TsFileOverlapRepairBoundaryTest.java create mode 100644 iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/udf/builtin/UDTFTimeDifferenceTest.java diff --git a/iotdb-core/calc-commons/src/test/java/org/apache/iotdb/calc/execution/operator/process/fill/linear/LinearFillTest.java b/iotdb-core/calc-commons/src/test/java/org/apache/iotdb/calc/execution/operator/process/fill/linear/LinearFillTest.java new file mode 100644 index 000000000000..330f433eca66 --- /dev/null +++ b/iotdb-core/calc-commons/src/test/java/org/apache/iotdb/calc/execution/operator/process/fill/linear/LinearFillTest.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.iotdb.calc.execution.operator.process.fill.linear; + +import org.apache.tsfile.block.column.Column; +import org.apache.tsfile.read.common.block.column.DoubleColumn; +import org.apache.tsfile.read.common.block.column.TimeColumn; +import org.junit.Test; + +import java.util.Optional; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +public class LinearFillTest { + + @Test + public void testInterpolationAcrossLongRange() { + assertMidpoint(new long[] {Long.MIN_VALUE, 0, Long.MAX_VALUE}); + } + + @Test + public void testDescendingInterpolationAcrossLongRange() { + assertMidpoint(new long[] {Long.MAX_VALUE, 0, Long.MIN_VALUE}); + } + + @Test + public void testSmallTimeDifferencesNearLongMaxValueRemainExact() { + assertMidpoint(new long[] {Long.MAX_VALUE - 2, Long.MAX_VALUE - 1, Long.MAX_VALUE}); + } + + private void assertMidpoint(long[] times) { + Column values = + new DoubleColumn( + 3, Optional.of(new boolean[] {false, true, false}), new double[] {0, 0, 10}); + Column filled = new DoubleLinearFill().fill(new TimeColumn(3, times), values, 0); + assertEquals(3, filled.getPositionCount()); + for (int i = 0; i < 3; i++) { + assertFalse(filled.isNull(i)); + assertEquals(i * 5.0, filled.getDouble(i), 1e-12); + } + } +} diff --git a/iotdb-core/calc-commons/src/test/java/org/apache/iotdb/calc/transformation/dag/column/unary/scalar/DateBinFunctionColumnTransformerTest.java b/iotdb-core/calc-commons/src/test/java/org/apache/iotdb/calc/transformation/dag/column/unary/scalar/DateBinFunctionColumnTransformerTest.java new file mode 100644 index 000000000000..94aefdebb046 --- /dev/null +++ b/iotdb-core/calc-commons/src/test/java/org/apache/iotdb/calc/transformation/dag/column/unary/scalar/DateBinFunctionColumnTransformerTest.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.iotdb.calc.transformation.dag.column.unary.scalar; + +import org.apache.iotdb.calc.transformation.dag.column.leaf.IdentityColumnTransformer; + +import org.junit.Test; + +import java.time.ZoneOffset; + +import static org.apache.iotdb.calc.transformation.dag.column.unary.scalar.DateBinFunctionColumnTransformer.dateBin; +import static org.apache.iotdb.calc.transformation.dag.column.unary.scalar.DateBinFunctionColumnTransformer.nextDateBin; +import static org.apache.tsfile.read.common.type.TimestampType.TIMESTAMP; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +public class DateBinFunctionColumnTransformerTest { + + @Test + public void testSourceMinusOriginOverflows() { + assertBin( + Long.MAX_VALUE, Long.MIN_VALUE, 10, Long.MAX_VALUE - 5, Long.MAX_VALUE, Long.MAX_VALUE); + } + + @Test + public void testNegativeDifferenceRoundsDownBeforeClamping() { + // The mathematical start is MIN_VALUE - 5; its end must be computed before clamping. + assertBin( + Long.MIN_VALUE, Long.MAX_VALUE, 10, Long.MIN_VALUE, Long.MIN_VALUE + 5, Long.MIN_VALUE + 4); + } + + @Test + public void testStepProductOverflowsButBinStartIsRepresentable() { + assertBin(-1, Long.MAX_VALUE - 1, Long.MAX_VALUE - 1, Long.MIN_VALUE + 2, 0, -1); + } + + @Test + public void testClosedEndIncludesLongMaxValue() { + assertBin(Long.MAX_VALUE, 0, 2, Long.MAX_VALUE - 1, Long.MAX_VALUE, Long.MAX_VALUE); + } + + @Test + public void testNextBinSaturatesWithoutWrapping() { + assertEquals(Long.MAX_VALUE, nextDateBin(10, Long.MAX_VALUE - 5)); + assertEquals(Long.MAX_VALUE, nextDateBin(10, Long.MAX_VALUE)); + assertEquals(Long.MIN_VALUE + 10, nextDateBin(10, Long.MIN_VALUE)); + } + + private void assertBin( + long source, long origin, long duration, long start, long end, long closedEnd) { + DateBinFunctionColumnTransformer transformer = + new DateBinFunctionColumnTransformer( + TIMESTAMP, + 0, + duration, + new IdentityColumnTransformer(TIMESTAMP, 0), + origin, + ZoneOffset.UTC); + assertEquals(start, dateBin(source, origin, 0, duration, ZoneOffset.UTC)); + assertArrayEquals(new long[] {start, end}, transformer.dateBinStartEnd(source)); + assertArrayEquals(new long[] {start, closedEnd}, transformer.dateBinStartEndClosed(source)); + } +} diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/exception/query/QueryTimeoutRuntimeExceptionTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/exception/query/QueryTimeoutRuntimeExceptionTest.java new file mode 100644 index 000000000000..2676b7745567 --- /dev/null +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/exception/query/QueryTimeoutRuntimeExceptionTest.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.iotdb.db.exception.query; + +import org.apache.iotdb.rpc.TSStatusCode; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class QueryTimeoutRuntimeExceptionTest { + + @Test + public void testDeadlineSaturatesAtLongMaxValue() { + final long startTime = Long.MAX_VALUE - 1; + final long currentTime = Long.MAX_VALUE; + final QueryTimeoutRuntimeException exception = + new QueryTimeoutRuntimeException(startTime, currentTime, 10); + + assertEquals( + String.format( + QueryTimeoutRuntimeException.QUERY_TIMEOUT_EXCEPTION_MESSAGE, + startTime, + Long.MAX_VALUE, + currentTime), + exception.getMessage()); + assertEquals(TSStatusCode.QUERY_TIMEOUT.getStatusCode(), exception.getErrorCode()); + assertTrue(exception.isUserException()); + } + + @Test + public void testDeadlineSaturatesAtLongMinValue() { + final long startTime = Long.MIN_VALUE + 1; + final long currentTime = Long.MIN_VALUE; + final QueryTimeoutRuntimeException exception = + new QueryTimeoutRuntimeException(startTime, currentTime, -10); + + assertEquals( + String.format( + QueryTimeoutRuntimeException.QUERY_TIMEOUT_EXCEPTION_MESSAGE, + startTime, + Long.MIN_VALUE, + currentTime), + exception.getMessage()); + } +} diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/source/dataregion/realtime/PipeRealtimeDataRegionSourceTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/source/dataregion/realtime/PipeRealtimeDataRegionSourceTest.java index 4677f2126b6c..378ab6feac08 100644 --- a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/source/dataregion/realtime/PipeRealtimeDataRegionSourceTest.java +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/source/dataregion/realtime/PipeRealtimeDataRegionSourceTest.java @@ -19,21 +19,73 @@ package org.apache.iotdb.db.pipe.source.dataregion.realtime; +import org.apache.iotdb.commons.conf.CommonDescriptor; import org.apache.iotdb.commons.consensus.index.impl.MinimumProgressIndex; +import org.apache.iotdb.commons.pipe.agent.task.meta.PipeTaskMeta; +import org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant; +import org.apache.iotdb.commons.pipe.config.plugin.configuraion.PipeTaskRuntimeConfiguration; +import org.apache.iotdb.commons.pipe.config.plugin.env.PipeTaskSourceRuntimeEnvironment; import org.apache.iotdb.commons.pipe.event.ProgressReportEvent; +import org.apache.iotdb.commons.utils.TimePartitionUtils; import org.apache.iotdb.db.pipe.event.common.heartbeat.PipeHeartbeatEvent; import org.apache.iotdb.db.pipe.event.realtime.PipeRealtimeEvent; import org.apache.iotdb.db.pipe.event.realtime.PipeRealtimeEventFactory; +import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameterValidator; +import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameters; import org.apache.iotdb.pipe.api.event.Event; import org.junit.Assert; import org.junit.Test; +import java.lang.reflect.Field; +import java.util.HashMap; + public class PipeRealtimeDataRegionSourceTest { private static final String TEST_REFERENCE_HOLDER = PipeRealtimeDataRegionSourceTest.class.getName(); + @Test + public void customizeUsesConfiguredTimePartitionOriginForBounds() throws Exception { + final long previousOrigin = CommonDescriptor.getInstance().getConfig().getTimePartitionOrigin(); + final long previousInterval = + CommonDescriptor.getInstance().getConfig().getTimePartitionInterval(); + + try { + final long origin = 1_000L; + final long interval = 3_600_000L; + CommonDescriptor.getInstance().getConfig().setTimePartitionOrigin(origin); + CommonDescriptor.getInstance().getConfig().setTimePartitionInterval(interval); + TimePartitionUtils.setTimePartitionOrigin(origin); + TimePartitionUtils.setTimePartitionInterval(interval); + + final PipeParameters parameters = + new PipeParameters( + new HashMap() { + { + put(PipeSourceConstant.EXTRACTOR_START_TIME_KEY, "0"); + put(PipeSourceConstant.EXTRACTOR_END_TIME_KEY, "3600000"); + } + }); + try (final ProgressReportTestSource source = new ProgressReportTestSource()) { + source.validate(new PipeParameterValidator(parameters)); + source.customize( + parameters, + new PipeTaskRuntimeConfiguration( + new PipeTaskSourceRuntimeEnvironment( + "pipe", 1L, -1, new PipeTaskMeta(MinimumProgressIndex.INSTANCE, 1)))); + + Assert.assertEquals(0, getPrivateLong(source, "startTimePartitionIdLowerBound")); + Assert.assertEquals(-1, getPrivateLong(source, "endTimePartitionIdUpperBound")); + } + } finally { + CommonDescriptor.getInstance().getConfig().setTimePartitionOrigin(previousOrigin); + CommonDescriptor.getInstance().getConfig().setTimePartitionInterval(previousInterval); + TimePartitionUtils.setTimePartitionOrigin(previousOrigin); + TimePartitionUtils.setTimePartitionInterval(previousInterval); + } + } + @Test public void progressReportEventReleasesDroppedHeartbeatEvent() throws Exception { try (final ProgressReportTestSource source = new ProgressReportTestSource()) { @@ -84,6 +136,12 @@ private static PipeRealtimeEvent createProgressReportEvent() { return event; } + private static long getPrivateLong(final Object target, final String fieldName) throws Exception { + final Field field = PipeRealtimeDataRegionSource.class.getDeclaredField(fieldName); + field.setAccessible(true); + return field.getLong(target); + } + private static class ProgressReportTestSource extends PipeRealtimeDataRegionSource { @Override diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/execution/operator/process/gapfill/GapFillBoundaryTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/execution/operator/process/gapfill/GapFillBoundaryTest.java new file mode 100644 index 000000000000..7c2358d94f51 --- /dev/null +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/execution/operator/process/gapfill/GapFillBoundaryTest.java @@ -0,0 +1,112 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.iotdb.db.queryengine.execution.operator.process.gapfill; + +import org.apache.iotdb.calc.execution.operator.CommonOperatorContext; +import org.apache.iotdb.calc.execution.operator.Operator; +import org.apache.iotdb.calc.execution.operator.process.gapfill.GapFillWoGroupWoMoOperator; + +import org.apache.tsfile.enums.TSDataType; +import org.apache.tsfile.read.common.block.TsBlock; +import org.apache.tsfile.read.common.block.TsBlockBuilder; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class GapFillBoundaryTest { + + private static final List DATA_TYPES = + Arrays.asList(TSDataType.TIMESTAMP, TSDataType.DOUBLE); + + @Test(timeout = 10000) + public void testLongMinRealRowDoesNotGeneratePrecedingGaps() throws Exception { + Operator child = childWithTimes(Long.MIN_VALUE, Long.MIN_VALUE + 2); + try (GapFillWoGroupWoMoOperator operator = + new GapFillWoGroupWoMoOperator( + mock(CommonOperatorContext.class), + child, + 0, + Long.MIN_VALUE, + Long.MIN_VALUE + 2, + DATA_TYPES, + 1)) { + TsBlock result = operator.next(); + assertEquals(3, result.getPositionCount()); + for (int i = 0; i < 3; i++) { + assertEquals(Long.MIN_VALUE + i, result.getColumn(0).getLong(i)); + assertEquals(i == 1, result.getColumn(1).isNull(i)); + } + assertEquals(10.0, result.getColumn(1).getDouble(0), 0); + assertEquals(10.0, result.getColumn(1).getDouble(2), 0); + assertFalse(operator.hasNext()); + assertTrue(operator.isFinished()); + } + } + + @Test(timeout = 10000) + public void testGapFillStopsAfterLongMaxValue() throws Exception { + Operator child = childWithTimes(Long.MAX_VALUE - 4); + try (GapFillWoGroupWoMoOperator operator = + new GapFillWoGroupWoMoOperator( + mock(CommonOperatorContext.class), + child, + 0, + Long.MAX_VALUE - 4, + Long.MAX_VALUE, + DATA_TYPES, + 2)) { + TsBlock first = operator.next(); + assertEquals(1, first.getPositionCount()); + assertEquals(Long.MAX_VALUE - 4, first.getColumn(0).getLong(0)); + assertTrue(operator.hasNext()); + + TsBlock gaps = operator.next(); + assertEquals(2, gaps.getPositionCount()); + assertEquals(Long.MAX_VALUE - 2, gaps.getColumn(0).getLong(0)); + assertEquals(Long.MAX_VALUE, gaps.getColumn(0).getLong(1)); + assertTrue(gaps.getColumn(1).isNull(0)); + assertTrue(gaps.getColumn(1).isNull(1)); + assertFalse(operator.hasNext()); + assertTrue(operator.isFinished()); + } + } + + private Operator childWithTimes(long... times) throws Exception { + TsBlockBuilder builder = new TsBlockBuilder(DATA_TYPES); + for (long time : times) { + builder.getTimeColumnBuilder().writeLong(0); + builder.getColumnBuilder(0).writeLong(time); + builder.getColumnBuilder(1).writeDouble(10); + builder.declarePosition(); + } + Operator child = mock(Operator.class); + when(child.hasNextWithTimer()).thenReturn(true, false); + when(child.nextWithTimer()).thenReturn(builder.build()); + when(child.isFinished()).thenReturn(true); + return child; + } +} diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/optimization/LimitOffsetPushDownTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/optimization/LimitOffsetPushDownTest.java index 6fd134940103..8263c48fd8d3 100644 --- a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/optimization/LimitOffsetPushDownTest.java +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/optimization/LimitOffsetPushDownTest.java @@ -35,11 +35,13 @@ import org.apache.iotdb.db.queryengine.plan.planner.plan.parameter.GroupByTimeParameter; import org.apache.iotdb.db.queryengine.plan.planner.plan.parameter.OrderByParameter; import org.apache.iotdb.db.queryengine.plan.statement.component.GroupByTimeComponent; +import org.apache.iotdb.db.queryengine.plan.statement.component.OrderByComponent; import org.apache.iotdb.db.queryengine.plan.statement.component.OrderByKey; import org.apache.iotdb.db.queryengine.plan.statement.component.Ordering; import org.apache.iotdb.db.queryengine.plan.statement.component.SortItem; import org.apache.iotdb.db.queryengine.plan.statement.crud.QueryStatement; +import org.apache.tsfile.utils.TimeDuration; import org.junit.Assert; import org.junit.Test; @@ -61,6 +63,87 @@ /** Use optimize rule: LimitOffsetPushDown and OrderByExpressionWithLimitChangeToTopK */ public class LimitOffsetPushDownTest { + @Test + public void testGroupByTimePushDownAcrossLongRange() { + QueryStatement statement = + newBoundaryGroupByStatement(Long.MIN_VALUE, Long.MAX_VALUE, 10, 2, 1); + LimitOffsetPushDown.pushDownLimitOffsetToTimeParameter(statement, ZoneId.of("UTC")); + + Assert.assertFalse(statement.isResultSetEmpty()); + Assert.assertEquals(Long.MIN_VALUE + 10, statement.getGroupByTimeComponent().getStartTime()); + Assert.assertEquals(Long.MIN_VALUE + 30, statement.getGroupByTimeComponent().getEndTime()); + Assert.assertEquals(0, statement.getRowLimit()); + Assert.assertEquals(0, statement.getRowOffset()); + } + + @Test + public void testDescendingGroupByTimePushDownAcrossLongRange() { + QueryStatement statement = + newBoundaryGroupByStatement(Long.MIN_VALUE, Long.MAX_VALUE, 10, 2, 1); + OrderByComponent orderBy = new OrderByComponent(); + orderBy.addSortItem(new SortItem(OrderByKey.TIME, Ordering.DESC)); + statement.setOrderByComponent(orderBy); + LimitOffsetPushDown.pushDownLimitOffsetToTimeParameter(statement, ZoneId.of("UTC")); + + Assert.assertFalse(statement.isResultSetEmpty()); + Assert.assertEquals(Long.MAX_VALUE - 25, statement.getGroupByTimeComponent().getStartTime()); + Assert.assertEquals(Long.MAX_VALUE - 5, statement.getGroupByTimeComponent().getEndTime()); + Assert.assertEquals(0, statement.getRowLimit()); + Assert.assertEquals(0, statement.getRowOffset()); + } + + @Test + public void testLargeLimitDoesNotWrapGroupByEndTime() { + QueryStatement statement = newBoundaryGroupByStatement(100, 200, 10, Long.MAX_VALUE, 0); + LimitOffsetPushDown.pushDownLimitOffsetToTimeParameter(statement, ZoneId.of("UTC")); + + Assert.assertFalse(statement.isResultSetEmpty()); + Assert.assertEquals(100, statement.getGroupByTimeComponent().getStartTime()); + Assert.assertEquals(200, statement.getGroupByTimeComponent().getEndTime()); + } + + @Test + public void testDeviceWindowCountProductOverflows() throws Exception { + List devices = + Arrays.asList(new PartialPath("root.sg.d1"), new PartialPath("root.sg.d2")); + QueryStatement statement = newBoundaryGroupByStatement(0, Long.MAX_VALUE, 1, 1, Long.MAX_VALUE); + + Assert.assertEquals( + Collections.singletonList(devices.get(1)), + LimitOffsetPushDown.pushDownLimitOffsetInGroupByTimeForDevice( + devices, statement, ZoneId.of("UTC"))); + Assert.assertFalse(statement.isResultSetEmpty()); + Assert.assertEquals(0, statement.getRowOffset()); + } + + @Test + public void testLargeLimitDoesNotWrapEndDeviceIndex() throws Exception { + List devices = + Arrays.asList(new PartialPath("root.sg.d1"), new PartialPath("root.sg.d2")); + QueryStatement statement = newBoundaryGroupByStatement(0, 100, 10, Long.MAX_VALUE, 0); + + Assert.assertEquals( + devices, + LimitOffsetPushDown.pushDownLimitOffsetInGroupByTimeForDevice( + devices, statement, ZoneId.of("UTC"))); + Assert.assertFalse(statement.isResultSetEmpty()); + Assert.assertEquals(Long.MAX_VALUE, statement.getRowLimit()); + } + + private QueryStatement newBoundaryGroupByStatement( + long start, long end, long interval, long limit, long offset) { + GroupByTimeComponent groupBy = new GroupByTimeComponent(); + groupBy.setStartTime(start); + groupBy.setEndTime(end); + groupBy.setInterval(new TimeDuration(0, interval)); + groupBy.setSlidingStep(new TimeDuration(0, interval)); + QueryStatement statement = new QueryStatement(); + statement.setGroupByTimeComponent(groupBy); + statement.setRowLimit(limit); + statement.setRowOffset(offset); + return statement; + } + @Test public void testNonAlignedPushDown() { checkPushDown( diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/planner/plan/parameter/SeriesScanOptionsTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/planner/plan/parameter/SeriesScanOptionsTest.java new file mode 100644 index 000000000000..2f3501ec1fbe --- /dev/null +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/planner/plan/parameter/SeriesScanOptionsTest.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.iotdb.db.queryengine.plan.planner.plan.parameter; + +import org.apache.tsfile.read.filter.basic.Filter; +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class SeriesScanOptionsTest { + + @Test + public void testUpdateFilterUsingTtlSaturatesNegativeOverflow() { + final Filter filter = SeriesScanOptions.updateFilterUsingTTL(null, Long.MIN_VALUE); + + assertTrue(filter.satisfyStartEndTime(Long.MAX_VALUE, Long.MAX_VALUE)); + assertFalse(filter.satisfyStartEndTime(0, 0)); + } +} diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/GapFillStartAndEndTimeExtractVisitorTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/GapFillStartAndEndTimeExtractVisitorTest.java new file mode 100644 index 000000000000..730a24d7a958 --- /dev/null +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/planner/ir/GapFillStartAndEndTimeExtractVisitorTest.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.iotdb.db.queryengine.plan.relational.planner.ir; + +import org.apache.iotdb.commons.exception.SemanticException; +import org.apache.iotdb.db.queryengine.plan.relational.planner.ir.GapFillStartAndEndTimeExtractVisitor.Context; + +import org.junit.Test; + +import java.time.ZoneOffset; + +import static org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.ComparisonExpression.Operator.GREATER_THAN; +import static org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.ComparisonExpression.Operator.GREATER_THAN_OR_EQUAL; +import static org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.ComparisonExpression.Operator.LESS_THAN; +import static org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.ComparisonExpression.Operator.LESS_THAN_OR_EQUAL; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertThrows; + +public class GapFillStartAndEndTimeExtractVisitorTest { + + @Test + public void testExclusiveLongMaxStartIsRejected() { + Context context = new Context(); + context.updateStartTime(Long.MAX_VALUE, GREATER_THAN); + context.updateEndTime(Long.MAX_VALUE, LESS_THAN_OR_EQUAL); + assertThrows(SemanticException.class, () -> context.getTimeRange(0, 0, 1, ZoneOffset.UTC)); + } + + @Test + public void testExclusiveLongMinEndIsRejected() { + Context context = new Context(); + context.updateStartTime(Long.MIN_VALUE, GREATER_THAN_OR_EQUAL); + context.updateEndTime(Long.MIN_VALUE, LESS_THAN); + assertThrows(SemanticException.class, () -> context.getTimeRange(0, 0, 1, ZoneOffset.UTC)); + } + + @Test + public void testInclusiveLongBoundariesArePreserved() { + Context context = new Context(); + context.updateStartTime(Long.MIN_VALUE, GREATER_THAN_OR_EQUAL); + context.updateEndTime(Long.MAX_VALUE, LESS_THAN_OR_EQUAL); + assertArrayEquals( + new long[] {Long.MIN_VALUE, Long.MAX_VALUE}, context.getTimeRange(0, 0, 1, ZoneOffset.UTC)); + } +} diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/DateExpressionBoundaryTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/DateExpressionBoundaryTest.java new file mode 100644 index 000000000000..c56f8f05c71b --- /dev/null +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/relational/sql/parser/DateExpressionBoundaryTest.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.iotdb.db.queryengine.plan.relational.sql.parser; + +import org.apache.iotdb.commons.exception.SemanticException; +import org.apache.iotdb.db.protocol.session.InternalClientSession; +import org.apache.iotdb.db.queryengine.plan.parser.StatementGenerator; + +import org.junit.Test; + +import java.time.ZoneOffset; +import java.util.function.Consumer; + +import static org.junit.Assert.assertThrows; + +public class DateExpressionBoundaryTest { + + @Test + public void testTreeDateExpressionBoundaries() { + assertDateExpressionBoundaries( + expression -> + StatementGenerator.createStatement( + "select s1 from root.sg.d1 where time > " + expression, ZoneOffset.UTC)); + } + + @Test + public void testTableDateExpressionBoundaries() { + SqlParser parser = new SqlParser(); + assertDateExpressionBoundaries( + expression -> + parser.createStatement( + "select s1 from table1 where time > " + expression, + ZoneOffset.UTC, + new InternalClientSession("date_boundary"))); + } + + private void assertDateExpressionBoundaries(Consumer parse) { + parse.accept("1970-01-01T00:00:00.000 + 9223372036854775807ms"); + parse.accept("1969-12-31T23:59:59.999 - 9223372036854775807ms"); + assertThrows( + SemanticException.class, + () -> parse.accept("1970-01-01T00:00:00.001 + 9223372036854775807ms")); + assertThrows( + SemanticException.class, + () -> parse.accept("1969-12-31T23:59:59.999 - 9223372036854775807ms - 1ms")); + } +} diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/transformation/dag/intermediate/SlidingTimeWindowBoundaryTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/transformation/dag/intermediate/SlidingTimeWindowBoundaryTest.java new file mode 100644 index 000000000000..54a78059993a --- /dev/null +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/transformation/dag/intermediate/SlidingTimeWindowBoundaryTest.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.iotdb.db.queryengine.transformation.dag.intermediate; + +import org.apache.iotdb.db.queryengine.plan.expression.Expression; +import org.apache.iotdb.db.queryengine.transformation.api.LayerReader; +import org.apache.iotdb.db.queryengine.transformation.api.LayerRowWindowReader; +import org.apache.iotdb.udf.api.access.RowWindow; +import org.apache.iotdb.udf.api.customizer.strategy.SlidingTimeWindowAccessStrategy; + +import org.apache.tsfile.block.column.Column; +import org.apache.tsfile.enums.TSDataType; +import org.apache.tsfile.read.common.block.column.IntColumn; +import org.apache.tsfile.read.common.block.column.TimeColumn; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Optional; + +import static org.apache.iotdb.db.queryengine.transformation.api.YieldableState.NOT_YIELDABLE_NO_MORE_DATA; +import static org.apache.iotdb.db.queryengine.transformation.api.YieldableState.YIELDABLE; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class SlidingTimeWindowBoundaryTest { + + @Test + public void testSingleInputSingleReference() throws Exception { + assertLastWindow( + new SingleInputSingleReferenceLayer( + mock(Expression.class), "boundary_single", 1, parentReader()), + 1); + } + + @Test + public void testSingleInputMultiReference() throws Exception { + assertLastWindow( + new SingleInputMultiReferenceLayer( + mock(Expression.class), "boundary_multi_reference", 1, parentReader()), + 1); + } + + @Test + public void testMultiInput() throws Exception { + assertLastWindow( + new MultiInputLayer( + mock(Expression.class), + "boundary_multi_input", + 1, + Arrays.asList(parentReader(), parentReader())), + 2); + } + + private void assertLastWindow(IntermediateLayer layer, int columnCount) throws Exception { + LayerRowWindowReader reader = + layer.constructRowWindowReader( + new SlidingTimeWindowAccessStrategy(10, 10, Long.MAX_VALUE - 3, Long.MAX_VALUE), 1); + assertEquals(YIELDABLE, reader.yield()); + RowWindow window = reader.currentWindow(); + assertEquals(Long.MAX_VALUE - 3, window.windowStartTime()); + assertEquals(Long.MAX_VALUE, window.windowEndTime()); + assertEquals(3, window.windowSize()); + for (int i = 0; i < 3; i++) { + assertEquals(Long.MAX_VALUE - 3 + i, window.getRow(i).getTime()); + for (int column = 0; column < columnCount; column++) { + assertEquals(i + 1, window.getRow(i).getInt(column)); + } + } + reader.readyForNext(); + assertEquals(NOT_YIELDABLE_NO_MORE_DATA, reader.yield()); + } + + private LayerReader parentReader() throws Exception { + LayerReader reader = mock(LayerReader.class); + when(reader.getDataTypes()).thenReturn(new TSDataType[] {TSDataType.INT32}); + when(reader.yield()).thenReturn(YIELDABLE, NOT_YIELDABLE_NO_MORE_DATA); + when(reader.current()) + .thenReturn( + new Column[] { + new IntColumn(3, Optional.empty(), new int[] {1, 2, 3}), + new TimeColumn( + 3, new long[] {Long.MAX_VALUE - 3, Long.MAX_VALUE - 2, Long.MAX_VALUE - 1}) + }); + return reader; + } +} diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/tools/TsFileOverlapRepairBoundaryTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/tools/TsFileOverlapRepairBoundaryTest.java new file mode 100644 index 000000000000..dd2255ec6280 --- /dev/null +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/tools/TsFileOverlapRepairBoundaryTest.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.iotdb.db.tools; + +import org.apache.iotdb.db.storageengine.dataregion.tsfile.TsFileResource; +import org.apache.iotdb.db.tools.validate.TsFileOverlapValidationAndRepairTool; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.file.Files; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +public class TsFileOverlapRepairBoundaryTest { + + @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Test + public void testCollisionAtLongMaxDoesNotMoveOrOverwriteFiles() throws Exception { + File source = createSource(); + File target = + new File(temporaryFolder.newFolder("unsequence", "root.sg", "0", "0"), source.getName()); + Files.write(target.toPath(), new byte[] {2}); + + InvocationTargetException exception = + assertThrows(InvocationTargetException.class, () -> repair(source)); + assertTrue(exception.getCause() instanceof IOException); + assertArrayEquals(new byte[] {1}, Files.readAllBytes(source.toPath())); + assertArrayEquals(new byte[] {2}, Files.readAllBytes(target.toPath())); + assertTrue(new File(source + TsFileResource.RESOURCE_SUFFIX).exists()); + } + + @Test + public void testLongMaxWithoutCollisionCanBeMoved() throws Exception { + File source = createSource(); + repair(source); + + File target = new File(temporaryFolder.getRoot(), "unsequence/root.sg/0/0/" + source.getName()); + assertFalse(source.exists()); + assertArrayEquals(new byte[] {1}, Files.readAllBytes(target.toPath())); + assertTrue(new File(target + TsFileResource.RESOURCE_SUFFIX).exists()); + } + + private File createSource() throws IOException { + File source = + new File( + temporaryFolder.newFolder("sequence", "root.sg", "0", "0"), + Long.MAX_VALUE + "-0-0-0.tsfile"); + Files.write(source.toPath(), new byte[] {1}); + Files.write(new File(source + TsFileResource.RESOURCE_SUFFIX).toPath(), new byte[] {3}); + return source; + } + + private void repair(File source) throws Exception { + Method method = + TsFileOverlapValidationAndRepairTool.class.getDeclaredMethod( + "moveSeqResourceToUnsequenceDir", TsFileResource.class); + method.setAccessible(true); + method.invoke(null, new TsFileResource(source)); + } +} diff --git a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/udf/builtin/UDTFTimeDifferenceTest.java b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/udf/builtin/UDTFTimeDifferenceTest.java new file mode 100644 index 000000000000..d7cfd4d87818 --- /dev/null +++ b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/udf/builtin/UDTFTimeDifferenceTest.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.apache.iotdb.commons.udf.builtin; + +import org.apache.iotdb.udf.api.access.Row; +import org.apache.iotdb.udf.api.collector.PointCollector; +import org.apache.iotdb.udf.api.customizer.config.UDTFConfigurations; +import org.apache.iotdb.udf.api.customizer.parameter.UDFParameters; +import org.apache.iotdb.udf.api.type.Type; + +import org.junit.Test; +import org.mockito.InOrder; + +import java.time.ZoneOffset; +import java.util.Collections; + +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +public class UDTFTimeDifferenceTest { + + @Test + public void testOverflowDoesNotCorruptFollowingDifference() throws Exception { + assertDifferences( + new long[] {Long.MIN_VALUE, 0, 1, Long.MAX_VALUE}, + new long[] {Long.MAX_VALUE, 1, Long.MAX_VALUE - 1}); + } + + @Test + public void testSmallDifferencesNearLongMaxValueRemainExact() throws Exception { + assertDifferences( + new long[] {Long.MAX_VALUE - 2, Long.MAX_VALUE - 1, Long.MAX_VALUE}, new long[] {1, 1}); + } + + private void assertDifferences(long[] times, long[] expected) throws Exception { + UDTFTimeDifference function = new UDTFTimeDifference(); + function.beforeStart( + new UDFParameters( + Collections.singletonList("root.sg.d1.s1"), + Collections.singletonList(Type.INT64), + Collections.emptyMap()), + new UDTFConfigurations(ZoneOffset.UTC)); + PointCollector collector = mock(PointCollector.class); + for (int i = 0; i < times.length; i++) { + Row row = mock(Row.class); + when(row.getTime()).thenReturn(times[i]); + function.transform(row, collector); + if (i == 0) { + verifyNoMoreInteractions(collector); + } + } + function.terminate(collector); + + InOrder order = inOrder(collector); + for (int i = 0; i < expected.length; i++) { + order.verify(collector).putLong(times[i + 1], expected[i]); + } + verifyNoMoreInteractions(collector); + } +}