Skip to content

Commit

Permalink
Exposing optional replaceMissingValueWith in lookup function and macros
Browse files Browse the repository at this point in the history
  • Loading branch information
pranavbhole committed Sep 9, 2023
1 parent 706b57c commit 6602841
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 5 deletions.
6 changes: 6 additions & 0 deletions docs/querying/lookups.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ SELECT
FROM sales
GROUP BY 1
```
Lookup function also accepts the 3rd argument called `$replaceMissingValueWith` as constant string, if you value is missing given lookups for queried key then lookup function return result value from `replaceMissingValueWith`
eg:
```
LOOKUP(store, 'store_to_country', 'NA')
```
If value is missing from `store_to_country` lookup for given key 'store' then it will return `NA`.

They can also be queried using the [JOIN operator](datasource.md#join):

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,11 @@ public String name()
@Override
public Expr apply(final List<Expr> args)
{
validationHelperCheckArgumentCount(args, 2);
validationHelperCheckMinArgumentCount(args, 2);

final Expr arg = args.get(0);
final Expr lookupExpr = args.get(1);
final String replaceMissingValueWith = getReplaceMissingValueWith(args);

validationHelperCheckArgIsLiteral(lookupExpr, "second argument");
if (lookupExpr.getLiteralValue() == null) {
Expand All @@ -69,7 +70,7 @@ public Expr apply(final List<Expr> args)
lookupExtractorFactoryContainerProvider,
lookupName,
false,
null,
replaceMissingValueWith,
false,
null
);
Expand Down Expand Up @@ -104,6 +105,15 @@ public ExpressionType getOutputType(InputBindingInspector inspector)
@Override
public String stringify()
{
if (replaceMissingValueWith != null) {
return StringUtils.format(
"%s(%s, %s, '%s')",
FN_NAME,
arg.stringify(),
lookupExpr.stringify(),
replaceMissingValueWith
);
}
return StringUtils.format("%s(%s, %s)", FN_NAME, arg.stringify(), lookupExpr.stringify());
}

Expand All @@ -116,4 +126,15 @@ public void decorateCacheKeyBuilder(CacheKeyBuilder builder)

return new LookupExpr(arg);
}

private String getReplaceMissingValueWith(final List<Expr> args)
{
if (args.size() > 2) {
final Expr missingValExpr = args.get(2);
if (missingValExpr.isLiteral()) {
return missingValExpr.getLiteralValue().toString();
}
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ public void testLookup()
{
assertExpr("lookup(x, 'lookyloo')", "xfoo");
}

@Test
public void testLookupMissingValue()
{
assertExpr("lookup(y, 'lookyloo', 'N/A')", "N/A");
}
@Test
public void testLookupNotFound()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,27 @@
import org.apache.druid.query.lookup.LookupExtractorFactoryContainerProvider;
import org.apache.druid.query.lookup.RegisteredLookupExtractionFn;
import org.apache.druid.segment.column.RowSignature;
import org.apache.druid.sql.calcite.expression.BasicOperandTypeChecker;
import org.apache.druid.sql.calcite.expression.DruidExpression;
import org.apache.druid.sql.calcite.expression.OperatorConversions;
import org.apache.druid.sql.calcite.expression.SqlOperatorConversion;
import org.apache.druid.sql.calcite.planner.PlannerContext;

import java.util.List;

public class QueryLookupOperatorConversion implements SqlOperatorConversion
{
private static final SqlFunction SQL_FUNCTION = OperatorConversions
.operatorBuilder("LOOKUP")
.operandTypes(SqlTypeFamily.CHARACTER, SqlTypeFamily.CHARACTER)
.operandTypeChecker(
BasicOperandTypeChecker.builder()
.operandTypes(
SqlTypeFamily.CHARACTER,
SqlTypeFamily.CHARACTER,
SqlTypeFamily.CHARACTER
)
.requiredOperandCount(2)
.build())
.returnTypeNullable(SqlTypeName.VARCHAR)
.functionCategory(SqlFunctionCategory.STRING)
.build();
Expand Down Expand Up @@ -73,14 +84,15 @@ public DruidExpression toDruidExpression(
inputExpressions -> {
final DruidExpression arg = inputExpressions.get(0);
final Expr lookupNameExpr = plannerContext.parseExpression(inputExpressions.get(1).getExpression());
final String replaceMissingValueWith = getReplaceMissingValueWith(inputExpressions, plannerContext);

if (arg.isSimpleExtraction() && lookupNameExpr.isLiteral()) {
return arg.getSimpleExtraction().cascade(
new RegisteredLookupExtractionFn(
lookupExtractorFactoryContainerProvider,
(String) lookupNameExpr.getLiteralValue(),
false,
null,
replaceMissingValueWith,
null,
true
)
Expand All @@ -91,4 +103,18 @@ public DruidExpression toDruidExpression(
}
);
}

private String getReplaceMissingValueWith(
final List<DruidExpression> inputExpressions,
final PlannerContext plannerContext
)
{
if (inputExpressions.size() > 2) {
final Expr missingValExpr = plannerContext.parseExpression(inputExpressions.get(2).getExpression());
if (missingValExpr.isLiteral()) {
return missingValExpr.getLiteralValue().toString();
}
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8786,6 +8786,52 @@ public void testFilterAndGroupByLookup()
)
);
}
@Test
public void testLookupReplaceMissingValueWith()
{
// Cannot vectorize due to extraction dimension specs.
cannotVectorize();

final RegisteredLookupExtractionFn extractionFn = new RegisteredLookupExtractionFn(
null,
"lookyloo",
false,
"Missing_Value",
null,
true
);

testQuery(
"SELECT LOOKUP(dim1, 'lookyloo', 'Missing_Value'), COUNT(*) FROM foo group by 1",
ImmutableList.of(
GroupByQuery.builder()
.setDataSource(CalciteTests.DATASOURCE1)
.setInterval(querySegmentSpec(Filtration.eternity()))
.setGranularity(Granularities.ALL)
.setDimensions(
dimensions(
new ExtractionDimensionSpec(
"dim1",
"d0",
ColumnType.STRING,
extractionFn
)
)
)
.setAggregatorSpecs(
aggregators(
new CountAggregatorFactory("a0")
)
)
.setContext(QUERY_CONTEXT_DEFAULT)
.build()
),
ImmutableList.of(
new Object[]{"Missing_Value", 5L},
new Object[]{"xabc", 1L}
)
);
}

@Test
public void testCountDistinctOfLookup()
Expand Down

0 comments on commit 6602841

Please sign in to comment.