Skip to content

Commit

Permalink
add IF NOT EXISTS clause to CREATE TABLE statement
Browse files Browse the repository at this point in the history
  • Loading branch information
mfussenegger committed May 7, 2015
1 parent e986d5a commit 18a8a97
Show file tree
Hide file tree
Showing 13 changed files with 156 additions and 18 deletions.
2 changes: 2 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Changes for Crate
Unreleased
==========

- Added the IF NOT EXISTS clause to the CREATE TABLE statement

2015/05/07 0.49.0
=================

Expand Down
10 changes: 9 additions & 1 deletion docs/sql/reference/create_table.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Synopsis

::

CREATE TABLE table_ident ( [
CREATE TABLE [ IF NOT EXISTS ] table_ident ( [
{ column_name data_type [ column_constraint [ ... ] ] | table_constraint }
[, ... ] ]
)
Expand Down Expand Up @@ -58,6 +58,7 @@ constraint can also be written as a table constraint; a column
constraint is only a notational convenience for use when the
constraint only affects one column.


Parameters
==========

Expand All @@ -70,6 +71,13 @@ Parameters
object specifiers. For more information on the data types
supported by Crate see .


IF NOT EXISTS Clause
====================

If the optional IF NOT EXISTS clause is used this statement won't do anything
if the table exists already.

.. _primary_key_constraint:

PRIMARY KEY Constraint
Expand Down
4 changes: 2 additions & 2 deletions sql-parser/src/main/java/io/crate/sql/parser/Statement.g
Original file line number Diff line number Diff line change
Expand Up @@ -812,10 +812,10 @@ createStatement
;

createTableStmt
: table
: ( IF NOT EXISTS )? table
tableElementList
crateTableOption*
(WITH '(' genericProperties ')' )? -> ^(CREATE_TABLE table tableElementList crateTableOption* genericProperties?)
(WITH '(' genericProperties ')' )? -> ^(CREATE_TABLE EXISTS? table tableElementList crateTableOption* genericProperties?)
;

createBlobTableStmt
Expand Down
11 changes: 10 additions & 1 deletion sql-parser/src/main/java/io/crate/sql/parser/StatementBuilder.g
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,16 @@ createTable returns [Statement value]
$value = new CreateTable($namedTable.value,
$tableElementList.value,
$crateTableOptionList.value,
$genericProperties.value);
$genericProperties.value,
false);
}
| ^(CREATE_TABLE EXISTS namedTable tableElementList crateTableOptionList genericProperties?)
{
$value = new CreateTable($namedTable.value,
$tableElementList.value,
$crateTableOptionList.value,
$genericProperties.value,
true);
}
;

Expand Down
13 changes: 11 additions & 2 deletions sql-parser/src/main/java/io/crate/sql/tree/CreateTable.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,27 @@ public class CreateTable extends Statement {

private final Table name;
private final List<TableElement> tableElements;
private final boolean ifNotExists;
private final List<CrateTableOption> crateTableOptions;
private final Optional<GenericProperties> properties;

public CreateTable(Table name,
List<TableElement> tableElements,
@Nullable List<CrateTableOption> crateTableOptions,
@Nullable GenericProperties genericProperties)
@Nullable GenericProperties genericProperties,
boolean ifNotExists)
{
this.name = name;
this.tableElements = tableElements;
this.ifNotExists = ifNotExists;
this.crateTableOptions = crateTableOptions != null ? crateTableOptions : ImmutableList.<CrateTableOption>of();
this.properties = Optional.fromNullable(genericProperties);
}

public boolean ifNotExists() {
return ifNotExists;
}

public Table name() {
return name;
}
Expand All @@ -72,7 +79,7 @@ public <R, C> R accept(AstVisitor<R, C> visitor, C context)
@Override
public int hashCode()
{
return Objects.hashCode(name, tableElements, crateTableOptions, properties);
return Objects.hashCode(name, tableElements, crateTableOptions, properties, ifNotExists);
}

@Override
Expand All @@ -83,6 +90,7 @@ public boolean equals(Object o) {
CreateTable that = (CreateTable) o;

if (properties != that.properties) return false;
if (ifNotExists != that.ifNotExists) return false;
if (!crateTableOptions.equals(that.crateTableOptions)) return false;
if (!name.equals(that.name)) return false;
if (!tableElements.equals(that.tableElements)) return false;
Expand All @@ -97,6 +105,7 @@ public String toString()
.add("name", name)
.add("tableElements", tableElements)
.add("crateTableOptions", crateTableOptions)
.add("ifNotExists", ifNotExists)
.add("properties", properties).toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ public void testStatementBuilder()
printStatement("copy foo partition (a=?) to DIRECTORY '/folder' with (some_param=4)");


printStatement("create table if not exists t (id integer primary key, name string)");
printStatement("create table t (id integer primary key, name string)");
printStatement("create table t (id integer primary key, name string) clustered into 3 shards");
printStatement("create table t (id integer primary key, name string) clustered into ? shards");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,32 @@ public class CreateTableAnalyzedStatement extends AbstractDDLAnalyzedStatement {
private Map<String, Object> mapping;
private ColumnIdent routingColumn;
private TableIdent tableIdent;
private boolean noOp = false;
private boolean ifNotExists = false;

public CreateTableAnalyzedStatement(FulltextAnalyzerResolver fulltextAnalyzerResolver){
this.fulltextAnalyzerResolver = fulltextAnalyzerResolver;
}

public void table(TableIdent tableIdent, ReferenceInfos referenceInfos) {
public void table(TableIdent tableIdent, boolean ifNotExists, ReferenceInfos referenceInfos) {
tableIdent.validate();
if (referenceInfos.tableExists(tableIdent)) {
if (ifNotExists) {
noOp = referenceInfos.tableExists(tableIdent);
} else if (referenceInfos.tableExists(tableIdent)) {
throw new TableAlreadyExistsException(tableIdent);
}
this.ifNotExists = ifNotExists;
this.tableIdent = tableIdent;
}

public boolean noOp() {
return noOp;
}

public boolean ifNotExists() {
return ifNotExists;
}

@Override
public <C, R> R accept(AnalyzedStatementVisitor<C, R> analyzedStatementVisitor, C context) {
return analyzedStatementVisitor.visitCreateTableStatement(this, context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public CreateTableAnalyzedStatement visitCreateTable(CreateTable node, Context c

private void setTableIdent(CreateTable node, Context context) {
TableIdent tableIdent = TableIdent.of(node.name(), context.analysis.parameterContext().defaultSchema());
context.statement.table(tableIdent, referenceInfos);
context.statement.table(tableIdent, node.ifNotExists(), referenceInfos);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@

import com.google.common.base.Joiner;
import io.crate.Constants;
import io.crate.executor.TaskResult;
import io.crate.metadata.PartitionName;
import io.crate.exceptions.Exceptions;
import io.crate.exceptions.TaskExecutionException;
import io.crate.executor.TaskResult;
import io.crate.metadata.PartitionName;
import io.crate.planner.node.ddl.CreateTableNode;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.admin.indices.alias.Alias;
Expand All @@ -40,6 +40,8 @@
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateResponse;
import org.elasticsearch.action.admin.indices.template.put.TransportPutIndexTemplateAction;
import org.elasticsearch.cluster.ClusterService;
import org.elasticsearch.indices.IndexAlreadyExistsException;
import org.elasticsearch.indices.IndexTemplateAlreadyExistsException;

import java.util.List;
import java.util.Locale;
Expand Down Expand Up @@ -129,6 +131,10 @@ private void setException(Throwable e) {
// this is a generic mapping parse exception,
// the cause has usually a better more detailed error message
result.setException(e.getCause());
} else if (planNode.ifNotExists() &&
(e instanceof IndexAlreadyExistsException
|| (e instanceof IndexTemplateAlreadyExistsException && planNode.createsPartitionedTable()))) {
result.set(SUCCESS_RESULT);
} else {
result.setException(e);
}
Expand Down
5 changes: 5 additions & 0 deletions sql/src/main/java/io/crate/planner/Planner.java
Original file line number Diff line number Diff line change
Expand Up @@ -410,12 +410,16 @@ protected Plan visitDropTableStatement(DropTableAnalyzedStatement analysis, Cont

@Override
protected Plan visitCreateTableStatement(CreateTableAnalyzedStatement analysis, Context context) {
if (analysis.noOp()) {
return NoopPlan.INSTANCE;
}
TableIdent tableIdent = analysis.tableIdent();

CreateTableNode createTableNode;
if (analysis.isPartitioned()) {
createTableNode = CreateTableNode.createPartitionedTableNode(
tableIdent,
analysis.ifNotExists(),
analysis.tableParameter().settings().getByPrefix("index."),
analysis.mapping(),
analysis.templateName(),
Expand All @@ -424,6 +428,7 @@ protected Plan visitCreateTableStatement(CreateTableAnalyzedStatement analysis,
} else {
createTableNode = CreateTableNode.createTableNode(
tableIdent,
analysis.ifNotExists(),
analysis.tableParameter().settings(),
analysis.mapping()
);
Expand Down
24 changes: 17 additions & 7 deletions sql/src/main/java/io/crate/planner/node/ddl/CreateTableNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,22 @@
public class CreateTableNode extends DDLPlanNode {

private final TableIdent tableIdent;
private boolean ifNotExists;
private final Settings settings;
private final Map<String, Object> mapping;


private final Optional<String> templateName;
private final Optional<String> templateIndexMatch;

private CreateTableNode(TableIdent tableIdent, Settings settings,
private CreateTableNode(TableIdent tableIdent,
boolean ifNotExists,
Settings settings,
Map<String, Object> mapping,
@Nullable String templateName,
@Nullable String templateIndexMatch) {
this.tableIdent = tableIdent;
this.ifNotExists = ifNotExists;
this.settings = settings;

this.mapping = mapping;
Expand All @@ -52,17 +56,19 @@ private CreateTableNode(TableIdent tableIdent, Settings settings,
}

public static CreateTableNode createPartitionedTableNode(TableIdent tableIdent,
Settings settings,
Map<String, Object> mapping,
String templateName,
String templateIndexMatch) {
return new CreateTableNode(tableIdent, settings, mapping, templateName, templateIndexMatch);
boolean ifNotExists,
Settings settings,
Map<String, Object> mapping,
String templateName,
String templateIndexMatch) {
return new CreateTableNode(tableIdent, ifNotExists, settings, mapping, templateName, templateIndexMatch);
}

public static CreateTableNode createTableNode(TableIdent tableIdent,
boolean ifNotExists,
Settings settings,
Map<String, Object> mapping) {
return new CreateTableNode(tableIdent, settings, mapping, null, null);
return new CreateTableNode(tableIdent, ifNotExists, settings, mapping, null, null);
}

public TableIdent tableIdent() {
Expand Down Expand Up @@ -93,4 +99,8 @@ public boolean createsPartitionedTable() {
public <C, R> R accept(PlanNodeVisitor<C, R> visitor, C context) {
return visitor.visitCreateTableNode(this, context);
}

public boolean ifNotExists() {
return ifNotExists;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ public void transportSetup() {
public void testCreateTableTask() throws Exception {
CreateTableNode createTableNode = CreateTableNode.createTableNode(
new TableIdent(null, "test"),
false,
TEST_SETTINGS,
TEST_MAPPING
);
Expand Down Expand Up @@ -144,6 +145,7 @@ public void testCreateTableWithOrphanedPartitions() throws Exception {
ensureGreen();
CreateTableNode createTableNode = CreateTableNode.createTableNode(
new TableIdent(null, "test"),
false,
TEST_SETTINGS,
TEST_MAPPING
);
Expand Down Expand Up @@ -176,6 +178,7 @@ public void testCreateTableWithOrphanedAlias() throws Exception {
ensureGreen();
CreateTableNode createTableNode = CreateTableNode.createTableNode(
new TableIdent(null, "test"),
false,
TEST_SETTINGS,
TEST_MAPPING
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Licensed to CRATE Technology GmbH ("Crate") under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership. Crate licenses
* this file to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may
* obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* However, if you have executed another commercial license agreement
* with Crate these terms will supersede the license and you may use the
* software solely pursuant to the terms of the relevant commercial agreement.
*/

package io.crate.integrationtests;

import io.crate.test.integration.CrateIntegrationTest;
import org.junit.Test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

@CrateIntegrationTest.ClusterScope(scope = CrateIntegrationTest.Scope.GLOBAL)
public class CreateTableIntegrationTest extends SQLTransportIntegrationTest {

@Test
public void testCreateTableIfNotExistsConcurrently() throws Throwable {
executeCreateTableThreaded("create table if not exists t (name string) with (number_of_replicas = 0)");
}

@Test
public void testCreatePartitionedTableIfNotExistsConcurrently() throws Throwable {
executeCreateTableThreaded("create table if not exists t " +
"(name string, p string) partitioned by (p) " +
"with (number_of_replicas = 0)");
}

private void executeCreateTableThreaded(final String statement) throws Throwable {
ExecutorService executorService = Executors.newFixedThreadPool(20);
final AtomicReference<Throwable> lastThrowable = new AtomicReference<>();

for (int i = 0; i < 20; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
try {
execute(statement);
} catch (Throwable t) {
lastThrowable.set(t);
}
}
});
}

executorService.shutdown();
executorService.awaitTermination(1500, TimeUnit.MILLISECONDS);

Throwable throwable = lastThrowable.get();
if (throwable != null) {
throw throwable;
}
}
}

0 comments on commit 18a8a97

Please sign in to comment.