Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 45 additions & 8 deletions doc/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,35 @@

The Data Query Language (DQL) building block is responsible for managing `SELECT` statements.

## Solution Strategy

### Fluent Programming

###### Statement Construction With Fluent Programming
`dsn~statement-construction-with-fluent-programming~1`

All statement builders use the "fluent programming" model, where the return type of each builder step determines the possible next structural elements that can be added.

Comment:

This is a design principle that cuts across the whole project. Therefore locating it in a single test or implementation part makes no sense.

Covers:

* `req~statement-structure-limited-at-compile-time~1`

## Runtime View

### Building Select Statements

#### Accessing the Clauses That Make Up a SELECT Statement
`dsn~select-statement.accessing-clauses~1`
`dsn~select-statement.out-of-order-clauses~1`

The DQL statement component allows getting the following clauses, provided that they already exist:
`SELECT` commands allow attaching the following clauses in any order:

* `FROM` clause
* `WHERE` clause
* `LIMIT` clause

Covers:

Expand All @@ -29,6 +47,11 @@ Tags: Select Statement Builder

### Building Boolean Expressions

#### Forwarded Requirements

* `dsn --> impl, utest: req~boolean-operators~1`
* `dsn --> impl, utest: req~comparison-operations~1`

#### Constructing Boolean Comparison Operations From Operator Strings
`dsn~boolean-operation.comparison.constructing-from-strings~1`

Expand Down Expand Up @@ -57,11 +80,6 @@ Covers:

Needs: impl, utest

#### Forwarded Requirements

* `dsn --> impl, utest : req~comparison-operations~1`
* `dsn --> impl, utest : req~boolean-operators~1`

### Building INSERT Statements

#### Forwarded Requirements
Expand All @@ -73,6 +91,25 @@ Needs: impl, utest

#### Forwarded Requirements

* `dsn --> req~rendering.sql.configurable-case~1`
* `dsn --> impl, utest: req~rendering.sql.configurable-case~1`
* `dsn --> impl, utest: req~rendering.sql.select~1`
* `dsn --> impl, utest: req~rendering.sql.insert~1`

#### Renderer add Double Quotes for Schema, Table and Column Identifiers
`dsn~rendering.add-double-quotes-for-schema-table-and-column-identifiers~1`

The renderer sets the following identifiers in double quotes if configured:

* Schema identifiers
* Table identifiers
* Column identifiers (except the asterisks)

Comment:

Examples are `"my_schema"."my_table"."my_field"`, `"MY_TABLE"."MyField"` and `"MyTable".*`

Covers:

* `req~rendering.sql.confiugrable-identifier-quoting~1`

Needs: impl, utest
39 changes: 37 additions & 2 deletions doc/system_requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,22 @@ This is necessary since complex statements are usually build as a result of mult

Covers:

* [feat~statment-definition~1](#statement-definition)
* [feat~statement-definition~1](#statement-definition)

Needs: dsn

#### Statement Structure Limited at Compile-time
`req~statement-structure-limited-at-compile-time~1`

ESB lets users create only valid statement structures at compile-time.

Rationale:

If users can't get illegal structures to compile, they don't need to spend time debugging them later.

Covers:

* [feat~compile-time-error-checking~1](#compile-time-error-checking)

Needs: dsn

Expand Down Expand Up @@ -187,7 +202,27 @@ Covers:

Needs: dsn

* Upper case / lower case
###### Configurable Identifier Quoting
`req~rendering.sql.confiugrable-identifier-quoting~1`

ESB allows users to choose whether the following identifiers should be quoted in the rendered query:

* Schema identifiers
* Table identifiers
* Column identifiers

Rationale:

The Exasol database for example requires identifiers to be enclosed in double quotes in order to enable case sensitivity.

Covers:

* [feat~sql-string-rendering~1](#sql-string-rendering)

Needs: dsn

#### TODO

* One line / pretty

#### SELECT Statement Rendering
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/com/exasol/sql/UnnamedPlaceholder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.exasol.sql;

import com.exasol.sql.expression.*;

public class UnnamedPlaceholder extends AbstractValueExpression implements ValueExpression {
@Override
public void accept(final ValueExpressionVisitor visitor) {
visitor.visit(this);
}
}
63 changes: 57 additions & 6 deletions src/main/java/com/exasol/sql/dml/Insert.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
/**
* This class implements an SQL {@link Select} statement
*/
// [impl->dsn~insert-statements~1]
public class Insert extends AbstractFragment implements SqlStatement, InsertFragment {
private final Table table;
private InsertFields fields;
private InsertFields insertFields;
private InsertValues insertValues;

/**
* Create a new instance of an {@link Insert} statement
Expand All @@ -27,10 +29,10 @@ public Insert(final String tableName) {
* @return <code>this</code> for fluent programming
*/
public synchronized Insert field(final String... names) {
if (this.fields == null) {
this.fields = new InsertFields(this);
if (this.insertFields == null) {
this.insertFields = new InsertFields(this);
}
this.fields.add(names);
this.insertFields.add(names);
return this;
}

Expand All @@ -43,14 +45,63 @@ public String getTableName() {
return this.table.getName();
}

/**
* Insert a list of concrete values
*
* @param values values to be inserted
* @return <code>this</code> for fluent programming
*/
// [impl->dsn~values-as-insert-source~1]
public synchronized Insert values(final Object... values) {
if (this.insertValues == null) {
this.insertValues = new InsertValues(this);
}
this.insertValues.add(values);
return this;
}

/**
* Add an unnamed value placeholder to the value list (this is useful for prepared statements)
*
* @return <code>this</code> for fluent programming
*/
// [impl->dsn~values-as-insert-source~1]
public synchronized Insert valuePlaceholder() {
if (this.insertValues == null) {
this.insertValues = new InsertValues(this);
}
this.insertValues.addPlaceholder();
return this;
}

/**
* Add a given number unnamed value placeholder to the value list (this is useful for prepared statements)
*
* @param amount number of placeholders to be added
* @return <code>this</code> for fluent programming
*/
// [impl->dsn~values-as-insert-source~1]
public synchronized Insert valuePlaceholders(final int amount) {
if (this.insertValues == null) {
this.insertValues = new InsertValues(this);
}
for (int i = 0; i < amount; ++i) {
this.insertValues.addPlaceholder();
}
return this;
}

@Override
public void accept(final InsertVisitor visitor) {
visitor.visit(this);
if (this.table != null) {
this.table.accept(visitor);
}
if (this.fields != null) {
this.fields.accept(visitor);
if (this.insertFields != null) {
this.insertFields.accept(visitor);
}
if (this.insertValues != null) {
this.insertValues.accept(visitor);
}
}
}
55 changes: 55 additions & 0 deletions src/main/java/com/exasol/sql/dml/InsertValues.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.exasol.sql.dml;

import java.util.ArrayList;
import java.util.List;

import com.exasol.sql.*;
import com.exasol.sql.expression.Value;
import com.exasol.sql.expression.ValueExpression;

/**
* Container class for values to be inserted by an INSERT statement.
*/
public class InsertValues extends AbstractFragment implements InsertFragment {
private final List<ValueExpression> values = new ArrayList<>();

/**
* Create a new instance of {@link InsertValues
*
* @param root root SQL statement
*/
public InsertValues(final Fragment root) {
super(root);
}

/**
* Add one or more values
*
* @param values values
*/
public void add(final Object... values) {
for (final Object value : values) {
this.getValues().add(new Value(value));
}
}

/**
* Get the values
*
* @return value
*/
public List<ValueExpression> getValues() {
return this.values;
}

@Override
public void accept(final InsertVisitor visitor) {
visitor.visit(this);
// sub-expression left out intentionally
visitor.leave(this);
}

public void addPlaceholder() {
this.values.add(new UnnamedPlaceholder());
}
}
4 changes: 4 additions & 0 deletions src/main/java/com/exasol/sql/dml/InsertVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ public interface InsertVisitor extends FragmentVisitor {
public void visit(InsertFields insertFields);

public void leave(InsertFields insertFields);

public void visit(InsertValues insertValues);

public void leave(InsertValues insertValues);
}
44 changes: 42 additions & 2 deletions src/main/java/com/exasol/sql/dml/rendering/InsertRenderer.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@
import com.exasol.sql.Field;
import com.exasol.sql.Table;
import com.exasol.sql.dml.*;
import com.exasol.sql.expression.ValueExpression;
import com.exasol.sql.rendering.AbstractFragmentRenderer;
import com.exasol.sql.rendering.StringRendererConfig;

/**
* The {@link InsertRenderer} turns SQL statement structures in to SQL strings.
*/
// [impl->dsn~rendering.sql.insert~1]
public class InsertRenderer extends AbstractFragmentRenderer implements InsertVisitor {
/**
* Create a new {@link InsertRenderer} with custom render settings.
Expand All @@ -24,24 +29,59 @@ public void visit(final Insert insert) {

@Override
public void visit(final Table table) {
append(table.getName());
appendAutoQuoted(table.getName());
setLastVisited(table);
}

@Override
public void visit(final Field field) {
appendCommaWhenNeeded(field);
append(field.getName());
appendAutoQuoted(field.getName());
setLastVisited(field);
}

@Override
public void visit(final InsertFields insertFields) {
append(" (");
setLastVisited(insertFields);
}

@Override
public void leave(final InsertFields insertFields) {
append(")");
}

@Override
public void visit(final InsertValues insertValues) {
appendKeyWord(" VALUES (");
for (final ValueExpression expression : insertValues.getValues()) {
appendCommaWhenNeeded(insertValues);
appendRenderedValueExpression(expression);
setLastVisited(insertValues);
}
}

@Override
public void leave(final InsertValues insertValues) {
append(")");
}

/**
* Create an {@link InsertRenderer} using the default renderer configuration
*
* @return insert renderer
*/
public static InsertRenderer create() {
return create(StringRendererConfig.createDefault());
}

/**
* Create an {@link InsertRenderer}
*
* @param config renderer configuration
* @return insert renderer
*/
public static InsertRenderer create(final StringRendererConfig config) {
return new InsertRenderer(config);
}
}
Loading