提交 230a1fcb authored 作者: Noel Grandin's avatar Noel Grandin

Merge remote-tracking branch 'upstream/master' into testing_pagestore

......@@ -5320,7 +5320,7 @@ public class Parser {
if (isPersistent) {
db.addSchemaObject(targetSession, view);
view.lock(targetSession, true, true);
targetSession.getDatabase().removeSchemaObject(targetSession, view);
db.removeSchemaObject(targetSession, view);
} else {
session.removeLocalTempTable(view);
}
......@@ -5330,7 +5330,7 @@ public class Parser {
isPersistent);
}
// both removeSchemaObject and removeLocalTempTable hold meta locks
targetSession.getDatabase().unlockMeta(targetSession);
db.unlockMeta(targetSession);
}
view.setTableExpression(true);
view.setTemporary(!isPersistent);
......@@ -5929,8 +5929,8 @@ public class Parser {
boolean isDualTable(String tableName) {
return ((schemaName == null || equalsToken(schemaName, "SYS")) && equalsToken("DUAL", tableName))
|| (database.getMode().sysDummy1 && (schemaName == null || equalsToken(schemaName, "SYSIBM")))
&& equalsToken("SYSDUMMY1", tableName);
|| (database.getMode().sysDummy1 && (schemaName == null || equalsToken(schemaName, "SYSIBM"))
&& equalsToken("SYSDUMMY1", tableName));
}
private Table readTableOrView() {
......
......@@ -5,6 +5,11 @@
*/
package org.h2.command.dml;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import org.h2.api.ErrorCode;
import org.h2.api.Trigger;
import org.h2.command.CommandInterface;
......@@ -12,23 +17,39 @@ import org.h2.engine.Constants;
import org.h2.engine.Database;
import org.h2.engine.Session;
import org.h2.engine.SysProperties;
import org.h2.expression.*;
import org.h2.expression.Alias;
import org.h2.expression.Comparison;
import org.h2.expression.ConditionAndOr;
import org.h2.expression.Expression;
import org.h2.expression.ExpressionColumn;
import org.h2.expression.ExpressionVisitor;
import org.h2.expression.Parameter;
import org.h2.index.Cursor;
import org.h2.index.Index;
import org.h2.index.IndexType;
import org.h2.message.DbException;
import org.h2.result.*;
import org.h2.table.*;
import org.h2.util.*;
import org.h2.result.LazyResult;
import org.h2.result.LocalResult;
import org.h2.result.ResultInterface;
import org.h2.result.ResultTarget;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.table.Column;
import org.h2.table.ColumnResolver;
import org.h2.table.IndexColumn;
import org.h2.table.JoinBatch;
import org.h2.table.Table;
import org.h2.table.TableFilter;
import org.h2.table.TableView;
import org.h2.util.ColumnNamer;
import org.h2.util.StatementBuilder;
import org.h2.util.StringUtils;
import org.h2.util.Utils;
import org.h2.value.Value;
import org.h2.value.ValueArray;
import org.h2.value.ValueNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
/**
* This class represents a simple SELECT statement.
*
......@@ -81,10 +102,22 @@ public class Select extends Query {
boolean[] groupByExpression;
/**
* The current group-by values.
* The array of current group-by expression data e.g. AggregateData.
*/
HashMap<Expression, Object> currentGroup;
Object[] currentGroupByExprData;
/**
* Maps an expression object to an index, to use in accessing the Object[] pointed to by groupByData.
*/
final HashMap<Expression,Integer> exprToIndexInGroupByData = new HashMap<>();
/**
* Map of group-by key to group-by expression data e.g. AggregateData
*/
private HashMap<Value, Object[]> groupByData;
/**
* Key into groupByData that produces currentGroupByExprData. Not used in lazy mode.
*/
ValueArray currentGroupsKey;
private int havingIndex;
private boolean isGroupQuery, isGroupSortedQuery;
private boolean isForUpdate, isForUpdateMvcc;
......@@ -151,8 +184,45 @@ public class Select extends Query {
return group;
}
public HashMap<Expression, Object> getCurrentGroup() {
return currentGroup;
/**
* Is there currently a group-by active
*/
public boolean isCurrentGroup() {
return currentGroupByExprData != null;
}
/**
* Get the group-by data for the current group and the passed in expression.
*/
public Object getCurrentGroupExprData(Expression expr) {
Integer index = exprToIndexInGroupByData.get(expr);
if (index == null) {
return null;
}
return currentGroupByExprData[index];
}
/**
* Set the group-by data for the current group and the passed in expression.
*/
public void setCurrentGroupExprData(Expression expr, Object obj) {
Integer index = exprToIndexInGroupByData.get(expr);
if (index != null) {
assert currentGroupByExprData[index] == null;
currentGroupByExprData[index] = obj;
return;
}
index = exprToIndexInGroupByData.size();
exprToIndexInGroupByData.put(expr, index);
if (index >= currentGroupByExprData.length) {
currentGroupByExprData = Arrays.copyOf(currentGroupByExprData, currentGroupByExprData.length * 2);
// this can be null in lazy mode
if (currentGroupsKey != null) {
// since we changed the size of the array, update the object in the groups map
groupByData.put(currentGroupsKey, currentGroupByExprData);
}
}
currentGroupByExprData[index] = obj;
}
public int getCurrentGroupRowId() {
......@@ -313,72 +383,79 @@ public class Select extends Query {
}
private void queryGroup(int columnCount, LocalResult result) {
ValueHashMap<HashMap<Expression, Object>> groups =
ValueHashMap.newInstance();
int rowNumber = 0;
setCurrentRowNumber(0);
currentGroup = null;
ValueArray defaultGroup = ValueArray.get(new Value[0]);
int sampleSize = getSampleSizeValue(session);
while (topTableFilter.next()) {
setCurrentRowNumber(rowNumber + 1);
if (isConditionMet()) {
Value key;
rowNumber++;
if (groupIndex == null) {
key = defaultGroup;
} else {
Value[] keyValues = new Value[groupIndex.length];
// update group
for (int i = 0; i < groupIndex.length; i++) {
int idx = groupIndex[i];
Expression expr = expressions.get(idx);
keyValues[i] = expr.getValue(session);
groupByData = new HashMap<>();
currentGroupByExprData = null;
currentGroupsKey = null;
exprToIndexInGroupByData.clear();
try {
int rowNumber = 0;
setCurrentRowNumber(0);
ValueArray defaultGroup = ValueArray.get(new Value[0]);
int sampleSize = getSampleSizeValue(session);
while (topTableFilter.next()) {
setCurrentRowNumber(rowNumber + 1);
if (isConditionMet()) {
rowNumber++;
if (groupIndex == null) {
currentGroupsKey = defaultGroup;
} else {
Value[] keyValues = new Value[groupIndex.length];
// update group
for (int i = 0; i < groupIndex.length; i++) {
int idx = groupIndex[i];
Expression expr = expressions.get(idx);
keyValues[i] = expr.getValue(session);
}
currentGroupsKey = ValueArray.get(keyValues);
}
Object[] values = groupByData.get(currentGroupsKey);
if (values == null) {
values = new Object[Math.max(exprToIndexInGroupByData.size(), expressions.size())];
groupByData.put(currentGroupsKey, values);
}
currentGroupByExprData = values;
currentGroupRowId++;
for (int i = 0; i < columnCount; i++) {
if (groupByExpression == null || !groupByExpression[i]) {
Expression expr = expressions.get(i);
expr.updateAggregate(session);
}
}
if (sampleSize > 0 && rowNumber >= sampleSize) {
break;
}
key = ValueArray.get(keyValues);
}
HashMap<Expression, Object> values = groups.get(key);
if (values == null) {
values = new HashMap<>();
groups.put(key, values);
}
if (groupIndex == null && groupByData.size() == 0) {
groupByData.put(defaultGroup,
new Object[Math.max(exprToIndexInGroupByData.size(), expressions.size())]);
}
for (Map.Entry<Value, Object[]> entry : groupByData.entrySet()) {
currentGroupsKey = (ValueArray) entry.getKey();
currentGroupByExprData = entry.getValue();
Value[] keyValues = currentGroupsKey.getList();
Value[] row = new Value[columnCount];
for (int j = 0; groupIndex != null && j < groupIndex.length; j++) {
row[groupIndex[j]] = keyValues[j];
}
currentGroup = values;
currentGroupRowId++;
for (int i = 0; i < columnCount; i++) {
if (groupByExpression == null || !groupByExpression[i]) {
Expression expr = expressions.get(i);
expr.updateAggregate(session);
for (int j = 0; j < columnCount; j++) {
if (groupByExpression != null && groupByExpression[j]) {
continue;
}
Expression expr = expressions.get(j);
row[j] = expr.getValue(session);
}
if (sampleSize > 0 && rowNumber >= sampleSize) {
break;
}
}
}
if (groupIndex == null && groups.size() == 0) {
groups.put(defaultGroup, new HashMap<Expression, Object>());
}
ArrayList<Value> keys = groups.keys();
for (Value v : keys) {
ValueArray key = (ValueArray) v;
currentGroup = groups.get(key);
Value[] keyValues = key.getList();
Value[] row = new Value[columnCount];
for (int j = 0; groupIndex != null && j < groupIndex.length; j++) {
row[groupIndex[j]] = keyValues[j];
}
for (int j = 0; j < columnCount; j++) {
if (groupByExpression != null && groupByExpression[j]) {
if (isHavingNullOrFalse(row)) {
continue;
}
Expression expr = expressions.get(j);
row[j] = expr.getValue(session);
}
if (isHavingNullOrFalse(row)) {
continue;
row = keepOnlyDistinct(row, columnCount);
result.addRow(row);
}
row = keepOnlyDistinct(row, columnCount);
result.addRow(row);
} finally {
groupByData = null;
currentGroupsKey = null;
currentGroupByExprData = null;
exprToIndexInGroupByData.clear();
}
}
......@@ -1475,13 +1552,15 @@ public class Select extends Query {
LazyResultGroupSorted(Expression[] expressions, int columnCount) {
super(expressions, columnCount);
currentGroup = null;
currentGroupByExprData = null;
currentGroupsKey = null;
}
@Override
public void reset() {
super.reset();
currentGroup = null;
currentGroupByExprData = null;
currentGroupsKey = null;
}
@Override
......@@ -1501,11 +1580,11 @@ public class Select extends Query {
Value[] row = null;
if (previousKeyValues == null) {
previousKeyValues = keyValues;
currentGroup = new HashMap<>();
currentGroupByExprData = new Object[Math.max(exprToIndexInGroupByData.size(), expressions.size())];
} else if (!Arrays.equals(previousKeyValues, keyValues)) {
row = createGroupSortedRow(previousKeyValues, columnCount);
previousKeyValues = keyValues;
currentGroup = new HashMap<>();
currentGroupByExprData = new Object[Math.max(exprToIndexInGroupByData.size(), expressions.size())];
}
currentGroupRowId++;
......
......@@ -283,8 +283,7 @@ public class Aggregate extends Expression {
// if (on != null) {
// on.updateAggregate();
// }
HashMap<Expression, Object> group = select.getCurrentGroup();
if (group == null) {
if (!select.isCurrentGroup()) {
// this is a different level (the enclosing query)
return;
}
......@@ -296,10 +295,10 @@ public class Aggregate extends Expression {
}
lastGroupRowId = groupRowId;
AggregateData data = (AggregateData) group.get(this);
AggregateData data = (AggregateData) select.getCurrentGroupExprData(this);
if (data == null) {
data = AggregateData.create(type);
group.put(this, data);
select.setCurrentGroupExprData(this, data);
}
Value v = on == null ? null : on.getValue(session);
if (type == AggregateType.GROUP_CONCAT) {
......@@ -372,13 +371,13 @@ public class Aggregate extends Expression {
DbException.throwInternalError("type=" + type);
}
}
HashMap<Expression, Object> group = select.getCurrentGroup();
if (group == null) {
throw DbException.get(ErrorCode.INVALID_USE_OF_AGGREGATE_FUNCTION_1, getSQL());
if (!select.isCurrentGroup()) {
throw DbException.get(ErrorCode.INVALID_USE_OF_AGGREGATE_FUNCTION_1, getSQL());
}
AggregateData data = (AggregateData) group.get(this);
AggregateData data = (AggregateData)select.getCurrentGroupExprData(this);
if (data == null) {
data = AggregateData.create(type);
select.setCurrentGroupExprData(this, data);
}
if (type == AggregateType.GROUP_CONCAT) {
Value[] array = ((AggregateDataCollecting) data).getArray();
......
......@@ -7,6 +7,7 @@ package org.h2.expression;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Map;
import org.h2.engine.Constants;
import org.h2.engine.Database;
import org.h2.util.ValueHashMap;
......@@ -47,9 +48,9 @@ class AggregateDataHistogram extends AggregateData {
}
ValueArray[] values = new ValueArray[distinctValues.size()];
int i = 0;
for (Value dv : distinctValues.keys()) {
AggregateDataHistogram d = distinctValues.get(dv);
values[i] = ValueArray.get(new Value[] { dv, ValueLong.get(d.count) });
for (Map.Entry<Value,AggregateDataHistogram> entry : distinctValues.entries()) {
AggregateDataHistogram d = entry.getValue();
values[i] = ValueArray.get(new Value[] { entry.getKey(), ValueLong.get(d.count) });
i++;
}
final CompareMode compareMode = database.getCompareMode();
......
......@@ -5,7 +5,6 @@
*/
package org.h2.expression;
import java.util.HashMap;
import org.h2.api.ErrorCode;
import org.h2.command.Parser;
import org.h2.command.dml.Select;
......@@ -159,14 +158,13 @@ public class ExpressionColumn extends Expression {
if (select == null) {
throw DbException.get(ErrorCode.MUST_GROUP_BY_COLUMN_1, getSQL());
}
HashMap<Expression, Object> values = select.getCurrentGroup();
if (values == null) {
if (!select.isCurrentGroup()) {
// this is a different level (the enclosing query)
return;
}
Value v = (Value) values.get(this);
Value v = (Value) select.getCurrentGroupExprData(this);
if (v == null) {
values.put(this, now);
select.setCurrentGroupExprData(this, now);
} else {
if (!database.areEqual(now, v)) {
throw DbException.get(ErrorCode.MUST_GROUP_BY_COLUMN_1, getSQL());
......@@ -178,9 +176,8 @@ public class ExpressionColumn extends Expression {
public Value getValue(Session session) {
Select select = columnResolver.getSelect();
if (select != null) {
HashMap<Expression, Object> values = select.getCurrentGroup();
if (values != null) {
Value v = (Value) values.get(this);
if (select.isCurrentGroup()) {
Value v = (Value) select.getCurrentGroupExprData(this);
if (v != null) {
return v;
}
......
......@@ -7,7 +7,6 @@ package org.h2.expression;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import org.h2.api.Aggregate;
import org.h2.api.ErrorCode;
import org.h2.command.Parser;
......@@ -167,15 +166,14 @@ public class JavaAggregate extends Expression {
@Override
public Value getValue(Session session) {
HashMap<Expression, Object> group = select.getCurrentGroup();
if (group == null) {
if (!select.isCurrentGroup()) {
throw DbException.get(ErrorCode.INVALID_USE_OF_AGGREGATE_FUNCTION_1, getSQL());
}
try {
Aggregate agg;
if (distinct) {
agg = getInstance();
AggregateDataCollecting data = (AggregateDataCollecting) group.get(this);
AggregateDataCollecting data = (AggregateDataCollecting) select.getCurrentGroupExprData(this);
if (data != null) {
for (Value value : data.values) {
if (args.length == 1) {
......@@ -191,7 +189,7 @@ public class JavaAggregate extends Expression {
}
}
} else {
agg = (Aggregate) group.get(this);
agg = (Aggregate) select.getCurrentGroupExprData(this);
if (agg == null) {
agg = getInstance();
}
......@@ -208,8 +206,7 @@ public class JavaAggregate extends Expression {
@Override
public void updateAggregate(Session session) {
HashMap<Expression, Object> group = select.getCurrentGroup();
if (group == null) {
if (!select.isCurrentGroup()) {
// this is a different level (the enclosing query)
return;
}
......@@ -229,10 +226,10 @@ public class JavaAggregate extends Expression {
try {
if (distinct) {
AggregateDataCollecting data = (AggregateDataCollecting) group.get(this);
AggregateDataCollecting data = (AggregateDataCollecting) select.getCurrentGroupExprData(this);
if (data == null) {
data = new AggregateDataCollecting();
group.put(this, data);
select.setCurrentGroupExprData(this, data);
}
Value[] argValues = new Value[args.length];
Value arg = null;
......@@ -243,10 +240,10 @@ public class JavaAggregate extends Expression {
}
data.add(session.getDatabase(), dataType, true, args.length == 1 ? arg : ValueArray.get(argValues));
} else {
Aggregate agg = (Aggregate) group.get(this);
Aggregate agg = (Aggregate) select.getCurrentGroupExprData(this);
if (agg == null) {
agg = getInstance();
group.put(this, agg);
select.setCurrentGroupExprData(this, agg);
}
Object[] argValues = new Object[args.length];
Object arg = null;
......
......@@ -1026,39 +1026,6 @@ public final class DataUtils {
}
}
/**
* An entry of a map.
*
* @param <K> the key type
* @param <V> the value type
*/
public static final class MapEntry<K, V> implements Map.Entry<K, V> {
private final K key;
private final V value;
public MapEntry(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(V value) {
throw newUnsupportedOperationException("Updating the value is not supported");
}
}
/**
* Get the configuration parameter value, or default.
*
......
......@@ -673,7 +673,7 @@ public class MVMap<K, V> extends AbstractMap<K, V>
@Override
public Entry<K, V> next() {
K k = cursor.next();
return new DataUtils.MapEntry<>(k, cursor.getValue());
return new SimpleImmutableEntry<>(k, cursor.getValue());
}
@Override
......
......@@ -5,6 +5,7 @@
*/
package org.h2.mvstore.db;
import java.util.AbstractMap;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
......@@ -18,7 +19,6 @@ import org.h2.index.BaseIndex;
import org.h2.index.Cursor;
import org.h2.index.IndexType;
import org.h2.message.DbException;
import org.h2.mvstore.DataUtils;
import org.h2.mvstore.tx.Transaction;
import org.h2.mvstore.tx.TransactionMap;
import org.h2.result.Row;
......@@ -261,7 +261,7 @@ public class MVPrimaryIndex extends BaseIndex {
Collections.<Entry<Value, Value>> emptyList().iterator());
}
Value value = map.get(v);
Entry<Value, Value> e = new DataUtils.MapEntry<Value, Value>(v, value);
Entry<Value, Value> e = new AbstractMap.SimpleImmutableEntry<Value, Value>(v, value);
List<Entry<Value, Value>> list = Collections.singletonList(e);
MVStoreCursor c = new MVStoreCursor(session, list.iterator());
c.next();
......
......@@ -9,6 +9,8 @@ import org.h2.mvstore.Cursor;
import org.h2.mvstore.DataUtils;
import org.h2.mvstore.MVMap;
import org.h2.mvstore.type.DataType;
import java.util.AbstractMap;
import java.util.Iterator;
import java.util.Map;
......@@ -671,7 +673,7 @@ public class TransactionMap<K, V> {
if (data != null && data.value != null) {
@SuppressWarnings("unchecked")
final V value = (V) data.value;
current = new DataUtils.MapEntry<>(key, value);
current = new AbstractMap.SimpleImmutableEntry<>(key, value);
currentKey = key;
return;
}
......
......@@ -5,20 +5,38 @@
*/
package org.h2.util;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import org.h2.message.DbException;
import org.h2.value.Value;
import org.h2.value.ValueNull;
/**
* This hash map supports keys of type Value.
* <p>
* ValueHashMap is a very simple implementation without allocation of additional
* objects for entries. It's very fast with good distribution of hashes, but if
* hashes have a lot of collisions this implementation tends to be very slow.
* <p>
* HashMap in archaic versions of Java have some overhead for allocation of
* entries, but slightly better behaviour with limited number of collisions,
* because collisions have no impact on non-colliding entries. HashMap in modern
* versions of Java also have the same overhead, but it builds a trees of keys
* with colliding hashes, that's why even if the all keys have exactly the same
* hash code it still offers a good performance similar to TreeMap. So
* ValueHashMap is faster in typical cases, but may behave really bad in some
* cases. HashMap is slower in typical cases, but its performance does not
* degrade too much even in the worst possible case (if keys are comparable).
*
* @param <V> the value type
*/
public class ValueHashMap<V> extends HashBase {
private Value[] keys;
private V[] values;
Value[] keys;
V[] values;
/**
* Create a new value hash map.
......@@ -54,7 +72,12 @@ public class ValueHashMap<V> extends HashBase {
}
private int getIndex(Value key) {
return key.hashCode() & mask;
int h = key.hashCode();
/*
* Add some protection against hashes with the same less significant bits
* (ValueDouble with integer values, for example).
*/
return (h ^ h >>> 16) & mask;
}
/**
......@@ -169,6 +192,51 @@ public class ValueHashMap<V> extends HashBase {
}
return list;
}
public Iterable<Map.Entry<Value, V>> entries() {
return new EntryIterable();
}
private final class EntryIterable implements Iterable<Map.Entry<Value, V>> {
EntryIterable() {
}
@Override
public Iterator<Map.Entry<Value, V>> iterator() {
return new EntryIterator();
}
}
private final class EntryIterator implements Iterator<Map.Entry<Value, V>> {
private int keysIndex = -1;
private int left = size;
EntryIterator() {
}
@Override
public boolean hasNext() {
return left > 0;
}
@Override
public Map.Entry<Value, V> next() {
if (left <= 0)
throw new NoSuchElementException();
left--;
for (;;) {
keysIndex++;
Value key = keys[keysIndex];
if (key != null && key != ValueNull.DELETED)
return new AbstractMap.SimpleImmutableEntry<Value, V>(key, values[keysIndex]);
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
/**
* Get the list of values.
......
......@@ -7,13 +7,12 @@ package org.h2.value;
import java.lang.reflect.Array;
import java.sql.PreparedStatement;
import java.util.ArrayList;
import java.util.Arrays;
import org.h2.engine.Constants;
import org.h2.engine.SysProperties;
import org.h2.util.MathUtils;
import org.h2.util.StatementBuilder;
import org.h2.util.Utils;
/**
* Implementation of the ARRAY data type.
......@@ -29,10 +28,6 @@ public class ValueArray extends Value {
this.values = list;
}
private ValueArray(Value[] list) {
this(Object.class, list);
}
/**
* Get or create a array value for the given value array.
* Do not clone the data.
......@@ -41,7 +36,7 @@ public class ValueArray extends Value {
* @return the value
*/
public static ValueArray get(Value[] list) {
return new ValueArray(list);
return new ValueArray(Object.class, list);
}
/**
......@@ -211,18 +206,28 @@ public class ValueArray extends Value {
if (!force) {
return this;
}
ArrayList<Value> list = Utils.newSmallArrayList();
for (Value v : values) {
v = v.convertPrecision(precision, true);
int length = values.length;
Value[] newValues = new Value[length];
int i = 0;
boolean modified = false;
for (; i < length; i++) {
Value old = values[i];
Value v = old.convertPrecision(precision, true);
if (v != old) {
modified = true;
}
// empty byte arrays or strings have precision 0
// they count as precision 1 here
precision -= Math.max(1, v.getPrecision());
if (precision < 0) {
break;
}
list.add(v);
newValues[i] = v;
}
if (i < length) {
return get(componentType, Arrays.copyOf(newValues, i));
}
return get(list.toArray(new Value[0]));
return modified ? get(componentType, newValues) : this;
}
}
......@@ -133,8 +133,12 @@ public class ValueDouble extends Value {
@Override
public int hashCode() {
long hash = Double.doubleToLongBits(value);
return (int) (hash ^ (hash >> 32));
/*
* NaNs are normalized in get() method, so it's safe to use
* doubleToRawLongBits() instead of doubleToLongBits() here.
*/
long hash = Double.doubleToRawLongBits(value);
return (int) (hash ^ (hash >>> 32));
}
@Override
......
......@@ -34,6 +34,7 @@ public class ValueFloat extends Value {
private static final ValueFloat ZERO = new ValueFloat(0.0F);
private static final ValueFloat ONE = new ValueFloat(1.0F);
private static final ValueFloat NAN = new ValueFloat(Float.NaN);
private final float value;
......@@ -88,7 +89,7 @@ public class ValueFloat extends Value {
return "POWER(0, -1)";
} else if (value == Float.NEGATIVE_INFINITY) {
return "(-POWER(0, -1))";
} else if (Double.isNaN(value)) {
} else if (Float.isNaN(value)) {
// NaN
return "SQRT(-1)";
}
......@@ -133,8 +134,11 @@ public class ValueFloat extends Value {
@Override
public int hashCode() {
long hash = Float.floatToIntBits(value);
return (int) (hash ^ (hash >> 32));
/*
* NaNs are normalized in get() method, so it's safe to use
* floatToRawIntBits() instead of floatToIntBits() here.
*/
return Float.floatToRawIntBits(value);
}
@Override
......@@ -160,6 +164,8 @@ public class ValueFloat extends Value {
} else if (d == 0.0F) {
// -0.0 == 0.0, and we want to return 0.0 for both
return ZERO;
} else if (Float.isNaN(d)) {
return NAN;
}
return (ValueFloat) Value.cache(new ValueFloat(d));
}
......
......@@ -851,6 +851,19 @@ public abstract class TestBase {
assertFalse(message, rs1.next());
}
/**
* Check if two objects are the same, and if not throw an exception.
*
* @param expected the expected value
* @param actual the actual value
* @throws AssertionError if the objects are not the same
*/
public void assertSame(Object expected, Object actual) {
if (expected != actual) {
fail(" expected: " + expected + " != actual: " + actual);
}
}
/**
* Check if the first value is larger or equal than the second value, and if
* not throw an exception.
......
......@@ -10,6 +10,10 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import org.h2.test.TestBase;
/**
......@@ -29,16 +33,25 @@ public abstract class AbstractBaseForCommonTableExpressions extends TestBase {
* @param closeAndReopenDatabaseConnectionOnIteration whether the connection
* should be re-opened each time
* @param expectedColumnTypes the expected datatypes of the result
* @param anyOrder whether any order of rows should be allowed.
* If {@code true}, this method may sort expectedRowData.
*/
void testRepeatedQueryWithSetup(int maxRetries, String[] expectedRowData, String[] expectedColumnNames,
int expectedNumberOfRows, String setupSQL, String withQuery,
int closeAndReopenDatabaseConnectionOnIteration, String[] expectedColumnTypes) throws SQLException {
int closeAndReopenDatabaseConnectionOnIteration, String[] expectedColumnTypes,
boolean anyOrder) throws SQLException {
deleteDb("commonTableExpressionQueries");
Connection conn = getConnection("commonTableExpressionQueries");
PreparedStatement prep;
ResultSet rs;
if (anyOrder) {
Arrays.sort(expectedRowData);
}
ArrayList<String> rowData = new ArrayList<>();
StringBuilder buf = new StringBuilder();
for (int queryRunTries = 1; queryRunTries <= maxRetries; queryRunTries++) {
Statement stat = conn.createStatement();
......@@ -65,17 +78,18 @@ public abstract class AbstractBaseForCommonTableExpressions extends TestBase {
expectedColumnTypes[columnIndex - 1], rs.getMetaData().getColumnTypeName(columnIndex));
}
int rowNdx = 0;
rowData.clear();
while (rs.next()) {
StringBuffer buf = new StringBuffer();
buf.setLength(0);
for (int columnIndex = 1; columnIndex <= rs.getMetaData().getColumnCount(); columnIndex++) {
buf.append("|" + rs.getString(columnIndex));
buf.append('|').append(rs.getString(columnIndex));
}
assertEquals(expectedRowData[rowNdx], buf.toString());
rowNdx++;
rowData.add(buf.toString());
}
assertEquals(expectedNumberOfRows, rowNdx);
if (anyOrder) {
Collections.sort(rowData);
}
assertEquals(expectedRowData, rowData.toArray(new String[0]));
rs.close();
prep.close();
......
......@@ -492,7 +492,7 @@ public class TestGeneralCommonTableQueries extends AbstractBaseForCommonTableExp
int expectedNumberOfRows = expectedRowData.length;
testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL,
withQuery, maxRetries - 1, expectedColumnTypes);
withQuery, maxRetries - 1, expectedColumnTypes, false);
}
......@@ -512,7 +512,7 @@ public class TestGeneralCommonTableQueries extends AbstractBaseForCommonTableExp
int expectedNumberOfRows = expectedRowData.length;
testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL,
withQuery, maxRetries - 1, expectedColumnTypes);
withQuery, maxRetries - 1, expectedColumnTypes, false);
}
......@@ -549,7 +549,7 @@ public class TestGeneralCommonTableQueries extends AbstractBaseForCommonTableExp
int expectedNumberOfRows = expectedRowData.length;
testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows,
setupSQL, withQuery, maxRetries - 1, expectedColumnTypes);
setupSQL, withQuery, maxRetries - 1, expectedColumnTypes, false);
} finally {
config = backupConfig;
}
......@@ -576,6 +576,6 @@ public class TestGeneralCommonTableQueries extends AbstractBaseForCommonTableExp
int expectedNumberOfRows = expectedRowData.length;
testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL,
withQuery, maxRetries - 1, expectedColumnTypes);
withQuery, maxRetries - 1, expectedColumnTypes, false);
}
}
......@@ -98,7 +98,7 @@ public class TestPersistentCommonTableExpressions extends AbstractBaseForCommonT
int expectedNumberOfRows = expectedRowData.length;
testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL,
withQuery, maxRetries - 1, expectedColumnTypes);
withQuery, maxRetries - 1, expectedColumnTypes, true);
}
......@@ -147,7 +147,7 @@ public class TestPersistentCommonTableExpressions extends AbstractBaseForCommonT
String[] expectedColumnTypes = new String[]{"INTEGER", "INTEGER", "INTEGER", "INTEGER"};
int expectedNumberOfRows = 11;
testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL,
withQuery, maxRetries - 1, expectedColumnTypes);
withQuery, maxRetries - 1, expectedColumnTypes, false);
}
private void testPersistentNonRecursiveTableInCreateView() throws Exception {
......@@ -186,7 +186,7 @@ public class TestPersistentCommonTableExpressions extends AbstractBaseForCommonT
String[] expectedColumnTypes = new String[]{"INTEGER", "INTEGER", "INTEGER", "INTEGER"};
int expectedNumberOfRows = 5;
testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL,
withQuery, maxRetries - 1, expectedColumnTypes);
withQuery, maxRetries - 1, expectedColumnTypes, false);
}
private void testPersistentNonRecursiveTableInCreateViewDropAllObjects() throws Exception {
......@@ -224,7 +224,7 @@ public class TestPersistentCommonTableExpressions extends AbstractBaseForCommonT
String[] expectedColumnTypes = new String[]{"INTEGER", "INTEGER", "INTEGER", "INTEGER"};
int expectedNumberOfRows = 5;
testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL,
withQuery, maxRetries - 1, expectedColumnTypes);
withQuery, maxRetries - 1, expectedColumnTypes, false);
}
private void testPersistentRecursiveTableInCreateViewDropAllObjects() throws Exception {
......@@ -271,6 +271,6 @@ public class TestPersistentCommonTableExpressions extends AbstractBaseForCommonT
String[] expectedColumnTypes = new String[]{"INTEGER", "INTEGER", "INTEGER", "INTEGER"};
int expectedNumberOfRows = 11;
testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL,
withQuery, maxRetries - 1, expectedColumnTypes);
withQuery, maxRetries - 1, expectedColumnTypes, false);
}
}
......@@ -6326,6 +6326,8 @@ SELECT 'abc', 'Papa Joe''s', CAST(-1 AS SMALLINT), CAST(2 AS BIGINT), CAST(0 AS
> abc Papa Joe's -1 2 0.0 0a0f 125 TRUE FALSE
> rows: 1
-- ' This apostrophe is here to fix syntax highlighting in the text editors.
SELECT CAST('abcd' AS VARCHAR(255)), CAST('ef_gh' AS VARCHAR(3));
> 'abcd' 'ef_'
> ------ -----
......@@ -6899,14 +6901,14 @@ INSERT INTO TEST VALUES(?, ?, ?);
};
> update count: 9
SELECT IFNULL(NAME, '') || ': ' || GROUP_CONCAT(VALUE ORDER BY NAME, VALUE DESC SEPARATOR ', ') FROM TEST GROUP BY NAME;
SELECT IFNULL(NAME, '') || ': ' || GROUP_CONCAT(VALUE ORDER BY NAME, VALUE DESC SEPARATOR ', ') FROM TEST GROUP BY NAME ORDER BY 1;
> (IFNULL(NAME, '') || ': ') || GROUP_CONCAT(VALUE ORDER BY NAME, VALUE DESC SEPARATOR ', ')
> ------------------------------------------------------------------------------------------
> : 3.10, -10.00
> Apples: 1.50, 1.20, 1.10
> Oranges: 2.05, 1.80
> Bananas: 2.50
> Cherries: 5.10
> : 3.10, -10.00
> Oranges: 2.05, 1.80
> rows (ordered): 5
SELECT GROUP_CONCAT(ID ORDER BY ID) FROM TEST;
......
......@@ -61,6 +61,7 @@ public class TestValue extends TestBase {
testCastTrim();
testValueResultSet();
testDataType();
testArray();
testUUID();
testDouble(false);
testDouble(true);
......@@ -330,6 +331,27 @@ public class TestValue extends TestBase {
assertEquals(123123123, ts.getNanos());
}
private void testArray() {
ValueArray src = ValueArray.get(String.class,
new Value[] {ValueString.get("1"), ValueString.get("22"), ValueString.get("333")});
assertEquals(6, src.getPrecision());
assertSame(src, src.convertPrecision(5, false));
assertSame(src, src.convertPrecision(6, true));
ValueArray exp = ValueArray.get(String.class,
new Value[] {ValueString.get("1"), ValueString.get("22"), ValueString.get("33")});
Value got = src.convertPrecision(5, true);
assertEquals(exp, got);
assertEquals(String.class, ((ValueArray) got).getComponentType());
exp = ValueArray.get(String.class, new Value[] {ValueString.get("1"), ValueString.get("22")});
got = src.convertPrecision(3, true);
assertEquals(exp, got);
assertEquals(String.class, ((ValueArray) got).getComponentType());
exp = ValueArray.get(String.class, new Value[0]);
got = src.convertPrecision(0, true);
assertEquals(exp, got);
assertEquals(String.class, ((ValueArray) got).getComponentType());
}
private void testUUID() {
long maxHigh = 0, maxLow = 0, minHigh = -1L, minLow = -1L;
for (int i = 0; i < 100; i++) {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论