Unverified 提交 734f8029 authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov 提交者: GitHub

Merge pull request #1671 from katzyn/misc

Assorted changes
......@@ -41,7 +41,8 @@ of H2 please use parentheses.
If FOR UPDATE is specified, the tables or rows are locked for writing.
This clause is not allowed in DISTINCT queries and in queries with non-window aggregates, GROUP BY, or HAVING clauses.
When using default MVStore engine only the selected rows are locked as in an UPDATE statement.
When using default MVStore engine only the selected rows are locked as in an UPDATE statement;
locking behavior for rows that were excluded from result using OFFSET / FETCH is undefined.
With PageStore engine the whole tables are locked.
","
SELECT * FROM TEST;
......@@ -5559,7 +5560,7 @@ OVER windowNameOrSpecification
","
Returns the ratio of a value to the sum of all values.
If argument is NULL or sum of all values is 0, then the value of function is NULL.
Window frame clause is not allowed for this function.
Window ordering and window frame clauses are not allowed for this function.
Window functions are currently experimental in H2 and should be used with caution.
They also may require a lot of memory for large queries.
......
......@@ -6,6 +6,7 @@
package org.h2.command;
import java.util.ArrayList;
import java.util.List;
import org.h2.api.DatabaseEventListener;
import org.h2.command.dml.Explain;
import org.h2.command.dml.Query;
......@@ -27,6 +28,35 @@ public class CommandContainer extends Command {
private boolean readOnlyKnown;
private boolean readOnly;
/**
* Clears CTE views for a specified statement.
*
* @param session the session
* @param prepared prepared statement
*/
static void clearCTE(Session session, Prepared prepared) {
List<TableView> cteCleanups = prepared.getCteCleanups();
if (cteCleanups != null) {
clearCTE(session, cteCleanups);
}
}
/**
* Clears CTE views.
*
* @param session the session
* @param views list of view
*/
static void clearCTE(Session session, List<TableView> views) {
for (TableView view : views) {
// check if view was previously deleted as their name is set to
// null
if (view.getName() != null) {
session.removeLocalTempTable(view);
}
}
}
CommandContainer(Session session, String sql, Prepared prepared) {
super(session, sql);
prepared.setCommand(this);
......@@ -123,15 +153,7 @@ public class CommandContainer extends Command {
super.stop();
// Clean up after the command was run in the session.
// Must restart query (and dependency construction) to reuse.
if (prepared.getCteCleanups() != null) {
for (TableView view : prepared.getCteCleanups()) {
// check if view was previously deleted as their name is set to
// null
if (view.getName() != null) {
session.removeLocalTempTable(view);
}
}
}
clearCTE(session, prepared);
}
@Override
......
......@@ -672,7 +672,12 @@ public class Parser {
if (!hasMore && currentTokenType != END) {
throw getSyntaxError();
}
try {
p.prepare();
} catch (Throwable t) {
CommandContainer.clearCTE(session, p);
throw t;
}
Command c = new CommandContainer(session, sql, p);
if (hasMore) {
String remaining = originalSQL.substring(parseIndex);
......@@ -4533,14 +4538,20 @@ public class Parser {
parseIndex = i;
return;
case CHAR_VALUE:
if (c == '0' && chars[i] == 'X') {
if (c == '0' && (chars[i] == 'X' || chars[i] == 'x')) {
// hex number
long number = 0;
start += 2;
i++;
while (true) {
c = chars[i];
if ((c < '0' || c > '9') && (c < 'A' || c > 'F')) {
if (c >= '0' && c <= '9') {
number = (number << 4) + c - '0';
} else if (c >= 'A' && c <= 'F') {
number = (number << 4) + c - ('A' - 10);
} else if (c >= 'a' && c <= 'f') {
number = (number << 4) + c - ('a' - 10);
} else {
checkLiterals(false);
currentValue = ValueInt.get((int) number);
currentTokenType = VALUE;
......@@ -4548,8 +4559,6 @@ public class Parser {
parseIndex = i;
return;
}
number = (number << 4) + c -
(c >= 'A' ? ('A' - 0xa) : ('0'));
if (number > Integer.MAX_VALUE) {
readHexDecimal(start, i);
return;
......@@ -4558,12 +4567,19 @@ public class Parser {
}
}
long number = c - '0';
while (true) {
loop: while (true) {
c = chars[i];
if (c < '0' || c > '9') {
if (c == '.' || c == 'E' || c == 'L') {
readDecimal(start, i);
break;
switch (c) {
case '.':
case 'E':
case 'e':
readDecimal(start, i, false);
break loop;
case 'L':
case 'l':
readDecimal(start, i, true);
break loop;
}
checkLiterals(false);
currentValue = ValueInt.get((int) number);
......@@ -4574,7 +4590,7 @@ public class Parser {
}
number = number * 10 + (c - '0');
if (number > Integer.MAX_VALUE) {
readDecimal(start, i);
readDecimal(start, i, true);
break;
}
i++;
......@@ -4587,7 +4603,7 @@ public class Parser {
parseIndex = i;
return;
}
readDecimal(i - 1, i);
readDecimal(i - 1, i, false);
return;
case CHAR_STRING: {
String result = null;
......@@ -4684,22 +4700,24 @@ public class Parser {
currentTokenType = VALUE;
}
private void readDecimal(int start, int i) {
private void readDecimal(int start, int i, boolean integer) {
char[] chars = sqlCommandChars;
int[] types = characterTypes;
// go until the first non-number
while (true) {
int t = types[i];
if (t != CHAR_DOT && t != CHAR_VALUE) {
if (t == CHAR_DOT) {
integer = false;
} else if (t != CHAR_VALUE) {
break;
}
i++;
}
boolean containsE = false;
if (chars[i] == 'E' || chars[i] == 'e') {
containsE = true;
i++;
if (chars[i] == '+' || chars[i] == '-') {
char c = chars[i];
if (c == 'E' || c == 'e') {
integer = false;
c = chars[++i];
if (c == '+' || c == '-') {
i++;
}
if (types[i] != CHAR_VALUE) {
......@@ -4710,14 +4728,14 @@ public class Parser {
}
}
parseIndex = i;
String sub = sqlCommand.substring(start, i);
checkLiterals(false);
BigDecimal bd;
if (!containsE && sub.indexOf('.') < 0) {
BigInteger bi = new BigInteger(sub);
if (integer && i - start <= 19) {
BigInteger bi = new BigInteger(sqlCommand.substring(start, i));
if (bi.compareTo(ValueLong.MAX_BI) <= 0) {
// parse constants like "10000000L"
if (chars[i] == 'L') {
c = chars[i];
if (c == 'L' || c == 'l') {
parseIndex++;
}
currentValue = ValueLong.get(bi.longValue());
......@@ -4727,9 +4745,9 @@ public class Parser {
bd = new BigDecimal(bi);
} else {
try {
bd = new BigDecimal(sub);
bd = new BigDecimal(sqlCommandChars, start, i - start);
} catch (NumberFormatException e) {
throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, e, sub);
throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, e, sqlCommand.substring(start, i));
}
}
currentValue = ValueDecimal.get(bd);
......@@ -6127,6 +6145,15 @@ public class Parser {
private Prepared parseWith() {
List<TableView> viewsCreated = new ArrayList<>();
try {
return parseWith1(viewsCreated);
} catch (Throwable t) {
CommandContainer.clearCTE(session, viewsCreated);
throw t;
}
}
private Prepared parseWith1(List<TableView> viewsCreated) {
readIf("RECURSIVE");
// This WITH statement is not a temporary view - it is part of a persistent view
......
......@@ -774,7 +774,7 @@ public class Select extends Query {
}
boolean fetchPercent = this.fetchPercent;
if (fetchPercent) {
// Need to check it row, because negative limit has special treatment later
// Need to check it now, because negative limit has special treatment later
if (limitRows < 0 || limitRows > 100) {
throw DbException.getInvalidValueException("FETCH PERCENT", limitRows);
}
......@@ -803,7 +803,7 @@ public class Select extends Query {
}
// Do not add rows before OFFSET to result if possible
boolean quickOffset = !fetchPercent;
if (sort != null && (!sortUsingIndex || isAnyDistinct() || withTies)) {
if (sort != null && (!sortUsingIndex || isAnyDistinct())) {
result = createLocalResult(result);
result.setSortOrder(sort);
if (!sortUsingIndex) {
......@@ -886,7 +886,9 @@ public class Select extends Query {
if (limitRows >= 0) {
result.setLimit(limitRows);
result.setFetchPercent(fetchPercent);
result.setWithTies(withTies);
if (withTies) {
result.setWithTies(sort);
}
}
if (result != null) {
result.done();
......
......@@ -264,7 +264,9 @@ public class SelectUnion extends Query {
if (v != ValueNull.INSTANCE) {
result.setLimit(v.getInt());
result.setFetchPercent(fetchPercent);
result.setWithTies(withTies);
if (withTies) {
result.setWithTies(sort);
}
}
}
l.close();
......
......@@ -451,6 +451,9 @@ public class WindowFunction extends DataAnalysisOperation {
throw DbException.getSyntaxError(sql, sql.length() - 1, "ORDER BY");
default:
}
} else if (type == WindowFunctionType.RATIO_TO_REPORT) {
String sql = getSQL();
throw DbException.getSyntaxError(sql, sql.length() - 1);
}
super.optimize(session);
if (args != null) {
......
......@@ -25,7 +25,6 @@ import org.h2.message.DbException;
import org.h2.message.Trace;
import org.h2.message.TraceObject;
import org.h2.result.SimpleResult;
import org.h2.util.StatementBuilder;
import org.h2.util.StringUtils;
import org.h2.value.ValueInt;
import org.h2.value.ValueString;
......@@ -1616,25 +1615,27 @@ public class JdbcDatabaseMetaData extends TraceObject implements
+ "FROM INFORMATION_SCHEMA.HELP WHERE SECTION = ?");
prep.setString(1, section);
ResultSet rs = prep.executeQuery();
StatementBuilder buff = new StatementBuilder();
StringBuilder builder = new StringBuilder();
while (rs.next()) {
String s = rs.getString(1).trim();
String[] array = StringUtils.arraySplit(s, ',', true);
for (String a : array) {
buff.appendExceptFirst(",");
if (builder.length() != 0) {
builder.append(',');
}
String f = a.trim();
int spaceIndex = f.indexOf(' ');
if (spaceIndex >= 0) {
// remove 'Function' from 'INSERT Function'
StringUtils.trimSubstring(buff.builder(), f, 0, spaceIndex);
StringUtils.trimSubstring(builder, f, 0, spaceIndex);
} else {
buff.append(f);
builder.append(f);
}
}
}
rs.close();
prep.close();
return buff.toString();
return builder.toString();
} catch (Exception e) {
throw logAndConvert(e);
}
......
......@@ -10,9 +10,7 @@ import java.util.ArrayList;
import org.h2.engine.SysProperties;
import org.h2.expression.ParameterInterface;
import org.h2.util.StatementBuilder;
import org.h2.util.StringUtils;
import org.h2.value.Value;
/**
* This class represents a trace module.
......@@ -239,29 +237,23 @@ public class Trace {
* @param parameters the parameter list
* @return the formatted text
*/
public static String formatParams(
ArrayList<? extends ParameterInterface> parameters) {
public static String formatParams(ArrayList<? extends ParameterInterface> parameters) {
if (parameters.isEmpty()) {
return "";
}
StatementBuilder buff = new StatementBuilder();
StringBuilder builder = new StringBuilder();
int i = 0;
boolean params = false;
for (ParameterInterface p : parameters) {
if (p.isValueSet()) {
if (!params) {
buff.append(" {");
params = true;
builder.append(i == 0 ? " {" : ", ") //
.append(++i).append(": ") //
.append(p.getParamValue().getTraceSQL());
}
buff.appendExceptFirst(", ");
Value v = p.getParamValue();
buff.append(++i).append(": ").append(v.getTraceSQL());
}
if (i != 0) {
builder.append('}');
}
if (params) {
buff.append('}');
}
return buff.toString();
return builder.toString();
}
/**
......
......@@ -25,7 +25,10 @@ public interface LocalResult extends ResultInterface, ResultTarget {
public void setMaxMemoryRows(int maxValue);
/**
* @param sort Sort order.
* Sets sort order to be used by this result. When rows are presorted by the
* query this method should not be used.
*
* @param sort the sort order
*/
public void setSortOrder(SortOrder sort);
......@@ -82,9 +85,14 @@ public interface LocalResult extends ResultInterface, ResultTarget {
public void setFetchPercent(boolean fetchPercent);
/**
* @param withTies whether tied rows should be included in result too
* Enables inclusion of tied rows to result and sets the sort order for tied
* rows. The specified sort order must be the same as sort order if sort
* order was set. Passed value will be used if sort order was not set that
* is possible when rows are presorted.
*
* @param withTiesSortOrder the sort order for tied rows
*/
public void setWithTies(boolean withTies);
public void setWithTies(SortOrder withTiesSortOrder);
/**
* Set the offset of the first row to return.
......
......@@ -38,7 +38,7 @@ public class LocalResultImpl implements LocalResult {
private int offset;
private int limit = -1;
private boolean fetchPercent;
private boolean withTies;
private SortOrder withTiesSortOrder;
private boolean limitsWereApplied;
private ResultExternal external;
private boolean distinct;
......@@ -132,11 +132,6 @@ public class LocalResultImpl implements LocalResult {
return copy;
}
/**
* Set the sort order.
*
* @param sort the sort order
*/
@Override
public void setSortOrder(SortOrder sort) {
this.sort = sort;
......@@ -365,8 +360,8 @@ public class LocalResultImpl implements LocalResult {
if (isAnyDistinct()) {
rows = distinctRows.values();
}
if (sort != null && limit != 0) {
boolean withLimit = limit > 0 && !withTies;
if (sort != null && limit != 0 && !limitsWereApplied) {
boolean withLimit = limit > 0 && withTiesSortOrder == null;
if (offset > 0 || withLimit) {
sort.sort(rows, offset, withLimit ? limit : rows.size());
} else {
......@@ -412,9 +407,9 @@ public class LocalResultImpl implements LocalResult {
return;
}
int to = offset + limit;
if (withTies && sort != null) {
if (withTiesSortOrder != null) {
Value[] expected = rows.get(to - 1);
while (to < rows.size() && sort.compare(expected, rows.get(to)) == 0) {
while (to < rows.size() && withTiesSortOrder.compare(expected, rows.get(to)) == 0) {
to++;
rowCount++;
}
......@@ -448,9 +443,9 @@ public class LocalResultImpl implements LocalResult {
addRowsToDisk();
}
}
if (withTies && sort != null && row != null) {
if (withTiesSortOrder != null && row != null) {
Value[] expected = row;
while ((row = temp.next()) != null && sort.compare(expected, row) == 0) {
while ((row = temp.next()) != null && withTiesSortOrder.compare(expected, row) == 0) {
rows.add(row);
rowCount++;
if (rows.size() > maxMemoryRows) {
......@@ -497,12 +492,10 @@ public class LocalResultImpl implements LocalResult {
this.fetchPercent = fetchPercent;
}
/**
* @param withTies whether tied rows should be included in result too
*/
@Override
public void setWithTies(boolean withTies) {
this.withTies = withTies;
public void setWithTies(SortOrder withTiesSortOrder) {
assert sort == null || sort == withTiesSortOrder;
this.withTiesSortOrder = withTiesSortOrder;
}
@Override
......
......@@ -27,9 +27,9 @@ public interface ResultTarget {
int getRowCount();
/**
* A hint that offset and limit may be ignored by this result because they
* were applied during the query. This is useful for WITH TIES clause
* because result may contain tied rows above limit.
* A hint that sorting, offset and limit may be ignored by this result
* because they were applied during the query. This is useful for WITH TIES
* clause because result may contain tied rows above limit.
*/
void limitsWereApplied();
......
......@@ -14,7 +14,6 @@ import java.util.ArrayList;
import org.h2.api.ErrorCode;
import org.h2.jdbc.JdbcConnection;
import org.h2.message.DbException;
import org.h2.util.StatementBuilder;
import org.h2.util.StringUtils;
import org.h2.util.Utils;
import org.h2.value.DataType;
......@@ -156,24 +155,26 @@ public class UpdatableRow {
return index;
}
private void appendColumnList(StatementBuilder buff, boolean set) {
buff.resetCount();
private void appendColumnList(StringBuilder builder, boolean set) {
for (int i = 0; i < columnCount; i++) {
buff.appendExceptFirst(",");
if (i > 0) {
builder.append(',');
}
String col = result.getColumnName(i);
StringUtils.quoteIdentifier(buff.builder(), col);
StringUtils.quoteIdentifier(builder, col);
if (set) {
buff.append("=? ");
builder.append("=? ");
}
}
}
private void appendKeyCondition(StatementBuilder buff) {
buff.append(" WHERE ");
buff.resetCount();
for (String k : key) {
buff.appendExceptFirst(" AND ");
StringUtils.quoteIdentifier(buff.builder(), k).append("=?");
private void appendKeyCondition(StringBuilder builder) {
builder.append(" WHERE ");
for (int i = 0; i < key.size(); i++) {
if (i > 0) {
builder.append(" AND ");
}
StringUtils.quoteIdentifier(builder, key.get(i)).append("=?");
}
}
......@@ -218,12 +219,12 @@ public class UpdatableRow {
* @return the row
*/
public Value[] readRow(Value[] row) throws SQLException {
StatementBuilder buff = new StatementBuilder("SELECT ");
appendColumnList(buff, false);
buff.append(" FROM ");
appendTableName(buff.builder());
appendKeyCondition(buff);
PreparedStatement prep = conn.prepareStatement(buff.toString());
StringBuilder builder = new StringBuilder("SELECT ");
appendColumnList(builder, false);
builder.append(" FROM ");
appendTableName(builder);
appendKeyCondition(builder);
PreparedStatement prep = conn.prepareStatement(builder.toString());
setKey(prep, 1, row);
ResultSet rs = prep.executeQuery();
if (!rs.next()) {
......@@ -244,10 +245,10 @@ public class UpdatableRow {
* @throws SQLException if this row has already been deleted
*/
public void deleteRow(Value[] current) throws SQLException {
StatementBuilder buff = new StatementBuilder("DELETE FROM ");
appendTableName(buff.builder());
appendKeyCondition(buff);
PreparedStatement prep = conn.prepareStatement(buff.toString());
StringBuilder builder = new StringBuilder("DELETE FROM ");
appendTableName(builder);
appendKeyCondition(builder);
PreparedStatement prep = conn.prepareStatement(builder.toString());
setKey(prep, 1, current);
int count = prep.executeUpdate();
if (count != 1) {
......@@ -264,15 +265,15 @@ public class UpdatableRow {
* @throws SQLException if the row has been deleted
*/
public void updateRow(Value[] current, Value[] updateRow) throws SQLException {
StatementBuilder buff = new StatementBuilder("UPDATE ");
appendTableName(buff.builder());
buff.append(" SET ");
appendColumnList(buff, true);
StringBuilder builder = new StringBuilder("UPDATE ");
appendTableName(builder);
builder.append(" SET ");
appendColumnList(builder, true);
// TODO updatable result set: we could add all current values to the
// where clause
// - like this optimistic ('no') locking is possible
appendKeyCondition(buff);
PreparedStatement prep = conn.prepareStatement(buff.toString());
appendKeyCondition(builder);
PreparedStatement prep = conn.prepareStatement(builder.toString());
int j = 1;
for (int i = 0; i < columnCount; i++) {
Value v = updateRow[i];
......@@ -296,23 +297,24 @@ public class UpdatableRow {
* @throws SQLException if the row could not be inserted
*/
public void insertRow(Value[] row) throws SQLException {
StatementBuilder buff = new StatementBuilder("INSERT INTO ");
appendTableName(buff.builder());
buff.append('(');
appendColumnList(buff, false);
buff.append(")VALUES(");
buff.resetCount();
StringBuilder builder = new StringBuilder("INSERT INTO ");
appendTableName(builder);
builder.append('(');
appendColumnList(builder, false);
builder.append(")VALUES(");
for (int i = 0; i < columnCount; i++) {
buff.appendExceptFirst(",");
if (i > 0) {
builder.append(',');
}
Value v = row[i];
if (v == null) {
buff.append("DEFAULT");
builder.append("DEFAULT");
} else {
buff.append('?');
builder.append('?');
}
}
buff.append(')');
PreparedStatement prep = conn.prepareStatement(buff.toString());
builder.append(')');
PreparedStatement prep = conn.prepareStatement(builder.toString());
for (int i = 0, j = 0; i < columnCount; i++) {
Value v = row[i];
if (v != null) {
......
......@@ -370,10 +370,12 @@ public class StringUtils {
if (array == null) {
return "null";
}
StatementBuilder buff = new StatementBuilder("new String[]{");
for (String a : array) {
buff.appendExceptFirst(", ");
buff.append(quoteJavaString(a));
StringBuilder buff = new StringBuilder("new String[]{");
for (int i = 0; i < array.length; i++) {
if (i > 0) {
buff.append(", ");
}
buff.append(quoteJavaString(array[i]));
}
return buff.append('}').toString();
}
......@@ -389,12 +391,14 @@ public class StringUtils {
if (array == null) {
return "null";
}
StatementBuilder buff = new StatementBuilder("new int[]{");
for (int a : array) {
buff.appendExceptFirst(", ");
buff.append(a);
StringBuilder builder = new StringBuilder("new int[]{");
for (int i = 0; i < array.length; i++) {
if (i > 0) {
builder.append(", ");
}
return buff.append('}').toString();
builder.append(array[i]);
}
return builder.append('}').toString();
}
/**
......@@ -498,21 +502,24 @@ public class StringUtils {
* @return the combined string
*/
public static String arrayCombine(String[] list, char separatorChar) {
StatementBuilder buff = new StatementBuilder();
for (String s : list) {
buff.appendExceptFirst(String.valueOf(separatorChar));
StringBuilder builder = new StringBuilder();
for (int i = 0; i < list.length; i++) {
if (i > 0) {
builder.append(separatorChar);
}
String s = list[i];
if (s == null) {
s = "";
continue;
}
for (int j = 0, length = s.length(); j < length; j++) {
char c = s.charAt(j);
if (c == '\\' || c == separatorChar) {
buff.append('\\');
builder.append('\\');
}
buff.append(c);
builder.append(c);
}
}
return buff.toString();
return builder.toString();
}
/**
......
......@@ -110,6 +110,13 @@ public class TestCompatibility extends TestDb {
stat.execute("select id from test t group by T.ID");
stat.execute("drop table test");
rs = stat.executeQuery("select 1e10, 1000000000000000000000e10, 0xfAfBl");
assertTrue(rs.next());
assertEquals(1e10, rs.getDouble(1));
assertEquals(1000000000000000000000e10, rs.getDouble(2));
assertEquals(0xfafbL, rs.getLong(3));
assertFalse(rs.next());
c.close();
}
......
......@@ -146,5 +146,11 @@ WITH CTE_TEST AS (TABLE TEST) ((TABLE CTE_TEST));
> 1 2
> rows: 1
WITH CTE_TEST AS (TABLE TEST) ((SELECT A, B FROM CTE_TEST2));
> exception TABLE_OR_VIEW_NOT_FOUND_1
WITH CTE_TEST AS (TABLE TEST) ((SELECT A, B, C FROM CTE_TEST));
> exception COLUMN_NOT_FOUND_1
DROP TABLE TEST;
> ok
......@@ -31,5 +31,8 @@ SELECT ID, N, RATIO_TO_REPORT(N) OVER() R2R FROM TEST;
> 5 -8 null
> rows: 5
SELECT RATIO_TO_REPORT(N) OVER (ORDER BY N) FROM TEST;
> exception SYNTAX_ERROR_1
DROP TABLE TEST;
> ok
......@@ -806,3 +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
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论