Skip to content

Commit 1814f98

Browse files
authored
Fix deserialization of continuations for queries that contain LIKE (#3207)
Previously, it would parse the query pattern as the escape character, making continuations useless. i.e. `LIKE 'XYZ' ESCAPE 'XYZ'`, regardless of what the original query had for the escape character, if it had one. Because I was curious what happens if the query pattern was a single character, I added some additional tests, and ran them with the old code to see if it would break in different ways. It didn't, I think in part because: `LIKE 'Z' ESCAPE 'Z'` is the same as `LIKE 'Z'`. I don't know if that is correct behavior, but it is what we do. Even though these tests didn't expose other issues, I felt like they were worth keeping. This Resolves: #3099
1 parent ca1e4b6 commit 1814f98

File tree

6 files changed

+89
-14
lines changed

6 files changed

+89
-14
lines changed

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/query/plan/cascades/values/PatternForLikeValue.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ public PValue toValueProto(@Nonnull final PlanSerializationContext serialization
188188
public static PatternForLikeValue fromProto(@Nonnull final PlanSerializationContext serializationContext,
189189
@Nonnull final PPatternForLikeValue patternForLikeValueProto) {
190190
return new PatternForLikeValue(Value.fromValueProto(serializationContext, Objects.requireNonNull(patternForLikeValueProto.getPatternChild())),
191-
Value.fromValueProto(serializationContext, Objects.requireNonNull(patternForLikeValueProto.getPatternChild())));
191+
Value.fromValueProto(serializationContext, Objects.requireNonNull(patternForLikeValueProto.getEscapeChild())));
192192
}
193193

194194
@Nonnull

fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/query/plan/cascades/LikeOperatorValueTest.java

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@
2222

2323
import com.apple.foundationdb.record.Bindings;
2424
import com.apple.foundationdb.record.EvaluationContext;
25+
import com.apple.foundationdb.record.PlanHashable;
26+
import com.apple.foundationdb.record.PlanSerializationContext;
2527
import com.apple.foundationdb.record.TestRecords7Proto;
28+
import com.apple.foundationdb.record.planprotos.PLikeOperatorValue;
2629
import com.apple.foundationdb.record.query.plan.cascades.typing.Type;
2730
import com.apple.foundationdb.record.query.plan.cascades.typing.TypeRepository;
2831
import com.apple.foundationdb.record.query.plan.cascades.typing.Typed;
@@ -33,6 +36,7 @@
3336
import com.apple.foundationdb.record.query.plan.cascades.values.LikeOperatorValue;
3437
import com.apple.foundationdb.record.query.plan.cascades.values.Value;
3538
import com.apple.foundationdb.record.query.plan.plans.QueryResult;
39+
import com.apple.foundationdb.record.query.plan.serialization.DefaultPlanSerializationRegistry;
3640
import com.google.common.collect.ImmutableList;
3741
import org.junit.jupiter.api.Assertions;
3842
import org.junit.jupiter.api.extension.ExtensionContext;
@@ -41,6 +45,7 @@
4145
import org.junit.jupiter.params.provider.ArgumentsProvider;
4246
import org.junit.jupiter.params.provider.ArgumentsSource;
4347

48+
import javax.annotation.Nonnull;
4449
import java.util.Arrays;
4550
import java.util.Optional;
4651
import java.util.stream.Stream;
@@ -188,18 +193,38 @@ void testSemanticException(Value lhs, Value rhs, Value escapeChar) {
188193
}
189194

190195
@ParameterizedTest
191-
@SuppressWarnings({"rawtypes", "unchecked", "ConstantConditions"})
196+
@SuppressWarnings({"ConstantConditions"})
192197
@ArgumentsSource(ValidInputArgumentsProvider.class)
193198
void testLike(String lhs, String rhs, final String escapeChar, Boolean result) {
199+
final LikeOperatorValue value = createLikeOperatorValue(lhs, rhs, escapeChar);
200+
Assertions.assertEquals(result, value.eval(null, evaluationContext));
201+
}
202+
203+
@ParameterizedTest
204+
@SuppressWarnings({"ConstantConditions"})
205+
@ArgumentsSource(ValidInputArgumentsProvider.class)
206+
void testLikeSerialization(String lhs, String rhs, final String escapeChar, Boolean result) {
207+
final LikeOperatorValue value = createLikeOperatorValue(lhs, rhs, escapeChar);
208+
final PLikeOperatorValue proto = value.toProto(
209+
new PlanSerializationContext(new DefaultPlanSerializationRegistry(),
210+
PlanHashable.CURRENT_FOR_CONTINUATION));
211+
final LikeOperatorValue deserialized = LikeOperatorValue.fromProto(new PlanSerializationContext(new DefaultPlanSerializationRegistry(),
212+
PlanHashable.CURRENT_FOR_CONTINUATION), proto);
213+
Assertions.assertEquals(result, deserialized.eval(null, evaluationContext));
214+
}
215+
216+
217+
@SuppressWarnings({"rawtypes", "unchecked", "ConstantConditions"})
218+
@Nonnull
219+
private static LikeOperatorValue createLikeOperatorValue(final String lhs, final String rhs, final String escapeChar) {
194220
BuiltInFunction like = new LikeOperatorValue.LikeFn();
195221
BuiltInFunction pattern = new PatternForLikeValue.PatternForLikeFn();
196222
Typed value = like.encapsulate(Arrays.asList(
197-
new LiteralValue<>(Type.primitiveType(Type.TypeCode.STRING), lhs),
198-
pattern.encapsulate(Arrays.asList(
199-
new LiteralValue<>(Type.primitiveType(Type.TypeCode.STRING), rhs),
200-
new LiteralValue<>(Type.primitiveType(Type.TypeCode.STRING), escapeChar)))));
201-
Assertions.assertTrue(value instanceof LikeOperatorValue);
202-
Object actualValue = ((LikeOperatorValue)value).eval(null, evaluationContext);
203-
Assertions.assertEquals(result, actualValue);
223+
new LiteralValue<>(Type.primitiveType(Type.TypeCode.STRING), lhs),
224+
pattern.encapsulate(Arrays.asList(
225+
new LiteralValue<>(Type.primitiveType(Type.TypeCode.STRING), rhs),
226+
new LiteralValue<>(Type.primitiveType(Type.TypeCode.STRING), escapeChar)))));
227+
Assertions.assertInstanceOf(LikeOperatorValue.class, value);
228+
return (LikeOperatorValue) value;
204229
}
205230
}

yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/CustomYamlConstructor.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,11 @@ public static LinedObject cast(@Nonnull Object obj, @Nonnull Supplier<String> ms
132132
Assert.thatUnchecked(obj instanceof LinedObject, ErrorCode.INTERNAL_ERROR, msg);
133133
return (LinedObject) obj;
134134
}
135+
136+
@Override
137+
public String toString() {
138+
return object + "@line:" + lineNumber;
139+
}
135140
}
136141

137142
private static class ConstructIgnore extends AbstractConstruct {

yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/Block.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ public interface Block {
5353
*/
5454
static Block parse(@Nonnull Object document, int blockNumber, @Nonnull YamlExecutionContext executionContext) {
5555
final var blockObject = Matchers.map(document, "block");
56-
Assert.thatUnchecked(blockObject.size() == 1, "Illegal Format: A block is expected to be a map of size 1");
56+
Assert.thatUnchecked(blockObject.size() == 1,
57+
"Illegal Format: A block is expected to be a map of size 1 (block: " + blockNumber + ") keys: " + blockObject.keySet());
5758
final var entry = Matchers.firstEntry(blockObject, "block key-value");
5859
final var linedObject = CustomYamlConstructor.LinedObject.cast(entry.getKey(), () -> "Invalid block key-value pair: " + entry);
5960
final var lineNumber = linedObject.getLineNumber();

yaml-tests/src/test/java/YamlIntegrationTests.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,8 +211,6 @@ public void updateDeleteReturning(YamlTest.Runner runner) throws Exception {
211211
}
212212

213213
@TestTemplate
214-
@ExcludeYamlTestConfig(value = YamlTestConfigFilters.DO_NOT_FORCE_CONTINUATIONS,
215-
reason = "Like continuation failure (https://github.com/FoundationDB/fdb-record-layer/issues/3099)")
216214
void like(YamlTest.Runner runner) throws Exception {
217215
runner.runYamsql("like.yamsql");
218216
}

yaml-tests/src/test/resources/like.yamsql

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,16 @@
1717
# See the License for the specific language governing permissions and
1818
# limitations under the License.
1919

20+
---
21+
options:
22+
# Prior to #3099 being fixed, every query that returned any data failed if there are continuations
23+
# Without testing any continuations there's not a lot of value here to running with multi-server,
24+
# So this is just being disabled entirely for older versions.
25+
supported_version: !current_version # https://github.com/FoundationDB/fdb-record-layer/issues/3099
2026
---
2127
schema_template:
2228
create table A(a1 string, primary key(a1))
29+
create table B(b1 integer, b2 string, primary key(b1))
2330
---
2431
setup:
2532
steps:
@@ -39,11 +46,49 @@ setup:
3946
('{abcdefghijk}'),
4047
('^$'),
4148
('\\||%');
49+
- query: insert into B values
50+
(1, 'Y'),
51+
(2, 'Z'),
52+
(3, 'A'),
53+
(4, 'Z'),
54+
(5, 'B');
4255
---
43-
# TODO (Investigate `Missing binding for __const_CONSTANT` error with queries when using plan from cache)
4456
test_block:
57+
# TODO (Investigate `Missing binding for __const_CONSTANT` error with queries when using plan from cache)
4558
preset: single_repetition_parallelized
4659
tests:
60+
-
61+
- query: select * from B WHERE B2 LIKE 'X'
62+
- result: []
63+
-
64+
- query: select * from B WHERE B2 LIKE 'Y'
65+
- result: [{1, 'Y'}]
66+
-
67+
# Issue #3099 found that the pattern was being put in the escape part of the LIKE on continuation
68+
# which for all the other queries, would result in an error that the escape should be of length 1.
69+
# I added these queries to see if that issue would expose differently if the query was only one character,
70+
# and it kind of did, it ended up exposing issue: https://github.com/FoundationDB/fdb-record-layer/issues/3216
71+
- query: select * from B WHERE B2 NOT LIKE 'Z'
72+
- result: [{1, 'Y'}, {3, 'A'}, {5, 'B'}]
73+
-
74+
- query: select * from B WHERE B2 NOT LIKE 'Z' ESCAPE 'Z'
75+
# This should error; see https://github.com/FoundationDB/fdb-record-layer/issues/3216
76+
- result: [{1, 'Y'}, {3, 'A'}, {5, 'B'}]
77+
-
78+
- query: select * from B WHERE B2 NOT LIKE '\'
79+
# This should error; see https://github.com/FoundationDB/fdb-record-layer/issues/3216
80+
- result: [{1, 'Y'}, {2, 'Z'}, {3, 'A'}, {4, Z}, {5, 'B'}]
81+
-
82+
- query: select * from B WHERE B2 LIKE 'Z'
83+
- result: [{2, 'Z'}, {4, 'Z'}]
84+
---
85+
test_block:
86+
# TODO (Investigate `Missing binding for __const_CONSTANT` error with queries when using plan from cache)
87+
preset: single_repetition_parallelized
88+
tests:
89+
-
90+
- query: select * from A WHERE A1 LIKE 'A'
91+
- result: []
4792
-
4893
- query: select * from A WHERE A1 LIKE 'abc'
4994
- result: []
@@ -152,5 +197,6 @@ test_block:
152197
{'学校'},
153198
{'مدرسة'},
154199
{'^$'},
155-
{'\\||%'} ]
200+
{'\\||%'}
201+
]
156202
...

0 commit comments

Comments
 (0)