Skip to content

Commit

Permalink
parser: Allows no target cols to be specified in IMPORT INTO query.
Browse files Browse the repository at this point in the history
This is an add on to cockroachdb#39023. It adds the grammar required to support
queries such as:
`IMPORT INTO test CSV DATA ('...');`

No target columns implies that all columns are imported into from the
data sources.

Release note: None
  • Loading branch information
adityamaru27 committed Jul 29, 2019
1 parent 4ab8f85 commit 844f55c
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 4 deletions.
2 changes: 2 additions & 0 deletions docs/generated/sql/bnf/import_csv.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ import_stmt ::=
| 'IMPORT' 'TABLE' table_name '(' table_elem_list ')' 'CSV' 'DATA' '(' file_location ( ( ',' file_location ) )* ')'
| 'IMPORT' 'INTO' table_name '(' insert_column_list ')' 'CSV' 'DATA' '(' file_location ( ( ',' file_location ) )* ')' 'WITH' kv_option_list
| 'IMPORT' 'INTO' table_name '(' insert_column_list ')' 'CSV' 'DATA' '(' file_location ( ( ',' file_location ) )* ')'
| 'IMPORT' 'INTO' table_name 'CSV' 'DATA' '(' file_location ( ( ',' file_location ) )* ')' 'WITH' kv_option_list
| 'IMPORT' 'INTO' table_name 'CSV' 'DATA' '(' file_location ( ( ',' file_location ) )* ')'
2 changes: 2 additions & 0 deletions docs/generated/sql/bnf/import_dump.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ import_stmt ::=
| 'IMPORT' 'TABLE' table_name 'FROM' import_format file_location
| 'IMPORT' 'INTO' table_name '(' insert_column_list ')' import_format 'DATA' '(' file_location ( ( ',' file_location ) )* ')' 'WITH' kv_option_list
| 'IMPORT' 'INTO' table_name '(' insert_column_list ')' import_format 'DATA' '(' file_location ( ( ',' file_location ) )* ')'
| 'IMPORT' 'INTO' table_name import_format 'DATA' '(' file_location ( ( ',' file_location ) )* ')' 'WITH' kv_option_list
| 'IMPORT' 'INTO' table_name import_format 'DATA' '(' file_location ( ( ',' file_location ) )* ')'
1 change: 1 addition & 0 deletions docs/generated/sql/bnf/stmt_block.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ import_stmt ::=
| 'IMPORT' 'TABLE' table_name 'CREATE' 'USING' string_or_placeholder import_format 'DATA' '(' string_or_placeholder_list ')' opt_with_options
| 'IMPORT' 'TABLE' table_name '(' table_elem_list ')' import_format 'DATA' '(' string_or_placeholder_list ')' opt_with_options
| 'IMPORT' 'INTO' table_name '(' insert_column_list ')' import_format 'DATA' '(' string_or_placeholder_list ')' opt_with_options
| 'IMPORT' 'INTO' table_name import_format 'DATA' '(' string_or_placeholder_list ')' opt_with_options

insert_stmt ::=
opt_with_clause 'INSERT' 'INTO' insert_target insert_rest returning_clause
Expand Down
45 changes: 41 additions & 4 deletions pkg/ccl/importccl/import_stmt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1743,6 +1743,45 @@ func TestImportIntoCSV(t *testing.T) {
sqlDB.Exec(t, "DROP DATABASE targetcols")
})

// Tests IMPORT INTO without any target columns specified. This implies an
// import of all columns in the exisiting table.
t.Run("no-target-cols-specified", func(t *testing.T) {
sqlDB.Exec(t, "CREATE DATABASE targetcols; USE targetcols")
sqlDB.Exec(t, `CREATE TABLE t (a INT, b STRING)`)

// Insert the test data
insert := []string{"''", "'text'", "'a'", "'e'", "'l'", "'t'", "'z'"}

if tx, err := db.Begin(); err != nil {
t.Fatal(err)
} else {
for i, v := range insert {
sqlDB.Exec(t, fmt.Sprintf("INSERT INTO t (a, b) VALUES (%d, %s)", i, v))
}

if err := tx.Commit(); err != nil {
t.Fatal(err)
}
}

sqlDB.Exec(t, fmt.Sprintf("IMPORT INTO t CSV DATA (%s)", testFiles.files[0]))

var result int
numExistingRows := len(insert)
// Verify that all columns have been populated with imported data.
sqlDB.QueryRow(t, `SELECT count(*) FROM t WHERE a IS NOT NULL`).Scan(&result)
if expect := numExistingRows + rowsPerFile; result != expect {
t.Fatalf("expected %d rows, got %d", expect, result)
}

sqlDB.QueryRow(t, `SELECT count(*) FROM t WHERE b IS NOT NULL`).Scan(&result)
if expect := numExistingRows + rowsPerFile; result != expect {
t.Fatalf("expected %d rows, got %d", expect, result)
}

sqlDB.Exec(t, "DROP DATABASE targetcols")
})

// IMPORT INTO does not support DEFAULT expressions for either target or
// non-target columns.
t.Run("import-into-check-no-default-cols", func(t *testing.T) {
Expand All @@ -1755,8 +1794,8 @@ func TestImportIntoCSV(t *testing.T) {
if tx, err := db.Begin(); err != nil {
t.Fatal(err)
} else {
for i := range insert {
sqlDB.Exec(t, fmt.Sprintf("INSERT INTO t (a, b) VALUES (%d, %s)", i, insert[i]))
for i, v := range insert {
sqlDB.Exec(t, fmt.Sprintf("INSERT INTO t (a, b) VALUES (%d, %s)", i, v))
}

if err := tx.Commit(); err != nil {
Expand All @@ -1771,8 +1810,6 @@ func TestImportIntoCSV(t *testing.T) {

sqlDB.Exec(t, "DROP DATABASE targetcols")
})
// TODO(adityamaru): Add test for IMPORT INTO without target columns specified
// once grammar has been added.
}

func BenchmarkImport(b *testing.B) {
Expand Down
1 change: 1 addition & 0 deletions pkg/sql/parser/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1294,6 +1294,7 @@ func TestParse(t *testing.T) {
{`IMPORT TABLE foo (id INT8, email STRING, age INT8) CSV DATA ('path/to/some/file', $1) WITH comma = ',', "nullif" = 'n/a', temp = $2`},
{`IMPORT TABLE foo FROM PGDUMPCREATE 'nodelocal:///foo/bar' WITH temp = 'path/to/temp'`},
{`IMPORT INTO foo(id, email) CSV DATA ('path/to/some/file', $1) WITH temp = 'path/to/temp'`},
{`IMPORT INTO foo CSV DATA ('path/to/some/file', $1) WITH temp = 'path/to/temp'`},

{`IMPORT PGDUMP 'nodelocal:///foo/bar' WITH temp = 'path/to/temp'`},
{`EXPLAIN IMPORT PGDUMP 'nodelocal:///foo/bar' WITH temp = 'path/to/temp'`},
Expand Down
5 changes: 5 additions & 0 deletions pkg/sql/parser/sql.y
Original file line number Diff line number Diff line change
Expand Up @@ -1839,6 +1839,11 @@ import_stmt:
name := $3.unresolvedObjectName().ToTableName()
$$.val = &tree.Import{Table: &name, Into: true, IntoCols: $5.nameList(), FileFormat: $7, Files: $10.exprs(), Options: $12.kvOptions()}
}
| IMPORT INTO table_name import_format DATA '(' string_or_placeholder_list ')' opt_with_options
{
name := $3.unresolvedObjectName().ToTableName()
$$.val = &tree.Import{Table: &name, Into: true, IntoCols: nil, FileFormat: $4, Files: $7.exprs(), Options: $9.kvOptions()}
}
| IMPORT error // SHOW HELP: IMPORT

// %Help: EXPORT - export data to file in a distributed manner
Expand Down
2 changes: 2 additions & 0 deletions pkg/sql/sem/tree/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ func (node *Import) Format(ctx *FmtCtx) {
ctx.WriteByte('(')
ctx.FormatNode(&node.IntoCols)
ctx.WriteString(") ")
} else {
ctx.WriteString(" ")
}
} else {
ctx.WriteString("TABLE ")
Expand Down

0 comments on commit 844f55c

Please sign in to comment.