Skip to content

Commit

Permalink
Add CIDR function to PPL (opensearch-project#3036) (opensearch-projec…
Browse files Browse the repository at this point in the history
…t#3110)

Signed-off-by: currantw <[email protected]>
  • Loading branch information
currantw authored Dec 3, 2024
1 parent 9d9730b commit 6712526
Show file tree
Hide file tree
Showing 42 changed files with 623 additions and 254 deletions.
1 change: 1 addition & 0 deletions core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ dependencies {
api group: 'com.google.code.gson', name: 'gson', version: '2.8.9'
api group: 'com.tdunning', name: 't-digest', version: '3.3'
api project(':common')
implementation "com.github.seancfoley:ipaddress:5.4.2"

testImplementation('org.junit.jupiter:junit-jupiter:5.9.3')
testImplementation group: 'org.hamcrest', name: 'hamcrest-library', version: '2.1'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ public class QueryEngineException extends RuntimeException {
public QueryEngineException(String message) {
super(message);
}

public QueryEngineException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@

/** Semantic Check Exception. */
public class SemanticCheckException extends QueryEngineException {

public SemanticCheckException(String message) {
super(message);
}

public SemanticCheckException(String message, Throwable cause) {
super(message, cause);
}
}
4 changes: 4 additions & 0 deletions core/src/main/java/org/opensearch/sql/expression/DSL.java
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,10 @@ public static FunctionExpression regexp(Expression... expressions) {
return compile(FunctionProperties.None, BuiltinFunctionName.REGEXP, expressions);
}

public static FunctionExpression cidrmatch(Expression... expressions) {
return compile(FunctionProperties.None, BuiltinFunctionName.CIDRMATCH, expressions);
}

public static FunctionExpression concat(Expression... expressions) {
return compile(FunctionProperties.None, BuiltinFunctionName.CONCAT, expressions);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
* <em>count</em> accepts values of all types.
*/
@UtilityClass
public class AggregatorFunction {
public class AggregatorFunctions {
/**
* Register Aggregation Function.
*
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ public enum BuiltinFunctionName {
/** Text Functions. */
TOSTRING(FunctionName.of("tostring")),

/** IP Functions. */
CIDRMATCH(FunctionName.of("cidrmatch")),

/** Arithmetic Operators. */
ADD(FunctionName.of("+")),
ADDFUNCTION(FunctionName.of("add")),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,17 @@
import org.opensearch.sql.data.type.ExprType;
import org.opensearch.sql.exception.ExpressionEvaluationException;
import org.opensearch.sql.expression.Expression;
import org.opensearch.sql.expression.aggregation.AggregatorFunction;
import org.opensearch.sql.expression.datetime.DateTimeFunction;
import org.opensearch.sql.expression.aggregation.AggregatorFunctions;
import org.opensearch.sql.expression.datetime.DateTimeFunctions;
import org.opensearch.sql.expression.datetime.IntervalClause;
import org.opensearch.sql.expression.operator.arthmetic.ArithmeticFunction;
import org.opensearch.sql.expression.operator.arthmetic.MathematicalFunction;
import org.opensearch.sql.expression.operator.convert.TypeCastOperator;
import org.opensearch.sql.expression.operator.predicate.BinaryPredicateOperator;
import org.opensearch.sql.expression.operator.predicate.UnaryPredicateOperator;
import org.opensearch.sql.expression.ip.IPFunctions;
import org.opensearch.sql.expression.operator.arthmetic.ArithmeticFunctions;
import org.opensearch.sql.expression.operator.arthmetic.MathematicalFunctions;
import org.opensearch.sql.expression.operator.convert.TypeCastOperators;
import org.opensearch.sql.expression.operator.predicate.BinaryPredicateOperators;
import org.opensearch.sql.expression.operator.predicate.UnaryPredicateOperators;
import org.opensearch.sql.expression.system.SystemFunctions;
import org.opensearch.sql.expression.text.TextFunction;
import org.opensearch.sql.expression.text.TextFunctions;
import org.opensearch.sql.expression.window.WindowFunctions;
import org.opensearch.sql.storage.StorageEngine;

Expand Down Expand Up @@ -69,18 +70,19 @@ public static synchronized BuiltinFunctionRepository getInstance() {
instance = new BuiltinFunctionRepository(new HashMap<>());

// Register all built-in functions
ArithmeticFunction.register(instance);
BinaryPredicateOperator.register(instance);
MathematicalFunction.register(instance);
UnaryPredicateOperator.register(instance);
AggregatorFunction.register(instance);
DateTimeFunction.register(instance);
ArithmeticFunctions.register(instance);
BinaryPredicateOperators.register(instance);
MathematicalFunctions.register(instance);
UnaryPredicateOperators.register(instance);
AggregatorFunctions.register(instance);
DateTimeFunctions.register(instance);
IntervalClause.register(instance);
WindowFunctions.register(instance);
TextFunction.register(instance);
TypeCastOperator.register(instance);
TextFunctions.register(instance);
TypeCastOperators.register(instance);
SystemFunctions.register(instance);
OpenSearchFunctions.register(instance);
IPFunctions.register(instance);
}
return instance;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public Pair<FunctionSignature, FunctionBuilder> resolve(FunctionSignature unreso
&& !FunctionSignature.isVarArgFunction(bestMatchEntry.getValue().getParamTypeList())) {
throw new ExpressionEvaluationException(
String.format(
"%s function expected %s, but get %s",
"%s function expected %s, but got %s",
functionName,
formatFunctions(functionBundle.keySet()),
unresolvedSignature.formatTypes()));
Expand Down
105 changes: 105 additions & 0 deletions core/src/main/java/org/opensearch/sql/expression/ip/IPFunctions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.sql.expression.ip;

import static org.opensearch.sql.data.type.ExprCoreType.BOOLEAN;
import static org.opensearch.sql.data.type.ExprCoreType.STRING;
import static org.opensearch.sql.expression.function.FunctionDSL.define;
import static org.opensearch.sql.expression.function.FunctionDSL.impl;
import static org.opensearch.sql.expression.function.FunctionDSL.nullMissingHandling;

import inet.ipaddr.AddressStringException;
import inet.ipaddr.IPAddressString;
import inet.ipaddr.IPAddressStringParameters;
import lombok.experimental.UtilityClass;
import org.opensearch.sql.data.model.ExprValue;
import org.opensearch.sql.data.model.ExprValueUtils;
import org.opensearch.sql.exception.SemanticCheckException;
import org.opensearch.sql.expression.function.BuiltinFunctionName;
import org.opensearch.sql.expression.function.BuiltinFunctionRepository;
import org.opensearch.sql.expression.function.DefaultFunctionResolver;

/** Utility class that defines and registers IP functions. */
@UtilityClass
public class IPFunctions {

public void register(BuiltinFunctionRepository repository) {
repository.register(cidrmatch());
}

private DefaultFunctionResolver cidrmatch() {

// TODO #3145: Add support for IP address data type.
return define(
BuiltinFunctionName.CIDRMATCH.getName(),
impl(nullMissingHandling(IPFunctions::exprCidrMatch), BOOLEAN, STRING, STRING));
}

/**
* Returns whether the given IP address is within the specified inclusive CIDR IP address range.
* Supports both IPv4 and IPv6 addresses.
*
* @param addressExprValue IP address as a string (e.g. "198.51.100.14" or
* "2001:0db8::ff00:42:8329").
* @param rangeExprValue IP address range in CIDR notation as a string (e.g. "198.51.100.0/24" or
* "2001:0db8::/32")
* @return true if the address is in the range; otherwise false.
* @throws SemanticCheckException if the address or range is not valid, or if they do not use the
* same version (IPv4 or IPv6).
*/
private ExprValue exprCidrMatch(ExprValue addressExprValue, ExprValue rangeExprValue) {

// TODO #3145: Update to support IP address data type.
String addressString = addressExprValue.stringValue();
String rangeString = rangeExprValue.stringValue();

final IPAddressStringParameters validationOptions =
new IPAddressStringParameters.Builder()
.allowEmpty(false)
.setEmptyAsLoopback(false)
.allow_inet_aton(false)
.allowSingleSegment(false)
.toParams();

// Get and validate IP address.
IPAddressString address =
new IPAddressString(addressExprValue.stringValue(), validationOptions);

try {
address.validate();
} catch (AddressStringException e) {
String msg =
String.format(
"IP address '%s' is not valid. Error details: %s", addressString, e.getMessage());
throw new SemanticCheckException(msg, e);
}

// Get and validate CIDR IP address range.
IPAddressString range = new IPAddressString(rangeExprValue.stringValue(), validationOptions);

try {
range.validate();
} catch (AddressStringException e) {
String msg =
String.format(
"CIDR IP address range '%s' is not valid. Error details: %s",
rangeString, e.getMessage());
throw new SemanticCheckException(msg, e);
}

// Address and range must use the same IP version (IPv4 or IPv6).
if (address.isIPv4() ^ range.isIPv4()) {
String msg =
String.format(
"IP address '%s' and CIDR IP address range '%s' are not compatible. Both must be"
+ " either IPv4 or IPv6.",
addressString, rangeString);
throw new SemanticCheckException(msg);
}

return ExprValueUtils.booleanValue(range.contains(address));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
* module, Accepts two numbers and produces a number.
*/
@UtilityClass
public class ArithmeticFunction {
public class ArithmeticFunctions {
/**
* Register Arithmetic Function.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
import org.opensearch.sql.expression.function.SerializableFunction;

@UtilityClass
public class MathematicalFunction {
public class MathematicalFunctions {
/**
* Register Mathematical Functions.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@
import org.opensearch.sql.expression.function.FunctionDSL;

@UtilityClass
public class TypeCastOperator {
public class TypeCastOperators {

/** Register Type Cast Operator. */
public static void register(BuiltinFunctionRepository repository) {
repository.register(castToString());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
* equalTo, Compare the left expression and right expression and produces a Boolean.
*/
@UtilityClass
public class BinaryPredicateOperator {
public class BinaryPredicateOperators {
/**
* Register Binary Predicate Function.
*
Expand Down Expand Up @@ -401,7 +401,7 @@ private static DefaultFunctionResolver notLike() {
BuiltinFunctionName.NOT_LIKE.getName(),
impl(
nullMissingHandling(
(v1, v2) -> UnaryPredicateOperator.not(OperatorUtils.matches(v1, v2))),
(v1, v2) -> UnaryPredicateOperators.not(OperatorUtils.matches(v1, v2))),
BOOLEAN,
STRING,
STRING));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
* The definition of unary predicate function not, Accepts one Boolean value and produces a Boolean.
*/
@UtilityClass
public class UnaryPredicateOperator {
public class UnaryPredicateOperators {

/** Register Unary Predicate Function. */
public static void register(BuiltinFunctionRepository repository) {
repository.register(not());
Expand All @@ -45,7 +46,7 @@ public static void register(BuiltinFunctionRepository repository) {
private static DefaultFunctionResolver not() {
return FunctionDSL.define(
BuiltinFunctionName.NOT.getName(),
FunctionDSL.impl(UnaryPredicateOperator::not, BOOLEAN, BOOLEAN));
FunctionDSL.impl(UnaryPredicateOperators::not, BOOLEAN, BOOLEAN));
}

/**
Expand Down Expand Up @@ -108,11 +109,10 @@ private static DefaultFunctionResolver ifFunction() {
org.apache.commons.lang3.tuple.Pair<FunctionSignature, FunctionBuilder>>>
functionsOne =
typeList.stream()
.map(v -> impl((UnaryPredicateOperator::exprIf), v, BOOLEAN, v, v))
.map(v -> impl((UnaryPredicateOperators::exprIf), v, BOOLEAN, v, v))
.collect(Collectors.toList());

DefaultFunctionResolver functionResolver = FunctionDSL.define(functionName, functionsOne);
return functionResolver;
return FunctionDSL.define(functionName, functionsOne);
}

private static DefaultFunctionResolver ifNull() {
Expand All @@ -125,43 +125,40 @@ private static DefaultFunctionResolver ifNull() {
org.apache.commons.lang3.tuple.Pair<FunctionSignature, FunctionBuilder>>>
functionsOne =
typeList.stream()
.map(v -> impl((UnaryPredicateOperator::exprIfNull), v, v, v))
.map(v -> impl((UnaryPredicateOperators::exprIfNull), v, v, v))
.collect(Collectors.toList());

DefaultFunctionResolver functionResolver = FunctionDSL.define(functionName, functionsOne);
return functionResolver;
return FunctionDSL.define(functionName, functionsOne);
}

private static DefaultFunctionResolver nullIf() {
FunctionName functionName = BuiltinFunctionName.NULLIF.getName();
List<ExprCoreType> typeList = ExprCoreType.coreTypes();

DefaultFunctionResolver functionResolver =
FunctionDSL.define(
functionName,
typeList.stream()
.map(v -> impl((UnaryPredicateOperator::exprNullIf), v, v, v))
.collect(Collectors.toList()));
return functionResolver;
return FunctionDSL.define(
functionName,
typeList.stream()
.map(v -> impl((UnaryPredicateOperators::exprNullIf), v, v, v))
.collect(Collectors.toList()));
}

/**
* v2 if v1 is null.
*
* @param v1 varable 1
* @param v2 varable 2
* @param v1 variable 1
* @param v2 variable 2
* @return v2 if v1 is null
*/
public static ExprValue exprIfNull(ExprValue v1, ExprValue v2) {
return (v1.isNull() || v1.isMissing()) ? v2 : v1;
}

/**
* return null if v1 equls to v2.
* return null if v1 equals to v2.
*
* @param v1 varable 1
* @param v2 varable 2
* @return null if v1 equls to v2
* @param v1 variable 1
* @param v2 variable 2
* @return null if v1 equals to v2
*/
public static ExprValue exprNullIf(ExprValue v1, ExprValue v2) {
return v1.equals(v2) ? LITERAL_NULL : v1;
Expand Down
Loading

0 comments on commit 6712526

Please sign in to comment.