-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature: Add IPv6 Match Function (#15212)
- Loading branch information
Showing
11 changed files
with
561 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 81 additions & 0 deletions
81
processing/src/main/java/org/apache/druid/query/expression/IPv6AddressExprUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
/* | ||
* Licensed to the Apache Software Foundation (ASF) under one | ||
* or more contributor license agreements. See the NOTICE file | ||
* distributed with this work for additional information | ||
* regarding copyright ownership. The ASF licenses this file | ||
* to you under the Apache License, Version 2.0 (the | ||
* "License"); you may not use this file except in compliance | ||
* with the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, | ||
* software distributed under the License is distributed on an | ||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
* KIND, either express or implied. See the License for the | ||
* specific language governing permissions and limitations | ||
* under the License. | ||
*/ | ||
|
||
package org.apache.druid.query.expression; | ||
|
||
import inet.ipaddr.IPAddressString; | ||
import inet.ipaddr.IPAddressStringParameters; | ||
import inet.ipaddr.ipv6.IPv6Address; | ||
|
||
import javax.annotation.Nullable; | ||
|
||
public class IPv6AddressExprUtils | ||
{ | ||
|
||
private static final IPAddressStringParameters IPV6_ADDRESS_PARAMS = new IPAddressStringParameters.Builder().allowSingleSegment(false).allow_inet_aton(false).allowIPv4(false).allowPrefix(false).allowEmpty(false).toParams(); | ||
private static final IPAddressStringParameters IPV6_SUBNET_PARAMS = new IPAddressStringParameters.Builder().allowSingleSegment(false).allow_inet_aton(false).allowEmpty(false).allowIPv4(false).toParams(); | ||
|
||
/** | ||
* @return True if argument is a valid IPv6 address semicolon separated string. Single segments, Inet addresses and subnets are not allowed. | ||
*/ | ||
static boolean isValidIPv6Address(@Nullable String addressString) | ||
{ | ||
return addressString != null && new IPAddressString(addressString, IPV6_ADDRESS_PARAMS).isIPv6(); | ||
} | ||
|
||
/** | ||
* @return True if argument is a valid IPv6 subnet address. | ||
*/ | ||
static boolean isValidIPv6Subnet(@Nullable String subnetString) | ||
{ | ||
return subnetString != null && new IPAddressString(subnetString, IPV6_SUBNET_PARAMS).isPrefixed(); | ||
} | ||
|
||
/** | ||
* @return IPv6 address if the supplied string is a valid semicolon separated IPv6 Address string. | ||
*/ | ||
@Nullable | ||
public static IPv6Address parse(@Nullable String string) | ||
{ | ||
IPAddressString ipAddressString = new IPAddressString(string, IPV6_ADDRESS_PARAMS); | ||
if (ipAddressString.isIPv6()) { | ||
return ipAddressString.getAddress().toIPv6(); | ||
} | ||
return null; | ||
} | ||
|
||
@Nullable | ||
public static IPAddressString parseString(@Nullable String string) | ||
{ | ||
IPAddressString ipAddressString = new IPAddressString(string, IPV6_ADDRESS_PARAMS); | ||
if (ipAddressString.isIPv6()) { | ||
return ipAddressString; | ||
} | ||
return null; | ||
} | ||
|
||
/** | ||
* @return IPv6 address from supplied array of bytes | ||
*/ | ||
@Nullable | ||
public static IPv6Address parse(@Nullable byte[] bytes) | ||
{ | ||
return bytes == null ? null : new IPv6Address(bytes); | ||
} | ||
} |
137 changes: 137 additions & 0 deletions
137
processing/src/main/java/org/apache/druid/query/expression/IPv6AddressMatchExprMacro.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
/* | ||
* Licensed to the Apache Software Foundation (ASF) under one | ||
* or more contributor license agreements. See the NOTICE file | ||
* distributed with this work for additional information | ||
* regarding copyright ownership. The ASF licenses this file | ||
* to you under the Apache License, Version 2.0 (the | ||
* "License"); you may not use this file except in compliance | ||
* with the License. You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, | ||
* software distributed under the License is distributed on an | ||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
* KIND, either express or implied. See the License for the | ||
* specific language governing permissions and limitations | ||
* under the License. | ||
*/ | ||
|
||
package org.apache.druid.query.expression; | ||
|
||
import inet.ipaddr.IPAddressString; | ||
import org.apache.druid.java.util.common.StringUtils; | ||
import org.apache.druid.math.expr.Expr; | ||
import org.apache.druid.math.expr.ExprEval; | ||
import org.apache.druid.math.expr.ExprMacroTable; | ||
import org.apache.druid.math.expr.ExpressionType; | ||
|
||
import javax.annotation.Nonnull; | ||
import javax.annotation.Nullable; | ||
import java.util.List; | ||
|
||
/** | ||
* <pre> | ||
* Implements an expression that checks if an IPv6 address belongs to a subnet. | ||
* | ||
* Expression signatures: | ||
* - long ipv6_match(string address, string subnet) | ||
* | ||
* Valid "address" argument formats are: | ||
* - IPv6 address string (e.g., "2001:4860:4860::8888") | ||
* | ||
* The argument format for the "subnet" argument should be a literal in CIDR notation | ||
* (e.g., "2001:db8::/64 "). | ||
* | ||
* If the "address" argument does not represent an IPv6 address then false is returned. | ||
* </pre> | ||
* | ||
*/ | ||
public class IPv6AddressMatchExprMacro implements ExprMacroTable.ExprMacro | ||
{ | ||
public static final String FN_NAME = "ipv6_match"; | ||
private static final int ARG_SUBNET = 1; | ||
|
||
@Override | ||
public String name() | ||
{ | ||
return FN_NAME; | ||
} | ||
|
||
@Override | ||
public Expr apply(final List<Expr> args) | ||
{ | ||
validationHelperCheckArgumentCount(args, 2); | ||
|
||
try { | ||
final Expr arg = args.get(0); | ||
final IPAddressString blockString = getSubnetInfo(args); | ||
|
||
class IPv6AddressMatchExpr extends ExprMacroTable.BaseScalarUnivariateMacroFunctionExpr | ||
{ | ||
private IPv6AddressMatchExpr(Expr arg) | ||
{ | ||
super(FN_NAME, arg); | ||
} | ||
|
||
@Nonnull | ||
@Override | ||
public ExprEval eval(final ObjectBinding bindings) | ||
{ | ||
ExprEval eval = arg.eval(bindings); | ||
boolean match; | ||
switch (eval.type().getType()) { | ||
case STRING: | ||
match = isStringMatch(eval.asString()); | ||
break; | ||
default: | ||
match = false; | ||
} | ||
return ExprEval.ofLongBoolean(match); | ||
} | ||
|
||
private boolean isStringMatch(String stringValue) | ||
{ | ||
IPAddressString addressString = IPv6AddressExprUtils.parseString(stringValue); | ||
return addressString != null && blockString.prefixContains(addressString); | ||
} | ||
|
||
@Override | ||
public Expr visit(Shuttle shuttle) | ||
{ | ||
return shuttle.visit(apply(shuttle.visitAll(args))); | ||
} | ||
|
||
@Override | ||
public String stringify() | ||
{ | ||
return StringUtils.format("%s(%s, %s)", FN_NAME, arg.stringify(), args.get(ARG_SUBNET).stringify()); | ||
} | ||
|
||
@Nullable | ||
@Override | ||
public ExpressionType getOutputType(InputBindingInspector inspector) | ||
{ | ||
return ExpressionType.LONG; | ||
} | ||
} | ||
|
||
return new IPv6AddressMatchExpr(arg); | ||
} | ||
catch (Exception e) { | ||
throw processingFailed(e, "failed to parse address"); | ||
} | ||
} | ||
|
||
private IPAddressString getSubnetInfo(List<Expr> args) | ||
{ | ||
String subnetArgName = "subnet"; | ||
Expr arg = args.get(ARG_SUBNET); | ||
validationHelperCheckArgIsLiteral(arg, subnetArgName); | ||
String subnet = (String) arg.getLiteralValue(); | ||
if (!IPv6AddressExprUtils.isValidIPv6Subnet(subnet)) { | ||
throw validationFailed(subnetArgName + " arg has an invalid format: " + subnet); | ||
} | ||
return new IPAddressString(subnet); | ||
} | ||
} |
Oops, something went wrong.