Uh oh!
There was an error while loading. Please reload this page.
[SPARK-20427][SQL] Read JDBC table use custom schema - #18266
Conversation
SparkQA
commented
Jun 11, 2017
Test build #77887 has finished for PR 18266 at commit
|
SparkQA
commented
Jun 11, 2017
Test build #77895 has finished for PR 18266 at commit
|
wangyum
commented
Jun 12, 2017
Jenkins, retest this please |
| def putMetadataArray(key: String, value: Array[Metadata]): this.type = put(key, value) | ||
| /** Puts a name. */ | ||
| def putName(name: String): this.type = put("name", name) |
There was a problem hiding this comment.
This interface change is not desired. See the PR #16209
You can further enhance our parser by supporting the data types that are not natively supported by Spark.
SparkQA
commented
Jun 12, 2017
Test build #77908 has finished for PR 18266 at commit
|
SparkQA
commented
Jun 15, 2017
Test build #78093 has finished for PR 18266 at commit
|
| } | ||
| test("SPARK-16848: jdbc API throws an exception for user specified schema") { | ||
| ignore("SPARK-16848: jdbc API throws an exception for user specified schema") { |
There was a problem hiding this comment.
JDBC didn't support specified schema before:
https://github.com/apache/spark/blob/v2.2.0-rc5/sql/core/src/main/scala/org/apache/spark/sql/DataFrameReader.scala#L188
There was a problem hiding this comment.
Then, we should remove this test case.
| StructField("N1", IntegerType, true, new MetadataBuilder().putString("name", "N1").build()), | ||
| StructField("N2", BooleanType, true, new MetadataBuilder().putString("name", "N2").build()))) | ||
| val dfRead = spark.read.schema(schema).jdbc(jdbcUrl, "custom_column_types", new Properties()) |
| val schema = StructType(Seq( | ||
| StructField("ID", DecimalType(DecimalType.MAX_PRECISION, 0), true, | ||
| new MetadataBuilder().putString("name", "ID").build()), | ||
| StructField("N1", IntegerType, true, new MetadataBuilder().putString("name", "N1").build()), |
There was a problem hiding this comment.
Why adding new MetadataBuilder().putString("name", "N1").build()?
There was a problem hiding this comment.
JDBCRDD will read metadata:
https://github.com/apache/spark/blob/v2.2.0-rc5/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/jdbc/JDBCRDD.scala#L85
I'll change here next commit.
| */ | ||
| private def pruneSchema(schema: StructType, columns: Array[String]): StructType = { | ||
| val fieldMap = Map(schema.fields.map(x => x.metadata.getString("name") -> x): _*) | ||
| val fieldMap = Map(schema.fields.map(x => x.name -> x): _*) |
There was a problem hiding this comment.
x.metadata.getString("name") always equals x.name:
https://github.com/apache/spark/blob/v2.2.0-rc5/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/jdbc/JdbcUtils.scala#L291
There was a problem hiding this comment.
This is not a related change. Could you revert it back?
There was a problem hiding this comment.
CatalystSqlParser.parseTableSchema(columnTypes) constructed StructType without metadata, error message:
key not found: name
java.util.NoSuchElementException: key not found: name
at scala.collection.MapLike$class.default(MapLike.scala:228)
at scala.collection.AbstractMap.default(Map.scala:59)
at scala.collection.MapLike$class.apply(MapLike.scala:141)
at scala.collection.AbstractMap.apply(Map.scala:59)
at org.apache.spark.sql.types.Metadata.get(Metadata.scala:111)
at org.apache.spark.sql.types.Metadata.getString(Metadata.scala:60)
at org.apache.spark.sql.execution.datasources.jdbc.JDBCRDD$$anonfun$1.apply(JDBCRDD.scala:83)
at org.apache.spark.sql.execution.datasources.jdbc.JDBCRDD$$anonfun$1.apply(JDBCRDD.scala:83)
SparkQA
commented
Jun 23, 2017
Test build #78526 has finished for PR 18266 at commit
|
| // TODO: to reuse the existing partition parameters for those partition specific options | ||
| val createTableOptions = parameters.getOrElse(JDBC_CREATE_TABLE_OPTIONS, "") | ||
| val createTableColumnTypes = parameters.get(JDBC_CREATE_TABLE_COLUMN_TYPES) | ||
| val customSchema = parameters.get(JDBC_CUSTOM_SCHEMA) |
gatorsmile
commented
Jun 30, 2017
I am fine to support customized schema for read path of JDBC relation. However, we need to check whether the user-specified schema matches the underlying the table schema. If not matched, we need to capture it earlier and issue a proper error message. |
SparkQA
commented
Jul 4, 2017
Test build #79139 has finished for PR 18266 at commit
|
SparkQA
commented
Jul 4, 2017
Test build #79137 has finished for PR 18266 at commit
|
SparkQA
commented
Jul 4, 2017
Test build #79136 has finished for PR 18266 at commit
|
gatorsmile
commented
Aug 15, 2017
The example in the PR description looks a little bit confusing. valdfRead= spark.read.schema(schema).jdbc(jdbcUrl, "tableWithCustomSchema", newProperties())Could you update it? |
| this.extraOptions ++= properties.asScala | ||
| // explicit url and dbtable should override all | ||
| this.extraOptions += (JDBCOptions.JDBC_URL -> url, JDBCOptions.JDBC_TABLE_NAME -> table) | ||
| if (userSpecifiedSchema.isDefined) { |
There was a problem hiding this comment.
Please also update another API in the line 273.
| // default will throw IllegalArgumentException | ||
| val e = intercept[org.apache.spark.SparkException] { | ||
| spark.read.jdbc(jdbcUrl, "custom_column_types", new Properties()).collect() |
There was a problem hiding this comment.
Nit: Change the table names in all the test cases.
SparkQA
commented
Aug 24, 2017
Test build #81084 has finished for PR 18266 at commit
|
sobusiak
commented
Sep 7, 2017
As far as I understand the proposed solution recommends using |
Yes, mapping to Double seems fine. this test passed: test("SPARK-20427/SPARK-20921: read table use custom schema by jdbc api") {
// default will throw IllegalArgumentExceptionvale = intercept[org.apache.spark.SparkException] {
spark.read.jdbc(jdbcUrl, "tableWithCustomSchema", newProperties()).collect()
}
assert(e.getMessage.contains(
"requirement failed: Decimal precision 39 exceeds max precision 38"))
// custom schema can read datavalprops = newProperties()
props.put("customDataFrameColumnTypes",
s"ID double, N1 int, N2 boolean")
valdfRead = spark.read.jdbc(jdbcUrl, "tableWithCustomSchema", props)
valrows = dfRead.collect()
// verify the data typevaltypes = rows(0).toSeq.map(x => x.getClass.toString)
assert(types(0).equals("class java.lang.Double"))
assert(types(1).equals("class java.lang.Integer"))
assert(types(2).equals("class java.lang.Boolean"))
// verify the valuevalvalues = rows(0)
assert(values.getDouble(0).equals(12312321321321312312312312123D))
assert(values.getInt(1).equals(1))
assert(values.getBoolean(2).equals(false))
} |
gatorsmile
commented
Sep 7, 2017
will review this today. |
| </tr> | ||
| <tr> | ||
| <td><code>customDataFrameColumnTypes</code></td> |
| <tr> | ||
| <td><code>customDataFrameColumnTypes</code></td> | ||
| <td> | ||
| The DataFrame column data types to use instead of the defaults when reading data from jdbc API. (e.g: <code>"id DECIMAL(38, 0), name STRING")</code>. The specified types should be valid spark sql data types. This option applies only to reading. |
There was a problem hiding this comment.
This is not limited to DataFrame.
The custom schema to use for reading data from JDBC connectors. For example,
"id DECIMAL(38, 0), name STRING"). The column names should be identical to the corresponding column names of JDBC table. Users can specify the corresponding data types of Spark SQL instead of using the defaults. This option applies only to reading.
| .option("dbtable", "schema.tablename") \ | ||
| .option("user", "username") \ | ||
| .option("password", "password") \ | ||
| .option("customDataFrameColumnTypes", "id DECIMAL(38, 0), name STRING") \ |
| val jdbcDF2 = spark.read | ||
| .jdbc("jdbc:postgresql:dbserver", "schema.tablename", connectionProperties) | ||
| // Specifying dataframe column data types on read | ||
| connectionProperties.put("customDataFrameColumnTypes", "id DECIMAL(38, 0), name STRING") |
| connectionProperties.put("password", "password") | ||
| val jdbcDF2 = spark.read | ||
| .jdbc("jdbc:postgresql:dbserver", "schema.tablename", connectionProperties) | ||
| // Specifying dataframe column data types on read |
There was a problem hiding this comment.
Specifying the custom data types of the read schema
| */ | ||
| def jdbc(url: String, table: String, properties: Properties): DataFrame = { | ||
| assertNoSpecifiedSchema("jdbc") | ||
| assertJdbcAPISpecifiedDataFrameSchema() |
There was a problem hiding this comment.
Users should be able to do it in either way. If users specify them in both schema() API and the customerSchema option, we should issue an exception.
SparkQA
commented
Sep 8, 2017
Test build #81536 has finished for PR 18266 at commit
|
| */ | ||
| private def pruneSchema(schema: StructType, columns: Array[String]): StructType = { | ||
| val fieldMap = Map(schema.fields.map(x => x.metadata.getString("name") -> x): _*) | ||
| val fieldMap = Map(schema.fields.map(x => x.name -> x): _*) |
There was a problem hiding this comment.
Sorry, I did not get your point. Could you show me an example? Is it a behavior breaking change?
There was a problem hiding this comment.
scala> org.apache.spark.sql.catalyst.parser.CatalystSqlParser.parseTableSchema("id int, name string").fields.map(x => x.metadata.getString("name") -> x)
java.util.NoSuchElementException: key not found: name
at scala.collection.MapLike$class.default(MapLike.scala:228)
at scala.collection.AbstractMap.default(Map.scala:59)
at scala.collection.MapLike$class.apply(MapLike.scala:141)
at scala.collection.AbstractMap.apply(Map.scala:59)
at org.apache.spark.sql.types.Metadata.get(Metadata.scala:111)
at org.apache.spark.sql.types.Metadata.getString(Metadata.scala:60)
at $anonfun$1.apply(<console>:24)
at $anonfun$1.apply(<console>:24)
at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:234)
at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:234)
at scala.collection.IndexedSeqOptimized$class.foreach(IndexedSeqOptimized.scala:33)
at scala.collection.mutable.ArrayOps$ofRef.foreach(ArrayOps.scala:186)
at scala.collection.TraversableLike$class.map(TraversableLike.scala:234)
at scala.collection.mutable.ArrayOps$ofRef.map(ArrayOps.scala:186)
... 48 elided
SparkQA
commented
Sep 10, 2017
Test build #81601 has finished for PR 18266 at commit
|
| .option("dbtable", "schema.tablename") \ | ||
| .option("user", "username") \ | ||
| .option("password", "password") \ | ||
| .option("customDataFrameColumnTypes", "id DECIMAL(38, 0), name STRING") \ |
| */ | ||
| private def pruneSchema(schema: StructType, columns: Array[String]): StructType = { | ||
| val fieldMap = Map(schema.fields.map(x => x.metadata.getString("name") -> x): _*) | ||
| val fieldMap = Map(schema.fields.map(x => x.name -> x): _*) |
There was a problem hiding this comment.
I see. Could we just get rid of the line where we put name in the metadata?
There was a problem hiding this comment.
It seems safe to remove this line.
| sqlContext.sessionState.conf.resolver) | ||
| } else { | ||
| schema | ||
| } |
There was a problem hiding this comment.
valtableSchema=JDBCRDD.resolveTable(jdbcOptions)
jdbcOptions.customSchema match {
caseSome(customSchema) =>JdbcUtils.parseUserSpecifiedColumnTypes(
tableSchema, customSchema, sparkSession.sessionState.conf.resolver)
caseNone=> tableSchema
}| */ | ||
| def parseUserSpecifiedColumnTypes( | ||
| schema: StructType, | ||
| columnTypes: String, |
There was a problem hiding this comment.
defgetCustomSchema(
tableSchema: StructType,
customSchema: String,
nameEquality: Resolver):StructType= {| userSchema.fieldNames.foreach { col => | ||
| schema.find(f => nameEquality(f.name, col)).getOrElse { | ||
| throw new AnalysisException( | ||
| s"${JDBCOptions.JDBC_CUSTOM_DATAFRAME_COLUMN_TYPES} option column $col not found in " + |
There was a problem hiding this comment.
valcolNames= tableSchema.fieldNames.mkString(",")
thrownewAnalysisException(s"Please provide all the columns, all columns are: $colNames")SparkQA
commented
Sep 13, 2017
Test build #81719 has finished for PR 18266 at commit
|
gatorsmile
commented
Sep 13, 2017
@wangyum Could you update the example in the PR description? |
wangyum
commented
Sep 13, 2017
@gatorsmile Done |
gatorsmile
commented
Sep 13, 2017
LGTM |
gatorsmile
commented
Sep 13, 2017
Thanks! Merged to master. |
…partial fields. ## What changes were proposed in this pull request? apache#18266 add a new feature to support read JDBC table use custom schema, but we must specify all the fields. For simplicity, this PR support specify partial fields. ## How was this patch tested? unit tests Author: Yuming Wang <wgyumg@gmail.com> Closesapache#19231 from wangyum/SPARK-22002.
What changes were proposed in this pull request?
Auto generated Oracle schema some times not we expect:
number(1)auto mapped to BooleanType, some times it's not we expect, per SPARK-20921.numberauto mapped to Decimal(38,10), It can't read big data, per SPARK-20427.This PR fix this issue by custom schema as follows:
or
How was this patch tested?
unit tests