Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -677,9 +677,16 @@ private static SqlOperandTypeChecker extractTypeCheckerFromUDF(
}

void populate() {
// register operators for comparison
registerOperator(NOTEQUAL, PPLBuiltinOperators.NOT_EQUALS_IP, SqlStdOperatorTable.NOT_EQUALS);
registerOperator(EQUAL, PPLBuiltinOperators.EQUALS_IP, SqlStdOperatorTable.EQUALS);
// register operators for comparison with wildcard and IP support
// Resolution order: IP types first, then all other types

// IP support (handles both wildcard and exact match for IP types)
register(EQUAL, new WildcardAwareIpEquals());
register(NOTEQUAL, new WildcardAwareIpNotEquals());

// General support (handles both wildcard and exact match for all other types)
register(EQUAL, new WildcardAwareEqualsFunc());
register(NOTEQUAL, new WildcardAwareNotEqualsFunc());
registerOperator(GREATER, PPLBuiltinOperators.GREATER_IP, SqlStdOperatorTable.GREATER_THAN);
registerOperator(GTE, PPLBuiltinOperators.GTE_IP, SqlStdOperatorTable.GREATER_THAN_OR_EQUAL);
registerOperator(LESS, PPLBuiltinOperators.LESS_IP, SqlStdOperatorTable.LESS_THAN);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.sql.expression.function;

import java.util.List;
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexLiteral;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.fun.SqlLibraryOperators;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.type.OperandTypes;
import org.apache.calcite.sql.type.SameOperandTypeChecker;
import org.apache.calcite.sql.type.SqlTypeFamily;
import org.apache.calcite.sql.type.SqlTypeName;
import org.opensearch.sql.data.type.ExprCoreType;
import org.opensearch.sql.expression.function.PPLFuncImpTable.FunctionImp2;

/**
* Wildcard-aware equals function that resolves to LIKE when wildcards are detected in string
* values. Supports OpenSearch native wildcards: * (zero or more chars) and ? (exactly one char).
* Also supports numeric fields by casting them to strings for wildcard matching.
*/
public class WildcardAwareEqualsFunc implements FunctionImp2 {

@Override
public RexNode resolve(RexBuilder builder, RexNode arg1, RexNode arg2) {
// Check if the second argument is a string literal with wildcards
if (arg2.isA(SqlKind.LITERAL) && SqlTypeFamily.CHARACTER.contains(arg2.getType())) {
String value = ((RexLiteral) arg2).getValueAs(String.class);
if (value != null && containsWildcards(value)) {
if (SqlTypeFamily.CHARACTER.contains(arg1.getType())) {
// Direct wildcard matching for string fields
String convertedValue = convertOpenSearchWildcardsToSql(value);
RexNode convertedLiteral = builder.makeLiteral(convertedValue);
return builder.makeCall(
SqlLibraryOperators.ILIKE, arg1, convertedLiteral, builder.makeLiteral("\\"));
} else if (SqlTypeFamily.NUMERIC.contains(arg1.getType()) ||
SqlTypeFamily.DATETIME.contains(arg1.getType()) ||
arg1.getType().getSqlTypeName().getName().equals("IP")) {
// For non-string fields with wildcards, we need to prevent incorrect wildcard pushdown
// Cast to VARCHAR and use regular EQUALS with the pattern
// This will filter in-memory, not push down as wildcard query
RexNode castToString =
builder.makeCast(builder.getTypeFactory().createSqlType(SqlTypeName.VARCHAR), arg1);

// Don't convert wildcards - keep them as-is for in-memory filtering
// Using EQUALS instead of LIKE/ILIKE to prevent wildcard query generation
return builder.makeCall(SqlStdOperatorTable.EQUALS, castToString, arg2);
}
}
}

// Fall back to standard equals for non-wildcard cases
return builder.makeCall(SqlStdOperatorTable.EQUALS, arg1, arg2);
}

@Override
public PPLTypeChecker getTypeChecker() {
// Support standard same-type equality
// For wildcard support, only allow STRING types
// Numeric fields with wildcards should use explicit CAST
return PPLTypeChecker.wrapUDT(List.of(
// Standard same-type equality
List.of(ExprCoreType.BOOLEAN, ExprCoreType.BOOLEAN),
List.of(ExprCoreType.INTEGER, ExprCoreType.INTEGER),
List.of(ExprCoreType.LONG, ExprCoreType.LONG),
List.of(ExprCoreType.FLOAT, ExprCoreType.FLOAT),
List.of(ExprCoreType.DOUBLE, ExprCoreType.DOUBLE),
List.of(ExprCoreType.STRING, ExprCoreType.STRING),
List.of(ExprCoreType.DATE, ExprCoreType.DATE),
List.of(ExprCoreType.TIME, ExprCoreType.TIME),
List.of(ExprCoreType.TIMESTAMP, ExprCoreType.TIMESTAMP),
List.of(ExprCoreType.IP, ExprCoreType.IP)

// REMOVED: Wildcard support for non-string types
// This prevents incorrect wildcard query generation on numeric fields
// Users should use: CAST(int_field AS STRING) = "1*"
));
}

protected boolean containsWildcards(String value) {
return value.contains("*") || value.contains("?");
}

/**
* Convert OpenSearch/Lucene wildcards to SQL LIKE wildcards. * (zero or more chars) -> % (zero or
* more chars) ? (exactly one char) -> _ (exactly one char)
*/
protected String convertOpenSearchWildcardsToSql(String value) {
return value.replace("*", "%").replace("?", "_");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.sql.expression.function;

import inet.ipaddr.IPAddressString;
import java.util.List;
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexLiteral;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.opensearch.sql.data.type.ExprCoreType;
import org.opensearch.sql.expression.function.PPLFuncImpTable.FunctionImp2;

/**
* Wildcard-aware IP address equals function that supports patterns like: - 192.168.*.* (matches any
* IP starting with 192.168) - 192.168.1.* (matches 192.168.1.0 to 192.168.1.255) - 192.168.?.1
* (matches 192.168.0.1 to 192.168.9.1)
*/
public class WildcardAwareIpEquals implements FunctionImp2 {

@Override
public RexNode resolve(RexBuilder builder, RexNode arg1, RexNode arg2) {
// Check if this is an IP comparison with wildcards
if (isIpType(arg1) && arg2.isA(SqlKind.LITERAL)) {
String value = ((RexLiteral) arg2).getValueAs(String.class);
if (value != null && containsWildcards(value)) {
// Convert wildcard IP pattern to range check
return createIpRangeCheck(builder, arg1, value, false);
}
} else if (arg1.isA(SqlKind.LITERAL) && isIpType(arg2)) {
String value = ((RexLiteral) arg1).getValueAs(String.class);
if (value != null && containsWildcards(value)) {
// Handle reverse order (literal first)
return createIpRangeCheck(builder, arg2, value, false);
}
}

// Fall back to standard IP equals
return builder.makeCall(PPLBuiltinOperators.EQUALS_IP, arg1, arg2);
}

@Override
public PPLTypeChecker getTypeChecker() {
// Accept IP type with IP or STRING (for wildcard patterns)
return PPLTypeChecker.wrapUDT(List.of(
List.of(ExprCoreType.IP, ExprCoreType.IP),
List.of(ExprCoreType.IP, ExprCoreType.STRING)
));
}

protected boolean isIpType(RexNode node) {
// Check if the node represents an IP type field
String typeName = node.getType().getSqlTypeName().getName();
return "IP".equals(typeName) || node.getType().toString().contains("IP");
}

protected boolean containsWildcards(String value) {
return value.contains("*") || value.contains("?");
}

/**
* Creates a range check for IP wildcard patterns. For example, 192.168.*.* becomes: ip >=
* 192.168.0.0 AND ip <= 192.168.255.255
*/
protected RexNode createIpRangeCheck(
RexBuilder builder, RexNode ipField, String pattern, boolean negate) {
try {
// Parse the IP pattern and calculate range
IpRange range = calculateIpRange(pattern);

// Create range comparison: ip >= minIp AND ip <= maxIp
RexNode minIpLiteral = builder.makeLiteral(range.min);
RexNode maxIpLiteral = builder.makeLiteral(range.max);

RexNode gteCheck = builder.makeCall(PPLBuiltinOperators.GTE_IP, ipField, minIpLiteral);
RexNode lteCheck = builder.makeCall(PPLBuiltinOperators.LTE_IP, ipField, maxIpLiteral);

RexNode rangeCheck = builder.makeCall(SqlStdOperatorTable.AND, gteCheck, lteCheck);

// Negate if needed (for NOT_EQUALS)
return negate ? builder.makeCall(SqlStdOperatorTable.NOT, rangeCheck) : rangeCheck;

} catch (Exception e) {
// If pattern parsing fails, fall back to standard comparison
return builder.makeCall(
negate ? PPLBuiltinOperators.NOT_EQUALS_IP : PPLBuiltinOperators.EQUALS_IP,
ipField,
builder.makeLiteral(pattern));
}
}

/** Calculates the IP range for a wildcard pattern. */
private IpRange calculateIpRange(String pattern) {
String[] octets = pattern.split("\\.");
if (octets.length != 4) {
throw new IllegalArgumentException("Invalid IP pattern: " + pattern);
}

StringBuilder minIp = new StringBuilder();
StringBuilder maxIp = new StringBuilder();

for (int i = 0; i < 4; i++) {
if (i > 0) {
minIp.append(".");
maxIp.append(".");
}

String octet = octets[i];
if ("*".equals(octet)) {
minIp.append("0");
maxIp.append("255");
} else if ("?".equals(octet)) {
minIp.append("0");
maxIp.append("9");
} else if (octet.contains("*")) {
// Handle patterns like "19*" -> 190-199
String prefix = octet.substring(0, octet.indexOf('*'));
minIp.append(prefix).append("0");
maxIp.append(prefix).append("9");
} else if (octet.contains("?")) {
// Handle patterns like "19?" -> 190-199
String prefix = octet.substring(0, octet.indexOf('?'));
minIp.append(prefix).append("0");
maxIp.append(prefix).append("9");
} else {
// Exact octet value
minIp.append(octet);
maxIp.append(octet);
}
}

return new IpRange(minIp.toString(), maxIp.toString());
}

private static class IpRange {
final String min;
final String max;

IpRange(String min, String max) {
// Validate IP addresses
IPAddressString minAddr = new IPAddressString(min);
IPAddressString maxAddr = new IPAddressString(max);

if (!minAddr.isValid() || !maxAddr.isValid()) {
throw new IllegalArgumentException("Invalid IP range: " + min + " - " + max);
}

this.min = min;
this.max = max;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.sql.expression.function;

import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexLiteral;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.sql.SqlKind;

/** Wildcard-aware IP address not-equals function. */
public class WildcardAwareIpNotEquals extends WildcardAwareIpEquals {

@Override
public RexNode resolve(RexBuilder builder, RexNode arg1, RexNode arg2) {
// Check if this is an IP comparison with wildcards
if (isIpType(arg1) && arg2.isA(SqlKind.LITERAL)) {
String value = ((RexLiteral) arg2).getValueAs(String.class);
if (value != null && containsWildcards(value)) {
// Create negated range check
return createIpRangeCheck(builder, arg1, value, true);
}
} else if (arg1.isA(SqlKind.LITERAL) && isIpType(arg2)) {
String value = ((RexLiteral) arg1).getValueAs(String.class);
if (value != null && containsWildcards(value)) {
// Handle reverse order (literal first)
return createIpRangeCheck(builder, arg2, value, true);
}
}

// Fall back to standard IP not-equals
return builder.makeCall(PPLBuiltinOperators.NOT_EQUALS_IP, arg1, arg2);
}
}
Loading
Loading