Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -75,6 +75,9 @@ class LocalHiveContext(sc: SparkContext) extends HiveContext(sc) {
*/
class HiveContext(sc: SparkContext) extends SQLContext(sc) {
self =>

@transient
protected[sql] val hiveParser = new HiveSqlParser

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we move this to HiveQl? It seems more reasonable to put all HiveQL parsing logic together.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

OK. I have moved it to HiveQl now.


// Change the default SQL dialect to HiveQL
override private[spark] def dialect: String = getConf(SQLConf.DIALECT, "hiveql")
Expand All @@ -95,15 +98,15 @@ class HiveContext(sc: SparkContext) extends SQLContext(sc) {
if (dialect == "sql") {
super.sql(sqlText)
} else if (dialect == "hiveql") {
new SchemaRDD(this, HiveQl.parseSql(sqlText))
new SchemaRDD(this, hiveParser(sqlText))
} else {
sys.error(s"Unsupported SQL dialect: $dialect. Try 'sql' or 'hiveql'")
}
}

@deprecated("hiveql() is deprecated as the sql function now parses using HiveQL by default. " +
s"The SQL dialect for parsing can be set using ${SQLConf.DIALECT}", "1.1")
def hiveql(hqlQuery: String): SchemaRDD = new SchemaRDD(this, HiveQl.parseSql(hqlQuery))
def hiveql(hqlQuery: String): SchemaRDD = new SchemaRDD(this, hiveParser(hqlQuery))

@deprecated("hql() is deprecated as the sql function now parses using HiveQL by default. " +
s"The SQL dialect for parsing can be set using ${SQLConf.DIALECT}", "1.1")
Expand Down
138 changes: 138 additions & 0 deletions sql/hive/src/main/scala/org/apache/spark/sql/hive/HiveSqlParser.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* 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 org.apache.spark.sql.hive

import scala.language.implicitConversions
import scala.util.parsing.combinator.syntactical.StandardTokenParsers
import scala.util.parsing.combinator.PackratParsers
import scala.util.parsing.input.CharArrayReader.EofCh
import org.apache.spark.sql.catalyst.plans.logical._
import org.apache.spark.sql.catalyst.SqlLexical
import scala.util.parsing.combinator.lexical.StdLexical

/**
* A simple Hive SQL pre parser. It parses the commands like cache,uncache etc and
* remaining actual query will be parsed by HiveQl.parseSql
*/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Since this class takes over all HiveQL parsing work (although it delegates to an underlying Hive parser), it's not accurate to call it a "pre parser", maybe this:

A parser that recognizes all HiveQL constructs together with several Spark SQL specific extensions like CACHE TABLE and UNCACHE TABLE.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Looks good. I updated as per your comment

class HiveSqlParser extends StandardTokenParsers with PackratParsers {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe ExtendedHiveQLParser is a better name? Since HiveQL is the definitive name and we're adding extensions to it in Spark SQL.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

OK. Changed the file name.


def apply(input: String): LogicalPlan = {
// Special-case out set commands since the value fields can be
// complex to handle without RegexParsers. Also this approach
// is clearer for the several possible cases of set commands.
if (input.trim.toLowerCase.startsWith("set")) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'd like to handle SET in parser combinator too, but we can leave this to another PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

OK.

input.trim.drop(3).split("=", 2).map(_.trim) match {
case Array("") => // "set"
SetCommand(None, None)
case Array(key) => // "set key"
SetCommand(Some(key), None)
case Array(key, value) => // "set key=value"
SetCommand(Some(key), Some(value))
}
} else if (input.trim.startsWith("!")) {
ShellCommand(input.drop(1))
} else {
phrase(query)(new lexical.Scanner(input)) match {
case Success(r, x) => r
case x => sys.error(x.toString)
}
}
}

protected case class Keyword(str: String)

protected val CACHE = Keyword("CACHE")
protected val SET = Keyword("SET")
protected val ADD = Keyword("ADD")
protected val JAR = Keyword("JAR")
protected val TABLE = Keyword("TABLE")
protected val AS = Keyword("AS")
protected val UNCACHE = Keyword("UNCACHE")
protected val FILE = Keyword("FILE")
protected val DFS = Keyword("DFS")
protected val SOURCE = Keyword("SOURCE")

protected implicit def asParser(k: Keyword): Parser[String] =
lexical.allCaseVersions(k.str).map(x => x : Parser[String]).reduce(_ | _)

protected def allCaseConverse(k: String): Parser[String] =
lexical.allCaseVersions(k).map(x => x : Parser[String]).reduce(_ | _)

protected val reservedWords =
this.getClass
.getMethods
.filter(_.getReturnType == classOf[Keyword])
.map(_.invoke(this).asInstanceOf[Keyword].str)

override val lexical = new SqlLexical(reservedWords)

protected lazy val query: Parser[LogicalPlan] = (
cache | unCache | addJar | addFile | dfs | source | hiveQl

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: I'd prefer uncache to unCache.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

updated

)

protected lazy val hiveQl: Parser[LogicalPlan] =
remainingQuery ^^ {
case r => HiveQl.parseSql(r.trim())
}

/** It returns all remaining query */
protected lazy val remainingQuery: Parser[String] = new Parser[String] {
def apply(in:Input) = Success(in.source.subSequence(in.offset, in.source.length).toString,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: space after :

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated

in.drop(in.source.length()))
}

/** It returns all query */
protected lazy val allQuery: Parser[String] = new Parser[String] {
def apply(in:Input) = Success(in.source.toString,
in.drop(in.source.length()))
}

protected lazy val cache: Parser[LogicalPlan] =
CACHE ~ TABLE ~> ident ~ opt(AS ~> hiveQl) ^^ {
case tableName ~ None => CacheCommand(tableName, true)
case tableName ~ Some(plan) =>
CacheTableAsSelectCommand(tableName, plan)
}

protected lazy val unCache: Parser[LogicalPlan] =
UNCACHE ~ TABLE ~> ident ^^ {
case tableName => CacheCommand(tableName, false)
}

protected lazy val addJar: Parser[LogicalPlan] =
ADD ~ JAR ~> remainingQuery ^^ {
case rq => AddJar(rq.trim())
}

protected lazy val addFile: Parser[LogicalPlan] =
ADD ~ FILE ~> remainingQuery ^^ {
case rq => AddFile(rq.trim())
}

protected lazy val dfs: Parser[LogicalPlan] =
DFS ~> allQuery ^^ {
case aq => NativeCommand(aq.trim())
}

protected lazy val source: Parser[LogicalPlan] =
SOURCE ~> remainingQuery ^^ {
case rq => SourceCommand(rq.trim())
}

}

Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ class TestHiveContext(sc: SparkContext) extends HiveContext(sc) {
val describedTable = "DESCRIBE (\\w+)".r

protected[hive] class HiveQLQueryExecution(hql: String) extends this.QueryExecution {
lazy val logical = HiveQl.parseSql(hql)
lazy val logical = hiveParser(hql)
def hiveExec() = runSqlHive(hql)
override def toString = hql + "\n" + super.toString
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class JavaHiveContext(sparkContext: JavaSparkContext) extends JavaSQLContext(spa
if (sqlContext.dialect == "sql") {
super.sql(sqlText)
} else if (sqlContext.dialect == "hiveql") {
new JavaSchemaRDD(sqlContext, HiveQl.parseSql(sqlText))
new JavaSchemaRDD(sqlContext, sqlContext.hiveParser(sqlText))
} else {
sys.error(s"Unsupported SQL dialect: ${sqlContext.dialect}. Try 'sql' or 'hiveql'")
}
Expand All @@ -45,5 +45,5 @@ class JavaHiveContext(sparkContext: JavaSparkContext) extends JavaSQLContext(spa
*/
@Deprecated
def hql(hqlQuery: String): JavaSchemaRDD =
new JavaSchemaRDD(sqlContext, HiveQl.parseSql(hqlQuery))
new JavaSchemaRDD(sqlContext, sqlContext.hiveParser(hqlQuery))
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,10 @@ class CachedTableSuite extends HiveComparisonTest {
}
assert(!TestHive.isCached("src"), "Table 'src' should not be cached")
}

test("'CACHE TABLE tableName AS SELECT ..'") {
TestHive.sql("CACHE TABLE testCacheTable AS SELECT * FROM src")
assert(TestHive.isCached("testCacheTable"), "Table 'testCacheTable' should be cached")
TestHive.uncacheTable("testCacheTable")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class StatisticsSuite extends QueryTest with BeforeAndAfterAll {

test("parse analyze commands") {
def assertAnalyzeCommand(analyzeCommand: String, c: Class[_]) {
val parsed = HiveQl.parseSql(analyzeCommand)
val parsed = new HiveSqlParser().apply(analyzeCommand)
val operators = parsed.collect {
case a: AnalyzeTable => a
case o => o
Expand Down