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
1 change: 1 addition & 0 deletions docs/sql-keywords.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ Below is a list of all the keywords in Spark SQL.
<tr><td>TRANSACTION</td><td>non-reserved</td><td>non-reserved</td><td>non-reserved</td></tr>
<tr><td>TRANSACTIONS</td><td>non-reserved</td><td>non-reserved</td><td>non-reserved</td></tr>
<tr><td>TRANSFORM</td><td>non-reserved</td><td>non-reserved</td><td>non-reserved</td></tr>
<tr><td>TRIM</td><td>non-reserved</td><td>non-reserved</td><td>non-reserved</td></tr>
<tr><td>TRUE</td><td>non-reserved</td><td>non-reserved</td><td>reserved</td></tr>
<tr><td>TRUNCATE</td><td>non-reserved</td><td>non-reserved</td><td>reserved</td></tr>
<tr><td>UNARCHIVE</td><td>non-reserved</td><td>non-reserved</td><td>non-reserved</td></tr>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -694,8 +694,6 @@ primaryExpression
| '(' query ')' #subqueryExpression
| qualifiedName '(' (setQuantifier? argument+=expression (',' argument+=expression)*)? ')'
(OVER windowSpec)? #functionCall
| qualifiedName '(' trimOption=(BOTH | LEADING | TRAILING) (argument+=expression)?
FROM argument+=expression ')' #functionCall
| IDENTIFIER '->' expression #lambda
| '(' IDENTIFIER (',' IDENTIFIER)+ ')' '->' expression #lambda
| value=primaryExpression '[' index=valueExpression ']' #subscript
Expand All @@ -705,6 +703,8 @@ primaryExpression
| EXTRACT '(' field=identifier FROM source=valueExpression ')' #extract
| (SUBSTR | SUBSTRING) '(' str=valueExpression (FROM | ',') pos=valueExpression
((FOR | ',') len=valueExpression)? ')' #substring
| TRIM '(' trimOption=(BOTH | LEADING | TRAILING) (trimStr=valueExpression)?
FROM srcStr=valueExpression ')' #trim
;

constant
Expand Down Expand Up @@ -1059,6 +1059,7 @@ ansiNonReserved
| TRANSACTION
| TRANSACTIONS
| TRANSFORM
| TRIM
| TRUE
| TRUNCATE
| UNARCHIVE
Expand Down Expand Up @@ -1319,6 +1320,7 @@ nonReserved
| TRANSACTION
| TRANSACTIONS
| TRANSFORM
| TRIM
| TRUE
| TRUNCATE
| TYPE
Expand Down Expand Up @@ -1577,6 +1579,7 @@ TRAILING: 'TRAILING';
TRANSACTION: 'TRANSACTION';
TRANSACTIONS: 'TRANSACTIONS';
TRANSFORM: 'TRANSFORM';
TRIM: 'TRIM';
TRUE: 'TRUE';
TRUNCATE: 'TRUNCATE';
TYPE: 'TYPE';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1403,29 +1403,28 @@ class AstBuilder(conf: SQLConf) extends SqlBaseBaseVisitor[AnyRef] with Logging
}

/**
* Create a (windowed)/trim Function expression.
* Create a Trim expression.
*/
override def visitTrim(ctx: TrimContext): Expression = withOrigin(ctx) {
val srcStr = expression(ctx.srcStr)
val trimStr = Option(ctx.trimStr).map(expression)
ctx.trimOption.getType match {
case SqlBaseParser.BOTH =>
StringTrim(srcStr, trimStr)
case SqlBaseParser.LEADING =>
StringTrimLeft(srcStr, trimStr)
case SqlBaseParser.TRAILING =>
StringTrimRight(srcStr, trimStr)
case other =>
throw new ParseException("Function trim doesn't support with " +
s"type $other. Please use BOTH, LEADING or TRAILING as trim type", ctx)
}
}

/**
* Create a (windowed) Function expression.
*/
override def visitFunctionCall(ctx: FunctionCallContext): Expression = withOrigin(ctx) {
def replaceFunctions(
funcID: FunctionIdentifier,
ctx: FunctionCallContext): FunctionIdentifier = {
val opt = ctx.trimOption
if (opt != null) {
if (ctx.qualifiedName.getText.toLowerCase(Locale.ROOT) != "trim") {
throw new ParseException(s"The specified function ${ctx.qualifiedName.getText} " +
s"doesn't support with option ${opt.getText}.", ctx)
}
opt.getType match {
case SqlBaseParser.BOTH => funcID
case SqlBaseParser.LEADING => funcID.copy(funcName = "ltrim")
case SqlBaseParser.TRAILING => funcID.copy(funcName = "rtrim")
case _ => throw new ParseException("Function trim doesn't support with " +
s"type ${opt.getType}. Please use BOTH, LEADING or TRAILING as trim type", ctx)
}
} else {
funcID
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old one was a hacky way.

// Create the function call.
val name = ctx.qualifiedName.getText
val isDistinct = Option(ctx.setQuantifier()).exists(_.DISTINCT != null)
Expand All @@ -1437,9 +1436,7 @@ class AstBuilder(conf: SQLConf) extends SqlBaseBaseVisitor[AnyRef] with Logging
case expressions =>
expressions
}
val funcId = replaceFunctions(visitFunctionName(ctx.qualifiedName), ctx)
val function = UnresolvedFunction(funcId, arguments, isDistinct)

val function = UnresolvedFunction(visitFunctionName(ctx.qualifiedName), arguments, isDistinct)

// Check if the function is evaluated in a windowed context.
ctx.windowSpec match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
package org.apache.spark.sql.catalyst.parser

import org.apache.spark.sql.catalyst.{FunctionIdentifier, TableIdentifier}
import org.apache.spark.sql.catalyst.analysis.{AnalysisTest, UnresolvedAttribute, UnresolvedFunction, UnresolvedGenerator, UnresolvedInlineTable, UnresolvedRelation, UnresolvedSubqueryColumnAliases, UnresolvedTableValuedFunction}
import org.apache.spark.sql.catalyst.analysis.{AnalysisTest, UnresolvedAlias, UnresolvedAttribute, UnresolvedFunction, UnresolvedGenerator, UnresolvedInlineTable, UnresolvedRelation, UnresolvedSubqueryColumnAliases, UnresolvedTableValuedFunction}
import org.apache.spark.sql.catalyst.expressions._
import org.apache.spark.sql.catalyst.plans._
import org.apache.spark.sql.catalyst.plans.logical._
Expand Down Expand Up @@ -702,33 +702,40 @@ class PlanParserSuite extends AnalysisTest {
}

test("TRIM function") {
intercept("select ltrim(both 'S' from 'SS abc S'", "missing ')' at '<EOF>'")
intercept("select rtrim(trailing 'S' from 'SS abc S'", "missing ')' at '<EOF>'")
def assertTrimPlans(inputSQL: String, expectedExpression: Expression): Unit = {
comparePlans(
parsePlan(inputSQL),
Project(Seq(UnresolvedAlias(expectedExpression)), OneRowRelation())
)
}

assertEqual(
intercept("select ltrim(both 'S' from 'SS abc S'", "mismatched input 'from' expecting {')'")
intercept("select rtrim(trailing 'S' from 'SS abc S'", "mismatched input 'from' expecting {')'")
Copy link
Member

@dongjoon-hyun dongjoon-hyun Jun 19, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the change of error message, but it looks okay. The previous error message is the following.

spark-sql> select ltrim(both 'S' from 'SS abc S';
Error in query:
missing ')' at '<EOF>'(line 1, pos 37)

== SQL ==
select ltrim(both 'S' from 'SS abc S'
-------------------------------------^^^


assertTrimPlans(
"SELECT TRIM(BOTH '@$%&( )abc' FROM '@ $ % & ()abc ' )",
OneRowRelation().select('TRIM.function("@$%&( )abc", "@ $ % & ()abc "))
StringTrim(Literal("@ $ % & ()abc "), Some(Literal("@$%&( )abc")))
)
assertEqual(
assertTrimPlans(
"SELECT TRIM(LEADING 'c []' FROM '[ ccccbcc ')",
OneRowRelation().select('ltrim.function("c []", "[ ccccbcc "))
StringTrimLeft(Literal("[ ccccbcc "), Some(Literal("c []")))
)
assertEqual(
assertTrimPlans(
"SELECT TRIM(TRAILING 'c&^,.' FROM 'bc...,,,&&&ccc')",
OneRowRelation().select('rtrim.function("c&^,.", "bc...,,,&&&ccc"))
StringTrimRight(Literal("bc...,,,&&&ccc"), Some(Literal("c&^,.")))
)

assertEqual(
assertTrimPlans(
"SELECT TRIM(BOTH FROM ' bunch o blanks ')",
OneRowRelation().select('TRIM.function(" bunch o blanks "))
StringTrim(Literal(" bunch o blanks "), None)
)
assertEqual(
assertTrimPlans(
"SELECT TRIM(LEADING FROM ' bunch o blanks ')",
OneRowRelation().select('ltrim.function(" bunch o blanks "))
StringTrimLeft(Literal(" bunch o blanks "), None)
)
assertEqual(
assertTrimPlans(
"SELECT TRIM(TRAILING FROM ' bunch o blanks ')",
OneRowRelation().select('rtrim.function(" bunch o blanks "))
StringTrimRight(Literal(" bunch o blanks "), None)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ class TableIdentifierParserSuite extends SparkFunSuite with SQLHelper {
"transaction",
"transactions",
"trigger",
"trim",
"true",
"truncate",
"unarchive",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ SELECT substring('Spark SQL' from -3);
SELECT substring('Spark SQL' from 5 for 1);

-- trim/ltrim/rtrim
SELECT trim('yxTomxx', 'xyz');
SELECT trim('xxxbarxxx', 'x');
SELECT ltrim('zzzytest', 'xyz');
SELECT ltrim('zzzytestxyz', 'xyz');
SELECT ltrim('xyxXxyLAST WORD', 'xy');
SELECT rtrim('testxxzx', 'xyz');
SELECT rtrim('xyztestxxzx', 'xyz');
SELECT rtrim('TURNERyxXxy', 'xy');
SELECT trim('yxTomxx', 'xyz'), trim(BOTH 'xyz' FROM 'yxTomxx');
SELECT trim('xxxbarxxx', 'x'), trim(BOTH 'x' FROM 'xxxbarxxx');
SELECT ltrim('zzzytest', 'xyz'), trim(LEADING 'xyz' FROM 'zzzytest');
SELECT ltrim('zzzytestxyz', 'xyz'), trim(LEADING 'xyz' FROM 'zzzytestxyz');
SELECT ltrim('xyxXxyLAST WORD', 'xy'), trim(LEADING 'xy' FROM 'xyxXxyLAST WORD');
SELECT rtrim('testxxzx', 'xyz'), trim(TRAILING 'xyz' FROM 'testxxzx');
SELECT rtrim('xyztestxxzx', 'xyz'), trim(TRAILING 'xyz' FROM 'xyztestxxzx');
SELECT rtrim('TURNERyxXxy', 'xy'), trim(TRAILING 'xy' FROM 'TURNERyxXxy');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the new test coverage!

Original file line number Diff line number Diff line change
Expand Up @@ -205,64 +205,64 @@ k


-- !query 25
SELECT trim('yxTomxx', 'xyz')
SELECT trim('yxTomxx', 'xyz'), trim(BOTH 'xyz' FROM 'yxTomxx')
-- !query 25 schema
struct<trim(yxTomxx, xyz):string>
struct<trim(yxTomxx, xyz):string,trim(yxTomxx, xyz):string>
-- !query 25 output
Tom
Tom Tom


-- !query 26
SELECT trim('xxxbarxxx', 'x')
SELECT trim('xxxbarxxx', 'x'), trim(BOTH 'x' FROM 'xxxbarxxx')
-- !query 26 schema
struct<trim(xxxbarxxx, x):string>
struct<trim(xxxbarxxx, x):string,trim(xxxbarxxx, x):string>
-- !query 26 output
bar
bar bar


-- !query 27
SELECT ltrim('zzzytest', 'xyz')
SELECT ltrim('zzzytest', 'xyz'), trim(LEADING 'xyz' FROM 'zzzytest')
-- !query 27 schema
struct<ltrim(zzzytest, xyz):string>
struct<ltrim(zzzytest, xyz):string,ltrim(zzzytest, xyz):string>
-- !query 27 output
test
test test


-- !query 28
SELECT ltrim('zzzytestxyz', 'xyz')
SELECT ltrim('zzzytestxyz', 'xyz'), trim(LEADING 'xyz' FROM 'zzzytestxyz')
-- !query 28 schema
struct<ltrim(zzzytestxyz, xyz):string>
struct<ltrim(zzzytestxyz, xyz):string,ltrim(zzzytestxyz, xyz):string>
-- !query 28 output
testxyz
testxyz testxyz


-- !query 29
SELECT ltrim('xyxXxyLAST WORD', 'xy')
SELECT ltrim('xyxXxyLAST WORD', 'xy'), trim(LEADING 'xy' FROM 'xyxXxyLAST WORD')
-- !query 29 schema
struct<ltrim(xyxXxyLAST WORD, xy):string>
struct<ltrim(xyxXxyLAST WORD, xy):string,ltrim(xyxXxyLAST WORD, xy):string>
-- !query 29 output
XxyLAST WORD
XxyLAST WORD XxyLAST WORD


-- !query 30
SELECT rtrim('testxxzx', 'xyz')
SELECT rtrim('testxxzx', 'xyz'), trim(TRAILING 'xyz' FROM 'testxxzx')
-- !query 30 schema
struct<rtrim(testxxzx, xyz):string>
struct<rtrim(testxxzx, xyz):string,rtrim(testxxzx, xyz):string>
-- !query 30 output
test
test test


-- !query 31
SELECT rtrim('xyztestxxzx', 'xyz')
SELECT rtrim('xyztestxxzx', 'xyz'), trim(TRAILING 'xyz' FROM 'xyztestxxzx')
-- !query 31 schema
struct<rtrim(xyztestxxzx, xyz):string>
struct<rtrim(xyztestxxzx, xyz):string,rtrim(xyztestxxzx, xyz):string>
-- !query 31 output
xyztest
xyztest xyztest


-- !query 32
SELECT rtrim('TURNERyxXxy', 'xy')
SELECT rtrim('TURNERyxXxy', 'xy'), trim(TRAILING 'xy' FROM 'TURNERyxXxy')
-- !query 32 schema
struct<rtrim(TURNERyxXxy, xy):string>
struct<rtrim(TURNERyxXxy, xy):string,rtrim(TURNERyxXxy, xy):string>
-- !query 32 output
TURNERyxX
TURNERyxX TURNERyxX