Skip to content
Open
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 @@ -768,7 +768,9 @@ else if (rel instanceof Intersect)

RowFactory<Row> rowFactory = ctx.rowHandler().factory(ctx.getTypeFactory(), rowType);

return new ScanNode<>(ctx, rowType, new TableFunctionScan<>(rowType, dataSupplier, rowFactory));
boolean emptyStrIsNull = IgniteSqlSemantics.emptyStringIsNull(ctx.unwrap(IgniteSqlSemantics.class));

return new ScanNode<>(ctx, rowType, new TableFunctionScan<>(rowType, dataSupplier, rowFactory, emptyStrIsNull));
}

/** {@inheritDoc} */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@

package org.apache.ignite.internal.processors.query.calcite.exec;

import java.util.BitSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.function.Supplier;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.sql.type.SqlTypeUtil;
import org.apache.ignite.internal.processors.query.IgniteSQLException;
import org.apache.ignite.internal.processors.query.calcite.exec.RowHandler.RowFactory;
import org.apache.ignite.internal.util.typedef.F;
import org.jetbrains.annotations.Nullable;

/** */
public class TableFunctionScan<Row> implements Iterable<Row> {
Expand All @@ -36,15 +39,30 @@ public class TableFunctionScan<Row> implements Iterable<Row> {
/** */
private final RowFactory<Row> rowFactory;

/** */
private final @Nullable BitSet strCols;

/** */
public TableFunctionScan(
RelDataType rowType,
Supplier<Iterable<?>> dataSupplier,
RowFactory<Row> rowFactory
RowFactory<Row> rowFactory,
boolean emptyStringIsNull
) {
this.rowType = rowType;
this.dataSupplier = dataSupplier;
this.rowFactory = rowFactory;

if (emptyStringIsNull) {
strCols = new BitSet(rowType.getFieldCount());

for (int i = 0; i < rowType.getFieldCount(); i++) {
if (SqlTypeUtil.isCharacter(rowType.getFieldList().get(i).getType()))
strCols.set(i);
}
}
else
strCols = null;
}

/** {@inheritDoc} */
Expand All @@ -66,6 +84,27 @@ private Row convertToRow(Object rowContainer) {
+ "] doesn't match defined columns number [" + rowType.getFieldCount() + "].");
}

return rowFactory.create(rowArr);
return rowFactory.create(nullIfEmpty(rowArr));
}

/** Converts empty strings returned for string columns to {@code null}. */
private Object[] nullIfEmpty(Object[] row) {
if (strCols == null)
return row;

Object[] res = row;

for (int i = strCols.nextSetBit(0); i >= 0; i = strCols.nextSetBit(i + 1)) {
Object val = row[i];

if (val instanceof String && ((String)val).isEmpty()) {
if (res == row)
res = row.clone();

res[i] = null;
}
}

return res;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
import org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.AccumulatorWrapper;
import org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.AccumulatorsFactory;
import org.apache.ignite.internal.processors.query.calcite.exec.exp.agg.AggregateType;
import org.apache.ignite.internal.processors.query.calcite.prepare.IgniteSqlSemantics;
import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.ExactBounds;
import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.MultiBounds;
import org.apache.ignite.internal.processors.query.calcite.prepare.bounds.RangeBounds;
Expand Down Expand Up @@ -107,6 +108,9 @@ public class ExpressionFactoryImpl<Row> implements ExpressionFactory<Row> {
/** */
private final RexBuilder rexBuilder;

/** */
private final boolean emptyStrIsNull;

/** */
private static final RelDataType EMPTY_TYPE = new RelDataTypeFactory.Builder(Commons.typeFactory()).build();

Expand All @@ -130,6 +134,8 @@ public ExpressionFactoryImpl(
this.typeFactory = typeFactory;
this.conformance = conformance;
this.rexBuilder = rexBuilder;

emptyStrIsNull = IgniteSqlSemantics.emptyStringIsNull(ctx.unwrap(IgniteSqlSemantics.class));
}

/** {@inheritDoc} */
Expand Down Expand Up @@ -549,7 +555,7 @@ private Scalar compile(List<RexNode> nodes, RelDataType type, boolean biInParams
Function1<String, InputGetter> correlates = new CorrelatesBuilder(builder, ctx_, hnd_).build(nodes);

List<Expression> projects = RexToLixTranslator.translateProjects(program, typeFactory, conformance,
builder, null, ctx_, inputGetter, correlates);
builder, null, ctx_, inputGetter, correlates, emptyStrIsNull);

assert nodes.size() == projects.size();

Expand Down Expand Up @@ -618,6 +624,7 @@ private String digest(List<RexNode> nodes, RelDataType type, boolean biParam) {
}

b.append(", biParam=").append(biParam);
b.append(", emptyStrIsNull=").append(emptyStrIsNull);

b.append(']');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,40 @@

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
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.SqlOperator;
import org.apache.calcite.sql.SqlUtil;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.type.SqlTypeUtil;
import org.apache.calcite.util.NlsString;
import org.apache.ignite.internal.processors.query.IgniteSQLException;
import org.apache.ignite.internal.processors.query.calcite.util.TypeUtils;
import org.jetbrains.annotations.Nullable;

import static org.apache.ignite.internal.processors.query.calcite.sql.fun.IgniteOwnSqlOperatorTable.NULL_IF_EMPTY;

/** */
public class IgniteRexBuilder extends RexBuilder {
/** */
private final boolean emptyStrIsNull;

/** */
public IgniteRexBuilder(RelDataTypeFactory typeFactory) {
this(typeFactory, false);
}

/** */
public IgniteRexBuilder(RelDataTypeFactory typeFactory, boolean emptyStrIsNull) {
super(typeFactory);

this.emptyStrIsNull = emptyStrIsNull;
}

/** {@inheritDoc} */
Expand All @@ -56,4 +75,37 @@ public IgniteRexBuilder(RelDataTypeFactory typeFactory) {

return super.makeLiteral(o, type, typeName);
}

/** {@inheritDoc} */
@Override public RexNode makeCall(SqlParserPos pos, RelDataType type, SqlOperator op, List<RexNode> exprs) {
return nullIfEmptyResult(pos, super.makeCall(pos, type, op, exprs), op);
}

/** {@inheritDoc} */
@Override public RexNode makeCall(SqlParserPos pos, SqlOperator op, List<? extends RexNode> exprs) {
return nullIfEmptyResult(pos, super.makeCall(pos, op, exprs), op);
}

/** {@inheritDoc} */
@Override public RexLiteral makeCharLiteral(NlsString str) {
// VALUES conversion can retain the original character literal after validation.
if (emptyStrIsNull && str.getValue().isEmpty())
return makeNullLiteral(SqlUtil.createNlsStringType(getTypeFactory(), str));

return super.makeCharLiteral(str);
}

/** Wraps a string expression so an empty result is represented as {@code null}. */
private RexNode nullIfEmptyResult(SqlParserPos pos, RexNode call, SqlOperator op) {
if (!emptyStrIsNull || op == NULL_IF_EMPTY || op.getKind() == SqlKind.AS || op.getKind() == SqlKind.CAST
|| op.getKind() == SqlKind.DESCENDING || op.getKind() == SqlKind.NULLS_FIRST
|| op.getKind() == SqlKind.NULLS_LAST
|| !SqlTypeUtil.isCharacter(call.getType())) {
return call;
}

RelDataType type = getTypeFactory().createTypeWithNullability(call.getType(), true);

return super.makeCall(pos, type, NULL_IF_EMPTY, List.of(call));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ public class IgniteSqlFunctions {
/** */
private static final int DFLT_NUM_PRECISION = IgniteTypeSystem.INSTANCE.getDefaultPrecision(SqlTypeName.DECIMAL);

/** */
private static final SqlFunctions.PosixRegexFunction POSIX_REGEX = new SqlFunctions.PosixRegexFunction();

/**
* Default constructor.
*/
Expand Down Expand Up @@ -342,4 +345,46 @@ public static boolean neAny(Object a, Object b) {

return SqlFunctions.neAny(a, b);
}

/** Converts an empty string value to {@code null}. */
public static @Nullable String nullIfEmpty(@Nullable String s) {
return s == null || s.isEmpty() ? null : s;
}

/** Case-sensitive POSIX regular expression match. */
public static @Nullable Boolean posixRegexCaseSensitive(@Nullable String s, @Nullable String regex) {
return posixRegex(s, regex, true, false);
}

/** Case-insensitive POSIX regular expression match. */
public static @Nullable Boolean posixRegexCaseInsensitive(@Nullable String s, @Nullable String regex) {
return posixRegex(s, regex, false, false);
}

/** Negated case-sensitive POSIX regular expression match. */
public static @Nullable Boolean negatedPosixRegexCaseSensitive(@Nullable String s, @Nullable String regex) {
return posixRegex(s, regex, true, true);
}

/** Negated case-insensitive POSIX regular expression match. */
public static @Nullable Boolean negatedPosixRegexCaseInsensitive(@Nullable String s, @Nullable String regex) {
return posixRegex(s, regex, false, true);
}

/**
* POSIX regular expression match.
*
* <p>The pattern is evaluated even when the source is {@code null}. This preserves an invalid-pattern error while
* the result of a valid match with a null operand remains {@code null}.</p>
*/
private static @Nullable Boolean posixRegex(@Nullable String s, @Nullable String regex, boolean caseSensitive, boolean negate) {
if (regex == null)
return null;

boolean matches = caseSensitive
? POSIX_REGEX.posixRegexSensitive(s == null ? "" : s, regex)
: POSIX_REGEX.posixRegexInsensitive(s == null ? "" : s, regex);

return s == null ? null : matches != negate;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ private static String compile(
final RexProgram program = programBuilder.getProgram();
final List<Expression> expressions =
RexToLixTranslator.translateProjects(program, javaTypeFactory,
conformance, blockBuilder, null, root_, getter, null);
conformance, blockBuilder, null, root_, getter, null, false);
blockBuilder.add(
Expressions.return_(null,
Expressions.newArrayInit(Object[].class, expressions)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@
import static org.apache.ignite.internal.processors.query.calcite.sql.fun.IgniteOwnSqlOperatorTable.GREATEST2;
import static org.apache.ignite.internal.processors.query.calcite.sql.fun.IgniteOwnSqlOperatorTable.LEAST2;
import static org.apache.ignite.internal.processors.query.calcite.sql.fun.IgniteOwnSqlOperatorTable.NULL_BOUND;
import static org.apache.ignite.internal.processors.query.calcite.sql.fun.IgniteOwnSqlOperatorTable.NULL_IF_EMPTY;
import static org.apache.ignite.internal.processors.query.calcite.sql.fun.IgniteOwnSqlOperatorTable.QUERY_ENGINE;
import static org.apache.ignite.internal.processors.query.calcite.sql.fun.IgniteOwnSqlOperatorTable.SYSTEM_RANGE;
import static org.apache.ignite.internal.processors.query.calcite.sql.fun.IgniteOwnSqlOperatorTable.TYPEOF;
Expand Down Expand Up @@ -324,6 +325,7 @@ public class RexImpTable {
defineMethod(SOUNDEX, BuiltInMethod.SOUNDEX.method, NullPolicy.STRICT);
defineMethod(DIFFERENCE, BuiltInMethod.DIFFERENCE.method, NullPolicy.STRICT);
defineMethod(REVERSE, BuiltInMethod.REVERSE.method, NullPolicy.STRICT);
defineMethod(NULL_IF_EMPTY, IgniteMethod.NULL_IF_EMPTY.method(), NullPolicy.NONE);

map.put(TRIM, new TrimImplementor());

Expand Down Expand Up @@ -455,16 +457,21 @@ public class RexImpTable {
BuiltInMethod.SIMILAR_ESCAPE.method);

// POSIX REGEX
ReflectiveImplementor insensitiveImplementor =
defineReflective(POSIX_REGEX_CASE_INSENSITIVE,
BuiltInMethod.POSIX_REGEX_INSENSITIVE.method);
ReflectiveImplementor sensitiveImplementor =
defineReflective(POSIX_REGEX_CASE_SENSITIVE,
BuiltInMethod.POSIX_REGEX_SENSITIVE.method);
AbstractRexCallImplementor insensitiveImplementor =
new ReflectiveImplementor(ImmutableList.of(BuiltInMethod.POSIX_REGEX_INSENSITIVE.method));
AbstractRexCallImplementor sensitiveImplementor =
new ReflectiveImplementor(ImmutableList.of(BuiltInMethod.POSIX_REGEX_SENSITIVE.method));

map.put(POSIX_REGEX_CASE_INSENSITIVE, new EmptyStringSemanticsImplementor(insensitiveImplementor,
new MethodImplementor(IgniteMethod.POSIX_REGEX_CASE_INSENSITIVE.method(), NullPolicy.NONE, false)));
map.put(POSIX_REGEX_CASE_SENSITIVE, new EmptyStringSemanticsImplementor(sensitiveImplementor,
new MethodImplementor(IgniteMethod.POSIX_REGEX_CASE_SENSITIVE.method(), NullPolicy.NONE, false)));
map.put(NEGATED_POSIX_REGEX_CASE_INSENSITIVE,
NotImplementor.of(insensitiveImplementor));
new EmptyStringSemanticsImplementor(NotImplementor.of(insensitiveImplementor),
new MethodImplementor(IgniteMethod.NEGATED_POSIX_REGEX_CASE_INSENSITIVE.method(), NullPolicy.NONE, false)));
map.put(NEGATED_POSIX_REGEX_CASE_SENSITIVE,
NotImplementor.of(sensitiveImplementor));
new EmptyStringSemanticsImplementor(NotImplementor.of(sensitiveImplementor),
new MethodImplementor(IgniteMethod.NEGATED_POSIX_REGEX_CASE_SENSITIVE.method(), NullPolicy.NONE, false)));
defineReflective(REGEXP_REPLACE_3,
BuiltInMethod.REGEXP_REPLACE3.method,
BuiltInMethod.REGEXP_REPLACE4.method,
Expand Down Expand Up @@ -2588,4 +2595,31 @@ public static RexCallImplementor createRexCallImplementor(
}
};
}

/** Selects an expression implementation according to the empty string SQL semantics. */
private static class EmptyStringSemanticsImplementor implements RexCallImplementor {
/** */
private final RexCallImplementor dfltImplementor;

/** */
private final RexCallImplementor emptyStrIsNullImplementor;

/** */
private EmptyStringSemanticsImplementor(RexCallImplementor dfltImplementor, RexCallImplementor emptyStrIsNullImplementor) {
this.dfltImplementor = dfltImplementor;
this.emptyStrIsNullImplementor = emptyStrIsNullImplementor;
}

/** {@inheritDoc} */
@Override public RexToLixTranslator.Result implement(
RexToLixTranslator translator,
RexCall call,
List<RexToLixTranslator.Result> arguments
) {
RexCallImplementor implementor = translator.emptyStringIsNull() ? emptyStrIsNullImplementor : dfltImplementor;

return implementor.implement(translator, call, arguments);
}
}

}
Loading
Loading