Skip to content
This repository was archived by the owner on Jun 8, 2026. It is now read-only.

Commit 5f5932a

Browse files
authored
Merge pull request cockroachdb#171440 from virajchogle/ctas-with-no-data-171333
sql: support CREATE TABLE AS ... WITH NO DATA
2 parents 2fb40b6 + 9f0d237 commit 5f5932a

9 files changed

Lines changed: 115 additions & 19 deletions

File tree

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
create_table_as_stmt ::=
2-
'CREATE' opt_persistence_temp_table 'TABLE' table_name '(' column_name create_as_col_qual_list ( ( ',' column_name create_as_col_qual_list | ',' family_def | ',' create_as_constraint_def ) )* ')' opt_with_storage_parameter_list 'AS' select_stmt 'ON' 'COMMIT' 'PRESERVE' 'ROWS'
3-
| 'CREATE' opt_persistence_temp_table 'TABLE' table_name opt_with_storage_parameter_list 'AS' select_stmt 'ON' 'COMMIT' 'PRESERVE' 'ROWS'
4-
| 'CREATE' opt_persistence_temp_table 'TABLE' 'IF' 'NOT' 'EXISTS' table_name '(' column_name create_as_col_qual_list ( ( ',' column_name create_as_col_qual_list | ',' family_def | ',' create_as_constraint_def ) )* ')' opt_with_storage_parameter_list 'AS' select_stmt 'ON' 'COMMIT' 'PRESERVE' 'ROWS'
5-
| 'CREATE' opt_persistence_temp_table 'TABLE' 'IF' 'NOT' 'EXISTS' table_name opt_with_storage_parameter_list 'AS' select_stmt 'ON' 'COMMIT' 'PRESERVE' 'ROWS'
2+
'CREATE' opt_persistence_temp_table 'TABLE' table_name '(' column_name create_as_col_qual_list ( ( ',' column_name create_as_col_qual_list | ',' family_def | ',' create_as_constraint_def ) )* ')' opt_with_storage_parameter_list 'AS' select_stmt opt_create_as_data 'ON' 'COMMIT' 'PRESERVE' 'ROWS'
3+
| 'CREATE' opt_persistence_temp_table 'TABLE' table_name opt_with_storage_parameter_list 'AS' select_stmt opt_create_as_data 'ON' 'COMMIT' 'PRESERVE' 'ROWS'
4+
| 'CREATE' opt_persistence_temp_table 'TABLE' 'IF' 'NOT' 'EXISTS' table_name '(' column_name create_as_col_qual_list ( ( ',' column_name create_as_col_qual_list | ',' family_def | ',' create_as_constraint_def ) )* ')' opt_with_storage_parameter_list 'AS' select_stmt opt_create_as_data 'ON' 'COMMIT' 'PRESERVE' 'ROWS'
5+
| 'CREATE' opt_persistence_temp_table 'TABLE' 'IF' 'NOT' 'EXISTS' table_name opt_with_storage_parameter_list 'AS' select_stmt opt_create_as_data 'ON' 'COMMIT' 'PRESERVE' 'ROWS'

docs/generated/sql/bnf/stmt_block.bnf

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2028,8 +2028,8 @@ create_table_stmt ::=
20282028
| 'CREATE' opt_persistence_temp_table 'TABLE' 'IF' 'NOT' 'EXISTS' table_name '(' opt_table_elem_list ')' opt_partition_by_table opt_table_with opt_create_table_on_commit opt_locality
20292029

20302030
create_table_as_stmt ::=
2031-
'CREATE' opt_persistence_temp_table 'TABLE' table_name create_as_opt_col_list opt_table_with 'AS' select_stmt opt_create_table_on_commit
2032-
| 'CREATE' opt_persistence_temp_table 'TABLE' 'IF' 'NOT' 'EXISTS' table_name create_as_opt_col_list opt_table_with 'AS' select_stmt opt_create_table_on_commit
2031+
'CREATE' opt_persistence_temp_table 'TABLE' table_name create_as_opt_col_list opt_table_with 'AS' select_stmt opt_create_as_data opt_create_table_on_commit
2032+
| 'CREATE' opt_persistence_temp_table 'TABLE' 'IF' 'NOT' 'EXISTS' table_name create_as_opt_col_list opt_table_with 'AS' select_stmt opt_create_as_data opt_create_table_on_commit
20332033

20342034
create_type_stmt ::=
20352035
'CREATE' 'TYPE' type_name 'AS' 'ENUM' '(' opt_enum_val_list ')'
@@ -2949,6 +2949,9 @@ create_as_opt_col_list ::=
29492949
'(' create_as_table_defs ')'
29502950
|
29512951

2952+
opt_create_as_data ::=
2953+
'WITH' 'NO' 'DATA'
2954+
29522955
opt_enum_val_list ::=
29532956
enum_val_list
29542957
|

pkg/sql/create_table.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -407,8 +407,9 @@ func (n *createTableNode) startExec(params runParams) error {
407407
}
408408

409409
// If we have a single statement txn we want to run CTAS async, and
410-
// consequently ensure it gets queued as a SchemaChange.
411-
if params.extendedEvalCtx.TxnIsSingleStmt {
410+
// consequently ensure it gets queued as a SchemaChange. WITH NO DATA
411+
// has no data to copy, so the descriptor can go PUBLIC immediately.
412+
if params.extendedEvalCtx.TxnIsSingleStmt && !n.n.WithNoData {
412413
desc.State = descpb.DescriptorState_ADD
413414
}
414415
} else {
@@ -538,8 +539,8 @@ func (n *createTableNode) startExec(params runParams) error {
538539
}
539540

540541
// If we are in a multi-statement txn or the source has placeholders, we
541-
// execute the CTAS query synchronously.
542-
if n.n.As() && !params.extendedEvalCtx.TxnIsSingleStmt {
542+
// execute the CTAS query synchronously. WITH NO DATA skips the row fill.
543+
if n.n.As() && !params.extendedEvalCtx.TxnIsSingleStmt && !n.n.WithNoData {
543544
err = func() error {
544545
// The data fill portion of CREATE AS must operate on a read snapshot,
545546
// so that it doesn't end up observing its own writes.

pkg/sql/logictest/testdata/logic_test/create_as

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,3 +688,64 @@ forks 30 1
688688
spoons 10 1
689689

690690
subtest end
691+
692+
# Test CREATE TABLE ... AS ... WITH NO DATA (#171333). The destination table
693+
# inherits the SELECT's column types but is left empty.
694+
subtest with_no_data
695+
696+
statement ok
697+
CREATE TABLE wnd_src (a INT, b STRING); INSERT INTO wnd_src VALUES (1, 'x'), (2, 'y')
698+
699+
statement ok
700+
CREATE TABLE wnd_empty AS SELECT * FROM wnd_src WITH NO DATA
701+
702+
query IT rowsort
703+
SELECT a, b FROM wnd_empty
704+
----
705+
706+
query TT
707+
SELECT column_name, data_type FROM [SHOW COLUMNS FROM wnd_empty] WHERE column_name IN ('a', 'b') ORDER BY column_name
708+
----
709+
a INT8
710+
b STRING
711+
712+
# Explicit WITH DATA still copies rows.
713+
statement ok
714+
CREATE TABLE wnd_full AS SELECT * FROM wnd_src WITH DATA
715+
716+
query IT rowsort
717+
SELECT a, b FROM wnd_full
718+
----
719+
1 x
720+
2 y
721+
722+
# Default (no clause) still copies rows.
723+
statement ok
724+
CREATE TABLE wnd_default AS SELECT * FROM wnd_src
725+
726+
query IT rowsort
727+
SELECT a, b FROM wnd_default
728+
----
729+
1 x
730+
2 y
731+
732+
# IF NOT EXISTS path accepts WITH NO DATA.
733+
statement ok
734+
CREATE TABLE IF NOT EXISTS wnd_ifne AS SELECT * FROM wnd_src WITH NO DATA
735+
736+
query IT rowsort
737+
SELECT a, b FROM wnd_ifne
738+
----
739+
740+
# Temporary table path accepts WITH NO DATA.
741+
statement ok
742+
SET experimental_enable_temp_tables = 'on'
743+
744+
statement ok
745+
CREATE TEMP TABLE wnd_temp AS SELECT * FROM wnd_src WITH NO DATA
746+
747+
query IT rowsort
748+
SELECT a, b FROM wnd_temp
749+
----
750+
751+
subtest end

pkg/sql/parser/parse_test.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -432,8 +432,6 @@ func TestUnimplementedSyntax(t *testing.T) {
432432

433433
{`CREATE TABLE a(b INT8) WITH OIDS`, 0, `create table with oids`, ``},
434434

435-
{`CREATE TABLE a AS SELECT b WITH NO DATA`, 0, `create table as with no data`, ``},
436-
437435
{`CREATE TABLE a(b INT8 REFERENCES c(x) MATCH PARTIAL`, 20305, `match partial`, ``},
438436
{`CREATE TABLE a(b INT8, FOREIGN KEY (b) REFERENCES c(x) MATCH PARTIAL)`, 20305, `match partial`, ``},
439437

pkg/sql/parser/sql.y

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1621,7 +1621,7 @@ func (u *sqlSymUnion) filterType() tree.FilterType {
16211621
%type <[]tree.RangePartition> range_partitions
16221622
%type <empty> opt_all_clause
16231623
%type <empty> opt_privileges_clause
1624-
%type <bool> distinct_clause opt_with_data
1624+
%type <bool> distinct_clause opt_with_data opt_create_as_data
16251625
%type <tree.DistinctOn> distinct_on_clause
16261626
%type <tree.NameList> opt_column_list insert_column_list opt_stats_columns query_stats_cols
16271627
// Note that "no index" variants exist to disable custom ORDER BY <index> syntax
@@ -11850,6 +11850,7 @@ create_table_as_stmt:
1185011850
IfNotExists: false,
1185111851
Defs: $5.tblDefs(),
1185211852
AsSource: $8.slct(),
11853+
WithNoData: $9.bool(),
1185311854
StorageParams: $6.storageParams(),
1185411855
OnCommit: $10.createTableOnCommitSetting(),
1185511856
Persistence: $2.persistence(),
@@ -11863,16 +11864,17 @@ create_table_as_stmt:
1186311864
IfNotExists: true,
1186411865
Defs: $8.tblDefs(),
1186511866
AsSource: $11.slct(),
11867+
WithNoData: $12.bool(),
1186611868
StorageParams: $9.storageParams(),
1186711869
OnCommit: $13.createTableOnCommitSetting(),
1186811870
Persistence: $2.persistence(),
1186911871
}
1187011872
}
1187111873

1187211874
opt_create_as_data:
11873-
/* EMPTY */ { /* no error */ }
11874-
| WITH DATA { /* SKIP DOC */ /* This is the default */ }
11875-
| WITH NO DATA { return unimplemented(sqllex, "create table as with no data") }
11875+
/* EMPTY */ { $$.val = false }
11876+
| WITH DATA { /* SKIP DOC */ $$.val = false }
11877+
| WITH NO DATA { $$.val = true }
1187611878

1187711879
/*
1187811880
* Redundancy here is needed to avoid shift/reduce conflicts,

pkg/sql/parser/testdata/create_table

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1984,6 +1984,30 @@ CREATE TABLE IF NOT EXISTS a AS SELECT (*) FROM b -- fully parenthesized
19841984
CREATE TABLE IF NOT EXISTS a AS SELECT * FROM b -- literals removed
19851985
CREATE TABLE IF NOT EXISTS _ AS SELECT * FROM _ -- identifiers removed
19861986

1987+
parse
1988+
CREATE TABLE a AS SELECT * FROM b WITH NO DATA
1989+
----
1990+
CREATE TABLE a AS SELECT * FROM b WITH NO DATA
1991+
CREATE TABLE a AS SELECT (*) FROM b WITH NO DATA -- fully parenthesized
1992+
CREATE TABLE a AS SELECT * FROM b WITH NO DATA -- literals removed
1993+
CREATE TABLE _ AS SELECT * FROM _ WITH NO DATA -- identifiers removed
1994+
1995+
parse
1996+
CREATE TABLE a AS SELECT * FROM b WITH DATA
1997+
----
1998+
CREATE TABLE a AS SELECT * FROM b -- normalized!
1999+
CREATE TABLE a AS SELECT (*) FROM b -- fully parenthesized
2000+
CREATE TABLE a AS SELECT * FROM b -- literals removed
2001+
CREATE TABLE _ AS SELECT * FROM _ -- identifiers removed
2002+
2003+
parse
2004+
CREATE TABLE IF NOT EXISTS a AS SELECT * FROM b WITH NO DATA
2005+
----
2006+
CREATE TABLE IF NOT EXISTS a AS SELECT * FROM b WITH NO DATA
2007+
CREATE TABLE IF NOT EXISTS a AS SELECT (*) FROM b WITH NO DATA -- fully parenthesized
2008+
CREATE TABLE IF NOT EXISTS a AS SELECT * FROM b WITH NO DATA -- literals removed
2009+
CREATE TABLE IF NOT EXISTS _ AS SELECT * FROM _ WITH NO DATA -- identifiers removed
2010+
19872011
parse
19882012
CREATE TABLE a AS SELECT * FROM b ORDER BY c
19892013
----

pkg/sql/sem/tree/create.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1550,9 +1550,10 @@ type CreateTable struct {
15501550
// In CREATE...AS queries, Defs represents a list of ColumnTableDefs, one for
15511551
// each column, and a ConstraintTableDef for each constraint on a subset of
15521552
// these columns.
1553-
Defs TableDefs
1554-
AsSource *Select
1555-
Locality *Locality
1553+
Defs TableDefs
1554+
AsSource *Select
1555+
WithNoData bool
1556+
Locality *Locality
15561557
}
15571558

15581559
// As returns true if this table represents a CREATE TABLE ... AS statement,
@@ -1609,6 +1610,9 @@ func (node *CreateTable) FormatBody(ctx *FmtCtx) {
16091610
}
16101611
ctx.WriteString(" AS ")
16111612
ctx.FormatNode(node.AsSource)
1613+
if node.WithNoData {
1614+
ctx.WriteString(" WITH NO DATA")
1615+
}
16121616
} else {
16131617
ctx.WriteString(" (")
16141618
ctx.FormatNode(&node.Defs)

pkg/sql/sem/tree/pretty.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1353,6 +1353,9 @@ func (node *CreateTable) Doc(p *PrettyCfg) pretty.Doc {
13531353
clauses := make([]pretty.Doc, 0, 4)
13541354
if node.As() {
13551355
clauses = append(clauses, p.Doc(node.AsSource))
1356+
if node.WithNoData {
1357+
clauses = append(clauses, pretty.Keyword("WITH NO DATA"))
1358+
}
13561359
}
13571360
if node.PartitionByTable != nil {
13581361
clauses = append(clauses, p.Doc(node.PartitionByTable))

0 commit comments

Comments
 (0)