Skip to content

Commit fbe8b6f

Browse files
lucas-dclrcqfhussonnois
authored andcommitted
feat(scel-api): add is_empty expression for string/array fields
1 parent 8d266f1 commit fbe8b6f

File tree

5 files changed

+113
-32
lines changed

5 files changed

+113
-32
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,11 @@
3535
import io.streamthoughts.kafka.connect.filepulse.expression.function.objects.Exists;
3636
import io.streamthoughts.kafka.connect.filepulse.expression.function.objects.IsNull;
3737
import io.streamthoughts.kafka.connect.filepulse.expression.function.objects.Nlv;
38-
import io.streamthoughts.kafka.connect.filepulse.expression.function.strings.Concat;
3938
import io.streamthoughts.kafka.connect.filepulse.expression.function.strings.ConcatWs;
39+
import io.streamthoughts.kafka.connect.filepulse.expression.function.strings.Concat;
4040
import io.streamthoughts.kafka.connect.filepulse.expression.function.strings.EndsWith;
4141
import io.streamthoughts.kafka.connect.filepulse.expression.function.strings.Hash;
42+
import io.streamthoughts.kafka.connect.filepulse.expression.function.strings.IsEmpty;
4243
import io.streamthoughts.kafka.connect.filepulse.expression.function.strings.Length;
4344
import io.streamthoughts.kafka.connect.filepulse.expression.function.strings.Lowercase;
4445
import io.streamthoughts.kafka.connect.filepulse.expression.function.strings.Matches;
@@ -88,6 +89,7 @@ private ExpressionFunctionExecutors() {
8889
register(new Exists());
8990
register(new Equals());
9091
register(new Trim());
92+
register(new IsEmpty());
9193
register(new ReplaceAll());
9294
register(new Uuid());
9395
register(new Concat());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2019-2021 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+
package io.streamthoughts.kafka.connect.filepulse.expression.function.strings;
20+
21+
import io.streamthoughts.kafka.connect.filepulse.data.Type;
22+
import io.streamthoughts.kafka.connect.filepulse.data.TypedValue;
23+
import io.streamthoughts.kafka.connect.filepulse.expression.ExpressionException;
24+
import io.streamthoughts.kafka.connect.filepulse.expression.function.AbstractTransformExpressionFunction;
25+
26+
public class IsEmpty extends AbstractTransformExpressionFunction {
27+
28+
29+
/**
30+
* {@inheritDoc}
31+
*/
32+
@Override
33+
public TypedValue transform(final TypedValue value) {
34+
if (value.type() != Type.ARRAY && value.type() != Type.STRING) {
35+
throw new ExpressionException("Expected type [ARRAY|STRING], was " + value.type());
36+
}
37+
38+
int size = value.type() == Type.ARRAY ? value.getArray().size() : value.getString().length();
39+
return TypedValue.bool(size == 0);
40+
}
41+
}

connect-file-pulse-expression/src/test/java/io/streamthoughts/kafka/connect/filepulse/expression/accessor/MapAdaptablePropertyAccessorTest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ public void should_read_property_from_map_given_simple_key() {
3636
Assert.assertEquals("value", value);
3737
}
3838

39+
@Test
40+
public void should_read_property_from_map_given_simple_keyeee() {
41+
MapAdaptablePropertyAccessor accessor = new MapAdaptablePropertyAccessor();
42+
Object value = accessor.read(context, Map.of("key", "value"), "key");
43+
Assert.assertEquals("value", value);
44+
}
45+
3946
@Test
4047
public void should_read_property_from_map_given_dotted_key() {
4148
MapAdaptablePropertyAccessor accessor = new MapAdaptablePropertyAccessor();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package io.streamthoughts.kafka.connect.filepulse.expression.function.strings;
2+
3+
import io.streamthoughts.kafka.connect.filepulse.data.Type;
4+
import io.streamthoughts.kafka.connect.filepulse.data.TypedValue;
5+
import io.streamthoughts.kafka.connect.filepulse.expression.Expression;
6+
import io.streamthoughts.kafka.connect.filepulse.expression.StandardEvaluationContext;
7+
import org.junit.Assert;
8+
import org.junit.Test;
9+
10+
import static io.streamthoughts.kafka.connect.filepulse.expression.parser.ExpressionParsers.parseExpression;
11+
12+
public class IsEmptyTest {
13+
private static final StandardEvaluationContext EMPTY_CONTEXT = new StandardEvaluationContext(new Object());
14+
15+
@Test
16+
public void should_return_false_with_not_empty_field() {
17+
Expression expression = parseExpression("{{ is_empty('notEmpty') }}");
18+
TypedValue result = expression.readValue(EMPTY_CONTEXT, TypedValue.class);
19+
Assert.assertEquals(Type.BOOLEAN, result.type());
20+
Assert.assertEquals(result.value(), false);
21+
}
22+
23+
@Test
24+
public void should_return_true_with_empty_field() {
25+
Expression expression = parseExpression("{{ is_empty('') }}");
26+
TypedValue result = expression.readValue(EMPTY_CONTEXT, TypedValue.class);
27+
Assert.assertEquals(Type.BOOLEAN, result.type());
28+
Assert.assertEquals(result.value(), true);
29+
}
30+
}

docs/content/en/docs/Developer Guide/accessing-data-and-metadata.md

Lines changed: 32 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -68,38 +68,39 @@ Note the use of double-quotes to define a substitution expressions
6868

6969
ScEL supports a number of predefined functions that can be used to apply a single transformation on a field.
7070

71-
| Function | Since | Description | Syntax |
72-
| -----------------|---------|---------------|-----------|
73-
| `and` | `2.4.0` | Checks if all of the given conditional expressions are `true`. | `{{ and(booleanExpression1, booleanExpression2, ...) }}` |
74-
| `concat` | | Concatenate two or more string expressions. | `{{ concat(expr1, expr2, ...) }}` |
75-
| `concat_ws` | | Concatenate two or more string expressions, using the specified separator between each. | `{{ concat_ws(separator, prefix, suffix, expr1, expr2, ...) }}` |
76-
| `contains` | | Returns `true` if an array field's value contains the specified value | `{{ contains(array, 'value') }}` |
77-
| `converts` | | Converts a field's value into the specified type | `{{ converts(field_expr, INTEGER) }}` |
78-
| `ends_with` | | Returns `true` if a string field's value end with the specified string suffix | `{{ ends_with(field_expr, 'suffix') }}` |
79-
| `equals` | | Returns `true` if a string or number fields's value equals the specified value | `{{ equals(field_expr, value) }}` |
80-
| `exists` | | Returns `true` if an object has the specified field | `{{ exists(obj_expr, field_expr) }}` |
81-
| `extract_array` | | Returns the element at the specified position of the specified array | `{{ extract_array(array, 0) }}` |
82-
| `gt` | `2.4.0` | Executes "*greater than operation*" on two values and returns `true` if the first value is greater than the second value, `false`, otherwise. | `{{ gt(expressionValue1, expressionValue2) }}` |
83-
| `hash` | | Hash a given string expression, using murmur2 algorithm | `{{ hash(field_expr) }}` |
84-
| `if` | `2.4.0` | Evaluates the given boolean expression and returns one value if `true` and another value if `false`. | `{{ if(booleanExpression, valueIfTrue, valueIfFalse ) }}` |
85-
| `is_null` | | Returns `true` if a field's value is null | `{{ is_null(field) }}` |
86-
| `length` | | Returns the number of elements into an array of the length of an string field | `{{ length(array) }}` |
87-
| `lt` | `2.4.0` | Executes "*less than operation*" on two values and returns `true` if the first value is less than the second value, `false`, otherwise. | `{{ lt(expressionValue1, expressionValue2) }}` |
88-
| `lowercase` | | Converts all of the characters in a string field's value to lower case | `{{ lowercase(field) }}` |
89-
| `matches` | | Returns `true` if a field's value match the specified regex | `{{ matches(field_expr, 'regex') }}` |
90-
| `md5` | | Computes the MD5 hash of string expression | `{{ md5(field_expr) }}` |
91-
| `nlv` | | Sets a default value if a field's value is null | `{{ length(array) }}` |
92-
| `not` | `2.4.0` | Reverses a boolean value | `{{ not(booleanExpression) }}` |
93-
| `or` | `2.4.0` | Checks if at least one of the given conditional expressions is `true`.. | `{{ or(booleanExpression1, booleanExpression2, ...) }}` |
94-
| `replace_all ` | | Replaces every subsequence of the field's value that matches the given pattern with the given replacement string. | `{{ replace_all(field_expr, 'regex', 'replacement') }}` |
95-
| `split` | | Split a string field's value into an array using the specified regex or character | `{{ split(field_expr, regex) }}` or `{{ split(field_expr, regex, limit) }}` |
96-
| `starts_with` | | Returns `true` if an a string field's value start with the specified string prefix | `{{ starts_with(field_expr, 'prefix') }}` |
71+
| Function | Since | Description | Syntax |
72+
|------------------|---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|
73+
| `and` | `2.4.0` | Checks if all of the given conditional expressions are `true`. | `{{ and(booleanExpression1, booleanExpression2, ...) }}` |
74+
| `concat` | | Concatenate two or more string expressions. | `{{ concat(expr1, expr2, ...) }}` |
75+
| `concat_ws` | | Concatenate two or more string expressions, using the specified separator between each. | `{{ concat_ws(separator, prefix, suffix, expr1, expr2, ...) }}` |
76+
| `contains` | | Returns `true` if an array field's value contains the specified value | `{{ contains(array, 'value') }}` |
77+
| `converts` | | Converts a field's value into the specified type | `{{ converts(field_expr, INTEGER) }}` |
78+
| `ends_with` | | Returns `true` if a string field's value end with the specified string suffix | `{{ ends_with(field_expr, 'suffix') }}` |
79+
| `equals` | | Returns `true` if a string or number fields's value equals the specified value | `{{ equals(field_expr, value) }}` |
80+
| `exists` | | Returns `true` if an object has the specified field | `{{ exists(obj_expr, field_expr) }}` |
81+
| `extract_array` | | Returns the element at the specified position of the specified array | `{{ extract_array(array, 0) }}` |
82+
| `gt` | `2.4.0` | Executes "*greater than operation*" on two values and returns `true` if the first value is greater than the second value, `false`, otherwise. | `{{ gt(expressionValue1, expressionValue2) }}` |
83+
| `hash` | | Hash a given string expression, using murmur2 algorithm | `{{ hash(field_expr) }}` |
84+
| `if` | `2.4.0` | Evaluates the given boolean expression and returns one value if `true` and another value if `false`. | `{{ if(booleanExpression, valueIfTrue, valueIfFalse ) }}` |
85+
| `is_null` | | Returns `true` if a field's value is null | `{{ is_null(field) }}` |
86+
| `is_empty` | | Returns `true` if an array as no elements or a string field has no characters | `{{ is_null(field) }}` |
87+
| `length` | | Returns the number of elements into an array or the length of a string field | `{{ length(array) }}` |
88+
| `lt` | `2.4.0` | Executes "*less than operation*" on two values and returns `true` if the first value is less than the second value, `false`, otherwise. | `{{ lt(expressionValue1, expressionValue2) }}` |
89+
| `lowercase` | | Converts all of the characters in a string field's value to lower case | `{{ lowercase(field) }}` |
90+
| `matches` | | Returns `true` if a field's value match the specified regex | `{{ matches(field_expr, 'regex') }}` |
91+
| `md5` | | Computes the MD5 hash of string expression | `{{ md5(field_expr) }}` |
92+
| `nlv` | | Sets a default value if a field's value is null | `{{ length(array) }}` |
93+
| `not` | `2.4.0` | Reverses a boolean value | `{{ not(booleanExpression) }}` |
94+
| `or` | `2.4.0` | Checks if at least one of the given conditional expressions is `true`.. | `{{ or(booleanExpression1, booleanExpression2, ...) }}` |
95+
| `replace_all ` | | Replaces every subsequence of the field's value that matches the given pattern with the given replacement string. | `{{ replace_all(field_expr, 'regex', 'replacement') }}` |
96+
| `split` | | Split a string field's value into an array using the specified regex or character | `{{ split(field_expr, regex) }}` or `{{ split(field_expr, regex, limit) }}` |
97+
| `starts_with` | | Returns `true` if an a string field's value start with the specified string prefix | `{{ starts_with(field_expr, 'prefix') }}` |
9798
| `timestamp_diff` | `2.4.0` | Calculates the amount of time between two epoch times in seconds or milliseconds. For more information on `unit` see [ChronoUnit](https://docs.oracle.com/javase/8/docs/api/java/time/temporal/ChronoUnit.html). | `{{ timestamp_diff(unit, epoch_time_expression1, epoch_time_expression2) }}` |
98-
| `to_timestamp` | `2.4.0` | Parses a given string value and returns the epoch-time in milliseconds. | `{{ to_timestamp(datetime_expression, pattern [, timezone]) }}` |
99-
| `trim` | | Trims the spaces from the beginning and end of a string. | `{{ trim(field_expr) }}` |
100-
| `unix_timestamp` | `2.4.0` | Returns the current time in milliseconds. | `{{ unix_timestamp() }}` |
101-
| `uppercase` | | Converts all of the characters in a string field's value to upper case | `{{ uppercase(field_expr) }}` |
102-
| `uuid` | | Create a Universally Unique Identifier (UUID) | `{{ uuid() }}` |
99+
| `to_timestamp` | `2.4.0` | Parses a given string value and returns the epoch-time in milliseconds. | `{{ to_timestamp(datetime_expression, pattern [, timezone]) }}` |
100+
| `trim` | | Trims the spaces from the beginning and end of a string. | `{{ trim(field_expr) }}` |
101+
| `unix_timestamp` | `2.4.0` | Returns the current time in milliseconds. | `{{ unix_timestamp() }}` |
102+
| `uppercase` | | Converts all of the characters in a string field's value to upper case | `{{ uppercase(field_expr) }}` |
103+
| `uuid` | | Create a Universally Unique Identifier (UUID) | `{{ uuid() }}` |
103104

104105

105106
In addition, ScEL supports nested functions.

0 commit comments

Comments
 (0)