Skip to content

Commit 1c75a3e

Browse files
committed
feat(expression): add new SCEL functions
Add new expressions: concat, concat_ws, hash, md5
1 parent 3809af0 commit 1c75a3e

File tree

41 files changed

+1094
-761
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1094
-761
lines changed

connect-file-pulse-api/src/main/java/io/streamthoughts/kafka/connect/filepulse/data/TypedValue.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,10 @@ public boolean isNull() {
340340
return value == null;
341341
}
342342

343+
public boolean isNotNull() {
344+
return value != null;
345+
}
346+
343347
public boolean isEmpty() {
344348
final Type type = schema.get().type();
345349
if (Type.STRING == type) {

connect-file-pulse-expression/src/main/antlr4/io/streamthoughts/kafka/connect/filepulse/expression/parser/antlr4/ScELParser.g4

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,11 @@ functionDeclaration
6565
;
6666

6767
functionParameters
68-
: LPAREN functionObjectParameter (COMMA value)* RPAREN
68+
: LPAREN (functionObjectParameter (COMMA functionObjectParameter)*?)* RPAREN
6969
;
7070

7171
functionObjectParameter
72-
: expression
72+
: expression | value
7373
;
7474

7575

connect-file-pulse-expression/src/main/java/io/streamthoughts/kafka/connect/filepulse/expression/FunctionExpression.java

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,17 @@
2727

2828
public class FunctionExpression extends AbstractExpression {
2929

30-
private final Expression valueExpression;
3130
private final ExpressionFunctionExecutor functionExecutor;
3231

3332
/**
3433
* Creates a new {@link FunctionExpression} instance.
3534
*
3635
* @param originalExpression the original string expression.
37-
* @param valueExpression the expression value
3836
* @param functionExecutor the function to be apply on the acceded value.
3937
*/
4038
public FunctionExpression(final String originalExpression,
41-
final Expression valueExpression,
4239
final ExpressionFunctionExecutor functionExecutor) {
4340
super(originalExpression);
44-
this.valueExpression = valueExpression;
4541
this.functionExecutor = functionExecutor;
4642
}
4743

@@ -59,8 +55,7 @@ public TypedValue readValue(final EvaluationContext context) {
5955
@SuppressWarnings("unchecked")
6056
@Override
6157
public <T> T readValue(final EvaluationContext context, final Class<T> expectedType) {
62-
TypedValue returned = valueExpression.readValue(context, TypedValue.class);
63-
final Object evaluated = functionExecutor.execute(returned);
58+
final Object evaluated = functionExecutor.execute(context);
6459

6560
if (evaluated != null && expectedType.isAssignableFrom(evaluated.getClass())) {
6661
return (T)evaluated;
@@ -86,10 +81,6 @@ public boolean canWrite() {
8681
return false;
8782
}
8883

89-
public Expression getValueExpression() {
90-
return valueExpression;
91-
}
92-
9384
public ExpressionFunctionExecutor getFunctionExecutor() {
9485
return functionExecutor;
9586
}
@@ -98,7 +89,6 @@ public ExpressionFunctionExecutor getFunctionExecutor() {
9889
public String toString() {
9990
return "[" +
10091
"originalExpression=" + originalExpression() +
101-
", valueExpression=" + valueExpression +
10292
", function=" + functionExecutor +
10393
']';
10494
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2019-2020 StreamThoughts.
3+
*
4+
* Licensed to the Apache Software Foundation (ASF) under one or more
5+
* contributor license agreements. See the NOTICE file distributed with
6+
* this work for additional information regarding copyright ownership.
7+
* The ASF licenses this file to You under the Apache License, Version 2.0
8+
* (the "License"); you may not use this file except in compliance with
9+
* the License. You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
20+
package io.streamthoughts.kafka.connect.filepulse.expression.function;
21+
22+
import io.streamthoughts.kafka.connect.filepulse.expression.EvaluationContext;
23+
24+
import java.util.List;
25+
26+
public interface Argument {
27+
28+
String name();
29+
30+
Object value();
31+
32+
List<String> errorMessages();
33+
34+
boolean isValid();
35+
36+
Object evaluate(final EvaluationContext context) ;
37+
}

connect-file-pulse-expression/src/main/java/io/streamthoughts/kafka/connect/filepulse/expression/function/Arguments.java

Lines changed: 121 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,29 @@
1818
*/
1919
package io.streamthoughts.kafka.connect.filepulse.expression.function;
2020

21+
import io.streamthoughts.kafka.connect.filepulse.expression.EvaluationContext;
22+
23+
import java.util.Arrays;
2124
import java.util.Collections;
2225
import java.util.Iterator;
26+
import java.util.LinkedList;
27+
import java.util.List;
28+
import java.util.Objects;
29+
import java.util.Optional;
2330
import java.util.stream.StreamSupport;
2431

25-
public interface Arguments extends Iterable<ArgumentValue> {
32+
public class Arguments<T extends Argument> implements Iterable<T> {
33+
34+
@SafeVarargs
35+
public static <T extends Argument> Arguments<T> of(final T... arguments) {
36+
return new Arguments<>(Arrays.asList(arguments));
37+
}
2638

27-
static Arguments empty() {
28-
return new Arguments() {
39+
public static <T extends Argument> Arguments<T> empty() {
40+
return new Arguments<T>() {
2941
@Override
30-
public Iterator<ArgumentValue> iterator() {
31-
return Collections.<ArgumentValue>emptyList().iterator();
42+
public Iterator<T> iterator() {
43+
return Collections.emptyIterator();
3244
}
3345

3446
@Override
@@ -38,29 +50,120 @@ public String toString() {
3850
};
3951
}
4052

41-
default boolean valid() {
53+
private final List<T> arguments;
54+
55+
/**
56+
* Creates a new {@link Arguments} instance.
57+
*/
58+
public Arguments() {
59+
this(new LinkedList<>());
60+
}
61+
62+
/**
63+
* Creates a new {@link Arguments} instance.
64+
*
65+
* @param argument the single argument.
66+
*/
67+
public Arguments(final T argument) {
68+
this(Collections.singletonList(argument));
69+
}
70+
71+
/**
72+
* Creates a new {@link Arguments} instance.
73+
* @param arguments the list of arguments.
74+
*
75+
*/
76+
public Arguments(final List<T> arguments) {
77+
this.arguments = arguments;
78+
}
79+
80+
private Arguments<T> add(final T argument) {
81+
arguments.add(argument);
82+
return this;
83+
}
84+
85+
/**
86+
* Returns the argument at the specified position in this list.
87+
*
88+
* @param index index of the argument to return
89+
* @return the argument at the specified position in this list
90+
* @throws IndexOutOfBoundsException if the index is out of range
91+
* ({@code index < 0 || index >= size()})
92+
*/
93+
public T get(final int index) {
94+
return arguments.get(index);
95+
}
96+
97+
public List<T> get(final int index, final int to) {
98+
return arguments.subList(index, to);
99+
}
100+
101+
public int size() {
102+
return arguments.size();
103+
}
104+
105+
@SuppressWarnings("unchecked")
106+
public <V> V valueOf(final String name) {
107+
Objects.requireNonNull(name, "name cannot be null");
108+
Optional<Object> value = arguments
109+
.stream()
110+
.filter(a -> a.name().equals(name))
111+
.findFirst()
112+
.map(Argument::value);
113+
if (value.isPresent()) return (V) value.get();
114+
115+
throw new IllegalArgumentException("No argument with name '" + name + "'");
116+
}
117+
118+
Arguments<GenericArgument> evaluate(final EvaluationContext context) {
119+
Arguments<GenericArgument> evaluated = new Arguments<>();
120+
for (T arg : arguments) {
121+
Object value = arg.evaluate(context);
122+
evaluated.add(new GenericArgument<>(arg.name(), value));
123+
}
124+
return evaluated;
125+
}
126+
127+
public boolean valid() {
42128
return StreamSupport
43129
.stream(this.spliterator(), true)
44-
.allMatch(ArgumentValue::isValid);
130+
.allMatch(Argument::isValid);
45131
}
46132

47-
default String buildErrorMessage() {
133+
String buildErrorMessage() {
48134
final StringBuilder errors = new StringBuilder();
49-
for (ArgumentValue value : this) {
135+
for (T value : arguments) {
50136
if (!value.errorMessages().isEmpty()) {
51-
for (String error : value.errorMessages()) {
137+
List<String> errorMessages = value.errorMessages();
138+
for (String error : errorMessages) {
52139
errors
53-
.append("\n\t")
54-
.append("Invalid argument with name='")
55-
.append(value.name()).append("'")
56-
.append(", value=")
57-
.append("'").append(value.value()).append("'")
58-
.append(" - ")
59-
.append(error)
60-
.append("\n\t");
140+
.append("\n\t")
141+
.append("Invalid argument with name='")
142+
.append(value.name()).append("'")
143+
.append(", value=")
144+
.append("'").append(value.value()).append("'")
145+
.append(" - ")
146+
.append(error)
147+
.append("\n\t");
61148
}
62149
}
63150
}
64151
return errors.toString();
65152
}
153+
154+
/**
155+
* {@inheritDoc}
156+
*/
157+
@Override
158+
public Iterator<T> iterator() {
159+
return arguments.iterator();
160+
}
161+
162+
/**
163+
* {@inheritDoc}
164+
*/
165+
@Override
166+
public String toString() {
167+
return arguments.toString();
168+
}
66169
}
Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,29 +16,29 @@
1616
* See the License for the specific language governing permissions and
1717
* limitations under the License.
1818
*/
19+
1920
package io.streamthoughts.kafka.connect.filepulse.expression.function;
2021

21-
import io.streamthoughts.kafka.connect.filepulse.data.Type;
2222
import io.streamthoughts.kafka.connect.filepulse.data.TypedValue;
23+
import io.streamthoughts.kafka.connect.filepulse.expression.EvaluationContext;
24+
import io.streamthoughts.kafka.connect.filepulse.expression.Expression;
2325

24-
public abstract class TypedExpressionFunction<E, T extends Arguments> implements ExpressionFunction<T> {
25-
26-
private final Type accept;
26+
public class ExpressionArgument extends GenericArgument<Expression> {
2727

2828
/**
29-
* Creates a new {@link TypedExpressionFunction} instance.
30-
*
31-
* @param accept the {@link Type} which is accepted.
29+
* Creates a new {@link ExpressionArgument} instance.
30+
* @param name the argument name.
31+
* @param expression the argument expression.
3232
*/
33-
protected TypedExpressionFunction(final Type accept) {
34-
this.accept = accept;
33+
public ExpressionArgument(final String name, final Expression expression) {
34+
super(name, expression);
3535
}
3636

3737
/**
3838
* {@inheritDoc}
3939
*/
4040
@Override
41-
public boolean accept(final TypedValue value) {
42-
return accept.equals(value.type());
41+
public TypedValue evaluate(EvaluationContext context) {
42+
return value().readValue(context, TypedValue.class);
4343
}
4444
}

connect-file-pulse-expression/src/main/java/io/streamthoughts/kafka/connect/filepulse/expression/function/ExpressionFunction.java

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,16 @@
1919
package io.streamthoughts.kafka.connect.filepulse.expression.function;
2020

2121
import io.streamthoughts.kafka.connect.filepulse.data.TypedValue;
22+
import io.streamthoughts.kafka.connect.filepulse.expression.Expression;
2223
import org.apache.kafka.connect.data.SchemaAndValue;
2324

25+
import java.util.ArrayList;
26+
import java.util.List;
27+
2428
/**
2529
* Default interface to define a function that can be used into an expression.
26-
*
27-
* @param <T> the type of {@link Arguments}.
2830
*/
29-
public interface ExpressionFunction<T extends Arguments> {
31+
public interface ExpressionFunction {
3032

3133
/**
3234
* Returns the case-insensitive function name.
@@ -38,32 +40,38 @@ default String name() {
3840
}
3941

4042
/**
41-
* Prepares the arguments that will be passed to {@link #apply(TypedValue, Arguments)}.
43+
* Prepares the arguments that will be passed to {@link #validate(Arguments)}.
4244
*
4345
* @param args list of {@link TypedValue} arguments.
4446
* @return an instance of {@link Arguments}.
4547
*/
46-
T prepare(final TypedValue[] args);
48+
default Arguments<?> prepare(final Expression[] args) {
49+
if (args.length == 0) return Arguments.empty();
50+
List<Argument> arguments = new ArrayList<>();
51+
for (int i = 0; i < args.length; i++) {
52+
arguments.add(new ExpressionArgument(String.valueOf(i), args[i]));
53+
}
54+
return new Arguments<>(arguments);
55+
}
4756

4857
/**
49-
* Checks whether this function accepts the specified value.
58+
* Checks whether this function accepts the given arguments.
5059
*
51-
* @param value the value to be checked.
52-
* @return {@code true} if this function can be executed on the value.
60+
* @param arguments the arguments value to be checked.
61+
* @return {@code true} if this function can be executed with the given arguments.
5362
*/
54-
default boolean accept(final TypedValue value) {
55-
return true;
63+
default Arguments<GenericArgument> validate(final Arguments<GenericArgument> arguments) {
64+
return arguments;
5665
}
5766

5867
/**
5968
* Executes the function on the specified value for the specified arguments.
6069
*
61-
* @param field the field on which to apply the function.
6270
* @param args the function arguments.
6371
*
6472
* @return a new {@link SchemaAndValue}.
6573
*/
66-
TypedValue apply(final TypedValue field, final T args);
74+
TypedValue apply(final Arguments<GenericArgument> args);
6775

6876
static String functionNameFor(final ExpressionFunction function) {
6977
// simple class name conversion to camelCase

0 commit comments

Comments
 (0)