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

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

...@@ -5320,7 +5320,7 @@ public class Parser { ...@@ -5320,7 +5320,7 @@ public class Parser {
if (isPersistent) { if (isPersistent) {
db.addSchemaObject(targetSession, view); db.addSchemaObject(targetSession, view);
view.lock(targetSession, true, true); view.lock(targetSession, true, true);
targetSession.getDatabase().removeSchemaObject(targetSession, view); db.removeSchemaObject(targetSession, view);
} else { } else {
session.removeLocalTempTable(view); session.removeLocalTempTable(view);
} }
...@@ -5330,7 +5330,7 @@ public class Parser { ...@@ -5330,7 +5330,7 @@ public class Parser {
isPersistent); isPersistent);
} }
// both removeSchemaObject and removeLocalTempTable hold meta locks // both removeSchemaObject and removeLocalTempTable hold meta locks
targetSession.getDatabase().unlockMeta(targetSession); db.unlockMeta(targetSession);
} }
view.setTableExpression(true); view.setTableExpression(true);
view.setTemporary(!isPersistent); view.setTemporary(!isPersistent);
...@@ -5929,8 +5929,8 @@ public class Parser { ...@@ -5929,8 +5929,8 @@ public class Parser {
boolean isDualTable(String tableName) { boolean isDualTable(String tableName) {
return ((schemaName == null || equalsToken(schemaName, "SYS")) && equalsToken("DUAL", tableName)) return ((schemaName == null || equalsToken(schemaName, "SYS")) && equalsToken("DUAL", tableName))
|| (database.getMode().sysDummy1 && (schemaName == null || equalsToken(schemaName, "SYSIBM"))) || (database.getMode().sysDummy1 && (schemaName == null || equalsToken(schemaName, "SYSIBM"))
&& equalsToken("SYSDUMMY1", tableName); && equalsToken("SYSDUMMY1", tableName));
} }
private Table readTableOrView() { private Table readTableOrView() {
......
...@@ -5,6 +5,11 @@ ...@@ -5,6 +5,11 @@
*/ */
package org.h2.command.dml; 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.ErrorCode;
import org.h2.api.Trigger; import org.h2.api.Trigger;
import org.h2.command.CommandInterface; import org.h2.command.CommandInterface;
...@@ -12,23 +17,39 @@ import org.h2.engine.Constants; ...@@ -12,23 +17,39 @@ import org.h2.engine.Constants;
import org.h2.engine.Database; import org.h2.engine.Database;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.engine.SysProperties; 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.Cursor;
import org.h2.index.Index; import org.h2.index.Index;
import org.h2.index.IndexType; import org.h2.index.IndexType;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.result.*; import org.h2.result.LazyResult;
import org.h2.table.*; import org.h2.result.LocalResult;
import org.h2.util.*; 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.Value;
import org.h2.value.ValueArray; import org.h2.value.ValueArray;
import org.h2.value.ValueNull; 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. * This class represents a simple SELECT statement.
* *
...@@ -81,9 +102,21 @@ public class Select extends Query { ...@@ -81,9 +102,21 @@ public class Select extends Query {
boolean[] groupByExpression; 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 int havingIndex;
private boolean isGroupQuery, isGroupSortedQuery; private boolean isGroupQuery, isGroupSortedQuery;
...@@ -151,8 +184,45 @@ public class Select extends Query { ...@@ -151,8 +184,45 @@ public class Select extends Query {
return group; 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() { public int getCurrentGroupRowId() {
...@@ -313,20 +383,21 @@ public class Select extends Query { ...@@ -313,20 +383,21 @@ public class Select extends Query {
} }
private void queryGroup(int columnCount, LocalResult result) { private void queryGroup(int columnCount, LocalResult result) {
ValueHashMap<HashMap<Expression, Object>> groups = groupByData = new HashMap<>();
ValueHashMap.newInstance(); currentGroupByExprData = null;
currentGroupsKey = null;
exprToIndexInGroupByData.clear();
try {
int rowNumber = 0; int rowNumber = 0;
setCurrentRowNumber(0); setCurrentRowNumber(0);
currentGroup = null;
ValueArray defaultGroup = ValueArray.get(new Value[0]); ValueArray defaultGroup = ValueArray.get(new Value[0]);
int sampleSize = getSampleSizeValue(session); int sampleSize = getSampleSizeValue(session);
while (topTableFilter.next()) { while (topTableFilter.next()) {
setCurrentRowNumber(rowNumber + 1); setCurrentRowNumber(rowNumber + 1);
if (isConditionMet()) { if (isConditionMet()) {
Value key;
rowNumber++; rowNumber++;
if (groupIndex == null) { if (groupIndex == null) {
key = defaultGroup; currentGroupsKey = defaultGroup;
} else { } else {
Value[] keyValues = new Value[groupIndex.length]; Value[] keyValues = new Value[groupIndex.length];
// update group // update group
...@@ -335,14 +406,14 @@ public class Select extends Query { ...@@ -335,14 +406,14 @@ public class Select extends Query {
Expression expr = expressions.get(idx); Expression expr = expressions.get(idx);
keyValues[i] = expr.getValue(session); keyValues[i] = expr.getValue(session);
} }
key = ValueArray.get(keyValues); currentGroupsKey = ValueArray.get(keyValues);
} }
HashMap<Expression, Object> values = groups.get(key); Object[] values = groupByData.get(currentGroupsKey);
if (values == null) { if (values == null) {
values = new HashMap<>(); values = new Object[Math.max(exprToIndexInGroupByData.size(), expressions.size())];
groups.put(key, values); groupByData.put(currentGroupsKey, values);
} }
currentGroup = values; currentGroupByExprData = values;
currentGroupRowId++; currentGroupRowId++;
for (int i = 0; i < columnCount; i++) { for (int i = 0; i < columnCount; i++) {
if (groupByExpression == null || !groupByExpression[i]) { if (groupByExpression == null || !groupByExpression[i]) {
...@@ -355,14 +426,14 @@ public class Select extends Query { ...@@ -355,14 +426,14 @@ public class Select extends Query {
} }
} }
} }
if (groupIndex == null && groups.size() == 0) { if (groupIndex == null && groupByData.size() == 0) {
groups.put(defaultGroup, new HashMap<Expression, Object>()); groupByData.put(defaultGroup,
new Object[Math.max(exprToIndexInGroupByData.size(), expressions.size())]);
} }
ArrayList<Value> keys = groups.keys(); for (Map.Entry<Value, Object[]> entry : groupByData.entrySet()) {
for (Value v : keys) { currentGroupsKey = (ValueArray) entry.getKey();
ValueArray key = (ValueArray) v; currentGroupByExprData = entry.getValue();
currentGroup = groups.get(key); Value[] keyValues = currentGroupsKey.getList();
Value[] keyValues = key.getList();
Value[] row = new Value[columnCount]; Value[] row = new Value[columnCount];
for (int j = 0; groupIndex != null && j < groupIndex.length; j++) { for (int j = 0; groupIndex != null && j < groupIndex.length; j++) {
row[groupIndex[j]] = keyValues[j]; row[groupIndex[j]] = keyValues[j];
...@@ -380,6 +451,12 @@ public class Select extends Query { ...@@ -380,6 +451,12 @@ public class Select extends Query {
row = keepOnlyDistinct(row, columnCount); row = keepOnlyDistinct(row, columnCount);
result.addRow(row); result.addRow(row);
} }
} finally {
groupByData = null;
currentGroupsKey = null;
currentGroupByExprData = null;
exprToIndexInGroupByData.clear();
}
} }
/** /**
...@@ -1475,13 +1552,15 @@ public class Select extends Query { ...@@ -1475,13 +1552,15 @@ public class Select extends Query {
LazyResultGroupSorted(Expression[] expressions, int columnCount) { LazyResultGroupSorted(Expression[] expressions, int columnCount) {
super(expressions, columnCount); super(expressions, columnCount);
currentGroup = null; currentGroupByExprData = null;
currentGroupsKey = null;
} }
@Override @Override
public void reset() { public void reset() {
super.reset(); super.reset();
currentGroup = null; currentGroupByExprData = null;
currentGroupsKey = null;
} }
@Override @Override
...@@ -1501,11 +1580,11 @@ public class Select extends Query { ...@@ -1501,11 +1580,11 @@ public class Select extends Query {
Value[] row = null; Value[] row = null;
if (previousKeyValues == null) { if (previousKeyValues == null) {
previousKeyValues = keyValues; previousKeyValues = keyValues;
currentGroup = new HashMap<>(); currentGroupByExprData = new Object[Math.max(exprToIndexInGroupByData.size(), expressions.size())];
} else if (!Arrays.equals(previousKeyValues, keyValues)) { } else if (!Arrays.equals(previousKeyValues, keyValues)) {
row = createGroupSortedRow(previousKeyValues, columnCount); row = createGroupSortedRow(previousKeyValues, columnCount);
previousKeyValues = keyValues; previousKeyValues = keyValues;
currentGroup = new HashMap<>(); currentGroupByExprData = new Object[Math.max(exprToIndexInGroupByData.size(), expressions.size())];
} }
currentGroupRowId++; currentGroupRowId++;
......
...@@ -283,8 +283,7 @@ public class Aggregate extends Expression { ...@@ -283,8 +283,7 @@ public class Aggregate extends Expression {
// if (on != null) { // if (on != null) {
// on.updateAggregate(); // on.updateAggregate();
// } // }
HashMap<Expression, Object> group = select.getCurrentGroup(); if (!select.isCurrentGroup()) {
if (group == null) {
// this is a different level (the enclosing query) // this is a different level (the enclosing query)
return; return;
} }
...@@ -296,10 +295,10 @@ public class Aggregate extends Expression { ...@@ -296,10 +295,10 @@ public class Aggregate extends Expression {
} }
lastGroupRowId = groupRowId; lastGroupRowId = groupRowId;
AggregateData data = (AggregateData) group.get(this); AggregateData data = (AggregateData) select.getCurrentGroupExprData(this);
if (data == null) { if (data == null) {
data = AggregateData.create(type); data = AggregateData.create(type);
group.put(this, data); select.setCurrentGroupExprData(this, data);
} }
Value v = on == null ? null : on.getValue(session); Value v = on == null ? null : on.getValue(session);
if (type == AggregateType.GROUP_CONCAT) { if (type == AggregateType.GROUP_CONCAT) {
...@@ -372,13 +371,13 @@ public class Aggregate extends Expression { ...@@ -372,13 +371,13 @@ public class Aggregate extends Expression {
DbException.throwInternalError("type=" + type); DbException.throwInternalError("type=" + type);
} }
} }
HashMap<Expression, Object> group = select.getCurrentGroup(); if (!select.isCurrentGroup()) {
if (group == null) {
throw DbException.get(ErrorCode.INVALID_USE_OF_AGGREGATE_FUNCTION_1, getSQL()); 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) { if (data == null) {
data = AggregateData.create(type); data = AggregateData.create(type);
select.setCurrentGroupExprData(this, data);
} }
if (type == AggregateType.GROUP_CONCAT) { if (type == AggregateType.GROUP_CONCAT) {
Value[] array = ((AggregateDataCollecting) data).getArray(); Value[] array = ((AggregateDataCollecting) data).getArray();
......
...@@ -7,6 +7,7 @@ package org.h2.expression; ...@@ -7,6 +7,7 @@ package org.h2.expression;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.Map;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.engine.Database; import org.h2.engine.Database;
import org.h2.util.ValueHashMap; import org.h2.util.ValueHashMap;
...@@ -47,9 +48,9 @@ class AggregateDataHistogram extends AggregateData { ...@@ -47,9 +48,9 @@ class AggregateDataHistogram extends AggregateData {
} }
ValueArray[] values = new ValueArray[distinctValues.size()]; ValueArray[] values = new ValueArray[distinctValues.size()];
int i = 0; int i = 0;
for (Value dv : distinctValues.keys()) { for (Map.Entry<Value,AggregateDataHistogram> entry : distinctValues.entries()) {
AggregateDataHistogram d = distinctValues.get(dv); AggregateDataHistogram d = entry.getValue();
values[i] = ValueArray.get(new Value[] { dv, ValueLong.get(d.count) }); values[i] = ValueArray.get(new Value[] { entry.getKey(), ValueLong.get(d.count) });
i++; i++;
} }
final CompareMode compareMode = database.getCompareMode(); final CompareMode compareMode = database.getCompareMode();
......
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
*/ */
package org.h2.expression; package org.h2.expression;
import java.util.HashMap;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.command.Parser; import org.h2.command.Parser;
import org.h2.command.dml.Select; import org.h2.command.dml.Select;
...@@ -159,14 +158,13 @@ public class ExpressionColumn extends Expression { ...@@ -159,14 +158,13 @@ public class ExpressionColumn extends Expression {
if (select == null) { if (select == null) {
throw DbException.get(ErrorCode.MUST_GROUP_BY_COLUMN_1, getSQL()); throw DbException.get(ErrorCode.MUST_GROUP_BY_COLUMN_1, getSQL());
} }
HashMap<Expression, Object> values = select.getCurrentGroup(); if (!select.isCurrentGroup()) {
if (values == null) {
// this is a different level (the enclosing query) // this is a different level (the enclosing query)
return; return;
} }
Value v = (Value) values.get(this); Value v = (Value) select.getCurrentGroupExprData(this);
if (v == null) { if (v == null) {
values.put(this, now); select.setCurrentGroupExprData(this, now);
} else { } else {
if (!database.areEqual(now, v)) { if (!database.areEqual(now, v)) {
throw DbException.get(ErrorCode.MUST_GROUP_BY_COLUMN_1, getSQL()); throw DbException.get(ErrorCode.MUST_GROUP_BY_COLUMN_1, getSQL());
...@@ -178,9 +176,8 @@ public class ExpressionColumn extends Expression { ...@@ -178,9 +176,8 @@ public class ExpressionColumn extends Expression {
public Value getValue(Session session) { public Value getValue(Session session) {
Select select = columnResolver.getSelect(); Select select = columnResolver.getSelect();
if (select != null) { if (select != null) {
HashMap<Expression, Object> values = select.getCurrentGroup(); if (select.isCurrentGroup()) {
if (values != null) { Value v = (Value) select.getCurrentGroupExprData(this);
Value v = (Value) values.get(this);
if (v != null) { if (v != null) {
return v; return v;
} }
......
...@@ -7,7 +7,6 @@ package org.h2.expression; ...@@ -7,7 +7,6 @@ package org.h2.expression;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.HashMap;
import org.h2.api.Aggregate; import org.h2.api.Aggregate;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.command.Parser; import org.h2.command.Parser;
...@@ -167,15 +166,14 @@ public class JavaAggregate extends Expression { ...@@ -167,15 +166,14 @@ public class JavaAggregate extends Expression {
@Override @Override
public Value getValue(Session session) { public Value getValue(Session session) {
HashMap<Expression, Object> group = select.getCurrentGroup(); if (!select.isCurrentGroup()) {
if (group == null) {
throw DbException.get(ErrorCode.INVALID_USE_OF_AGGREGATE_FUNCTION_1, getSQL()); throw DbException.get(ErrorCode.INVALID_USE_OF_AGGREGATE_FUNCTION_1, getSQL());
} }
try { try {
Aggregate agg; Aggregate agg;
if (distinct) { if (distinct) {
agg = getInstance(); agg = getInstance();
AggregateDataCollecting data = (AggregateDataCollecting) group.get(this); AggregateDataCollecting data = (AggregateDataCollecting) select.getCurrentGroupExprData(this);
if (data != null) { if (data != null) {
for (Value value : data.values) { for (Value value : data.values) {
if (args.length == 1) { if (args.length == 1) {
...@@ -191,7 +189,7 @@ public class JavaAggregate extends Expression { ...@@ -191,7 +189,7 @@ public class JavaAggregate extends Expression {
} }
} }
} else { } else {
agg = (Aggregate) group.get(this); agg = (Aggregate) select.getCurrentGroupExprData(this);
if (agg == null) { if (agg == null) {
agg = getInstance(); agg = getInstance();
} }
...@@ -208,8 +206,7 @@ public class JavaAggregate extends Expression { ...@@ -208,8 +206,7 @@ public class JavaAggregate extends Expression {
@Override @Override
public void updateAggregate(Session session) { public void updateAggregate(Session session) {
HashMap<Expression, Object> group = select.getCurrentGroup(); if (!select.isCurrentGroup()) {
if (group == null) {
// this is a different level (the enclosing query) // this is a different level (the enclosing query)
return; return;
} }
...@@ -229,10 +226,10 @@ public class JavaAggregate extends Expression { ...@@ -229,10 +226,10 @@ public class JavaAggregate extends Expression {
try { try {
if (distinct) { if (distinct) {
AggregateDataCollecting data = (AggregateDataCollecting) group.get(this); AggregateDataCollecting data = (AggregateDataCollecting) select.getCurrentGroupExprData(this);
if (data == null) { if (data == null) {
data = new AggregateDataCollecting(); data = new AggregateDataCollecting();
group.put(this, data); select.setCurrentGroupExprData(this, data);
} }
Value[] argValues = new Value[args.length]; Value[] argValues = new Value[args.length];
Value arg = null; Value arg = null;
...@@ -243,10 +240,10 @@ public class JavaAggregate extends Expression { ...@@ -243,10 +240,10 @@ public class JavaAggregate extends Expression {
} }
data.add(session.getDatabase(), dataType, true, args.length == 1 ? arg : ValueArray.get(argValues)); data.add(session.getDatabase(), dataType, true, args.length == 1 ? arg : ValueArray.get(argValues));
} else { } else {
Aggregate agg = (Aggregate) group.get(this); Aggregate agg = (Aggregate) select.getCurrentGroupExprData(this);
if (agg == null) { if (agg == null) {
agg = getInstance(); agg = getInstance();
group.put(this, agg); select.setCurrentGroupExprData(this, agg);
} }
Object[] argValues = new Object[args.length]; Object[] argValues = new Object[args.length];
Object arg = null; Object arg = null;
......
...@@ -1026,39 +1026,6 @@ public final class DataUtils { ...@@ -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. * Get the configuration parameter value, or default.
* *
......
...@@ -673,7 +673,7 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -673,7 +673,7 @@ public class MVMap<K, V> extends AbstractMap<K, V>
@Override @Override
public Entry<K, V> next() { public Entry<K, V> next() {
K k = cursor.next(); K k = cursor.next();
return new DataUtils.MapEntry<>(k, cursor.getValue()); return new SimpleImmutableEntry<>(k, cursor.getValue());
} }
@Override @Override
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
*/ */
package org.h2.mvstore.db; package org.h2.mvstore.db;
import java.util.AbstractMap;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
...@@ -18,7 +19,6 @@ import org.h2.index.BaseIndex; ...@@ -18,7 +19,6 @@ import org.h2.index.BaseIndex;
import org.h2.index.Cursor; import org.h2.index.Cursor;
import org.h2.index.IndexType; import org.h2.index.IndexType;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.mvstore.DataUtils;
import org.h2.mvstore.tx.Transaction; import org.h2.mvstore.tx.Transaction;
import org.h2.mvstore.tx.TransactionMap; import org.h2.mvstore.tx.TransactionMap;
import org.h2.result.Row; import org.h2.result.Row;
...@@ -261,7 +261,7 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -261,7 +261,7 @@ public class MVPrimaryIndex extends BaseIndex {
Collections.<Entry<Value, Value>> emptyList().iterator()); Collections.<Entry<Value, Value>> emptyList().iterator());
} }
Value value = map.get(v); 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); List<Entry<Value, Value>> list = Collections.singletonList(e);
MVStoreCursor c = new MVStoreCursor(session, list.iterator()); MVStoreCursor c = new MVStoreCursor(session, list.iterator());
c.next(); c.next();
......
...@@ -9,6 +9,8 @@ import org.h2.mvstore.Cursor; ...@@ -9,6 +9,8 @@ import org.h2.mvstore.Cursor;
import org.h2.mvstore.DataUtils; import org.h2.mvstore.DataUtils;
import org.h2.mvstore.MVMap; import org.h2.mvstore.MVMap;
import org.h2.mvstore.type.DataType; import org.h2.mvstore.type.DataType;
import java.util.AbstractMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
...@@ -671,7 +673,7 @@ public class TransactionMap<K, V> { ...@@ -671,7 +673,7 @@ public class TransactionMap<K, V> {
if (data != null && data.value != null) { if (data != null && data.value != null) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final V value = (V) data.value; final V value = (V) data.value;
current = new DataUtils.MapEntry<>(key, value); current = new AbstractMap.SimpleImmutableEntry<>(key, value);
currentKey = key; currentKey = key;
return; return;
} }
......
...@@ -5,20 +5,38 @@ ...@@ -5,20 +5,38 @@
*/ */
package org.h2.util; package org.h2.util;
import java.util.AbstractMap;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueNull; import org.h2.value.ValueNull;
/** /**
* This hash map supports keys of type Value. * 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 * @param <V> the value type
*/ */
public class ValueHashMap<V> extends HashBase { public class ValueHashMap<V> extends HashBase {
private Value[] keys; Value[] keys;
private V[] values; V[] values;
/** /**
* Create a new value hash map. * Create a new value hash map.
...@@ -54,7 +72,12 @@ public class ValueHashMap<V> extends HashBase { ...@@ -54,7 +72,12 @@ public class ValueHashMap<V> extends HashBase {
} }
private int getIndex(Value key) { 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;
} }
/** /**
...@@ -170,6 +193,51 @@ public class ValueHashMap<V> extends HashBase { ...@@ -170,6 +193,51 @@ public class ValueHashMap<V> extends HashBase {
return list; 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. * Get the list of values.
* *
......
...@@ -7,13 +7,12 @@ package org.h2.value; ...@@ -7,13 +7,12 @@ package org.h2.value;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.util.ArrayList; import java.util.Arrays;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.engine.SysProperties; import org.h2.engine.SysProperties;
import org.h2.util.MathUtils; import org.h2.util.MathUtils;
import org.h2.util.StatementBuilder; import org.h2.util.StatementBuilder;
import org.h2.util.Utils;
/** /**
* Implementation of the ARRAY data type. * Implementation of the ARRAY data type.
...@@ -29,10 +28,6 @@ public class ValueArray extends Value { ...@@ -29,10 +28,6 @@ public class ValueArray extends Value {
this.values = list; this.values = list;
} }
private ValueArray(Value[] list) {
this(Object.class, list);
}
/** /**
* Get or create a array value for the given value array. * Get or create a array value for the given value array.
* Do not clone the data. * Do not clone the data.
...@@ -41,7 +36,7 @@ public class ValueArray extends Value { ...@@ -41,7 +36,7 @@ public class ValueArray extends Value {
* @return the value * @return the value
*/ */
public static ValueArray get(Value[] list) { 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 { ...@@ -211,18 +206,28 @@ public class ValueArray extends Value {
if (!force) { if (!force) {
return this; return this;
} }
ArrayList<Value> list = Utils.newSmallArrayList(); int length = values.length;
for (Value v : values) { Value[] newValues = new Value[length];
v = v.convertPrecision(precision, true); 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 // empty byte arrays or strings have precision 0
// they count as precision 1 here // they count as precision 1 here
precision -= Math.max(1, v.getPrecision()); precision -= Math.max(1, v.getPrecision());
if (precision < 0) { if (precision < 0) {
break; 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 { ...@@ -133,8 +133,12 @@ public class ValueDouble extends Value {
@Override @Override
public int hashCode() { 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 @Override
......
...@@ -34,6 +34,7 @@ public class ValueFloat extends Value { ...@@ -34,6 +34,7 @@ public class ValueFloat extends Value {
private static final ValueFloat ZERO = new ValueFloat(0.0F); private static final ValueFloat ZERO = new ValueFloat(0.0F);
private static final ValueFloat ONE = new ValueFloat(1.0F); private static final ValueFloat ONE = new ValueFloat(1.0F);
private static final ValueFloat NAN = new ValueFloat(Float.NaN);
private final float value; private final float value;
...@@ -88,7 +89,7 @@ public class ValueFloat extends Value { ...@@ -88,7 +89,7 @@ public class ValueFloat extends Value {
return "POWER(0, -1)"; return "POWER(0, -1)";
} else if (value == Float.NEGATIVE_INFINITY) { } else if (value == Float.NEGATIVE_INFINITY) {
return "(-POWER(0, -1))"; return "(-POWER(0, -1))";
} else if (Double.isNaN(value)) { } else if (Float.isNaN(value)) {
// NaN // NaN
return "SQRT(-1)"; return "SQRT(-1)";
} }
...@@ -133,8 +134,11 @@ public class ValueFloat extends Value { ...@@ -133,8 +134,11 @@ public class ValueFloat extends Value {
@Override @Override
public int hashCode() { 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 @Override
...@@ -160,6 +164,8 @@ public class ValueFloat extends Value { ...@@ -160,6 +164,8 @@ public class ValueFloat extends Value {
} else if (d == 0.0F) { } else if (d == 0.0F) {
// -0.0 == 0.0, and we want to return 0.0 for both // -0.0 == 0.0, and we want to return 0.0 for both
return ZERO; return ZERO;
} else if (Float.isNaN(d)) {
return NAN;
} }
return (ValueFloat) Value.cache(new ValueFloat(d)); return (ValueFloat) Value.cache(new ValueFloat(d));
} }
......
...@@ -851,6 +851,19 @@ public abstract class TestBase { ...@@ -851,6 +851,19 @@ public abstract class TestBase {
assertFalse(message, rs1.next()); 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 * Check if the first value is larger or equal than the second value, and if
* not throw an exception. * not throw an exception.
......
...@@ -10,6 +10,10 @@ import java.sql.PreparedStatement; ...@@ -10,6 +10,10 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import org.h2.test.TestBase; import org.h2.test.TestBase;
/** /**
...@@ -29,16 +33,25 @@ public abstract class AbstractBaseForCommonTableExpressions extends TestBase { ...@@ -29,16 +33,25 @@ public abstract class AbstractBaseForCommonTableExpressions extends TestBase {
* @param closeAndReopenDatabaseConnectionOnIteration whether the connection * @param closeAndReopenDatabaseConnectionOnIteration whether the connection
* should be re-opened each time * should be re-opened each time
* @param expectedColumnTypes the expected datatypes of the result * @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, void testRepeatedQueryWithSetup(int maxRetries, String[] expectedRowData, String[] expectedColumnNames,
int expectedNumberOfRows, String setupSQL, String withQuery, int expectedNumberOfRows, String setupSQL, String withQuery,
int closeAndReopenDatabaseConnectionOnIteration, String[] expectedColumnTypes) throws SQLException { int closeAndReopenDatabaseConnectionOnIteration, String[] expectedColumnTypes,
boolean anyOrder) throws SQLException {
deleteDb("commonTableExpressionQueries"); deleteDb("commonTableExpressionQueries");
Connection conn = getConnection("commonTableExpressionQueries"); Connection conn = getConnection("commonTableExpressionQueries");
PreparedStatement prep; PreparedStatement prep;
ResultSet rs; ResultSet rs;
if (anyOrder) {
Arrays.sort(expectedRowData);
}
ArrayList<String> rowData = new ArrayList<>();
StringBuilder buf = new StringBuilder();
for (int queryRunTries = 1; queryRunTries <= maxRetries; queryRunTries++) { for (int queryRunTries = 1; queryRunTries <= maxRetries; queryRunTries++) {
Statement stat = conn.createStatement(); Statement stat = conn.createStatement();
...@@ -65,17 +78,18 @@ public abstract class AbstractBaseForCommonTableExpressions extends TestBase { ...@@ -65,17 +78,18 @@ public abstract class AbstractBaseForCommonTableExpressions extends TestBase {
expectedColumnTypes[columnIndex - 1], rs.getMetaData().getColumnTypeName(columnIndex)); expectedColumnTypes[columnIndex - 1], rs.getMetaData().getColumnTypeName(columnIndex));
} }
int rowNdx = 0; rowData.clear();
while (rs.next()) { while (rs.next()) {
StringBuffer buf = new StringBuffer(); buf.setLength(0);
for (int columnIndex = 1; columnIndex <= rs.getMetaData().getColumnCount(); columnIndex++) { 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()); rowData.add(buf.toString());
rowNdx++;
} }
if (anyOrder) {
assertEquals(expectedNumberOfRows, rowNdx); Collections.sort(rowData);
}
assertEquals(expectedRowData, rowData.toArray(new String[0]));
rs.close(); rs.close();
prep.close(); prep.close();
......
...@@ -492,7 +492,7 @@ public class TestGeneralCommonTableQueries extends AbstractBaseForCommonTableExp ...@@ -492,7 +492,7 @@ public class TestGeneralCommonTableQueries extends AbstractBaseForCommonTableExp
int expectedNumberOfRows = expectedRowData.length; int expectedNumberOfRows = expectedRowData.length;
testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL, testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL,
withQuery, maxRetries - 1, expectedColumnTypes); withQuery, maxRetries - 1, expectedColumnTypes, false);
} }
...@@ -512,7 +512,7 @@ public class TestGeneralCommonTableQueries extends AbstractBaseForCommonTableExp ...@@ -512,7 +512,7 @@ public class TestGeneralCommonTableQueries extends AbstractBaseForCommonTableExp
int expectedNumberOfRows = expectedRowData.length; int expectedNumberOfRows = expectedRowData.length;
testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL, testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL,
withQuery, maxRetries - 1, expectedColumnTypes); withQuery, maxRetries - 1, expectedColumnTypes, false);
} }
...@@ -549,7 +549,7 @@ public class TestGeneralCommonTableQueries extends AbstractBaseForCommonTableExp ...@@ -549,7 +549,7 @@ public class TestGeneralCommonTableQueries extends AbstractBaseForCommonTableExp
int expectedNumberOfRows = expectedRowData.length; int expectedNumberOfRows = expectedRowData.length;
testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows,
setupSQL, withQuery, maxRetries - 1, expectedColumnTypes); setupSQL, withQuery, maxRetries - 1, expectedColumnTypes, false);
} finally { } finally {
config = backupConfig; config = backupConfig;
} }
...@@ -576,6 +576,6 @@ public class TestGeneralCommonTableQueries extends AbstractBaseForCommonTableExp ...@@ -576,6 +576,6 @@ public class TestGeneralCommonTableQueries extends AbstractBaseForCommonTableExp
int expectedNumberOfRows = expectedRowData.length; int expectedNumberOfRows = expectedRowData.length;
testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL, testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL,
withQuery, maxRetries - 1, expectedColumnTypes); withQuery, maxRetries - 1, expectedColumnTypes, false);
} }
} }
...@@ -98,7 +98,7 @@ public class TestPersistentCommonTableExpressions extends AbstractBaseForCommonT ...@@ -98,7 +98,7 @@ public class TestPersistentCommonTableExpressions extends AbstractBaseForCommonT
int expectedNumberOfRows = expectedRowData.length; int expectedNumberOfRows = expectedRowData.length;
testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL, testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL,
withQuery, maxRetries - 1, expectedColumnTypes); withQuery, maxRetries - 1, expectedColumnTypes, true);
} }
...@@ -147,7 +147,7 @@ public class TestPersistentCommonTableExpressions extends AbstractBaseForCommonT ...@@ -147,7 +147,7 @@ public class TestPersistentCommonTableExpressions extends AbstractBaseForCommonT
String[] expectedColumnTypes = new String[]{"INTEGER", "INTEGER", "INTEGER", "INTEGER"}; String[] expectedColumnTypes = new String[]{"INTEGER", "INTEGER", "INTEGER", "INTEGER"};
int expectedNumberOfRows = 11; int expectedNumberOfRows = 11;
testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL, testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL,
withQuery, maxRetries - 1, expectedColumnTypes); withQuery, maxRetries - 1, expectedColumnTypes, false);
} }
private void testPersistentNonRecursiveTableInCreateView() throws Exception { private void testPersistentNonRecursiveTableInCreateView() throws Exception {
...@@ -186,7 +186,7 @@ public class TestPersistentCommonTableExpressions extends AbstractBaseForCommonT ...@@ -186,7 +186,7 @@ public class TestPersistentCommonTableExpressions extends AbstractBaseForCommonT
String[] expectedColumnTypes = new String[]{"INTEGER", "INTEGER", "INTEGER", "INTEGER"}; String[] expectedColumnTypes = new String[]{"INTEGER", "INTEGER", "INTEGER", "INTEGER"};
int expectedNumberOfRows = 5; int expectedNumberOfRows = 5;
testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL, testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL,
withQuery, maxRetries - 1, expectedColumnTypes); withQuery, maxRetries - 1, expectedColumnTypes, false);
} }
private void testPersistentNonRecursiveTableInCreateViewDropAllObjects() throws Exception { private void testPersistentNonRecursiveTableInCreateViewDropAllObjects() throws Exception {
...@@ -224,7 +224,7 @@ public class TestPersistentCommonTableExpressions extends AbstractBaseForCommonT ...@@ -224,7 +224,7 @@ public class TestPersistentCommonTableExpressions extends AbstractBaseForCommonT
String[] expectedColumnTypes = new String[]{"INTEGER", "INTEGER", "INTEGER", "INTEGER"}; String[] expectedColumnTypes = new String[]{"INTEGER", "INTEGER", "INTEGER", "INTEGER"};
int expectedNumberOfRows = 5; int expectedNumberOfRows = 5;
testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL, testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL,
withQuery, maxRetries - 1, expectedColumnTypes); withQuery, maxRetries - 1, expectedColumnTypes, false);
} }
private void testPersistentRecursiveTableInCreateViewDropAllObjects() throws Exception { private void testPersistentRecursiveTableInCreateViewDropAllObjects() throws Exception {
...@@ -271,6 +271,6 @@ public class TestPersistentCommonTableExpressions extends AbstractBaseForCommonT ...@@ -271,6 +271,6 @@ public class TestPersistentCommonTableExpressions extends AbstractBaseForCommonT
String[] expectedColumnTypes = new String[]{"INTEGER", "INTEGER", "INTEGER", "INTEGER"}; String[] expectedColumnTypes = new String[]{"INTEGER", "INTEGER", "INTEGER", "INTEGER"};
int expectedNumberOfRows = 11; int expectedNumberOfRows = 11;
testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL, 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 ...@@ -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 > abc Papa Joe's -1 2 0.0 0a0f 125 TRUE FALSE
> rows: 1 > 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)); SELECT CAST('abcd' AS VARCHAR(255)), CAST('ef_gh' AS VARCHAR(3));
> 'abcd' 'ef_' > 'abcd' 'ef_'
> ------ ----- > ------ -----
...@@ -6899,14 +6901,14 @@ INSERT INTO TEST VALUES(?, ?, ?); ...@@ -6899,14 +6901,14 @@ INSERT INTO TEST VALUES(?, ?, ?);
}; };
> update count: 9 > 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 ', ') > (IFNULL(NAME, '') || ': ') || GROUP_CONCAT(VALUE ORDER BY NAME, VALUE DESC SEPARATOR ', ')
> ------------------------------------------------------------------------------------------ > ------------------------------------------------------------------------------------------
> : 3.10, -10.00
> Apples: 1.50, 1.20, 1.10 > Apples: 1.50, 1.20, 1.10
> Oranges: 2.05, 1.80
> Bananas: 2.50 > Bananas: 2.50
> Cherries: 5.10 > Cherries: 5.10
> : 3.10, -10.00 > Oranges: 2.05, 1.80
> rows (ordered): 5 > rows (ordered): 5
SELECT GROUP_CONCAT(ID ORDER BY ID) FROM TEST; SELECT GROUP_CONCAT(ID ORDER BY ID) FROM TEST;
......
...@@ -61,6 +61,7 @@ public class TestValue extends TestBase { ...@@ -61,6 +61,7 @@ public class TestValue extends TestBase {
testCastTrim(); testCastTrim();
testValueResultSet(); testValueResultSet();
testDataType(); testDataType();
testArray();
testUUID(); testUUID();
testDouble(false); testDouble(false);
testDouble(true); testDouble(true);
...@@ -330,6 +331,27 @@ public class TestValue extends TestBase { ...@@ -330,6 +331,27 @@ public class TestValue extends TestBase {
assertEquals(123123123, ts.getNanos()); 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() { private void testUUID() {
long maxHigh = 0, maxLow = 0, minHigh = -1L, minLow = -1L; long maxHigh = 0, maxLow = 0, minHigh = -1L, minLow = -1L;
for (int i = 0; i < 100; i++) { for (int i = 0; i < 100; i++) {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论