|
| 1 | +/* |
| 2 | + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one |
| 3 | + * or more contributor license agreements. Licensed under the Elastic License |
| 4 | + * 2.0; you may not use this file except in compliance with the Elastic License |
| 5 | + * 2.0. |
| 6 | + */ |
| 7 | + |
| 8 | +package org.elasticsearch.xpack.esql.expression.function.scalar.string; |
| 9 | + |
| 10 | +import org.apache.lucene.util.BytesRef; |
| 11 | +import org.elasticsearch.common.breaker.CircuitBreaker; |
| 12 | +import org.elasticsearch.common.unit.ByteSizeValue; |
| 13 | +import org.elasticsearch.common.util.BigArrays; |
| 14 | +import org.elasticsearch.common.util.MockBigArrays; |
| 15 | +import org.elasticsearch.common.util.PageCacheRecycler; |
| 16 | +import org.elasticsearch.compute.data.Block; |
| 17 | +import org.elasticsearch.compute.data.BlockFactory; |
| 18 | +import org.elasticsearch.compute.data.BlockUtils; |
| 19 | +import org.elasticsearch.compute.data.Page; |
| 20 | +import org.elasticsearch.compute.operator.DriverContext; |
| 21 | +import org.elasticsearch.compute.test.TestBlockFactory; |
| 22 | +import org.elasticsearch.test.ESTestCase; |
| 23 | +import org.elasticsearch.xpack.esql.core.expression.FieldAttribute; |
| 24 | +import org.elasticsearch.xpack.esql.core.expression.function.scalar.ScalarFunction; |
| 25 | +import org.elasticsearch.xpack.esql.core.tree.Source; |
| 26 | +import org.elasticsearch.xpack.esql.core.type.DataType; |
| 27 | +import org.elasticsearch.xpack.esql.core.type.EsField; |
| 28 | +import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase; |
| 29 | +import org.junit.After; |
| 30 | + |
| 31 | +import java.util.ArrayList; |
| 32 | +import java.util.Collections; |
| 33 | +import java.util.List; |
| 34 | +import java.util.Map; |
| 35 | + |
| 36 | +import static org.hamcrest.Matchers.equalTo; |
| 37 | + |
| 38 | +/** |
| 39 | + * These tests create rows that are 1MB in size. Test classes |
| 40 | + * which extend AbstractScalarFunctionTestCase rerun test cases with |
| 41 | + * many randomized inputs. Unfortunately, tests are run with |
| 42 | + * limited memory, and instantiating many copies of these |
| 43 | + * tests with large rows causes out of memory. |
| 44 | + */ |
| 45 | +public class ReplaceStaticTests extends ESTestCase { |
| 46 | + |
| 47 | + public void testLimit() { |
| 48 | + int textLength = (int) ScalarFunction.MAX_BYTES_REF_RESULT_SIZE / 10; |
| 49 | + String text = randomAlphaOfLength((int) ScalarFunction.MAX_BYTES_REF_RESULT_SIZE / 10); |
| 50 | + String regex = "^(.+)$"; |
| 51 | + |
| 52 | + // 10 times the original text + the remainder |
| 53 | + String extraString = "a".repeat((int) ScalarFunction.MAX_BYTES_REF_RESULT_SIZE % 10); |
| 54 | + assert textLength * 10 + extraString.length() == ScalarFunction.MAX_BYTES_REF_RESULT_SIZE; |
| 55 | + String newStr = "$0$0$0$0$0$0$0$0$0$0" + extraString; |
| 56 | + |
| 57 | + String result = process(text, regex, newStr); |
| 58 | + assertThat(result, equalTo(newStr.replaceAll("\\$\\d", text))); |
| 59 | + } |
| 60 | + |
| 61 | + public void testTooBig() { |
| 62 | + String textAndNewStr = randomAlphaOfLength((int) (ScalarFunction.MAX_BYTES_REF_RESULT_SIZE / 10)); |
| 63 | + String regex = "."; |
| 64 | + |
| 65 | + String result = process(textAndNewStr, regex, textAndNewStr); |
| 66 | + assertNull(result); |
| 67 | + assertWarnings( |
| 68 | + "Line -1:-1: evaluation of [] failed, treating result as null. Only first 20 failures recorded.", |
| 69 | + "Line -1:-1: java.lang.IllegalArgumentException: " |
| 70 | + + "Creating strings with more than [" |
| 71 | + + ScalarFunction.MAX_BYTES_REF_RESULT_SIZE |
| 72 | + + "] bytes is not supported" |
| 73 | + ); |
| 74 | + } |
| 75 | + |
| 76 | + public void testTooBigWithGroups() { |
| 77 | + int textLength = (int) ScalarFunction.MAX_BYTES_REF_RESULT_SIZE / 10; |
| 78 | + String text = randomAlphaOfLength(textLength); |
| 79 | + String regex = "(.+)"; |
| 80 | + |
| 81 | + // 10 times the original text + the remainder + 1 |
| 82 | + String extraString = "a".repeat(1 + (int) ScalarFunction.MAX_BYTES_REF_RESULT_SIZE % 10); |
| 83 | + assert textLength * 10 + extraString.length() == ScalarFunction.MAX_BYTES_REF_RESULT_SIZE + 1; |
| 84 | + String newStr = "$0$1$0$1$0$1$0$1$0$1" + extraString; |
| 85 | + |
| 86 | + String result = process(text, regex, newStr); |
| 87 | + assertNull(result); |
| 88 | + assertWarnings( |
| 89 | + "Line -1:-1: evaluation of [] failed, treating result as null. Only first 20 failures recorded.", |
| 90 | + "Line -1:-1: java.lang.IllegalArgumentException: " |
| 91 | + + "Creating strings with more than [" |
| 92 | + + ScalarFunction.MAX_BYTES_REF_RESULT_SIZE |
| 93 | + + "] bytes is not supported" |
| 94 | + ); |
| 95 | + } |
| 96 | + |
| 97 | + public String process(String text, String regex, String newStr) { |
| 98 | + try ( |
| 99 | + var eval = AbstractScalarFunctionTestCase.evaluator( |
| 100 | + new Replace( |
| 101 | + Source.EMPTY, |
| 102 | + field("text", DataType.KEYWORD), |
| 103 | + field("regex", DataType.KEYWORD), |
| 104 | + field("newStr", DataType.KEYWORD) |
| 105 | + ) |
| 106 | + ).get(driverContext()); |
| 107 | + Block block = eval.eval(row(List.of(new BytesRef(text), new BytesRef(regex), new BytesRef(newStr)))); |
| 108 | + ) { |
| 109 | + return block.isNull(0) ? null : ((BytesRef) BlockUtils.toJavaObject(block, 0)).utf8ToString(); |
| 110 | + } |
| 111 | + } |
| 112 | + |
| 113 | + /** |
| 114 | + * The following fields and methods were borrowed from AbstractScalarFunctionTestCase |
| 115 | + */ |
| 116 | + private final List<CircuitBreaker> breakers = Collections.synchronizedList(new ArrayList<>()); |
| 117 | + |
| 118 | + private static Page row(List<Object> values) { |
| 119 | + return new Page(1, BlockUtils.fromListRow(TestBlockFactory.getNonBreakingInstance(), values)); |
| 120 | + } |
| 121 | + |
| 122 | + private static FieldAttribute field(String name, DataType type) { |
| 123 | + return new FieldAttribute(Source.synthetic(name), name, new EsField(name, type, Map.of(), true)); |
| 124 | + } |
| 125 | + |
| 126 | + private DriverContext driverContext() { |
| 127 | + BigArrays bigArrays = new MockBigArrays(PageCacheRecycler.NON_RECYCLING_INSTANCE, ByteSizeValue.ofMb(256)).withCircuitBreaking(); |
| 128 | + CircuitBreaker breaker = bigArrays.breakerService().getBreaker(CircuitBreaker.REQUEST); |
| 129 | + breakers.add(breaker); |
| 130 | + return new DriverContext(bigArrays, new BlockFactory(breaker, bigArrays)); |
| 131 | + } |
| 132 | + |
| 133 | + @After |
| 134 | + public void allMemoryReleased() { |
| 135 | + for (CircuitBreaker breaker : breakers) { |
| 136 | + assertThat(breaker.getUsed(), equalTo(0L)); |
| 137 | + } |
| 138 | + } |
| 139 | +} |
0 commit comments