Unverified 提交 fc67a911 authored 作者: ScaY's avatar ScaY 提交者: GitHub

Merge branch 'master' into scay-issue#832

......@@ -498,7 +498,7 @@ qualified class name. The class must implement the interface
Admin rights are required to execute this command.
This command commits an open transaction in this connection.
","
CREATE AGGREGATE MEDIAN FOR ""com.acme.db.Median""
CREATE AGGREGATE SIMPLE_MEDIAN FOR ""com.acme.db.Median""
"
"Commands (DDL)","CREATE ALIAS","
......@@ -821,7 +821,7 @@ Drops an existing user-defined aggregate function.
Admin rights are required to execute this command.
This command commits an open transaction in this connection.
","
DROP AGGREGATE MEDIAN
DROP AGGREGATE SIMPLE_MEDIAN
"
"Commands (DDL)","DROP ALIAS","
......@@ -2801,6 +2801,19 @@ Aggregates are only allowed in select statements.
VAR_SAMP(X)
"
"Functions (Aggregate)","MEDIAN","
MEDIAN( [ DISTINCT ] value )
","
The value separating the higher half of a values from the lower half.
Returns the middle value or an interpolated value between two middle values if number of values is even.
Interpolation is only supported for numeric, date, and time data types.
NULL values are ignored in the calculation.
If no rows are selected, the result is NULL.
Aggregates are only allowed in select statements.
","
MEDIAN(X)
"
"Functions (Numeric)","ABS","
ABS ( { numeric } )
","
......
......@@ -77,7 +77,7 @@ public class Select extends Query {
boolean[] groupByExpression;
/**
* Thhe current group-by values.
* The current group-by values.
*/
HashMap<Expression, Object> currentGroup;
......
......@@ -123,10 +123,15 @@ public class Aggregate extends Expression {
/**
* The aggregate type for HISTOGRAM(expression).
*/
HISTOGRAM
HISTOGRAM,
/**
* The aggregate type for MEDIAN(expression).
*/
MEDIAN
}
private static final HashMap<String, AggregateType> AGGREGATES = new HashMap<>(24);
private static final HashMap<String, AggregateType> AGGREGATES = new HashMap<>(25);
private final AggregateType type;
private final Select select;
......@@ -187,6 +192,7 @@ public class Aggregate extends Expression {
addAggregate("HISTOGRAM", AggregateType.HISTOGRAM);
addAggregate("BIT_OR", AggregateType.BIT_OR);
addAggregate("BIT_AND", AggregateType.BIT_AND);
addAggregate("MEDIAN", AggregateType.MEDIAN);
}
private static void addAggregate(String name, AggregateType type) {
......@@ -287,7 +293,7 @@ public class Aggregate extends Expression {
Table table = select.getTopTableFilter().getTable();
return ValueLong.get(table.getRowCount(session));
case MIN:
case MAX:
case MAX: {
boolean first = type == AggregateType.MIN;
Index index = getMinMaxColumnIndex();
int sortType = index.getIndexColumns()[0].sortType;
......@@ -303,6 +309,10 @@ public class Aggregate extends Expression {
v = row.getValue(index.getColumns()[0].getColumnId());
}
return v;
}
case MEDIAN: {
return AggregateDataMedian.getFromIndex(session, on, dataType);
}
default:
DbException.throwInternalError("type=" + type);
}
......@@ -434,6 +444,7 @@ public class Aggregate extends Expression {
break;
case MIN:
case MAX:
case MEDIAN:
break;
case STDDEV_POP:
case STDDEV_SAMP:
......@@ -568,6 +579,9 @@ public class Aggregate extends Expression {
case BIT_OR:
text = "BIT_OR";
break;
case MEDIAN:
text = "MEDIAN";
break;
default:
throw DbException.throwInternalError("type=" + type);
}
......@@ -606,6 +620,11 @@ public class Aggregate extends Expression {
case MAX:
Index index = getMinMaxColumnIndex();
return index != null;
case MEDIAN:
if (distinct) {
return false;
}
return AggregateDataMedian.getMedianColumnIndex(on) != null;
default:
return false;
}
......
......@@ -31,6 +31,8 @@ abstract class AggregateData {
return new AggregateDataCount();
} else if (aggregateType == AggregateType.HISTOGRAM) {
return new AggregateDataHistogram();
} else if (aggregateType == AggregateType.MEDIAN) {
return new AggregateDataMedian();
} else {
return new AggregateDataDefault(aggregateType);
}
......
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.expression;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import org.h2.engine.Database;
import org.h2.engine.Session;
import org.h2.index.Cursor;
import org.h2.index.Index;
import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.table.Column;
import org.h2.table.IndexColumn;
import org.h2.table.Table;
import org.h2.table.TableFilter;
import org.h2.util.DateTimeUtils;
import org.h2.value.CompareMode;
import org.h2.value.Value;
import org.h2.value.ValueDate;
import org.h2.value.ValueDecimal;
import org.h2.value.ValueDouble;
import org.h2.value.ValueFloat;
import org.h2.value.ValueInt;
import org.h2.value.ValueLong;
import org.h2.value.ValueNull;
import org.h2.value.ValueTime;
import org.h2.value.ValueTimestamp;
import org.h2.value.ValueTimestampTimeZone;
/**
* Data stored while calculating a MEDIAN aggregate.
*/
class AggregateDataMedian extends AggregateData {
private Collection<Value> values;
private static boolean isNullsLast(Index index) {
IndexColumn ic = index.getIndexColumns()[0];
int sortType = ic.sortType;
return (sortType & SortOrder.NULLS_LAST) != 0
|| (sortType & SortOrder.DESCENDING) != 0 && (sortType & SortOrder.NULLS_FIRST) == 0;
}
static Index getMedianColumnIndex(Expression on) {
if (on instanceof ExpressionColumn) {
ExpressionColumn col = (ExpressionColumn) on;
Column column = col.getColumn();
TableFilter filter = col.getTableFilter();
if (filter != null) {
Table table = filter.getTable();
ArrayList<Index> indexes = table.getIndexes();
Index result = null;
if (indexes != null) {
boolean nullable = column.isNullable();
for (int i = 1, size = indexes.size(); i < size; i++) {
Index index = indexes.get(i);
if (!index.canFindNext()) {
continue;
}
if (!index.isFirstColumn(column)) {
continue;
}
if (result == null || result.getColumns().length > index.getColumns().length
// Prefer index without nulls last for nullable columns
|| nullable && isNullsLast(result) && !isNullsLast(index)) {
result = index;
}
}
}
return result;
}
}
return null;
}
static Value getFromIndex(Session session, Expression on, int dataType) {
Index index = getMedianColumnIndex(on);
long count = index.getRowCount(session);
if (count == 0) {
return ValueNull.INSTANCE;
}
Cursor cursor = index.find(session, null, null);
cursor.next();
int columnId = index.getColumns()[0].getColumnId();
ExpressionColumn expr = (ExpressionColumn) on;
if (expr.getColumn().isNullable()) {
boolean hasNulls = false;
SearchRow row;
/*
* Try to skip nulls from the start first with the same cursor that will be used
* to read values.
*/
while (count > 0) {
row = cursor.getSearchRow();
if (row == null) {
return ValueNull.INSTANCE;
}
if (row.getValue(columnId) == ValueNull.INSTANCE) {
count--;
cursor.next();
hasNulls = true;
} else
break;
}
if (count == 0) {
return ValueNull.INSTANCE;
}
/*
* If no nulls found and if index orders nulls last create a second cursor to
* count nulls at the end.
*/
if (!hasNulls && isNullsLast(index)) {
TableFilter tableFilter = expr.getTableFilter();
SearchRow check = tableFilter.getTable().getTemplateSimpleRow(true);
check.setValue(columnId, ValueNull.INSTANCE);
Cursor nullsCursor = index.find(session, check, check);
while (nullsCursor.next()) {
count--;
}
if (count <= 0) {
return ValueNull.INSTANCE;
}
}
}
long skip = (count - 1) / 2;
for (int i = 0; i < skip; i++) {
cursor.next();
}
SearchRow row = cursor.getSearchRow();
if (row == null) {
return ValueNull.INSTANCE;
}
Value v = row.getValue(columnId);
if (v == ValueNull.INSTANCE) {
return v;
}
if ((count & 1) == 0) {
cursor.next();
row = cursor.getSearchRow();
if (row == null) {
return v;
}
Value v2 = row.getValue(columnId);
if (v2 == ValueNull.INSTANCE) {
return v;
}
return getMedian(v, v2, dataType, session.getDatabase().getCompareMode());
}
return v;
}
@Override
void add(Database database, int dataType, boolean distinct, Value v) {
if (v == ValueNull.INSTANCE) {
return;
}
Collection<Value> c = values;
if (c == null) {
values = c = distinct ? new HashSet<Value>() : new ArrayList<Value>();
}
c.add(v);
}
@Override
Value getValue(Database database, int dataType, boolean distinct) {
Collection<Value> c = values;
// Non-null collection cannot be empty here
if (c == null) {
return ValueNull.INSTANCE;
}
if (distinct && c instanceof ArrayList) {
c = new HashSet<>(c);
}
Value[] a = c.toArray(new Value[0]);
final CompareMode mode = database.getCompareMode();
Arrays.sort(a, new Comparator<Value>() {
@Override
public int compare(Value o1, Value o2) {
return o1.compareTo(o2, mode);
}
});
int len = a.length;
int idx = len / 2;
Value v1 = a[idx];
if ((len & 1) == 1) {
return v1.convertTo(dataType);
}
return getMedian(a[idx - 1], v1, dataType, mode);
}
static Value getMedian(Value v0, Value v1, int dataType, CompareMode mode) {
if (v0.compareTo(v1, mode) == 0) {
return v0.convertTo(dataType);
}
switch (dataType) {
case Value.BYTE:
case Value.SHORT:
case Value.INT:
return ValueInt.get((v0.getInt() + v1.getInt()) / 2).convertTo(dataType);
case Value.LONG:
return ValueLong.get((v0.getLong() + v1.getLong()) / 2);
case Value.DECIMAL:
return ValueDecimal.get(v0.getBigDecimal().add(v1.getBigDecimal()).divide(BigDecimal.valueOf(2)));
case Value.FLOAT:
return ValueFloat.get((v0.getFloat() + v1.getFloat()) / 2);
case Value.DOUBLE:
return ValueDouble.get((v0.getFloat() + v1.getDouble()) / 2);
case Value.TIME: {
return ValueTime.fromMillis((v0.getTime().getTime() + v1.getTime().getTime()) / 2);
}
case Value.DATE: {
ValueDate d0 = (ValueDate) v0.convertTo(Value.DATE), d1 = (ValueDate) v1.convertTo(Value.DATE);
return ValueDate.fromDateValue(
DateTimeUtils.dateValueFromAbsoluteDay((DateTimeUtils.absoluteDayFromDateValue(d0.getDateValue())
+ DateTimeUtils.absoluteDayFromDateValue(d1.getDateValue())) / 2));
}
case Value.TIMESTAMP: {
ValueTimestamp ts0 = (ValueTimestamp) v0.convertTo(Value.TIMESTAMP),
ts1 = (ValueTimestamp) v1.convertTo(Value.TIMESTAMP);
long dateSum = DateTimeUtils.absoluteDayFromDateValue(ts0.getDateValue())
+ DateTimeUtils.absoluteDayFromDateValue(ts1.getDateValue());
long nanos = (ts0.getTimeNanos() + ts1.getTimeNanos()) / 2;
if ((dateSum & 1) != 0) {
nanos += DateTimeUtils.NANOS_PER_DAY / 2;
if (nanos >= DateTimeUtils.NANOS_PER_DAY) {
nanos -= DateTimeUtils.NANOS_PER_DAY;
dateSum++;
}
}
return ValueTimestamp.fromDateValueAndNanos(DateTimeUtils.dateValueFromAbsoluteDay(dateSum / 2), nanos);
}
case Value.TIMESTAMP_TZ: {
ValueTimestampTimeZone ts0 = (ValueTimestampTimeZone) v0.convertTo(Value.TIMESTAMP_TZ),
ts1 = (ValueTimestampTimeZone) v1.convertTo(Value.TIMESTAMP_TZ);
long dateSum = DateTimeUtils.absoluteDayFromDateValue(ts0.getDateValue())
+ DateTimeUtils.absoluteDayFromDateValue(ts1.getDateValue());
long nanos = (ts0.getTimeNanos() + ts1.getTimeNanos()) / 2;
int offset = ts0.getTimeZoneOffsetMins() + ts1.getTimeZoneOffsetMins();
if ((dateSum & 1) != 0) {
nanos += DateTimeUtils.NANOS_PER_DAY / 2;
}
if ((offset & 1) != 0) {
nanos += 30L * 1000000000;
}
if (nanos >= DateTimeUtils.NANOS_PER_DAY) {
nanos -= DateTimeUtils.NANOS_PER_DAY;
dateSum++;
}
return ValueTimestampTimeZone.fromDateValueAndNanos(DateTimeUtils.dateValueFromAbsoluteDay(dateSum / 2),
nanos, (short) (offset / 2));
}
default:
// Just return first
return v0.convertTo(dataType);
}
}
}
......@@ -58,7 +58,7 @@ public class Operation extends Expression {
* This operation represents a modulus as in 5 % 2.
*/
MODULUS
};
}
private OpType opType;
private Expression left, right;
......
......@@ -23,6 +23,7 @@ import javax.sql.XAConnection;
import javax.sql.XADataSource;
import org.h2.Driver;
import org.h2.jdbc.JdbcConnection;
import org.h2.message.DbException;
import org.h2.message.TraceObject;
import org.h2.util.StringUtils;
......@@ -401,23 +402,31 @@ public class JdbcDataSource extends TraceObject implements XADataSource,
}
/**
* [Not supported] Return an object of this class if possible.
* Return an object of this class if possible.
*
* @param iface the class
*/
@Override
@SuppressWarnings("unchecked")
public <T> T unwrap(Class<T> iface) throws SQLException {
throw unsupported("unwrap");
try {
if (isWrapperFor(iface)) {
return (T) this;
}
throw DbException.getInvalidValueException("iface", iface);
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* [Not supported] Checks if unwrap can return an object of this class.
* Checks if unwrap can return an object of this class.
*
* @param iface the class
*/
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
throw unsupported("isWrapperFor");
return iface != null && iface.isAssignableFrom(getClass());
}
/**
......
......@@ -43,11 +43,19 @@ public class DateTimeUtils {
*/
public static final TimeZone UTC = TimeZone.getTimeZone("UTC");
private static final long NANOS_PER_DAY = MILLIS_PER_DAY * 1000000;
/**
* The number of nanoseconds per day.
*/
public static final long NANOS_PER_DAY = MILLIS_PER_DAY * 1000000;
private static final int SHIFT_YEAR = 9;
private static final int SHIFT_MONTH = 5;
/**
* Date value for 1970-01-01.
*/
private static final int EPOCH_DATE_VALUE = (1970 << SHIFT_YEAR) + (1 << SHIFT_MONTH) + 1;
private static final int[] NORMAL_DAYS_PER_MONTH = { 0, 31, 28, 31, 30, 31,
30, 31, 31, 30, 31, 30, 31 };
......@@ -65,12 +73,12 @@ public class DateTimeUtils {
* have that problem, and while it is still a small memory leak, it is not a
* class loader memory leak.
*/
private static final ThreadLocal<Calendar> CACHED_CALENDAR = new ThreadLocal<>();
private static final ThreadLocal<GregorianCalendar> CACHED_CALENDAR = new ThreadLocal<>();
/**
* A cached instance of Calendar used when a timezone is specified.
*/
private static final ThreadLocal<Calendar> CACHED_CALENDAR_NON_DEFAULT_TIMEZONE =
private static final ThreadLocal<GregorianCalendar> CACHED_CALENDAR_NON_DEFAULT_TIMEZONE =
new ThreadLocal<>();
/**
......@@ -102,8 +110,8 @@ public class DateTimeUtils {
*
* @return a calendar instance. A cached instance is returned where possible
*/
private static Calendar getCalendar() {
Calendar c = CACHED_CALENDAR.get();
private static GregorianCalendar getCalendar() {
GregorianCalendar c = CACHED_CALENDAR.get();
if (c == null) {
c = DateTimeUtils.createGregorianCalendar();
CACHED_CALENDAR.set(c);
......@@ -118,8 +126,8 @@ public class DateTimeUtils {
* @param tz timezone for the calendar, is never null
* @return a calendar instance. A cached instance is returned where possible
*/
private static Calendar getCalendar(TimeZone tz) {
Calendar c = CACHED_CALENDAR_NON_DEFAULT_TIMEZONE.get();
private static GregorianCalendar getCalendar(TimeZone tz) {
GregorianCalendar c = CACHED_CALENDAR_NON_DEFAULT_TIMEZONE.get();
if (c == null || !c.getTimeZone().equals(tz)) {
c = DateTimeUtils.createGregorianCalendar(tz);
CACHED_CALENDAR_NON_DEFAULT_TIMEZONE.set(c);
......@@ -137,7 +145,7 @@ public class DateTimeUtils {
*
* @return a new calendar instance.
*/
public static Calendar createGregorianCalendar() {
public static GregorianCalendar createGregorianCalendar() {
return new GregorianCalendar();
}
......@@ -151,7 +159,7 @@ public class DateTimeUtils {
* @param tz timezone for the calendar, is never null
* @return a new calendar instance.
*/
public static Calendar createGregorianCalendar(TimeZone tz) {
public static GregorianCalendar createGregorianCalendar(TimeZone tz) {
return new GregorianCalendar(tz);
}
......@@ -520,9 +528,15 @@ public class DateTimeUtils {
*/
public static long getMillis(TimeZone tz, int year, int month, int day,
int hour, int minute, int second, int millis) {
GregorianCalendar c;
if (tz == null) {
c = getCalendar();
} else {
c = getCalendar(tz);
}
c.setLenient(false);
try {
return getTimeTry(false, tz, year, month, day, hour, minute, second,
millis);
return convertToMillis(c, year, month, day, hour, minute, second, millis);
} catch (IllegalArgumentException e) {
// special case: if the time simply doesn't exist because of
// daylight saving time changes, use the lenient version
......@@ -531,12 +545,10 @@ public class DateTimeUtils {
if (hour < 0 || hour > 23) {
throw e;
}
return getTimeTry(true, tz, year, month, day, hour, minute,
second, millis);
} else if (message.indexOf("DAY_OF_MONTH") > 0) {
int maxDay;
if (month == 2) {
maxDay = new GregorianCalendar().isLeapYear(year) ? 29 : 28;
maxDay = c.isLeapYear(year) ? 29 : 28;
} else {
maxDay = 30 + ((month + (month > 7 ? 1 : 0)) & 1);
}
......@@ -547,26 +559,11 @@ public class DateTimeUtils {
// using the timezone Brasilia and others,
// for example for 2042-10-12 00:00:00.
hour += 6;
return getTimeTry(true, tz, year, month, day, hour, minute,
second, millis);
} else {
return getTimeTry(true, tz, year, month, day, hour, minute,
second, millis);
}
}
}
private static long getTimeTry(boolean lenient, TimeZone tz, int year,
int month, int day, int hour, int minute, int second, int millis) {
Calendar c;
if (tz == null) {
c = getCalendar();
} else {
c = getCalendar(tz);
}
c.setLenient(lenient);
c.setLenient(true);
return convertToMillis(c, year, month, day, hour, minute, second, millis);
}
}
private static long convertToMillis(Calendar cal, int year, int month, int day,
int hour, int minute, int second, int millis) {
......@@ -595,19 +592,45 @@ public class DateTimeUtils {
* @param field the field type
* @return the value
*/
public static long getDatePart(Value date, int field) {
Calendar c = valueToCalendar(date);
if(field == Function.EPOCH) {
return c.getTime().getTime() / 1000;
}
if (field == Calendar.YEAR) {
return getYear(c);
}
int value = c.get(field);
if (field == Calendar.MONTH) {
return value + 1;
}
return value;
public static int getDatePart(Value date, int field) {
long dateValue = EPOCH_DATE_VALUE;
long timeNanos = 0;
if (date instanceof ValueTimestamp) {
ValueTimestamp v = (ValueTimestamp) date;
dateValue = v.getDateValue();
timeNanos = v.getTimeNanos();
} else if (date instanceof ValueDate) {
dateValue = ((ValueDate) date).getDateValue();
} else if (date instanceof ValueTime) {
timeNanos = ((ValueTime) date).getNanos();
} else if (date instanceof ValueTimestampTimeZone) {
ValueTimestampTimeZone v = (ValueTimestampTimeZone) date;
dateValue = v.getDateValue();
timeNanos = v.getTimeNanos();
} else {
ValueTimestamp v = (ValueTimestamp) date.convertTo(Value.TIMESTAMP);
date = v; // For valueToCalendar() to avoid second convertTo() call
dateValue = v.getDateValue();
timeNanos = v.getTimeNanos();
}
switch (field) {
case Calendar.YEAR:
return yearFromDateValue(dateValue);
case Calendar.MONTH:
return monthFromDateValue(dateValue);
case Calendar.DAY_OF_MONTH:
return dayFromDateValue(dateValue);
case Calendar.HOUR_OF_DAY:
return (int) (timeNanos / 3_600_000_000_000L % 24);
case Calendar.MINUTE:
return (int) (timeNanos / 60_000_000_000L % 60);
case Calendar.SECOND:
return (int) (timeNanos / 1_000_000_000 % 60);
case Calendar.MILLISECOND:
return (int) (timeNanos / 1_000_000 % 1_000);
}
return valueToCalendar(date).get(field);
}
/**
......
......@@ -12,7 +12,6 @@ import java.lang.reflect.Method;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import org.h2.message.DbException;
import org.h2.value.Value;
import org.h2.value.ValueDate;
......@@ -61,58 +60,103 @@ public class LocalDateTimeUtils {
*/
public static final Class<?> OFFSET_DATE_TIME;
// Class<java.time.ZoneOffset>
/**
* {@code Class<java.time.ZoneOffset>} or {@code null}.
*/
private static final Class<?> ZONE_OFFSET;
// java.time.LocalTime#ofNanoOfDay()
/**
* {@code java.time.LocalTime#ofNanoOfDay()} or {@code null}.
*/
private static final Method LOCAL_TIME_OF_NANO;
// java.time.LocalTime#toNanoOfDay()
/**
* {@code java.time.LocalTime#toNanoOfDay()} or {@code null}.
*/
private static final Method LOCAL_TIME_TO_NANO;
// java.time.LocalDate#of(int, int, int)
/**
* {@code java.time.LocalDate#of(int, int, int)} or {@code null}.
*/
private static final Method LOCAL_DATE_OF_YEAR_MONTH_DAY;
// java.time.LocalDate#parse(CharSequence)
/**
* {@code java.time.LocalDate#parse(CharSequence)} or {@code null}.
*/
private static final Method LOCAL_DATE_PARSE;
// java.time.LocalDate#getYear()
/**
* {@code java.time.LocalDate#getYear()} or {@code null}.
*/
private static final Method LOCAL_DATE_GET_YEAR;
// java.time.LocalDate#getMonthValue()
/**
* {@code java.time.LocalDate#getMonthValue()} or {@code null}.
*/
private static final Method LOCAL_DATE_GET_MONTH_VALUE;
// java.time.LocalDate#getDayOfMonth()
/**
* {@code java.time.LocalDate#getDayOfMonth()} or {@code null}.
*/
private static final Method LOCAL_DATE_GET_DAY_OF_MONTH;
// java.time.LocalDate#atStartOfDay()
/**
* {@code java.time.LocalDate#atStartOfDay()} or {@code null}.
*/
private static final Method LOCAL_DATE_AT_START_OF_DAY;
// java.sql.Timestamp.from(java.time.Instant)
/**
* {@code java.sql.Timestamp.from(java.time.Instant)} or {@code null}.
*/
private static final Method TIMESTAMP_FROM;
// java.sql.Timestamp.toInstant()
/**
* {@code java.sql.Timestamp.toInstant()} or {@code null}.
*/
private static final Method TIMESTAMP_TO_INSTANT;
// java.time.LocalTime#parse(CharSequence)
/**
* {@code java.time.LocalTime#parse(CharSequence)} or {@code null}.
*/
private static final Method LOCAL_TIME_PARSE;
// java.time.LocalDateTime#plusNanos(long)
/**
* {@code java.time.LocalDateTime#plusNanos(long)} or {@code null}.
*/
private static final Method LOCAL_DATE_TIME_PLUS_NANOS;
// java.time.LocalDateTime#toLocalDate()
/**
* {@code java.time.LocalDateTime#toLocalDate()} or {@code null}.
*/
private static final Method LOCAL_DATE_TIME_TO_LOCAL_DATE;
// java.time.LocalDateTime#toLocalTime()
/**
* {@code java.time.LocalDateTime#toLocalTime()} or {@code null}.
*/
private static final Method LOCAL_DATE_TIME_TO_LOCAL_TIME;
// java.time.LocalDateTime#parse(CharSequence)
/**
* {@code java.time.LocalDateTime#parse(CharSequence)} or {@code null}.
*/
private static final Method LOCAL_DATE_TIME_PARSE;
// java.time.ZoneOffset#ofTotalSeconds(int)
/**
* {@code java.time.ZoneOffset#ofTotalSeconds(int)} or {@code null}.
*/
private static final Method ZONE_OFFSET_OF_TOTAL_SECONDS;
// java.time.OffsetDateTime#of(LocalDateTime, ZoneOffset)
/**
* {@code java.time.OffsetDateTime#of(LocalDateTime, ZoneOffset)} or
* {@code null}.
*/
private static final Method OFFSET_DATE_TIME_OF_LOCAL_DATE_TIME_ZONE_OFFSET;
// java.time.OffsetDateTime#parse(CharSequence)
/**
* {@code java.time.OffsetDateTime#parse(CharSequence)} or {@code null}.
*/
private static final Method OFFSET_DATE_TIME_PARSE;
// java.time.OffsetDateTime#toLocalDateTime()
/**
* {@code java.time.OffsetDateTime#toLocalDateTime()} or {@code null}.
*/
private static final Method OFFSET_DATE_TIME_TO_LOCAL_DATE_TIME;
// java.time.OffsetDateTime#getOffset()
/**
* {@code java.time.OffsetDateTime#getOffset()} or {@code null}.
*/
private static final Method OFFSET_DATE_TIME_GET_OFFSET;
// java.time.ZoneOffset#getTotalSeconds()
/**
* {@code java.time.ZoneOffset#getTotalSeconds()} or {@code null}.
*/
private static final Method ZONE_OFFSET_GET_TOTAL_SECONDS;
private static final boolean IS_JAVA8_DATE_API_PRESENT;
......
......@@ -740,12 +740,12 @@ public class TestFunctions extends TestBase implements AggregateFunction {
deleteDb("functions");
Connection conn = getConnection("functions");
Statement stat = conn.createStatement();
stat.execute("CREATE AGGREGATE MEDIAN FOR \"" +
stat.execute("CREATE AGGREGATE SIMPLE_MEDIAN FOR \"" +
MedianStringType.class.getName() + "\"");
stat.execute("CREATE AGGREGATE IF NOT EXISTS MEDIAN FOR \"" +
stat.execute("CREATE AGGREGATE IF NOT EXISTS SIMPLE_MEDIAN FOR \"" +
MedianStringType.class.getName() + "\"");
ResultSet rs = stat.executeQuery(
"SELECT MEDIAN(X) FROM SYSTEM_RANGE(1, 9)");
"SELECT SIMPLE_MEDIAN(X) FROM SYSTEM_RANGE(1, 9)");
rs.next();
assertEquals("5", rs.getString(1));
conn.close();
......@@ -756,22 +756,22 @@ public class TestFunctions extends TestBase implements AggregateFunction {
conn = getConnection("functions");
stat = conn.createStatement();
stat.executeQuery("SELECT MEDIAN(X) FROM SYSTEM_RANGE(1, 9)");
stat.executeQuery("SELECT SIMPLE_MEDIAN(X) FROM SYSTEM_RANGE(1, 9)");
DatabaseMetaData meta = conn.getMetaData();
rs = meta.getProcedures(null, null, "MEDIAN");
rs = meta.getProcedures(null, null, "SIMPLE_MEDIAN");
assertTrue(rs.next());
assertFalse(rs.next());
rs = stat.executeQuery("SCRIPT");
boolean found = false;
while (rs.next()) {
String sql = rs.getString(1);
if (sql.contains("MEDIAN")) {
if (sql.contains("SIMPLE_MEDIAN")) {
found = true;
}
}
assertTrue(found);
stat.execute("DROP AGGREGATE MEDIAN");
stat.execute("DROP AGGREGATE IF EXISTS MEDIAN");
stat.execute("DROP AGGREGATE SIMPLE_MEDIAN");
stat.execute("DROP AGGREGATE IF EXISTS SIMPLE_MEDIAN");
conn.close();
}
......@@ -779,12 +779,12 @@ public class TestFunctions extends TestBase implements AggregateFunction {
deleteDb("functions");
Connection conn = getConnection("functions");
Statement stat = conn.createStatement();
stat.execute("CREATE AGGREGATE MEDIAN FOR \"" +
stat.execute("CREATE AGGREGATE SIMPLE_MEDIAN FOR \"" +
MedianString.class.getName() + "\"");
stat.execute("CREATE AGGREGATE IF NOT EXISTS MEDIAN FOR \"" +
stat.execute("CREATE AGGREGATE IF NOT EXISTS SIMPLE_MEDIAN FOR \"" +
MedianString.class.getName() + "\"");
ResultSet rs = stat.executeQuery(
"SELECT MEDIAN(X) FROM SYSTEM_RANGE(1, 9)");
"SELECT SIMPLE_MEDIAN(X) FROM SYSTEM_RANGE(1, 9)");
rs.next();
assertEquals("5", rs.getString(1));
conn.close();
......@@ -795,22 +795,22 @@ public class TestFunctions extends TestBase implements AggregateFunction {
conn = getConnection("functions");
stat = conn.createStatement();
stat.executeQuery("SELECT MEDIAN(X) FROM SYSTEM_RANGE(1, 9)");
stat.executeQuery("SELECT SIMPLE_MEDIAN(X) FROM SYSTEM_RANGE(1, 9)");
DatabaseMetaData meta = conn.getMetaData();
rs = meta.getProcedures(null, null, "MEDIAN");
rs = meta.getProcedures(null, null, "SIMPLE_MEDIAN");
assertTrue(rs.next());
assertFalse(rs.next());
rs = stat.executeQuery("SCRIPT");
boolean found = false;
while (rs.next()) {
String sql = rs.getString(1);
if (sql.contains("MEDIAN")) {
if (sql.contains("SIMPLE_MEDIAN")) {
found = true;
}
}
assertTrue(found);
stat.execute("DROP AGGREGATE MEDIAN");
stat.execute("DROP AGGREGATE IF EXISTS MEDIAN");
stat.execute("DROP AGGREGATE SIMPLE_MEDIAN");
stat.execute("DROP AGGREGATE IF EXISTS SIMPLE_MEDIAN");
conn.close();
}
......
......@@ -262,7 +262,8 @@ public class TestIndex extends TestBase {
c.close();
}
private void testConcurrentUpdateRun(ConcurrentUpdateThread[] threads, PreparedStatement check) throws SQLException {
private void testConcurrentUpdateRun(ConcurrentUpdateThread[] threads, PreparedStatement check)
throws SQLException {
for (ConcurrentUpdateThread t : threads) {
t.start();
}
......
......@@ -15,9 +15,12 @@ import javax.naming.StringRefAddr;
import javax.naming.spi.ObjectFactory;
import javax.sql.ConnectionEvent;
import javax.sql.ConnectionEventListener;
import javax.sql.DataSource;
import javax.sql.XAConnection;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import org.h2.api.ErrorCode;
import org.h2.jdbcx.JdbcDataSource;
import org.h2.jdbcx.JdbcDataSourceFactory;
import org.h2.jdbcx.JdbcXAConnection;
......@@ -71,6 +74,7 @@ public class TestDataSource extends TestBase {
}
testDataSourceFactory();
testDataSource();
testUnwrap();
testXAConnection();
deleteDb("dataSource");
}
......@@ -190,4 +194,20 @@ public class TestDataSource extends TestBase {
conn.close();
}
private void testUnwrap() throws SQLException {
JdbcDataSource ds = new JdbcDataSource();
assertTrue(ds.isWrapperFor(Object.class));
assertTrue(ds.isWrapperFor(DataSource.class));
assertTrue(ds.isWrapperFor(JdbcDataSource.class));
assertFalse(ds.isWrapperFor(String.class));
assertTrue(ds == ds.unwrap(Object.class));
assertTrue(ds == ds.unwrap(DataSource.class));
try {
ds.unwrap(String.class);
fail();
} catch (SQLException ex) {
assertEquals(ErrorCode.INVALID_VALUE_2, ex.getErrorCode());
}
}
}
......@@ -99,7 +99,7 @@ public class TestScript extends TestBase {
testScript("datatypes/" + s + ".sql");
}
for (String s : new String[] { "avg", "bit-and", "bit-or", "count",
"group-concat", "max", "min", "selectivity", "stddev-pop",
"group-concat", "max", "median", "min", "selectivity", "stddev-pop",
"stddev-samp", "sum", "var-pop", "var-samp" }) {
testScript("functions/aggregate/" + s + ".sql");
}
......
-- Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
-- and the EPL 1.0 (http://h2database.com/html/license.html).
-- Initial Developer: H2 Group
--
-- ASC
create table test(v tinyint);
> ok
create index test_idx on test(v asc);
> ok
insert into test values (20), (20), (10);
> update count: 3
select median(v) from test;
> MEDIAN(V)
> ---------
> 20
insert into test values (null);
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ---------
> 20
select median(distinct v) from test;
> MEDIAN(DISTINCT V)
> ------------------
> 15
insert into test values (10);
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ---------
> 15
drop table test;
> ok
-- ASC NULLS FIRST
create table test(v tinyint);
> ok
create index test_idx on test(v asc nulls first);
> ok
insert into test values (20), (20), (10);
> update count: 3
select median(v) from test;
> MEDIAN(V)
> ---------
> 20
insert into test values (null);
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ---------
> 20
select median(distinct v) from test;
> MEDIAN(DISTINCT V)
> ------------------
> 15
insert into test values (10);
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ---------
> 15
drop table test;
> ok
-- ASC NULLS LAST
create table test(v tinyint);
> ok
create index test_idx on test(v asc nulls last);
> ok
insert into test values (20), (20), (10);
> update count: 3
select median(v) from test;
> MEDIAN(V)
> ---------
> 20
insert into test values (null);
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ---------
> 20
select median(distinct v) from test;
> MEDIAN(DISTINCT V)
> ------------------
> 15
insert into test values (10);
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ---------
> 15
drop table test;
> ok
-- DESC
create table test(v tinyint);
> ok
create index test_idx on test(v desc);
> ok
insert into test values (20), (20), (10);
> update count: 3
select median(v) from test;
> MEDIAN(V)
> ---------
> 20
insert into test values (null);
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ---------
> 20
select median(distinct v) from test;
> MEDIAN(DISTINCT V)
> ------------------
> 15
insert into test values (10);
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ---------
> 15
drop table test;
> ok
-- DESC NULLS FIRST
create table test(v tinyint);
> ok
create index test_idx on test(v desc nulls first);
> ok
insert into test values (20), (20), (10);
> update count: 3
select median(v) from test;
> MEDIAN(V)
> ---------
> 20
insert into test values (null);
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ---------
> 20
select median(distinct v) from test;
> MEDIAN(DISTINCT V)
> ------------------
> 15
insert into test values (10);
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ---------
> 15
drop table test;
> ok
-- DESC NULLS LAST
create table test(v tinyint);
> ok
create index test_idx on test(v desc nulls last);
> ok
insert into test values (20), (20), (10);
> update count: 3
select median(v) from test;
> MEDIAN(V)
> ---------
> 20
insert into test values (null);
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ---------
> 20
select median(distinct v) from test;
> MEDIAN(DISTINCT V)
> ------------------
> 15
insert into test values (10);
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ---------
> 15
drop table test;
> ok
create table test(v tinyint);
> ok
insert into test values (20), (20), (10);
> update count: 3
select median(v) from test;
> MEDIAN(V)
> ---------
> 20
insert into test values (null);
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ---------
> 20
select median(distinct v) from test;
> MEDIAN(DISTINCT V)
> ------------------
> 15
insert into test values (10);
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ---------
> 15
drop table test;
> ok
create table test(v smallint);
> ok
insert into test values (20), (20), (10);
> update count: 3
select median(v) from test;
> MEDIAN(V)
> ---------
> 20
insert into test values (null);
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ---------
> 20
select median(distinct v) from test;
> MEDIAN(DISTINCT V)
> ------------------
> 15
insert into test values (10);
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ---------
> 15
drop table test;
> ok
create table test(v int);
> ok
insert into test values (20), (20), (10);
> update count: 3
select median(v) from test;
> MEDIAN(V)
> ---------
> 20
insert into test values (null);
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ---------
> 20
select median(distinct v) from test;
> MEDIAN(DISTINCT V)
> ------------------
> 15
insert into test values (10);
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ---------
> 15
drop table test;
> ok
create table test(v bigint);
> ok
insert into test values (20), (20), (10);
> update count: 3
select median(v) from test;
> MEDIAN(V)
> ---------
> 20
insert into test values (null);
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ---------
> 20
select median(distinct v) from test;
> MEDIAN(DISTINCT V)
> ------------------
> 15
insert into test values (10);
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ---------
> 15
drop table test;
> ok
create table test(v real);
> ok
insert into test values (2), (2), (1);
> update count: 3
select median(v) from test;
> MEDIAN(V)
> ---------
> 2.0
insert into test values (null);
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ---------
> 2.0
select median(distinct v) from test;
> MEDIAN(DISTINCT V)
> ------------------
> 1.5
insert into test values (1);
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ---------
> 1.5
drop table test;
> ok
create table test(v double);
> ok
insert into test values (2), (2), (1);
> update count: 3
select median(v) from test;
> MEDIAN(V)
> ---------
> 2.0
insert into test values (null);
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ---------
> 2.0
select median(distinct v) from test;
> MEDIAN(DISTINCT V)
> ------------------
> 1.5
insert into test values (1);
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ---------
> 1.5
drop table test;
> ok
create table test(v numeric(1));
> ok
insert into test values (2), (2), (1);
> update count: 3
select median(v) from test;
> MEDIAN(V)
> ---------
> 2
insert into test values (null);
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ---------
> 2
select median(distinct v) from test;
> MEDIAN(DISTINCT V)
> ------------------
> 1.5
insert into test values (1);
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ---------
> 1.5
drop table test;
> ok
create table test(v time);
> ok
insert into test values ('20:00:00'), ('20:00:00'), ('10:00:00');
> update count: 3
select median(v) from test;
> MEDIAN(V)
> ---------
> 20:00:00
insert into test values (null);
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ---------
> 20:00:00
select median(distinct v) from test;
> MEDIAN(DISTINCT V)
> ------------------
> 15:00:00
insert into test values ('10:00:00');
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ---------
> 15:00:00
drop table test;
> ok
create table test(v date);
> ok
insert into test values ('2000-01-20'), ('2000-01-20'), ('2000-01-10');
> update count: 3
select median(v) from test;
> MEDIAN(V)
> ----------
> 2000-01-20
insert into test values (null);
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ----------
> 2000-01-20
select median(distinct v) from test;
> MEDIAN(DISTINCT V)
> ------------------
> 2000-01-15
insert into test values ('2000-01-10');
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ----------
> 2000-01-15
drop table test;
> ok
create table test(v timestamp);
> ok
insert into test values ('2000-01-20 20:00:00'), ('2000-01-20 20:00:00'), ('2000-01-10 10:00:00');
> update count: 3
select median(v) from test;
> MEDIAN(V)
> ---------------------
> 2000-01-20 20:00:00.0
insert into test values (null);
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ---------------------
> 2000-01-20 20:00:00.0
select median(distinct v) from test;
> MEDIAN(DISTINCT V)
> ---------------------
> 2000-01-15 15:00:00.0
insert into test values ('2000-01-10 10:00:00');
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ---------------------
> 2000-01-15 15:00:00.0
delete from test;
> update count: 5
insert into test values ('2000-01-20 20:00:00'), ('2000-01-21 20:00:00');
> update count: 2
select median(v) from test;
> MEDIAN(V)
> ---------------------
> 2000-01-21 08:00:00.0
drop table test;
> ok
create table test(v timestamp with time zone);
> ok
insert into test values ('2000-01-20 20:00:00+04'), ('2000-01-20 20:00:00+04'), ('2000-01-10 10:00:00+02');
> update count: 3
select median(v) from test;
> MEDIAN(V)
> ------------------------
> 2000-01-20 20:00:00.0+04
insert into test values (null);
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ------------------------
> 2000-01-20 20:00:00.0+04
select median(distinct v) from test;
> MEDIAN(DISTINCT V)
> ------------------------
> 2000-01-15 15:00:00.0+03
insert into test values ('2000-01-10 10:00:00+02');
> update count: 1
select median(v) from test;
> MEDIAN(V)
> ------------------------
> 2000-01-15 15:00:00.0+03
delete from test;
> update count: 5
insert into test values ('2000-01-20 20:00:00+10:15'), ('2000-01-21 20:00:00-09');
> update count: 2
select median(v) from test;
> MEDIAN(V)
> ---------------------------
> 2000-01-21 08:00:30.0+00:37
drop table test;
> ok
-- with group by
create table test(name varchar, value int);
> ok
insert into test values ('Group 2A', 10), ('Group 2A', 10), ('Group 2A', 20),
('Group 1X', 40), ('Group 1X', 50), ('Group 3B', null);
> update count: 6
select name, median(value) from test group by name order by name;
> NAME MEDIAN(VALUE)
> -------- -------------
> Group 1X 45
> Group 2A 10
> Group 3B null
> rows (ordered): 3
drop table test;
> ok
-- with filter
create table test(v int);
> ok
insert into test values (20), (20), (10);
> update count: 3
select median(v) from test where v <> 20;
> MEDIAN(V)
> ---------
> 10
create index test_idx on test(v asc);
> ok
select median(v) from test where v <> 20;
> MEDIAN(V)
> ---------
> 10
drop table test;
> ok
-- two-column index
create table test(v int, v2 int);
> ok
create index test_idx on test(v, v2);
> ok
insert into test values (20, 1), (10, 2), (20, 3);
> update count: 3
select median(v) from test;
> MEDIAN(V)
> ---------
> 20
drop table test;
> ok
-- not null column
create table test (v int not null);
> ok
create index test_idx on test(v desc);
> ok
select median(v) from test;
> MEDIAN(V)
> ---------
> null
insert into test values (10), (20);
> update count: 2
select median(v) from test;
> MEDIAN(V)
> ---------
> 15
insert into test values (20), (10), (20);
> update count: 3
select median(v) from test;
> MEDIAN(V)
> ---------
> 20
drop table test;
> ok
......@@ -139,7 +139,7 @@ public class TestTools extends TestBase {
c.runTool("-web", "-webPort", "9002", "-tool", "-browser", "-tcp",
"-tcpPort", "9003", "-pg", "-pgPort", "9004");
assertContains(lastUrl, ":9002");
c.shutdown();
shutdownConsole(c);
// check if starting the browser works
c.runTool("-web", "-webPort", "9002", "-tool");
......@@ -169,7 +169,7 @@ public class TestTools extends TestBase {
// ignore
}
c.shutdown();
shutdownConsole(c);
// trying to use the same port for two services should fail,
// but also stop the first service
......@@ -184,7 +184,19 @@ public class TestTools extends TestBase {
} else {
System.clearProperty(SysProperties.H2_BROWSER);
}
shutdownConsole(c);
}
}
private static void shutdownConsole(Console c) {
c.shutdown();
if (Thread.currentThread().isInterrupted()) {
// Clear interrupted state so test can continue its work safely
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// Ignore
}
}
}
......
......@@ -307,10 +307,10 @@ public class TestValue extends TestBase {
}
private void testTimestamp() {
ValueTimestamp vts = ValueTimestamp.parse("2000-01-15 10:20:30.333222111");
ValueTimestamp valueTs = ValueTimestamp.parse("2000-01-15 10:20:30.333222111");
Timestamp ts = Timestamp.valueOf("2000-01-15 10:20:30.333222111");
assertEquals(ts.toString(), vts.getString());
assertEquals(ts, vts.getTimestamp());
assertEquals(ts.toString(), valueTs.getString());
assertEquals(ts, valueTs.getTimestamp());
Calendar c = Calendar.getInstance(TimeZone.getTimeZone("Europe/Berlin"));
c.set(2018, 02, 25, 1, 59, 00);
c.set(Calendar.MILLISECOND, 123);
......
......@@ -762,3 +762,4 @@ assorted reimplemented hangups confirmation predefined
mdy destfile hclf forbids spellchecking selfdestruct expects accident jacocoagent cli historic mitigate
jacoco xdata invokes sourcefiles classfiles duplication crypto stacktraces prt directions handled overly asm hardcoded
interpolated thead
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论