Skip to content

Commit 7dd233e

Browse files
mergify[bot]wyb
andauthored
[BugFix] Allow non-reserved keywords FLOOR/CEIL as column names (backport #75241) (backport #75292) (#75295)
Signed-off-by: wyb <wybb86@gmail.com> Co-authored-by: wyb <wybb86@gmail.com>
1 parent c8f0380 commit 7dd233e

6 files changed

Lines changed: 109 additions & 65 deletions

File tree

fe/fe-core/src/main/java/com/starrocks/sql/ast/UnitBoundary.java

Lines changed: 0 additions & 48 deletions
This file was deleted.

fe/fe-core/src/main/java/com/starrocks/sql/parser/AstBuilder.java

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,6 @@
466466
import com.starrocks.sql.ast.TruncateTableStmt;
467467
import com.starrocks.sql.ast.UninstallPluginStmt;
468468
import com.starrocks.sql.ast.UnionRelation;
469-
import com.starrocks.sql.ast.UnitBoundary;
470469
import com.starrocks.sql.ast.UnitIdentifier;
471470
import com.starrocks.sql.ast.UnsupportedStmt;
472471
import com.starrocks.sql.ast.UpdateFailPointStatusStatement;
@@ -589,6 +588,13 @@ public class AstBuilder extends StarRocksBaseVisitor<ParseNode> {
589588
Lists.newArrayList(FunctionSet.SUBSTR, FunctionSet.SUBSTRING,
590589
FunctionSet.FROM_UNIXTIME, FunctionSet.FROM_UNIXTIME_MS,
591590
FunctionSet.STR2DATE);
591+
592+
// Boundary keywords accepted as the third argument of time_slice / date_slice
593+
// (e.g. time_slice(dt, interval 5 minute, FLOOR)); the lower-case form is what the
594+
// function implementation expects.
595+
private static final String TIME_SLICE_BOUNDARY_FLOOR = "floor";
596+
private static final String TIME_SLICE_BOUNDARY_CEIL = "ceil";
597+
592598
// rewriter
593599
private static final CompoundPredicateExprRewriter COMPOUND_PREDICATE_EXPR_REWRITER = new CompoundPredicateExprRewriter();
594600

@@ -7311,6 +7317,23 @@ private static void addArgumentUseTypeInt(Expr value, List<Expr> exprs) {
73117317
}
73127318
}
73137319

7320+
// time_slice / date_slice accept FLOOR or CEIL (case-insensitive) as their boundary argument,
7321+
// e.g. time_slice(dt, interval 5 minute, FLOOR). FLOOR and CEIL are non-reserved keywords, so the
7322+
// parser builds a column reference (SlotRef) for the bare keyword; recognize it here and normalize
7323+
// to the lower-case string the function implementation expects.
7324+
private static String getTimeSliceBoundary(ParseNode boundaryArg, String functionName) {
7325+
if (boundaryArg instanceof SlotRef) {
7326+
SlotRef slotRef = (SlotRef) boundaryArg;
7327+
if (slotRef.getTblNameWithoutAnalyzed() == null && slotRef.getColumnName() != null) {
7328+
String boundary = slotRef.getColumnName().toLowerCase();
7329+
if (boundary.equals(TIME_SLICE_BOUNDARY_FLOOR) || boundary.equals(TIME_SLICE_BOUNDARY_CEIL)) {
7330+
return boundary;
7331+
}
7332+
}
7333+
}
7334+
throw new ParsingException(PARSER_ERROR_MSG.wrongTypeOfArgs(functionName), boundaryArg.getPos());
7335+
}
7336+
73147337
@Override
73157338
public ParseNode visitSimpleFunctionCall(StarRocksParser.SimpleFunctionCallContext context) {
73167339
String fullFunctionName = getQualifiedName(context.qualifiedName()).toString();
@@ -7328,7 +7351,7 @@ public ParseNode visitSimpleFunctionCall(StarRocksParser.SimpleFunctionCallConte
73287351
IntervalLiteral intervalLiteral = (IntervalLiteral) e2;
73297352
FunctionCallExpr functionCallExpr = new FunctionCallExpr(fnName, getArgumentsForTimeSlice(e1,
73307353
intervalLiteral.getValue(), intervalLiteral.getUnitIdentifier().getDescription().toLowerCase(),
7331-
"floor"), pos);
7354+
TIME_SLICE_BOUNDARY_FLOOR), pos);
73327355

73337356
return functionCallExpr;
73347357
} else if (context.expression().size() == 3) {
@@ -7340,13 +7363,10 @@ public ParseNode visitSimpleFunctionCall(StarRocksParser.SimpleFunctionCallConte
73407363
IntervalLiteral intervalLiteral = (IntervalLiteral) e2;
73417364

73427365
ParseNode e3 = visit(context.expression(2));
7343-
if (!(e3 instanceof UnitBoundary)) {
7344-
throw new ParsingException(PARSER_ERROR_MSG.wrongTypeOfArgs(functionName), e3.getPos());
7345-
}
7346-
UnitBoundary unitBoundary = (UnitBoundary) e3;
7366+
String boundary = getTimeSliceBoundary(e3, functionName);
73477367
FunctionCallExpr functionCallExpr = new FunctionCallExpr(fnName, getArgumentsForTimeSlice(e1,
73487368
intervalLiteral.getValue(), intervalLiteral.getUnitIdentifier().getDescription().toLowerCase(),
7349-
unitBoundary.getDescription().toLowerCase()), pos);
7369+
boundary), pos);
73507370

73517371
return functionCallExpr;
73527372
} else if (context.expression().size() == 4) {
@@ -8042,11 +8062,6 @@ public ParseNode visitUnitIdentifier(StarRocksParser.UnitIdentifierContext conte
80428062
return new UnitIdentifier(context.getText(), createPos(context));
80438063
}
80448064

8045-
@Override
8046-
public ParseNode visitUnitBoundary(StarRocksParser.UnitBoundaryContext context) {
8047-
return new UnitBoundary(context.getText(), createPos(context));
8048-
}
8049-
80508065
@Override
80518066
public ParseNode visitDereference(StarRocksParser.DereferenceContext ctx) {
80528067
Expr base = (Expr) visit(ctx.base);

fe/fe-core/src/main/java/com/starrocks/sql/parser/StarRocks.g4

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2576,7 +2576,6 @@ literalExpression
25762576
| (DATE | DATETIME) string #dateLiteral
25772577
| string #stringLiteral
25782578
| interval #intervalLiteral
2579-
| unitBoundary #unitBoundaryLiteral
25802579
| binary #binaryLiteral
25812580
| PARAMETER #Parameter
25822581
;
@@ -2940,10 +2939,6 @@ unitIdentifier
29402939
: YEAR | MONTH | WEEK | DAY | HOUR | MINUTE | SECOND | QUARTER | MILLISECOND | MICROSECOND
29412940
;
29422941

2943-
unitBoundary
2944-
: FLOOR | CEIL
2945-
;
2946-
29472942
type
29482943
: baseType
29492944
| decimalType

fe/fe-core/src/test/java/com/starrocks/sql/parser/ParserTest.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,38 @@ void testNonReservedWords_2() {
189189
}
190190
}
191191

192+
@Test
193+
void testFloorCeilAsIdentifier() {
194+
// FLOOR and CEIL are non-reserved keywords (the time_slice/date_slice boundary argument).
195+
// They must remain usable as ordinary identifiers (column / table names) and must not be
196+
// parsed as a UnitBoundary literal in expression position. Regression for:
197+
// "class com.starrocks.sql.ast.UnitBoundary cannot be cast to ...ast.expression.Expr".
198+
SessionVariable sessionVariable = new SessionVariable();
199+
List<String> sqls = Lists.newArrayList(
200+
// bare keyword as a column reference (the originally reported failure)
201+
"select floor from t",
202+
"select ceil from t",
203+
"select floor, ceil from t",
204+
"select floor + 1 as c from t",
205+
"select * from t where floor > 1 and ceil < 2",
206+
"select floor as f from t group by floor order by floor",
207+
// back-quoted / qualified / as a table name
208+
"select `floor` from t",
209+
"select t.floor from t",
210+
"select * from floor",
211+
// the time_slice/date_slice boundary keyword must still parse
212+
"select time_slice(th, interval 1 year, floor) from t",
213+
"select time_slice(th, interval 1 year, CEIL) from t",
214+
"select date_slice(th, interval 1 year, ceil) from t");
215+
for (String sql : sqls) {
216+
try {
217+
SqlParser.parse(sql, sessionVariable).get(0);
218+
} catch (Exception e) {
219+
fail("sql should succeed: " + sql + " errMsg: " + e.getMessage());
220+
}
221+
}
222+
}
223+
192224
@Test
193225
void testParseLargeDecimal() {
194226
String sql = "select cast(1 as decimal(65,0))";
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
-- name: test_keyword_column_name
2+
-- FLOOR and CEIL are non-reserved keywords (the time_slice/date_slice boundary argument).
3+
-- Regression: they must be usable as ordinary column names in queries, and must not be parsed
4+
-- as a UnitBoundary literal (which previously raised
5+
-- "class com.starrocks.sql.ast.UnitBoundary cannot be cast to ...ast.expression.Expr").
6+
create database db_${uuid0};
7+
-- result:
8+
-- !result
9+
use db_${uuid0};
10+
-- result:
11+
-- !result
12+
create table t (floor int, ceil int) distributed by hash(floor);
13+
-- result:
14+
-- !result
15+
insert into t values(1, 2);
16+
-- result:
17+
-- !result
18+
select floor from t;
19+
-- result:
20+
1
21+
-- !result
22+
select ceil from t;
23+
-- result:
24+
2
25+
-- !result
26+
select floor, ceil from t;
27+
-- result:
28+
1 2
29+
-- !result
30+
select floor + ceil as s from t;
31+
-- result:
32+
3
33+
-- !result
34+
drop database db_${uuid0};
35+
-- result:
36+
-- !result
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
-- name: test_keyword_column_name
2+
-- FLOOR and CEIL are non-reserved keywords (the time_slice/date_slice boundary argument).
3+
-- Regression: they must be usable as ordinary column names in queries, and must not be parsed
4+
-- as a UnitBoundary literal (which previously raised
5+
-- "class com.starrocks.sql.ast.UnitBoundary cannot be cast to ...ast.expression.Expr").
6+
create database db_${uuid0};
7+
use db_${uuid0};
8+
create table t (floor int, ceil int) distributed by hash(floor);
9+
insert into t values(1, 2);
10+
select floor from t;
11+
select ceil from t;
12+
select floor, ceil from t;
13+
select floor + ceil as s from t;
14+
drop database db_${uuid0};

0 commit comments

Comments
 (0)