[SPARK-33902][SQL] Support CREATE TABLE LIKE for V2#54809
[SPARK-33902][SQL] Support CREATE TABLE LIKE for V2#54809viirya wants to merge 15 commits intoapache:masterfrom
Conversation
6e695fe to
6e3053c
Compare
|
I'll take a look later today. |
| // For CREATE TABLE LIKE, use the v1 command if both the target and source are in the session | ||
| // catalog (or a V1-compatible catalog extension). If source is in a different catalog, fall | ||
| // through to the V2 execution path (CreateTableLikeExec via DataSourceV2Strategy). | ||
| case CreateTableLike( |
There was a problem hiding this comment.
What does this mean for DSv2 connectors that override the session catalog?
There was a problem hiding this comment.
yea, agree, we should add a test for sessionCatalog
There was a problem hiding this comment.
Good catch. When a connector like Iceberg overrides the session catalog, the target resolves through ResolvedV1Identifier (since supportsV1Command returns true for the session catalog), but the source — a native Iceberg Table — does NOT match ResolvedV1TableOrViewIdentifier (which requires V1Table). So ResolveSessionCatalog falls through and CreateTableLikeExec handles it, passing the Iceberg Table directly. The target createTable call goes to V2SessionCatalog, which delegates to the Iceberg catalog extension. This should work, but deserves a test. I can add one if you'd like.
| // CHAR/VARCHAR types are preserved as declared (without internal metadata expansion). | ||
| val columns = sourceTable match { | ||
| case v1: V1Table => | ||
| val rawSchema = CharVarcharUtils.getRawSchema(v1.catalogTable.schema) |
There was a problem hiding this comment.
Do we have tests for this?
There was a problem hiding this comment.
Yes — the test "CHAR and VARCHAR types are preserved from v1 source to v2 target" in CreateTableLikeSuite covers this. It creates a V1 source with CHAR(10) and VARCHAR(20), runs CREATE TABLE testcat.dst LIKE src, and asserts schema("name").dataType === CharType(10) and schema("tag").dataType === VarcharType(20).
| case class CreateTableLikeExec( | ||
| targetCatalog: TableCatalog, | ||
| targetIdent: Identifier, | ||
| sourceTable: Table, |
There was a problem hiding this comment.
Does this mean it would only work for creating V2 table from another V2 table?
There was a problem hiding this comment.
Oh, this can be V1Table that wraps CatalogTable?
There was a problem hiding this comment.
Correct on both. sourceTable: Table is the V2 Table interface, which can be any implementation. For session catalog sources, ResolveRelations wraps the CatalogTable in a V1Table, which implements Table. So V1→V2 works: the source is a V1Table and we handle it explicitly in the match block at line 57 to preserve CHAR/VARCHAR types.
| val partitioning = sourceTable.partitioning | ||
|
|
||
| // 3. Resolve provider: USING clause overrides, else copy from source. | ||
| val resolvedProvider = provider.orElse { |
There was a problem hiding this comment.
Isn't this source provider but not target? Can we actually populate this?
There was a problem hiding this comment.
What does DSv1 do and is it applicable?
There was a problem hiding this comment.
Yes, this is the source provider being copied to the target — which is exactly the semantics of CREATE TABLE LIKE: the target inherits the source's format unless overridden by a USING clause. This matches V1 CreateTableLikeCommand behavior, which also copies the source provider. The copied provider goes into PROP_PROVIDER in finalProps and is passed to catalog.createTable. Whether the target catalog uses it is catalog-specific: InMemoryCatalog stores it as-is; V2SessionCatalog validates it via DataSource.lookupDataSource.
| locationProp | ||
|
|
||
| try { | ||
| // Constraints from the source table are intentionally NOT copied for several reasons: |
There was a problem hiding this comment.
This comment is too long to be included here, let's shorten it?
|
@gengliangwang @cloud-fan, can you folks help review as well? |
|
cc @szehon-ho as well |
| // If constraint copying is desired, use ALTER TABLE ADD CONSTRAINT after creation. | ||
| // If we wanted to support them in the future, the right approach would be to add an | ||
| // INCLUDING CONSTRAINTS clause (as PostgreSQL does) rather than copying blindly. | ||
| val tableInfo = new TableInfo.Builder() |
There was a problem hiding this comment.
Good point. CatalogV2Util.convertTableProperties (used by CreateTableExec) calls withDefaultOwnership to add the current user as owner. We should do the same by adding CatalogV2Util.withDefaultOwnership(finalProps). I'll add that.
| * - Source table's TBLPROPERTIES (user-specified `properties` are used instead) | ||
| * - Statistics, owner, create time | ||
| */ | ||
| case class CreateTableLikeExec( |
There was a problem hiding this comment.
Do we have V1 -> V2 within as well across catalog tests?
There was a problem hiding this comment.
Yes — "v2 target, v1 source: schema and partitioning are copied" tests V1 source (default.src in session catalog) → V2 target (testcat.dst). The "cross-catalog" and "3-part name" tests cover V2→V2 across catalogs.
|
The proposed behavior seems different from I wonder if we can delegate what to copy on |
| ResolvedTable(_, _, table, _), | ||
| fileFormat: CatalogStorageFormat, provider, properties, ifNotExists) => | ||
| CreateTableLikeExec( | ||
| catalog.asTableCatalog, ident, table, fileFormat, provider, properties, ifNotExists) :: Nil |
There was a problem hiding this comment.
The three CreateTableLike match cases (for ResolvedTable, ResolvedPersistentView, ResolvedTempView) are nearly identical. Consider consolidating into a single pattern:
case CreateTableLike(
ResolvedIdentifier(catalog, ident), source,
fileFormat: CatalogStorageFormat, provider, properties, ifNotExists) =>
val table = source match {
case ResolvedTable(_, _, t, _) => t
case ResolvedPersistentView(_, _, meta) => V1Table(meta)
case ResolvedTempView(_, meta) => V1Table(meta)
}
CreateTableLikeExec(
catalog.asTableCatalog, ident, table, fileFormat, provider, properties, ifNotExists) :: NilThere was a problem hiding this comment.
Good suggestion. I can refactor to the single pattern you proposed, with the source table resolved in an inner match. This is cleaner and removes the duplication.
| targetCatalog: TableCatalog, | ||
| targetIdent: Identifier, | ||
| sourceTable: Table, | ||
| fileFormat: CatalogStorageFormat, |
There was a problem hiding this comment.
fileFormat: CatalogStorageFormat carries inputFormat/outputFormat/serde fields, but only locationUri is used (line 84). Consider narrowing the exec's parameter to location: Option[URI] to make the contract explicit, leaving the full CatalogStorageFormat only in the logical plan (where the V1 fallback path needs it).
There was a problem hiding this comment.
Valid. Only locationUri is used in CreateTableLikeExec. I'll change the exec's parameter to location: Option[URI] and extract it at the DataSourceV2Strategy callsite.
| val v1 = "CREATE TABLE table1 LIKE table2" | ||
| // Helper to extract fields from the new CreateTableLike unresolved plan. | ||
| // The parser now emits CreateTableLike (v2 logical plan) instead of | ||
| // CreateTableLikeCommand, so both name and source are unresolved identifiers. |
There was a problem hiding this comment.
The source is UnresolvedTableOrView, not an unresolved identifier:
| // CreateTableLikeCommand, so both name and source are unresolved identifiers. | |
| // CreateTableLikeCommand, so the name is an UnresolvedIdentifier and the source is an UnresolvedTableOrView. |
There was a problem hiding this comment.
Good catch, I'll apply the suggestion.
|
BTW, consider unifying to a single CreateTableLikeExec — The current PR keeps two execution paths: V1 fallback via CreateTableLikeCommand (for V1-V1 cases) and the new CreateTableLikeExec (for V2 targets). The test "v2 source, v1 target" already proves CreateTableLikeExec works for session catalog targets via V2SessionCatalog. |
| * @param properties User-specified TBLPROPERTIES. | ||
| * @param ifNotExists IF NOT EXISTS flag. | ||
| */ | ||
| case class CreateTableLike( |
There was a problem hiding this comment.
can we have one single command (UnaryRunnableCommand)? I thought that's the preferred way now to reduce plan complexity in the different stages
I considered this, but prefer to keep the separation consistent with how every other V2 DDL command in Spark is structured — ResolveSessionCatalog routes session catalog targets back to V1 commands, and DataSourceV2Strategy handles the rest. Unifying would require CreateTableLikeExec to absorb Hive serde semantics (STORED AS, ROW FORMAT, etc.) that properly belong to CreateTableLikeCommand. The V2→V1 test passing with a simple parquet table doesn't cover those cases. I think we'd rather keep the boundary clean and follow the established pattern. |
DBR's behavior is format-aware: what gets copied depends on the source/target format combination. A Delta target gets constraints and configuration; a non-Delta target doesn't. This implies the copy logic is delegated to the table format (Delta) rather than being hardcoded in a single generic exec node. In our implementation, CreateTableLikeExec is format-agnostic — it always copies only columns, partitioning, and provider, regardless of what the source or target catalog supports. The "delegate to format implementation" design (where Delta controls what it copies) is appealing but would require a new TableCatalog API (e.g. createTableLike), which is a larger scope. It could be treated like a special behavior if the format wants to implement it. For this PR we kept the behavior simple and consistent with existing V1 CreateTableLikeCommand. We can think it as default behavior. I'm happy to file a follow-up JIRA to explore catalog-delegated copy semantics, including comments and constraint propagation for supporting catalogs. |
|
I took a look at the PR with fresh eyes today. I do think @sarutak brings a valid point. Where This is similar to what Iceberg does with its |
|
@aokolnychyi Yeah, that's exactly I'm thinking about. |
|
The downside of adding a new method like @viirya, can you do a deeper analysis to ensure we pick the right design but also make this feature truly usable? |
We could mitigate this by adding a new The main question is this enough to get away without |
The analysis is quite long. I put it into a Google Doc:
In short, with the approach of TableCatalogCapability + Marker + Source Properties, it should be able to achieve same features like createTableLike, but it might be fragile on some features like property filtering etc. Consider the fragile features in current createTable + TableCatalogCapability + Marker + Source Properties, maybe we should go for createTableLike. The secondary reason is evolvability. If in the future we need to pass additional context (e.g., ifNotExists, a version hint, or source catalog metadata), Approach createTab can add a parameter. Approach TableCatalogCapability would need to introduce yet another synthetic property key, making the convention grow unboundedly. Approach TableCatalogCapability's only real advantage is avoiding a new TableCatalog method. But given that TableCatalog already has methods like createTable, alterTable, dropTable, adding createTableLike follows the established pattern and is a natural extension. The Spark connector API has precedent for adding new methods with default implementations or throwing exception for backward compatibility — connectors opt in when ready. |
|
+1 to the Two concrete cases where the current exec falls short for Iceberg: sort order is not captured by Thanks @viirya, @aokolnychyi, @gengliangwang, @szehon-ho, and @sarutak for pushing this forward. |
|
@jzhuge Thanks for the Iceberg examples — sort order and format-version are exactly the kind of format-specific semantics that can't be handled generically. This further confirms createTableLike is the right direction. |
Documentation helps but doesn't enforce consistency. Here's a more structural idea. The root cause of discrepancy is that the target connector has to know what is important to copy from the source — but it has no way to know source-format-specific semantics. Delta knows which properties are internal and must be filtered. Iceberg knows its sort order and format version must be preserved. A generic target connector cannot know any of this. One way to address this is to introduce a source-side contract: instead of the target being responsible for deciding what to copy, the source table exposes a pre-built TableInfo representing what it considers correct to copy for a LIKE operation. Something like a new method on the Table interface: The flow would then be:
This cleanly separates responsibilities:
The remaining discrepancy is only on the target side — e.g., a target that doesn't support constraints simply drops them. This is unavoidable and acceptable: you cannot force a target format to support features it doesn't have. But at least the source's intent is clearly expressed and correctly packaged, rather than relying on the target to reverse-engineer it. |
I'd recommend overrides only — tableInfo should contain only what the user explicitly specified in the command (TBLPROPERTIES, LOCATION, USING). Pre-merging in Spark would bring back the disambiguation problem that connectors would need to reverse-engineer which properties came from the user vs Spark, losing the clean separation that makes createTableLike better than the capability approach. With overrides-only, the contract is clear: read source state from sourceTable, apply user intent from tableInfo, connector decides what gets copied and how to merge. This matches your earlier intuition that "it was kind of nice to have overrides explicitly." |
|
How about adding a capability-gated // New capability
enum TableCatalogCapability {
SUPPORT_CREATE_TABLE_LIKE
}
// New default method in TableCatalog
default Table createTableLike(
Identifier ident,
Identifier sourceIdent,
TableInfo overrides) {
throw new UnsupportedOperationException(
name() + " does not support CREATE TABLE LIKE");
}And in Spark's execution layer: if (catalog.capabilities().contains(SUPPORT_CREATE_TABLE_LIKE)) {
// Connector handles everything: loads source, filters internal props, applies overrides
catalog.createTableLike(ident, sourceIdent, overrides)
} else {
// Fallback: Spark loads source, builds merged TableInfo, calls createTable
val source = catalog.loadTable(sourceIdent)
val merged = buildTableInfoFromSource(source, overrides)
catalog.createTable(ident, merged)
}This gives connectors like Delta full control over internal property filtering/ protocol inheritance in while still providing a reasonable fallback for simpler connectors that don't opt in. The fallback path handles the "good enough" case; connectors with complex internal state (Delta, Iceberg) implement the capability for correctness. |
|
I'm +1 to @viirya 's solution for now. @gengliangwang In this solution, |
|
I don't see how I also don't think All in all, I think |
|
I think @aokolnychyi's If the need for a source-side contract like |
|
@sarutak I see. Using |
|
Agree on that. Okay since we got consensus, I will proceed with this direction and update this PR. |
8236b58 to
c5af564
Compare
| */ | ||
| default Table createTableLike(Identifier ident, TableInfo tableInfo, Table sourceTable) | ||
| throws TableAlreadyExistsException, NoSuchNamespaceException { | ||
| return createTable(ident, tableInfo); |
There was a problem hiding this comment.
Is this actually safe or should we throw an exception by default?
There was a problem hiding this comment.
Yea, this is what I wanted to ask. Should we provide at least a default create table like for connectors not implement this API and just do minimum thing like columns and partitioning? But by doing that, we have to carry columns and partitions in the TableInfo so it raises the question in #54809 (comment).
A consistent way might be just throw an exception.
There was a problem hiding this comment.
I'm going to remove the default implementation now.
| // read source metadata, including properties and constraints, directly from sourceTable. | ||
| val tableInfo = new TableInfo.Builder() | ||
| .withColumns(columns) | ||
| .withPartitions(partitioning) |
There was a problem hiding this comment.
This seems inconsistent. It does NOT seem like TableInfo contains only overrides, it seems like it actually copies some but not all state from the source table, making it really confusing. And if it copies partitioning, why doesn't it copy constraints?
There was a problem hiding this comment.
Same question for properties too. If we copied partitioning, why didn't we copy properties? This isn't strictly overrides or am I missing something?
There was a problem hiding this comment.
Removed them as no default implementation now.
gengliangwang
left a comment
There was a problem hiding this comment.
Review
The design cleanly follows the established V2 DDL pipeline (parser → logical plan → ResolveCatalogs → ResolveSessionCatalog V1 fallback → DataSourceV2Strategy → exec → catalog API). The new createTableLike(ident, tableInfo, sourceTable) API with overrides-only tableInfo is a good contract for connector-delegated copy semantics.
Two general observations:
- Stale Scaladoc in
SparkSqlParser.scala:1144: The comment still says "Create a [[CreateTableLikeCommand]] command" but the method now returns aCreateTableLikelogical plan. - Missing CHAR/VARCHAR preservation test: The old
CreateTableLikeExechad explicitCharVarcharUtils.getRawSchemahandling for V1Table sources. The refactor to thecreateTableLikeAPI removed this (correctly — it’s now the connector’s responsibility), but the CHAR/VARCHAR test mentioned in earlier review comments is no longer present. Consider adding a test that creates a V1 source with CHAR/VARCHAR columns, runsCREATE TABLE testcat.dst LIKE src, and verifies type preservation through the connector path.
sql/catalyst/src/main/java/org/apache/spark/sql/connector/catalog/TableCatalog.java
Outdated
Show resolved
Hide resolved
## What changes were proposed in this pull request? Previously, `CREATE TABLE LIKE` was implemented only via `CreateTableLikeCommand`, which bypassed the V2 catalog pipeline entirely. This meant: - 3-part names (catalog.namespace.table) caused a parse error - 2-part names targeting a V2 catalog caused `NoSuchDatabaseException` This PR adds a V2 execution path for `CREATE TABLE LIKE`: - Grammar: change `tableIdentifier` (2-part max) to `identifierReference` (N-part) for both target and source, consistent with all other DDL commands - Parser: emit `CreateTableLike` (new V2 logical plan) instead of `CreateTableLikeCommand` directly - `ResolveCatalogs`: resolve the target `UnresolvedIdentifier` to `ResolvedIdentifier` - `ResolveSessionCatalog`: route back to `CreateTableLikeCommand` when both target and source are V1 tables/views in the session catalog (V1->V1 path) - `DataSourceV2Strategy`: convert `CreateTableLike` to new `CreateTableLikeExec` - `CreateTableLikeExec`: physical exec that copies schema and partitioning from the resolved source `Table` and calls `TableCatalog.createTable()` ## How was this patch tested? - `CreateTableLikeSuite`: new integration tests covering V2 target with V1/V2 source, cross-catalog, views as source, IF NOT EXISTS, property behavior, and V1 fallback regression - `DDLParserSuite`: updated existing `create table like` test to match the new `CreateTableLike` plan shape; added 3-part name parsing test Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add two tests covering the case where the source is a V2 table in a non-session catalog and the target resolves to the session catalog. These exercise the CreateTableLikeExec → V2SessionCatalog path and confirm that schema and partitioning are correctly propagated. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add two tests to CreateTableLikeSuite documenting that pure V2 catalogs (e.g. InMemoryCatalog) accept any provider string without validation, while V2SessionCatalog rejects non-existent providers by delegating to DataSource.lookupDataSource. This is consistent with how CreateTableExec handles the USING clause for other V2 DDL commands. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…CREATE TABLE LIKE Two new tests covering previously untested code paths in CreateTableLikeExec: - Source provider is copied to V2 target as PROP_PROVIDER when no USING override is given, consistent with how CreateTableExec handles other V2 DDL. - CHAR(n)/VARCHAR(n) types declared on a V1 source are preserved in the V2 target via CharVarcharUtils.getRawSchema, not collapsed to StringType. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add inline comment explaining the six reasons withConstraints is intentionally omitted: V1 behavior parity, ForeignKey cross-catalog dangling references, constraint name collision risk, validation status semantics on empty tables, NOT NULL already captured in nullability, and PostgreSQL precedent (INCLUDING CONSTRAINTS opt-in). Also notes the path forward if constraint copying is added in the future. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Clarify that V1 tables (CatalogTable) have no constraint objects at all since CHECK/PRIMARY KEY/UNIQUE/FOREIGN KEY are V2-only concepts added in Spark 4.1.0, rather than saying CreateTableLikeCommand "never copied" them which implies an intentional decision rather than absence of the feature. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ed identifiers After the CREATE TABLE LIKE V2 change, the target and source identifiers in CreateTableLikeCommand are now fully qualified (spark_catalog.default.*) because ResolvedV1Identifier explicitly adds the catalog component via ident.asTableIdentifier.copy(catalog = Some(catalog.name)), and ResolvedV1TableIdentifier returns t.catalogTable.identifier which also includes the catalog. Update the analyzer golden file accordingly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Narrow CreateTableLikeExec parameter from fileFormat: CatalogStorageFormat to location: Option[URI] since only locationUri is used; extract at the DataSourceV2Strategy callsite (gengliangwang) - Add withDefaultOwnership to finalProps so the target table records the current user as owner, consistent with CreateTableExec (aokolnychyi) - Consolidate three CreateTableLike pattern match cases in DataSourceV2Strategy into a single case with an inner match on the source (gengliangwang) - Shorten the constraint comment and add a note on source provider inheritance (aokolnychyi) - Fix DDLParserSuite comment: source is UnresolvedTableOrView, not an unresolved identifier (gengliangwang) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tests that when a DSv2 connector overrides the session catalog via CatalogExtension (e.g. Iceberg SparkSessionCatalog), CREATE TABLE LIKE with a native V2 source correctly uses CreateTableLikeExec and creates the target as a native V2 table in the extension catalog. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ed copy semantics Add a new default method `createTableLike(Identifier, TableInfo, Table)` to `TableCatalog` so that connectors (Delta, Iceberg, etc.) can implement format-specific CREATE TABLE LIKE semantics by accessing the resolved source `Table` object directly (e.g. Delta protocol inheritance, Iceberg sort order and format version). `TableInfo` contains user-specified overrides (TBLPROPERTIES, LOCATION), resolved provider, and current user as owner. Source TBLPROPERTIES and constraints are NOT bulk-copied; connectors read them from `sourceTable`. The default implementation falls back to `createTable(ident, tableInfo)`. `CreateTableLikeExec` is updated to call `createTableLike` instead of `createTable`. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
InMemoryTableCatalog overrides createTableLike to demonstrate connector-specific copy semantics: source properties are merged into the target (user overrides win), and source constraints are copied from sourceTable.constraints() directly. BasicInMemoryTableCatalog does not override createTableLike and uses the default fallback, which copies only schema, partitioning, and user-specified overrides. Tests added to CatalogSuite covering: - User-specified properties in tableInfo are applied to the target - Source properties are copied by the connector implementation - User-specified properties override source properties - Source constraints are copied by the connector implementation - Default fallback does not copy source properties CreateTableLikeSuite updated to reflect that InMemoryTableCatalog's createTableLike copies source properties, and adds a test for user override precedence. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… by default Key changes: - TableCatalog.createTableLike default now throws UnsupportedOperationException instead of falling back to createTable; connectors must explicitly implement it - TableInfo passed to createTableLike contains only user-specified overrides (TBLPROPERTIES, LOCATION, resolved provider, owner); schema, partitioning, and constraints are NOT pre-populated -- connectors read all source metadata directly from sourceTable - CreateTableLikeExec no longer extracts columns/partitioning into TableInfo; removed CharVarcharUtils usage (CHAR/VARCHAR preservation is connector-specific) - InMemoryTableCatalog.createTableLike updated to read columns, partitioning, and constraints from sourceTable directly - TableInfo.Builder.columns defaults to empty array (no longer null) so properties-only builds succeed while requireNonNull guard is preserved - Removed V2->V1 (session catalog target) support and related tests; that path is unsupported -- connectors targeting the session catalog must override createTableLike themselves - Updated CatalogSuite test to verify UnsupportedOperationException is thrown for catalogs that do not override createTableLike Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…dOperationException The session catalog does not implement createTableLike, so CREATE TABLE LIKE targeting it with a V2 source should throw UnsupportedOperationException. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
"There is no default implementation" was contradictory since the method is declared with 'default' and has a body. Reword to accurately say the default implementation throws UnsupportedOperationException. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… stale Scaladoc - InMemoryTableCatalog.createTableLike now applies CharVarcharUtils.getRawSchema when the source is a V1Table, preserving CHAR/VARCHAR column types as declared rather than collapsed to StringType. This illustrates the pattern connectors should follow to preserve declared types from V1 sources. - Add test "CHAR and VARCHAR types are preserved from v1 source to v2 target" in CreateTableLikeSuite to verify end-to-end type fidelity. - Fix stale Scaladoc in SparkSqlParser.scala: "Create a [[CreateTableLikeCommand]] command" → "Create a [[CreateTableLike]] logical plan." Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
eb58fad to
3b94076
Compare
What changes were proposed in this pull request?
Previously,
CREATE TABLE LIKEwas implemented only viaCreateTableLikeCommand, which bypassed the V2 catalog pipeline entirely. This meant:NoSuchDatabaseExceptionThis PR adds a V2 execution path for
CREATE TABLE LIKE:tableIdentifier(2-part max) toidentifierReference(N-part) for both target and source, consistent with all other DDL commandsCreateTableLike(new V2 logical plan) instead ofCreateTableLikeCommanddirectlycreateTableLikeAPI toTableCatalogfor connector-delegated copy semanticsResolveCatalogs: resolve the targetUnresolvedIdentifiertoResolvedIdentifierResolveSessionCatalog: route back toCreateTableLikeCommandwhen both target and source are V1 tables/views in the session catalog (V1->V1 path)DataSourceV2Strategy: convertCreateTableLiketo newCreateTableLikeExecCreateTableLikeExec: physical exec that callsTableCatalog.createTableLike()on the target catalogWhy are the changes needed?
CREATE TABLE LIKEwas implemented solely viaCreateTableLikeCommand, a V1-only command that bypasses the DataSource V2 analysis pipeline entirely. As a result, it was impossible to useCREATE TABLE LIKEto create a table in a non-session V2 catalog (e.g., testcat.dst): a 2-part name like testcat.dst was misinterpreted as database testcat in the session catalog and threwNoSuchDatabaseException, while a 3-part name like testcat.ns.dst was a parse error because the grammar only accepted 2-part tableIdentifier.This change routes
CREATE TABLE LIKEthrough the standard V2 DDL pipeline so that V2 catalog targets are fully supported, while preserving the existing V1 behavior when both target and source resolve to the session catalog.Does this PR introduce any user-facing change?
Yes.
CREATE TABLE LIKEDDL command supports V2.How was this patch tested?
CreateTableLikeSuite: new integration tests covering V2 target with V1/V2 source, cross-catalog, views as source, IF NOT EXISTS, property behavior, and V1 fallback regression, etc.DDLParserSuite: updated existingcreate table liketest to match the newCreateTableLikeplan shape; added 3-part name parsing testWas this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Sonnet 4.6