-
Notifications
You must be signed in to change notification settings - Fork 29.3k
[SPARK-32402][SQL] Implement ALTER TABLE in JDBC Table Catalog #29324
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
1f38d8a
9164379
dc75eee
ce00d06
3672250
b868b1d
6c32593
8170ec3
81f34d9
1b2a53c
c14d508
d0e2a88
114b7a3
db266fa
a7eccc1
dfc7387
ef55b5e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -34,6 +34,7 @@ import org.apache.spark.sql.catalyst.encoders.RowEncoder | |
| import org.apache.spark.sql.catalyst.expressions.SpecificInternalRow | ||
| import org.apache.spark.sql.catalyst.parser.CatalystSqlParser | ||
| import org.apache.spark.sql.catalyst.util.{CaseInsensitiveMap, DateTimeUtils, GenericArrayData} | ||
| import org.apache.spark.sql.connector.catalog.TableChange | ||
| import org.apache.spark.sql.execution.datasources.jdbc.connection.ConnectionProvider | ||
| import org.apache.spark.sql.jdbc.{JdbcDialect, JdbcDialects, JdbcType} | ||
| import org.apache.spark.sql.types._ | ||
|
|
@@ -94,13 +95,7 @@ object JdbcUtils extends Logging { | |
| * Drops a table from the JDBC database. | ||
| */ | ||
| def dropTable(conn: Connection, table: String, options: JDBCOptions): Unit = { | ||
| val statement = conn.createStatement | ||
| try { | ||
| statement.setQueryTimeout(options.queryTimeout) | ||
| statement.executeUpdate(s"DROP TABLE $table") | ||
| } finally { | ||
| statement.close() | ||
| } | ||
| executeStatement(conn, options, s"DROP TABLE $table") | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -184,7 +179,7 @@ object JdbcUtils extends Logging { | |
| } | ||
| } | ||
|
|
||
| private def getJdbcType(dt: DataType, dialect: JdbcDialect): JdbcType = { | ||
| private[sql] def getJdbcType(dt: DataType, dialect: JdbcDialect): JdbcType = { | ||
| dialect.getJDBCType(dt).orElse(getCommonJDBCType(dt)).getOrElse( | ||
| throw new IllegalArgumentException(s"Can't get JDBC type for ${dt.catalogString}")) | ||
| } | ||
|
|
@@ -882,13 +877,7 @@ object JdbcUtils extends Logging { | |
| // table_options or partition_options. | ||
| // E.g., "CREATE TABLE t (name string) ENGINE=InnoDB DEFAULT CHARSET=utf8" | ||
| val sql = s"CREATE TABLE $tableName ($strSchema) $createTableOptions" | ||
| val statement = conn.createStatement | ||
| try { | ||
| statement.setQueryTimeout(options.queryTimeout) | ||
| statement.executeUpdate(sql) | ||
| } finally { | ||
| statement.close() | ||
| } | ||
| executeStatement(conn, options, sql) | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -900,10 +889,41 @@ object JdbcUtils extends Logging { | |
| newTable: String, | ||
| options: JDBCOptions): Unit = { | ||
| val dialect = JdbcDialects.get(options.url) | ||
| executeStatement(conn, options, dialect.renameTable(oldTable, newTable)) | ||
| } | ||
|
|
||
| /** | ||
| * Update a table from the JDBC database. | ||
| */ | ||
| def alterTable( | ||
| conn: Connection, | ||
| tableName: String, | ||
| changes: Seq[TableChange], | ||
| options: JDBCOptions): Unit = { | ||
| val dialect = JdbcDialects.get(options.url) | ||
| conn.setAutoCommit(false) | ||
| val statement = conn.createStatement | ||
| try { | ||
| statement.setQueryTimeout(options.queryTimeout) | ||
|
Comment on lines
+923
to
+935
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Other methods have similar code: val statement = ...
try {
statement.setQueryTimeout(options.queryTimeout)
statement.execute ...
} finally {
statement.close()
}Could you put it to a private method. |
||
| statement.executeUpdate(dialect.renameTable(oldTable, newTable)) | ||
| for (sql <- dialect.alterTable(tableName, changes)) { | ||
| statement.executeUpdate(sql) | ||
| } | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am debating if I should use
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happens if one of the statements fails? Do we leave the table in partially modified state? Should we perform all the statements atomically?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed |
||
| conn.commit() | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you check that transactions are supported like
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @cloud-fan proposed in off-line discussions to do not use transaction if |
||
| } catch { | ||
| case e: SQLException => | ||
| if (conn != null) conn.rollback() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if all JDBC servers support it. At least Spark thriftserver doesn't support it. How about we limit the scope to only support ALTER TABLE when
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would check
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that also works, but may require more test cases (one more dimension to test)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My tests have |
||
| throw e | ||
| } finally { | ||
| statement.close() | ||
| conn.setAutoCommit(true) | ||
| } | ||
| } | ||
|
|
||
| private def executeStatement(conn: Connection, options: JDBCOptions, sql: String): Unit = { | ||
| val statement = conn.createStatement | ||
| try { | ||
| statement.setQueryTimeout(options.queryTimeout) | ||
| statement.executeUpdate(sql) | ||
| } finally { | ||
| statement.close() | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,9 +19,14 @@ package org.apache.spark.sql.jdbc | |
|
|
||
| import java.sql.{Connection, Date, Timestamp} | ||
|
|
||
| import scala.collection.mutable.ArrayBuilder | ||
|
|
||
| import org.apache.commons.lang3.StringUtils | ||
|
|
||
| import org.apache.spark.annotation.{DeveloperApi, Since} | ||
| import org.apache.spark.sql.connector.catalog.TableChange | ||
| import org.apache.spark.sql.connector.catalog.TableChange._ | ||
| import org.apache.spark.sql.execution.datasources.jdbc.JdbcUtils | ||
| import org.apache.spark.sql.types._ | ||
|
|
||
| /** | ||
|
|
@@ -184,15 +189,65 @@ abstract class JdbcDialect extends Serializable { | |
| /** | ||
| * Rename an existing table. | ||
| * | ||
| * TODO (SPARK-32382): Override this method in the dialects that don't support such syntax. | ||
| * | ||
| * @param oldTable The existing table. | ||
| * @param newTable New name of the table. | ||
| * @return The SQL statement to use for renaming the table. | ||
| */ | ||
| def renameTable(oldTable: String, newTable: String): String = { | ||
| s"ALTER TABLE $oldTable RENAME TO $newTable" | ||
| } | ||
|
|
||
| /** | ||
| * Alter an existing table. | ||
| * | ||
| * @param tableName The name of the table to be altered. | ||
| * @param changes Changes to apply to the table. | ||
| * @return The SQL statements to use for altering the table. | ||
| */ | ||
| def alterTable(tableName: String, changes: Seq[TableChange]): Array[String] = { | ||
| val updateClause = ArrayBuilder.make[String] | ||
| for (change <- changes) { | ||
| change match { | ||
| case add: AddColumn => | ||
| add.fieldNames match { | ||
| case Array(name) => | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should have a better error message if the field name has more than one parts.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems to me fieldName always has only one element.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we do believe it always has one element, maybe add an assert?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. People can alter a nested field, that why the type of
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this look OK?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We will fail at this pattern match. One way is
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @cloud-fan I changed code to what you suggested. |
||
| val dataType = JdbcUtils.getJdbcType(add.dataType(), this).databaseTypeDefinition | ||
| updateClause += s"ALTER TABLE $tableName ADD COLUMN $name $dataType" | ||
| } | ||
|
cloud-fan marked this conversation as resolved.
Outdated
|
||
| case rename: RenameColumn => | ||
| rename.fieldNames match { | ||
| case Array(name) => | ||
| updateClause += s"ALTER TABLE $tableName RENAME COLUMN $name TO ${rename.newName}" | ||
| } | ||
| case delete: DeleteColumn => | ||
| delete.fieldNames match { | ||
| case Array(name) => | ||
| updateClause += s"ALTER TABLE $tableName DROP COLUMN $name" | ||
| } | ||
| case update: UpdateColumnType => | ||
| update.fieldNames match { | ||
| case Array(name) => | ||
| val dataType = JdbcUtils.getJdbcType(update.newDataType(), this) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: We know |
||
| .databaseTypeDefinition | ||
| updateClause += s"ALTER TABLE $tableName ALTER COLUMN $name $dataType" | ||
| } | ||
| case update: UpdateColumnNullability => | ||
| update.fieldNames match { | ||
| case Array(name) => | ||
| if (update.nullable()) { | ||
| updateClause += s"ALTER TABLE $tableName ALTER COLUMN $name SET NULL" | ||
| } else { | ||
| updateClause += s"ALTER TABLE $tableName ALTER COLUMN $name SET NOT NULL" | ||
| } | ||
|
huaxingao marked this conversation as resolved.
Outdated
|
||
| } | ||
| // scalastyle:off throwerror | ||
| case _ => throw new NotImplementedError(s"JDBC alterTable has Unsupported" + | ||
| s" TableChange $change") | ||
| // scalastyle:on throwerror | ||
| } | ||
| } | ||
| updateClause.result() | ||
| } | ||
| } | ||
|
|
||
| /** | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's okay to remove
private[sql]becauseexecutionis already in the private package (see also SPARK-16964)