mirror of https://github.com/apache/druid.git
Add concat and textcat SQL functions (#6005)
This commit is contained in:
parent
b7d42edb0f
commit
efab3b0160
|
@ -125,6 +125,8 @@ String functions accept strings, and return a type appropriate to the function.
|
|||
|Function|Notes|
|
||||
|--------|-----|
|
||||
|`x \|\| y`|Concat strings x and y.|
|
||||
|`CONCAT(expr, expr...)`|Concats a list of expressions.|
|
||||
|`TEXTCAT(expr, expr)`|Two argument version of CONCAT.|
|
||||
|`LENGTH(expr)`|Length of expr in UTF-16 code units.|
|
||||
|`CHAR_LENGTH(expr)`|Synonym for `LENGTH`.|
|
||||
|`CHARACTER_LENGTH(expr)`|Synonym for `LENGTH`.|
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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 io.druid.sql.calcite.expression.builtin;
|
||||
|
||||
import io.druid.sql.calcite.expression.DruidExpression;
|
||||
import io.druid.sql.calcite.expression.OperatorConversions;
|
||||
import io.druid.sql.calcite.expression.SqlOperatorConversion;
|
||||
import io.druid.sql.calcite.planner.Calcites;
|
||||
import io.druid.sql.calcite.planner.PlannerContext;
|
||||
import io.druid.sql.calcite.table.RowSignature;
|
||||
import org.apache.calcite.rex.RexNode;
|
||||
import org.apache.calcite.sql.SqlFunction;
|
||||
import org.apache.calcite.sql.SqlFunctionCategory;
|
||||
import org.apache.calcite.sql.SqlKind;
|
||||
import org.apache.calcite.sql.type.OperandTypes;
|
||||
import org.apache.calcite.sql.type.ReturnTypes;
|
||||
import org.apache.calcite.sql.type.SqlTypeName;
|
||||
|
||||
public class ConcatOperatorConversion implements SqlOperatorConversion
|
||||
{
|
||||
private static final SqlFunction SQL_FUNCTION = new SqlFunction(
|
||||
"CONCAT",
|
||||
SqlKind.OTHER_FUNCTION,
|
||||
ReturnTypes.explicit(
|
||||
factory -> Calcites.createSqlType(factory, SqlTypeName.VARCHAR)
|
||||
),
|
||||
null,
|
||||
OperandTypes.SAME_VARIADIC,
|
||||
SqlFunctionCategory.STRING
|
||||
);
|
||||
|
||||
@Override
|
||||
public SqlFunction calciteOperator()
|
||||
{
|
||||
return SQL_FUNCTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DruidExpression toDruidExpression(
|
||||
final PlannerContext plannerContext,
|
||||
final RowSignature rowSignature,
|
||||
final RexNode rexNode
|
||||
)
|
||||
{
|
||||
return OperatorConversions.convertCall(
|
||||
plannerContext,
|
||||
rowSignature,
|
||||
rexNode,
|
||||
druidExpressions -> DruidExpression.of(
|
||||
null,
|
||||
DruidExpression.functionCall("concat", druidExpressions)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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 io.druid.sql.calcite.expression.builtin;
|
||||
|
||||
import io.druid.sql.calcite.expression.DruidExpression;
|
||||
import io.druid.sql.calcite.expression.OperatorConversions;
|
||||
import io.druid.sql.calcite.expression.SqlOperatorConversion;
|
||||
import io.druid.sql.calcite.planner.PlannerContext;
|
||||
import io.druid.sql.calcite.table.RowSignature;
|
||||
import org.apache.calcite.rex.RexNode;
|
||||
import org.apache.calcite.sql.SqlFunction;
|
||||
import org.apache.calcite.sql.SqlFunctionCategory;
|
||||
import org.apache.calcite.sql.type.SqlTypeFamily;
|
||||
import org.apache.calcite.sql.type.SqlTypeName;
|
||||
|
||||
public class TextcatOperatorConversion implements SqlOperatorConversion
|
||||
{
|
||||
private static final SqlFunction SQL_FUNCTION = OperatorConversions
|
||||
.operatorBuilder("textcat")
|
||||
.operandTypes(SqlTypeFamily.CHARACTER, SqlTypeFamily.CHARACTER)
|
||||
.requiredOperands(2)
|
||||
.returnType(SqlTypeName.VARCHAR)
|
||||
.functionCategory(SqlFunctionCategory.STRING)
|
||||
.build();
|
||||
|
||||
@Override
|
||||
public SqlFunction calciteOperator()
|
||||
{
|
||||
return SQL_FUNCTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DruidExpression toDruidExpression(
|
||||
final PlannerContext plannerContext,
|
||||
final RowSignature rowSignature,
|
||||
final RexNode rexNode
|
||||
)
|
||||
{
|
||||
return OperatorConversions.convertCall(
|
||||
plannerContext,
|
||||
rowSignature,
|
||||
rexNode,
|
||||
druidExpressions -> DruidExpression.of(
|
||||
null,
|
||||
DruidExpression.functionCall("concat", druidExpressions)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -42,6 +42,7 @@ import io.druid.sql.calcite.expression.UnarySuffixOperatorConversion;
|
|||
import io.druid.sql.calcite.expression.builtin.BTrimOperatorConversion;
|
||||
import io.druid.sql.calcite.expression.builtin.CastOperatorConversion;
|
||||
import io.druid.sql.calcite.expression.builtin.CeilOperatorConversion;
|
||||
import io.druid.sql.calcite.expression.builtin.ConcatOperatorConversion;
|
||||
import io.druid.sql.calcite.expression.builtin.DateTruncOperatorConversion;
|
||||
import io.druid.sql.calcite.expression.builtin.ExtractOperatorConversion;
|
||||
import io.druid.sql.calcite.expression.builtin.FloorOperatorConversion;
|
||||
|
@ -52,6 +53,7 @@ import io.druid.sql.calcite.expression.builtin.RegexpExtractOperatorConversion;
|
|||
import io.druid.sql.calcite.expression.builtin.ReinterpretOperatorConversion;
|
||||
import io.druid.sql.calcite.expression.builtin.StrposOperatorConversion;
|
||||
import io.druid.sql.calcite.expression.builtin.SubstringOperatorConversion;
|
||||
import io.druid.sql.calcite.expression.builtin.TextcatOperatorConversion;
|
||||
import io.druid.sql.calcite.expression.builtin.TimeArithmeticOperatorConversion;
|
||||
import io.druid.sql.calcite.expression.builtin.TimeExtractOperatorConversion;
|
||||
import io.druid.sql.calcite.expression.builtin.TimeFloorOperatorConversion;
|
||||
|
@ -146,6 +148,8 @@ public class DruidOperatorTable implements SqlOperatorTable
|
|||
.add(new RegexpExtractOperatorConversion())
|
||||
.add(new StrposOperatorConversion())
|
||||
.add(new SubstringOperatorConversion())
|
||||
.add(new ConcatOperatorConversion())
|
||||
.add(new TextcatOperatorConversion())
|
||||
.add(new AliasedOperatorConversion(new SubstringOperatorConversion(), "SUBSTR"))
|
||||
.add(new TimeArithmeticOperatorConversion.TimeMinusIntervalOperatorConversion())
|
||||
.add(new TimeArithmeticOperatorConversion.TimePlusIntervalOperatorConversion())
|
||||
|
|
|
@ -6824,6 +6824,114 @@ public class CalciteQueryTest extends CalciteTestBase
|
|||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConcat() throws Exception
|
||||
{
|
||||
testQuery(
|
||||
"SELECT CONCAT(dim1, '-', dim1, '_', dim1) as dimX FROM foo",
|
||||
ImmutableList.of(
|
||||
newScanQueryBuilder()
|
||||
.dataSource(CalciteTests.DATASOURCE1)
|
||||
.intervals(QSS(Filtration.eternity()))
|
||||
.virtualColumns(EXPRESSION_VIRTUAL_COLUMN(
|
||||
"v0",
|
||||
"concat(\"dim1\",'-',\"dim1\",'_',\"dim1\")",
|
||||
ValueType.STRING
|
||||
))
|
||||
.columns("v0")
|
||||
.resultFormat(ScanQuery.RESULT_FORMAT_COMPACTED_LIST)
|
||||
.context(QUERY_CONTEXT_DEFAULT)
|
||||
.build()
|
||||
),
|
||||
ImmutableList.of(
|
||||
new Object[]{"-_"},
|
||||
new Object[]{"10.1-10.1_10.1"},
|
||||
new Object[]{"2-2_2"},
|
||||
new Object[]{"1-1_1"},
|
||||
new Object[]{"def-def_def"},
|
||||
new Object[]{"abc-abc_abc"}
|
||||
)
|
||||
);
|
||||
|
||||
testQuery(
|
||||
"SELECT CONCAT(dim1, CONCAT(dim2,'x'), m2, 9999, dim1) as dimX FROM foo",
|
||||
ImmutableList.of(
|
||||
newScanQueryBuilder()
|
||||
.dataSource(CalciteTests.DATASOURCE1)
|
||||
.intervals(QSS(Filtration.eternity()))
|
||||
.virtualColumns(EXPRESSION_VIRTUAL_COLUMN(
|
||||
"v0",
|
||||
"concat(\"dim1\",concat(\"dim2\",'x'),\"m2\",9999,\"dim1\")",
|
||||
ValueType.STRING
|
||||
))
|
||||
.columns("v0")
|
||||
.resultFormat(ScanQuery.RESULT_FORMAT_COMPACTED_LIST)
|
||||
.context(QUERY_CONTEXT_DEFAULT)
|
||||
.build()
|
||||
),
|
||||
ImmutableList.of(
|
||||
new Object[]{"ax1.09999"},
|
||||
new Object[]{"10.1x2.0999910.1"},
|
||||
new Object[]{"2x3.099992"},
|
||||
new Object[]{"1ax4.099991"},
|
||||
new Object[]{"defabcx5.09999def"},
|
||||
new Object[]{"abcx6.09999abc"}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTextcat() throws Exception
|
||||
{
|
||||
testQuery(
|
||||
"SELECT textcat(dim1, dim1) as dimX FROM foo",
|
||||
ImmutableList.of(
|
||||
newScanQueryBuilder()
|
||||
.dataSource(CalciteTests.DATASOURCE1)
|
||||
.intervals(QSS(Filtration.eternity()))
|
||||
.virtualColumns(EXPRESSION_VIRTUAL_COLUMN("v0", "concat(\"dim1\",\"dim1\")", ValueType.STRING))
|
||||
.columns("v0")
|
||||
.resultFormat(ScanQuery.RESULT_FORMAT_COMPACTED_LIST)
|
||||
.context(QUERY_CONTEXT_DEFAULT)
|
||||
.build()
|
||||
),
|
||||
ImmutableList.of(
|
||||
new Object[]{""},
|
||||
new Object[]{"10.110.1"},
|
||||
new Object[]{"22"},
|
||||
new Object[]{"11"},
|
||||
new Object[]{"defdef"},
|
||||
new Object[]{"abcabc"}
|
||||
)
|
||||
);
|
||||
|
||||
testQuery(
|
||||
"SELECT textcat(dim1, CAST(m2 as VARCHAR)) as dimX FROM foo",
|
||||
ImmutableList.of(
|
||||
newScanQueryBuilder()
|
||||
.dataSource(CalciteTests.DATASOURCE1)
|
||||
.intervals(QSS(Filtration.eternity()))
|
||||
.virtualColumns(EXPRESSION_VIRTUAL_COLUMN(
|
||||
"v0",
|
||||
"concat(\"dim1\",CAST(\"m2\", 'STRING'))",
|
||||
ValueType.STRING
|
||||
))
|
||||
.columns("v0")
|
||||
.resultFormat(ScanQuery.RESULT_FORMAT_COMPACTED_LIST)
|
||||
.context(QUERY_CONTEXT_DEFAULT)
|
||||
.build()
|
||||
),
|
||||
ImmutableList.of(
|
||||
new Object[]{"1.0"},
|
||||
new Object[]{"10.12.0"},
|
||||
new Object[]{"23.0"},
|
||||
new Object[]{"14.0"},
|
||||
new Object[]{"def5.0"},
|
||||
new Object[]{"abc6.0"}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private void testQuery(
|
||||
final String sql,
|
||||
final List<Query> expectedQueries,
|
||||
|
|
Loading…
Reference in New Issue