Unverified 提交 131734ba authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov 提交者: GitHub

Merge pull request #1678 from katzyn/values

Parse table value constructor in more places
......@@ -21,6 +21,10 @@ Change Log
<h2>Next Version (unreleased)</h2>
<ul>
<li>Issue #1677: Unable to use VALUES keyword in WHERE clause
</li>
<li>Issue #1672: Deadlock on MVStore close in TestOutOfMemory
</li>
<li>Issue #1665: TestCrashAPI: NPE with ENUM in MINUS operator
</li>
<li>Issue #1602: Combine type, precision, scale, display size and extTypeInfo into one object
......
......@@ -415,11 +415,13 @@ To keep the content of an in-memory database as long as the virtual machine is a
<p>
The database files can be encrypted.
Three encryption algorithms are supported:
</p>
<ul>
<li>"AES" - also known as Rijndael, only AES-128 is implemented.</li>
<li>"XTEA" - the 32 round version.</li>
<li>"FOG" - pseudo-encryption only useful for hiding data from a text editor.</li>
</ul>
<p>
To use file encryption, you need to specify the encryption algorithm (the 'cipher')
and the file password (in addition to the user password) when connecting to the database.
</p>
......@@ -906,8 +908,10 @@ or the SQL statement <code>SET MODE MySQL</code>. Use this mode for compatibilit
digits are not truncated, but the value is rounded.
</li><li>Concatenating <code>NULL</code> with another value
results in the other value.
</li><li>ON DUPLICATE KEY UPDATE is supported in INSERT statements.
</li><li>INSERT IGNORE is partially supported and may be used to skip rows with duplicate keys if ON DUPLICATE KEY UPDATE is not specified.
</li><li>ON DUPLICATE KEY UPDATE is supported in INSERT statements, due to this feature VALUES has special non-standard
meaning is some contexts.
</li><li>INSERT IGNORE is partially supported and may be used to skip rows with duplicate keys if ON DUPLICATE KEY
UPDATE is not specified.
</li><li>REGEXP_REPLACE() uses \ for back-references for compatibility with MariaDB.
</li><li>Datetime value functions return the same value within a command.
</li></ul>
......@@ -1467,6 +1471,7 @@ For some examples, have a look at the code in <code>org.h2.test.db.TestTableEngi
<p>
In order to create your own TableEngine, you need to implement the <code>org.h2.api.TableEngine</code> interface e.g.
something like this:
</p>
<pre>
package acme;
public static class MyTableEngine implements org.h2.api.TableEngine {
......@@ -1480,12 +1485,13 @@ public static class MyTableEngine implements org.h2.api.TableEngine {
}
}
</pre>
<p>
and then create the table from SQL like this:
</p>
<pre>
CREATE TABLE TEST(ID INT, NAME VARCHAR)
ENGINE "acme.MyTableEngine";
</pre>
</p>
<p>
It is also possible to pass in parameters to the table engine, like so:
</p>
......
......@@ -129,6 +129,7 @@ import org.h2.command.ddl.TruncateTable;
import org.h2.command.dml.AlterTableSet;
import org.h2.command.dml.BackupCommand;
import org.h2.command.dml.Call;
import org.h2.command.dml.CommandWithValues;
import org.h2.command.dml.Delete;
import org.h2.command.dml.ExecuteProcedure;
import org.h2.command.dml.Explain;
......@@ -253,7 +254,8 @@ import org.h2.value.ValueTimestampTimeZone;
public class Parser {
private static final String WITH_STATEMENT_SUPPORTS_LIMITED_SUB_STATEMENTS =
"WITH statement supports only SELECT, TABLE, CREATE TABLE, INSERT, UPDATE, MERGE or DELETE statements";
"WITH statement supports only SELECT, TABLE, VALUES, " +
"CREATE TABLE, INSERT, UPDATE, MERGE or DELETE statements";
// used during the tokenizer phase
private static final int CHAR_END = 1, CHAR_VALUE = 2, CHAR_QUOTED = 3;
......@@ -755,11 +757,8 @@ public class Parser {
case FROM:
case SELECT:
case TABLE:
c = parseSelect();
break;
case VALUES:
read();
c = parseValues();
c = parseSelect();
break;
case WITH:
read();
......@@ -1450,6 +1449,7 @@ public class Parser {
switch (currentTokenType) {
case FROM:
case SELECT:
case VALUES:
case WITH:
select = true;
break;
......@@ -1492,10 +1492,7 @@ public class Parser {
command.setKeys(keys);
}
if (readIf(VALUES)) {
do {
read(OPEN_PAREN);
command.addRow(parseValuesForInsert());
} while (readIf(COMMA));
parseValuesForCommand(command);
} else {
command.setQuery(parseSelect());
}
......@@ -1682,11 +1679,7 @@ public class Parser {
Expression[] expr = {};
command.addRow(expr);
} else if (readIf(VALUES)) {
read(OPEN_PAREN);
do {
command.addRow(parseValuesForInsert());
// the following condition will allow (..),; and (..);
} while (readIf(COMMA) && readIf(OPEN_PAREN));
parseValuesForCommand(command);
} else if (readIf("SET")) {
if (columns != null) {
throw getSyntaxError();
......@@ -1725,28 +1718,35 @@ public class Parser {
command.setColumns(columns);
}
if (readIf(VALUES)) {
do {
read(OPEN_PAREN);
command.addRow(parseValuesForInsert());
} while (readIf(COMMA));
parseValuesForCommand(command);
} else {
command.setQuery(parseSelect());
}
return command;
}
private Expression[] parseValuesForInsert() {
private void parseValuesForCommand(CommandWithValues command) {
ArrayList<Expression> values = Utils.newSmallArrayList();
if (!readIf(CLOSE_PAREN)) {
do {
if (readIf("DEFAULT")) {
values.add(null);
} else {
values.add(readExpression());
do {
values.clear();
boolean multiColumn;
if (readIf(ROW)) {
read(OPEN_PAREN);
multiColumn = true;
} else {
multiColumn = readIf(OPEN_PAREN);
}
if (multiColumn) {
if (!readIf(CLOSE_PAREN)) {
do {
values.add(readIf("DEFAULT") ? null : readExpression());
} while (readIfMore(false));
}
} while (readIfMore(false));
}
return values.toArray(new Expression[0]);
} else {
values.add(readIf("DEFAULT") ? null : readExpression());
}
command.addRow(values.toArray(new Expression[0]));
} while (readIf(COMMA));
}
private TableFilter readTableFilter() {
......@@ -2350,6 +2350,7 @@ public class Parser {
case FROM:
case SELECT:
case TABLE:
case VALUES:
case WITH:
case OPEN_PAREN:
Query query = parseSelect();
......@@ -2696,6 +2697,8 @@ public class Parser {
command.setExpressions(expressions);
setSQL(command, "TABLE", start);
return command;
} else if (readIf(VALUES)) {
return parseValues();
} else {
throw getSyntaxError();
}
......@@ -3936,8 +3939,12 @@ public class Parser {
read();
break;
case VALUES:
read();
r = readKeywordFunction("VALUES");
if (database.getMode().onDuplicateKeyUpdate) {
read();
r = readKeywordFunction("VALUES");
} else {
r = new Subquery(parseSelect());
}
break;
case CASE:
read();
......@@ -5823,20 +5830,24 @@ public class Parser {
TableFilter filter = parseValuesTable(0);
command.setWildcard();
command.addTableFilter(filter, true);
command.init();
return command;
}
private TableFilter parseValuesTable(int orderInFrom) {
Schema mainSchema = database.getSchema(Constants.SCHEMA_MAIN);
TableFunction tf = (TableFunction) Function.getFunction(database,
"TABLE");
TableFunction tf = (TableFunction) Function.getFunction(database, "TABLE");
ArrayList<Column> columns = Utils.newSmallArrayList();
ArrayList<ArrayList<Expression>> rows = Utils.newSmallArrayList();
do {
int i = 0;
ArrayList<Expression> row = Utils.newSmallArrayList();
boolean multiColumn = readIf(OPEN_PAREN);
boolean multiColumn;
if (readIf(ROW)) {
read(OPEN_PAREN);
multiColumn = true;
} else {
multiColumn = readIf(OPEN_PAREN);
}
do {
Expression expr = readExpression();
expr = expr.optimize(session);
......@@ -5885,9 +5896,7 @@ public class Parser {
tf.setColumns(columns);
tf.doneWithParameters();
Table table = new FunctionTable(mainSchema, session, tf, tf);
return new TableFilter(session, table, null,
rightsChecked, currentSelect, orderInFrom,
null);
return new TableFilter(session, table, null, rightsChecked, currentSelect, orderInFrom, null);
}
private Call parseCall() {
......@@ -6163,7 +6172,7 @@ public class Parser {
while (readIf(OPEN_PAREN)) {
parentheses++;
}
if (isToken(SELECT)) {
if (isToken(SELECT) || isToken(VALUES)) {
p = parseWithQuery();
} else if (isToken(TABLE)) {
int index = lastParseIndex;
......
/*
* Copyright 2004-2019 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.command.dml;
import java.util.ArrayList;
import org.h2.command.Prepared;
import org.h2.engine.Session;
import org.h2.expression.Expression;
import org.h2.util.Utils;
/**
* Command that supports VALUES clause.
*/
public abstract class CommandWithValues extends Prepared {
protected final ArrayList<Expression[]> valuesExpressionList = Utils.newSmallArrayList();
/**
* Creates new instance of command with VALUES clause.
*
* @param session
* the session
*/
protected CommandWithValues(Session session) {
super(session);
}
/**
* Add a row to this command.
*
* @param expr
* the list of values
*/
public void addRow(Expression[] expr) {
valuesExpressionList.add(expr);
}
}
......@@ -12,7 +12,6 @@ import org.h2.api.ErrorCode;
import org.h2.api.Trigger;
import org.h2.command.Command;
import org.h2.command.CommandInterface;
import org.h2.command.Prepared;
import org.h2.engine.GeneratedKeys;
import org.h2.engine.Mode;
import org.h2.engine.Right;
......@@ -35,7 +34,6 @@ import org.h2.table.Column;
import org.h2.table.Table;
import org.h2.table.TableFilter;
import org.h2.util.StatementBuilder;
import org.h2.util.Utils;
import org.h2.value.Value;
import org.h2.value.ValueNull;
......@@ -43,11 +41,10 @@ import org.h2.value.ValueNull;
* This class represents the statement
* INSERT
*/
public class Insert extends Prepared implements ResultTarget {
public class Insert extends CommandWithValues implements ResultTarget {
private Table table;
private Column[] columns;
private final ArrayList<Expression[]> list = Utils.newSmallArrayList();
private Query query;
private boolean sortedInsertMode;
private int rowNumber;
......@@ -117,15 +114,6 @@ public class Insert extends Prepared implements ResultTarget {
duplicateKeyAssignmentMap.put(column, expression);
}
/**
* Add a row to this merge statement.
*
* @param expr the list of values
*/
public void addRow(Expression[] expr) {
list.add(expr);
}
@Override
public int update() {
Index index = null;
......@@ -157,14 +145,14 @@ public class Insert extends Prepared implements ResultTarget {
rowNumber = 0;
GeneratedKeys generatedKeys = session.getGeneratedKeys();
generatedKeys.initialize(table);
int listSize = list.size();
int listSize = valuesExpressionList.size();
if (listSize > 0) {
Mode mode = session.getDatabase().getMode();
int columnLen = columns.length;
for (int x = 0; x < listSize; x++) {
generatedKeys.nextRow();
Row newRow = table.getTemplateRow();
Expression[] expr = list.get(x);
Expression[] expr = valuesExpressionList.get(x);
setCurrentRowNumber(x + 1);
for (int i = 0; i < columnLen; i++) {
Column c = columns[i];
......@@ -294,13 +282,13 @@ public class Insert extends Prepared implements ResultTarget {
if (sortedInsertMode) {
buff.append("SORTED ");
}
if (!list.isEmpty()) {
if (!valuesExpressionList.isEmpty()) {
buff.append("VALUES ");
int row = 0;
if (list.size() > 1) {
if (valuesExpressionList.size() > 1) {
buff.append('\n');
}
for (Expression[] expr : list) {
for (Expression[] expr : valuesExpressionList) {
if (row++ > 0) {
buff.append(",\n");
}
......@@ -317,15 +305,15 @@ public class Insert extends Prepared implements ResultTarget {
@Override
public void prepare() {
if (columns == null) {
if (!list.isEmpty() && list.get(0).length == 0) {
if (!valuesExpressionList.isEmpty() && valuesExpressionList.get(0).length == 0) {
// special case where table is used as a sequence
columns = new Column[0];
} else {
columns = table.getColumns();
}
}
if (!list.isEmpty()) {
for (Expression[] expr : list) {
if (!valuesExpressionList.isEmpty()) {
for (Expression[] expr : valuesExpressionList) {
if (expr.length != columns.length) {
throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH);
}
......@@ -400,7 +388,7 @@ public class Insert extends Prepared implements ResultTarget {
ArrayList<String> variableNames = new ArrayList<>(
duplicateKeyAssignmentMap.size());
Expression[] row = (currentRow == null) ? list.get((int) getCurrentRowNumber() - 1)
Expression[] row = (currentRow == null) ? valuesExpressionList.get((int) getCurrentRowNumber() - 1)
: new Expression[columns.length];
for (int i = 0; i < columns.length; i++) {
String key = table.getSchema().getName() + "." +
......
......@@ -27,20 +27,18 @@ import org.h2.table.Column;
import org.h2.table.Table;
import org.h2.table.TableFilter;
import org.h2.util.StatementBuilder;
import org.h2.util.Utils;
import org.h2.value.Value;
/**
* This class represents the statement
* MERGE
*/
public class Merge extends Prepared {
public class Merge extends CommandWithValues {
private Table targetTable;
private TableFilter targetTableFilter;
private Column[] columns;
private Column[] keys;
private final ArrayList<Expression[]> valuesExpressionList = Utils.newSmallArrayList();
private Query query;
private Prepared update;
......@@ -72,15 +70,6 @@ public class Merge extends Prepared {
this.query = query;
}
/**
* Add a row to this merge statement.
*
* @param expr the list of values
*/
public void addRow(Expression[] expr) {
valuesExpressionList.add(expr);
}
@Override
public int update() {
int count;
......
......@@ -25,18 +25,16 @@ import org.h2.result.Row;
import org.h2.table.Column;
import org.h2.table.Table;
import org.h2.util.StatementBuilder;
import org.h2.util.Utils;
import org.h2.value.Value;
/**
* This class represents the MySQL-compatibility REPLACE statement
*/
public class Replace extends Prepared {
public class Replace extends CommandWithValues {
private Table table;
private Column[] columns;
private Column[] keys;
private final ArrayList<Expression[]> list = Utils.newSmallArrayList();
private Query query;
private Prepared update;
......@@ -68,15 +66,6 @@ public class Replace extends Prepared {
this.query = query;
}
/**
* Add a row to this replace statement.
*
* @param expr the list of values
*/
public void addRow(Expression[] expr) {
list.add(expr);
}
@Override
public int update() {
int count = 0;
......@@ -84,10 +73,10 @@ public class Replace extends Prepared {
session.getUser().checkRight(table, Right.UPDATE);
setCurrentRowNumber(0);
Mode mode = session.getDatabase().getMode();
if (!list.isEmpty()) {
for (int x = 0, size = list.size(); x < size; x++) {
if (!valuesExpressionList.isEmpty()) {
for (int x = 0, size = valuesExpressionList.size(); x < size; x++) {
setCurrentRowNumber(x + 1);
Expression[] expr = list.get(x);
Expression[] expr = valuesExpressionList.get(x);
Row newRow = table.getTemplateRow();
for (int i = 0, len = columns.length; i < len; i++) {
Column c = columns[i];
......@@ -214,10 +203,10 @@ public class Replace extends Prepared {
}
buff.append(')');
buff.append('\n');
if (!list.isEmpty()) {
if (!valuesExpressionList.isEmpty()) {
buff.append("VALUES ");
int row = 0;
for (Expression[] expr : list) {
for (Expression[] expr : valuesExpressionList) {
if (row++ > 0) {
buff.append(", ");
}
......@@ -234,15 +223,15 @@ public class Replace extends Prepared {
@Override
public void prepare() {
if (columns == null) {
if (!list.isEmpty() && list.get(0).length == 0) {
if (!valuesExpressionList.isEmpty() && valuesExpressionList.get(0).length == 0) {
// special case where table is used as a sequence
columns = new Column[0];
} else {
columns = table.getColumns();
}
}
if (!list.isEmpty()) {
for (Expression[] expr : list) {
if (!valuesExpressionList.isEmpty()) {
for (Expression[] expr : valuesExpressionList) {
if (expr.length != columns.length) {
throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH);
}
......
......@@ -23,8 +23,7 @@ INSERT INTO VERSION VALUES
(134, '1.4.184', '2014-12-19'),
(133, '1.4.183', '2014-12-13'),
(132, '1.4.182', '2014-10-17'),
(131, '1.4.181', '2014-08-06'),
;
(131, '1.4.181', '2014-08-06');
CREATE TABLE CHANNEL(TITLE VARCHAR, LINK VARCHAR, DESC VARCHAR,
LANGUAGE VARCHAR, PUB TIMESTAMP, LAST TIMESTAMP, AUTHOR VARCHAR);
......
......@@ -154,8 +154,8 @@ public class TestScript extends TestDb {
"dropDomain", "dropIndex", "dropSchema", "truncateTable" }) {
testScript("ddl/" + s + ".sql");
}
for (String s : new String[] { "delete", "error_reporting", "insertIgnore", "merge", "mergeUsing", "replace",
"script", "select", "show", "table", "with" }) {
for (String s : new String[] { "delete", "error_reporting", "insert", "insertIgnore", "merge", "mergeUsing",
"replace", "script", "select", "show", "table", "with" }) {
testScript("dml/" + s + ".sql");
}
for (String s : new String[] { "help" }) {
......
-- Copyright 2004-2019 H2 Group. Multiple-Licensed under the MPL 2.0,
-- and the EPL 1.0 (http://h2database.com/html/license.html).
-- Initial Developer: H2 Group
--
CREATE TABLE TEST(A INT, B INT);
> ok
INSERT INTO TEST VALUES ROW (1, 2), (3, 4), ROW (5, 6);
> update count: 3
INSERT INTO TEST(a) VALUES 7;
> update count: 1
INSERT INTO TEST(a) VALUES 8, 9;
> update count: 2
TABLE TEST;
> A B
> - ----
> 1 2
> 3 4
> 5 6
> 7 null
> 8 null
> 9 null
> rows: 6
DROP TABLE TEST;
> ok
......@@ -509,3 +509,44 @@ SELECT RAND() A, RAND() + 1 B, RAND() + 1 C, RAND() D, RAND() + 2 E, RAND() + 3
> rows: 1
@reconnect on
CREATE TABLE TEST (A INT, B INT, C INT);
> ok
INSERT INTO TEST VALUES (11, 12, 13), (21, 22, 23), (31, 32, 33);
> update count: 3
SELECT * FROM TEST WHERE (A, B) IN (VALUES (11, 12), (21, 22), (41, 42));
> A B C
> -- -- --
> 11 12 13
> 21 22 23
> rows: 2
SELECT * FROM TEST WHERE (A, B) = (VALUES (11, 12));
> A B C
> -- -- --
> 11 12 13
> rows: 1
DROP TABLE TEST;
> ok
VALUES (1, 2);
> C1 C2
> -- --
> 1 2
> rows: 1
VALUES ROW (1, 2);
> C1 C2
> -- --
> 1 2
> rows: 1
VALUES 1, 2;
> C1
> --
> 1
> 2
> rows: 2
......@@ -146,6 +146,12 @@ WITH CTE_TEST AS (TABLE TEST) ((TABLE CTE_TEST));
> 1 2
> rows: 1
WITH CTE_TEST AS (VALUES (1, 2)) ((SELECT * FROM CTE_TEST));
> C1 C2
> -- --
> 1 2
> rows: 1
WITH CTE_TEST AS (TABLE TEST) ((SELECT A, B FROM CTE_TEST2));
> exception TABLE_OR_VIEW_NOT_FOUND_1
......
......@@ -806,4 +806,4 @@ econd irst bcef ordinality nord unnest
analyst occupation distributive josaph aor engineer sajeewa isuru randil kevin doctor businessman artist ashan
corrupts splitted disruption unintentional octets preconditions predicates subq objectweb insn opcodes
preserves masking holder unboxing avert iae transformed subtle reevaluate exclusions subclause ftbl rgr
presorted inclusion
presorted inclusion contexts
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论