提交 16459470 authored 作者: Uncle-pan's avatar Uncle-pan

Merge remote-tracking branch 'origin/master' into clean-test-master

......@@ -8,7 +8,7 @@ cache:
matrix:
include:
- jdk: oraclejdk10
- jdk: openjdk10
dist: trusty
group: edge
sudo: required
......
......@@ -3,14 +3,17 @@
# Initial Developer: H2 Group
"SECTION","TOPIC","SYNTAX","TEXT","EXAMPLE"
"Commands (DML)","SELECT","
SELECT [ TOP term ] [ DISTINCT | ALL ] selectExpression [,...]
SELECT [ TOP term [ WITH TIES ] ]
[ DISTINCT [ ON ( expression [,...] ) ] | ALL ]
selectExpression [,...]
FROM tableExpression [,...] [ WHERE expression ]
[ GROUP BY expression [,...] ] [ HAVING expression ]
[ { UNION [ ALL ] | MINUS | EXCEPT | INTERSECT } select ]
[ ORDER BY order [,...] ]
[ { LIMIT expression [ OFFSET expression ] [ SAMPLE_SIZE rowCountInt ] }
| { [ OFFSET expression { ROW | ROWS } ]
[ { FETCH { FIRST | NEXT } expression { ROW | ROWS } ONLY } ] } ]
[ FETCH { FIRST | NEXT } expression { ROW | ROWS }
{ ONLY | WITH TIES } ] } ]
[ FOR UPDATE ]
","
Selects data from a table or multiple tables.
......@@ -42,6 +45,8 @@ SELECT * FROM TEST LIMIT 1000;
SELECT * FROM (SELECT ID, COUNT(*) FROM TEST
GROUP BY ID UNION SELECT NULL, COUNT(*) FROM TEST)
ORDER BY 1 NULLS LAST;
SELECT DISTINCT C1, C2 FROM TEST;
SELECT DISTINCT ON(C1) C1, C2 FROM TEST ORDER BY C1;
"
"Commands (DML)","INSERT","
......
......@@ -21,6 +21,140 @@ Change Log
<h2>Next Version (unreleased)</h2>
<ul>
<li>PR #1302: Use OpenJDK instead of OracleJDK 10 in Travis builds due to Travis problem
</li>
<li>Issue #1032: Error when executing "SELECT DISTINCT ON"
</li>
<li>Issue #1295: ConditionInSelect violates requirements of LocalResult
</li>
<li>PR #1296: Assorted minor changes
</li>
<li>PR #1293: Move HELP and SHOW tests into own files
</li>
<li>PR #1291: Fix update count for REPLACE and move some SQL tests into separate files
</li>
<li>PR #1290: Do not load the whole LOBs into memory for comparison operation
</li>
<li>Issue #408: DISTINCT does not properly work with ORDER BY on function like LOWER
</li>
<li>PR #1286: Fix MVTempResult implementations for results with invisible columns
</li>
<li>Issue #1284: Nanoseconds of timestamps from old H2 versions are not read properly
</li>
<li>PR #1283: Clean up interaction between LocalResult and ResultExternal
</li>
<li>Issue #1265: OOME is not handled properly in TraceObject.logAndConvert()
</li>
<li>Issue #1061: Regression: Braces after WITH clause not allowed anymore
</li>
<li>PR #1277: Assorted changes in Parser
</li>
<li>PR #1276: Improve support of ARRAY and SQLXML in JDBC layer
</li>
<li>PR #1275: Do not quote other lower case characters
</li>
<li>PR #1274: Use token type in Parser instead of string comparisons
</li>
<li>PR #1272: Reduce code duplication in Parser
</li>
<li>PR #1271: Minor memory leak
</li>
<li>PR #1270: drop TableView isPersistent field
</li>
<li>PR #1269: Eliminate commit of empty batch in some tests
</li>
<li>Issue #1266: Add INFORMATION_SCHEMA.COLUMNS.DATETIME_PRECISION
</li>
<li>Issue #1261: How to discover stored enum types through INFORMATION_SCHEMA
</li>
<li>Issue #1258: Failing to remove index when using schema.table
</li>
<li>PR #1256: misc tiny refactorings
</li>
<li>PR #1255: Minor changes in MERGE USING, DATE_TRUNC, and EXTRACT
</li>
<li>Issue #1214: Internal compiler believes that "3 warnings" is an error
</li>
<li>PR #1252: Assorted minor changes
</li>
<li>PR #1251: Fix SQL representation of CAST for types with fractional seconds precision
</li>
<li>PR #1250: Batch append mode for MVMap
</li>
<li>PR #1248: StringIndexOutOfBoundsException due to undoLog map
</li>
<li>PR #1246: Detect disabled tests
</li>
<li>PR #1242: Add implementation of SQLXML interface
</li>
<li>PR #1241: Various tweaks in attempting to fix TestDiskFull test
</li>
<li>PR #1240: Optimise ValueLobDB comparison methods
</li>
<li>PR #1239: Don't try to find tools.jar on Java 9+
</li>
<li>PR #1238: remove unfinished android API
</li>
<li>PR #1237: remove JaQu
</li>
<li>PR #1236: remove STORE_LOCAL_TIME code
</li>
<li>PR #1235: Do not use deprecated Class.newInstance()
</li>
<li>PR #1234: Fix NPE in Parser.parseMergeUsing()
</li>
<li>PR #1233: Simplify old lob ValueLob class
</li>
<li>Issue 1227: lob growth in pagestore mode
</li>
<li>PR #1230: clean up some javadoc and some throws clauses
</li>
<li>PR #1229: Create UndoLog only when necessary and remove outdated code
</li>
<li>PR #1228: Remove some PageStore+MVCC leftovers
</li>
<li>PR #1226: Fix inconsistencies in checks for transaction isolation level
</li>
<li>PR #1224: Enable Java 10 testing on Travis
</li>
<li>PR #1223: Fix issues with testing on latest Java versions
</li>
<li>PR #1222: Leftovers handling
</li>
<li>Issue #1220: JDK-9 build fails due to usage of java.xml.bind in external authentication
</li>
<li>PR #1218: Test utilities only once during TestAll
</li>
<li>PR #1217: Postpone session.endStatement() until after commit
</li>
<li>PR #1213: KillRestart fix
</li>
<li>PR #1211: Assorted minor changes
</li>
<li>Issue #1204: Always use MVCC with MVStore and never use it with PageStore
</li>
<li>PR #1206: Forbid reconnects in non-regular modes in TestScript
</li>
<li>PR #1205: Misc test fixes
</li>
<li>Issue 1198: Enable MULTI_THREADED by default for MVStore mode
</li>
<li>Issue #1195: Calling setBytes to set VARCHAR field fails
</li>
<li>PR #1197: Fix or suppress errors in tests
</li>
<li>PR #1194: TestKillRestartMulti: A map named undoLog-1 already exists
</li>
<li>PR #1193: enable TestRandomSQL on non-memory databases
</li>
<li>PR #1191: External authentication with datasource issue
</li>
<li>PR #1188: Undo log split to reduce contention
</li>
<li>PR #1186: TransactionMap::sizeAsLong() optimized - temp map eliminated
</li>
<li>PR #1185: Improve naming of the object id field in Prepared
</li>
<li>Issue #1196: Feature request for MS SQL Server Compatibility Mode
</li>
<li>Issue #1177: Resource leak in Recover tool
......
......@@ -103,7 +103,7 @@ See also <a href="build.html#providing_patches">Providing Patches</a>.
</li><li>Linked schema using CSV files: one schema for a directory of files; support indexes for CSV files.
</li><li>iReport to support H2.
</li><li>Include SMTP (mail) client (alert on cluster failure, low disk space,...).
</li><li>Option for SCRIPT to only process one or a set of schemas or tables, and append to a file.
</li><li>Option for SCRIPT to append to a file.
</li><li>JSON parser and functions.
</li><li>Copy database: tool with config GUI and batch mode, extensible (example: compare).
</li><li>Document, implement tool for long running transactions using user-defined compensation statements.
......@@ -124,7 +124,6 @@ See also <a href="build.html#providing_patches">Providing Patches</a>.
</li><li>SQL Server 2005, Oracle: support COUNT(*) OVER(). See http://www.orafusion.com/art_anlytc.htm
</li><li>SQL 2003: http://www.wiscorp.com/sql_2003_standard.zip
</li><li>Version column (number/sequence and timestamp based).
</li><li>Optimize getGeneratedKey: send last identity after each execute (server).
</li><li>Test and document UPDATE TEST SET (ID, NAME) = (SELECT ID*10, NAME || '!' FROM TEST T WHERE T.ID=TEST.ID).
</li><li>Max memory rows / max undo log size: use block count / row size not row count.
</li><li>Implement point-in-time recovery.
......@@ -149,8 +148,7 @@ See also <a href="build.html#providing_patches">Providing Patches</a>.
</li><li>Backup tool should work with other databases as well.
</li><li>Console: -ifExists doesn't work for the console. Add a flag to disable other dbs.
</li><li>Check if 'FSUTIL behavior set disablelastaccess 1' improves the performance (fsutil behavior query disablelastaccess).
</li><li>Java static code analysis: http://pmd.sourceforge.net/
</li><li>Java static code analysis: http://www.eclipse.org/tptp/
</li><li>Java static code analysis: https://pmd.github.io/
</li><li>Compatibility for CREATE SCHEMA AUTHORIZATION.
</li><li>Implement Clob / Blob truncate and the remaining functionality.
</li><li>File locking: writing a system property to detect concurrent access from the same VM (different classloaders).
......@@ -167,7 +165,7 @@ See also <a href="build.html#providing_patches">Providing Patches</a>.
</li><li>Support updatable views with join on primary keys (to extend a table).
</li><li>Public interface for functions (not public static).
</li><li>Support reading the transaction log.
</li><li>Feature matrix as in <a href="http://www.inetsoftware.de/products/jdbc/mssql/features/default.asp">i-net software</a>.
</li><li>Feature matrix.
</li><li>Updatable result set on table without primary key or unique index.
</li><li>Allow execution time prepare for SELECT * FROM CSVREAD(?, 'columnNameString')
</li><li>Support data type INTERVAL
......@@ -213,7 +211,6 @@ See also <a href="build.html#providing_patches">Providing Patches</a>.
</li><li>Server: use one listener (detect if the request comes from an PG or TCP client)
</li><li>Optimize SELECT MIN(ID), MAX(ID), COUNT(*) FROM TEST WHERE ID BETWEEN 100 AND 200
</li><li>Sequence: PostgreSQL compatibility (rename, create) http://www.postgresql.org/docs/8.2/static/sql-altersequence.html
</li><li>DISTINCT: support large result sets by sorting on all columns (additionally) and then removing duplicates.
</li><li>Support a special trigger on all tables to allow building a transaction log reader.
</li><li>File system with a background writer thread; test if this is faster
</li><li>Better document the source code (high level documentation).
......@@ -228,7 +225,6 @@ See also <a href="build.html#providing_patches">Providing Patches</a>.
</li><li>Improve documentation of access rights.
</li><li>Support opening a database that is in the classpath, maybe using a new file system. Workaround: detect jar file using getClass().getProtectionDomain().getCodeSource().getLocation().
</li><li>Remember the user defined data type (domain) of a column.
</li><li>MVCC: support multi-threaded kernel with multi-version concurrency.
</li><li>Auto-server: add option to define the port range or list.
</li><li>Support Jackcess (MS Access databases)
</li><li>Built-in methods to write large objects (BLOB and CLOB): FILE_WRITE('test.txt', 'Hello World')
......@@ -254,7 +250,6 @@ See also <a href="build.html#providing_patches">Providing Patches</a>.
</li><li>Access rights: add missing features (users should be 'owner' of objects; missing rights for sequences; dropping objects)
</li><li>Support NOCACHE table option (Oracle).
</li><li>Support table partitioning.
</li><li>Add regular javadocs (using the default doclet, but another css) to the homepage.
</li><li>The database should be kept open for a longer time when using the server mode.
</li><li>Javadocs: for each tool, add a copy &amp; paste sample in the class level.
</li><li>Javadocs: add @author tags.
......@@ -264,7 +259,6 @@ See also <a href="build.html#providing_patches">Providing Patches</a>.
</li><li>Write (log) to system table before adding to internal data structures.
</li><li>Support direct lookup for MIN and MAX when using WHERE (see todo.txt / Direct Lookup).
</li><li>Support other array types (String[], double[]) in PreparedStatement.setObject(int, Object) (with test case).
</li><li>MVCC should not be memory bound (uncommitted data is kept in memory in the delta index; maybe using a regular b-tree index solves the problem).
</li><li>Oracle compatibility: support NLS_DATE_FORMAT.
</li><li>Support for Thread.interrupt to cancel running statements.
</li><li>Cluster: add feature to make sure cluster nodes can not get out of sync (for example by stopping one process).
......@@ -298,7 +292,6 @@ See also <a href="build.html#providing_patches">Providing Patches</a>.
</li><li>Support TRUNCATE .. CASCADE like PostgreSQL.
</li><li>Fulltext search: lazy result generation using SimpleRowSource.
</li><li>Fulltext search: support alternative syntax: WHERE FTL_CONTAINS(name, 'hello').
</li><li>MySQL compatibility: support REPLACE, see http://dev.mysql.com/doc/refman/6.0/en/replace.html and issue 73.
</li><li>MySQL compatibility: support INSERT INTO table SET column1 = value1, column2 = value2
</li><li>Docs: add a one line description for each functions and SQL statements at the top (in the link section).
</li><li>Javadoc search: weight for titles should be higher ('random' should list Functions as the best match).
......@@ -319,7 +312,6 @@ See also <a href="build.html#providing_patches">Providing Patches</a>.
</li><li>MySQL compatibility: for auto_increment columns, convert 0 to next value (as when inserting NULL).
</li><li>Optimization for multi-column IN: use an index if possible. Example: (A, B) IN((1, 2), (2, 3)).
</li><li>Optimization for EXISTS: convert to inner join or IN(..) if possible.
</li><li>Functions: support hashcode(value); cryptographic and fast
</li><li>Serialized file lock: support long running queries.
</li><li>Network: use 127.0.0.1 if other addresses don't work.
</li><li>Pluggable network protocol (currently Socket/ServerSocket over TCP/IP) - see also TransportServer with master slave replication.
......@@ -352,7 +344,6 @@ See also <a href="build.html#providing_patches">Providing Patches</a>.
</li><li>Compatibility: Java functions with SQLJ Part1 http://www.acm.org/sigmod/record/issues/9912/standards.pdf.gz
</li><li>Compatibility: Java functions with SQL/PSM (Persistent Stored Modules) - need to find the documentation.
</li><li>CACHE_SIZE: automatically use a fraction of Runtime.maxMemory - maybe automatically the second level cache.
</li><li>Support date/time/timestamp as documented in http://en.wikipedia.org/wiki/ISO_8601
</li><li>PostgreSQL compatibility: when in PG mode, treat BYTEA data like PG.
</li><li>Support =ANY(array) as in PostgreSQL. See also http://www.postgresql.org/docs/8.0/interactive/arrays.html
</li><li>IBM DB2 compatibility: support PREVIOUS VALUE FOR sequence.
......@@ -380,7 +371,6 @@ See also <a href="build.html#providing_patches">Providing Patches</a>.
</li><li>Issue 126: The index name should be "IDX_" plus the constraint name unless there is a conflict, in which case append a number.
</li><li>Issue 127: Support activation/deactivation of triggers
</li><li>Issue 130: Custom log event listeners
</li><li>Issue 131: IBM DB2 compatibility: sysibm.sysdummy1
</li><li>Issue 132: Use Java enum trigger type.
</li><li>Issue 134: IBM DB2 compatibility: session global variables.
</li><li>Cluster: support load balance with values for each server / auto detect.
......@@ -430,7 +420,6 @@ See also <a href="build.html#providing_patches">Providing Patches</a>.
</li><li>Maybe use PhantomReference instead of finalize.
</li><li>Database file name suffix: should only have one dot by default. Example: .h2db
</li><li>Issue 196: Function based indexes
</li><li>ALTER TABLE ... ADD COLUMN IF NOT EXISTS columnName.
</li><li>Fix the disk space leak (killing the process at the exact right moment will increase
the disk space usage; this space is not re-used). See TestDiskSpaceLeak.java
</li><li>ROWNUM: Oracle compatibility when used within a subquery. Issue 198.
......@@ -464,7 +453,6 @@ See also <a href="build.html#providing_patches">Providing Patches</a>.
or to prevent to login with the same username and password from different IPs.
Possibly using the DatabaseEventListener API, or a new API.
</li><li>Compatibility for data type CHAR (Derby, HSQLDB). Issue 212.
</li><li>Compatibility with MySQL TIMESTAMPDIFF. Issue 209.
</li><li>Optimizer: use a histogram of the data, specially for non-normal distributions.
</li><li>Trigger: allow declaring as source code (like functions).
</li><li>User defined aggregate: allow declaring as source code (like functions).
......@@ -510,8 +498,6 @@ See also <a href="build.html#providing_patches">Providing Patches</a>.
</li><li>Single-column primary key values are always stored explicitly. This is not required.
</li><li>Compatibility with MySQL: support CREATE TABLE TEST(NAME VARCHAR(255) CHARACTER SET UTF8).
</li><li>CALL is incompatible with other databases because it returns a result set, so that CallableStatement.execute() returns true.
</li><li>Optimization for large lists for column IN(1, 2, 3, 4,...) - currently an list is used, could potentially use a hash set
(maybe only for a part of the values - the ones that can be evaluated).
</li><li>Compatibility for ARRAY data type (Oracle: VARRAY(n) of VARCHAR(m); HSQLDB: VARCHAR(n) ARRAY; Postgres: VARCHAR(n)[]).
</li><li>PostgreSQL compatible array literal syntax: ARRAY[['a', 'b'], ['c', 'd']]
</li><li>PostgreSQL compatibility: UPDATE with FROM.
......@@ -550,7 +536,6 @@ See also <a href="build.html#providing_patches">Providing Patches</a>.
</li><li>MySQL compatibility: index names only need to be unique for the given table.
</li><li>Issue 352: constraints: distinguish between 'no action' and 'restrict'. Currently, only restrict is supported,
and 'no action' is internally mapped to 'restrict'. The database meta data returns 'restrict' in all cases.
</li><li>Oracle compatibility: support MEDIAN aggregate function.
</li><li>Issue 348: Oracle compatibility: division should return a decimal result.
</li><li>Read rows on demand: instead of reading the whole row, only read up to that column that is requested.
Keep an pointer to the data area and the column id that is already read.
......
......@@ -152,7 +152,7 @@
90119=User data type {0} already exists
90120=User data type {0} not found
90121=Database is already closed (to disable automatic closing at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL)
90122=Operation not supported for table {0} when there are views on the table: {1}
90122=The WITH TIES clause is not allowed without a corresponding ORDER BY clause.
90123=Cannot mix indexed and non-indexed parameters
90124=File not found: {0}
90125=Invalid class, expected {0} but got {1}
......
......@@ -1756,6 +1756,12 @@ public class ErrorCode {
*/
public static final int DATABASE_CALLED_AT_SHUTDOWN = 90121;
/**
* The error with code <code>90122</code> is thrown when
* WITH TIES clause is used without ORDER BY clause.
*/
public static final int WITH_TIES_WITHOUT_ORDER_BY = 90122;
/**
* The error with code <code>90123</code> is thrown when
* trying mix regular parameters and indexed parameters in the same
......@@ -2002,7 +2008,7 @@ public class ErrorCode {
public static final int AUTHENTICATOR_NOT_AVAILABLE = 90144;
// next are 90122, 90145
// next is 90145
private ErrorCode() {
// utility class
......
......@@ -2381,7 +2381,12 @@ public class Parser {
read("ROWS");
}
}
read("ONLY");
if (readIf(WITH)) {
read("TIES");
command.setWithTies(true);
} else {
read("ONLY");
}
}
currentSelect = temp;
if (readIf(LIMIT)) {
......@@ -2527,6 +2532,10 @@ public class Parser {
// SELECT TOP 1 (+?) AS A FROM TEST
Expression limit = readTerm().optimize(session);
command.setLimit(limit);
if (readIf(WITH)) {
read("TIES");
command.setWithTies(true);
}
} else if (readIf(LIMIT)) {
Expression offset = readTerm().optimize(session);
command.setOffset(offset);
......@@ -2535,7 +2544,16 @@ public class Parser {
}
currentSelect = temp;
if (readIf(DISTINCT)) {
command.setDistinct(true);
if (readIf(ON)) {
read(OPEN_PAREN);
ArrayList<Expression> distinctExpressions = Utils.newSmallArrayList();
do {
distinctExpressions.add(readExpression());
} while (readIfMore(true));
command.setDistinct(distinctExpressions.toArray(new Expression[0]));
} else {
command.setDistinct();
}
} else {
readIf(ALL);
}
......@@ -2749,9 +2767,7 @@ public class Parser {
} else {
if (isSelect()) {
Query query = parseSelect();
// can not be lazy because we have to call
// method ResultInterface.containsDistinct
// which is not supported for lazy execution
// TODO lazy result causes timeout in TestFuzzOptimizations
query.setNeverLazy(true);
r = new ConditionInSelect(database, r, query, false,
Comparison.EQUAL);
......@@ -5585,7 +5601,7 @@ public class Parser {
* data and table if we don't have a working CTE already.
*/
Table recursiveTable = TableView.createShadowTableForRecursiveTableExpression(
isTemporary, session, cteViewName, schema, columns, database);
isTemporary, session, cteViewName, schema, columns, database);
List<Column> columnTemplateList;
String[] querySQLOutput = {null};
try {
......
......@@ -7,6 +7,7 @@ package org.h2.command.dml;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import org.h2.api.ErrorCode;
import org.h2.command.Prepared;
......@@ -31,6 +32,7 @@ import org.h2.result.SortOrder;
import org.h2.table.ColumnResolver;
import org.h2.table.Table;
import org.h2.table.TableFilter;
import org.h2.util.StringUtils;
import org.h2.util.Utils;
import org.h2.value.Value;
import org.h2.value.ValueInt;
......@@ -46,6 +48,11 @@ public abstract class Query extends Prepared {
*/
protected Expression limitExpr;
/**
* Whether tied rows should be included in result too.
*/
protected boolean withTies;
/**
* The offset expression as specified in the LIMIT .. OFFSET clause.
*/
......@@ -255,14 +262,29 @@ public abstract class Query extends Prepared {
/**
* Set the distinct flag.
*
* @param b the new value
*/
public void setDistinct(boolean b) {
distinct = b;
public void setDistinct() {
distinct = true;
}
public boolean isDistinct() {
/**
* Set the distinct flag only if it is possible, may be used as a possible
* optimization only.
*/
public abstract void setDistinctIfPossible();
/**
* @return whether this query is a plain {@code DISTINCT} query
*/
public boolean isStandardDistinct() {
return distinct;
}
/**
* @return whether this query is a {@code DISTINCT} or
* {@code DISTINCT ON (...)} query
*/
public boolean isAnyDistinct() {
return distinct;
}
......@@ -401,102 +423,109 @@ public abstract class Query extends Prepared {
static void initOrder(Session session,
ArrayList<Expression> expressions,
ArrayList<String> expressionSQL,
ArrayList<SelectOrderBy> orderList,
List<SelectOrderBy> orderList,
int visible,
boolean mustBeInResult,
ArrayList<TableFilter> filters) {
Database db = session.getDatabase();
for (SelectOrderBy o : orderList) {
Expression e = o.expression;
if (e == null) {
continue;
}
// special case: SELECT 1 AS A FROM DUAL ORDER BY A
// (oracle supports it, but only in order by, not in group by and
// not in having):
// SELECT 1 AS A FROM DUAL ORDER BY -A
boolean isAlias = false;
int idx = expressions.size();
if (e instanceof ExpressionColumn) {
// order by expression
ExpressionColumn exprCol = (ExpressionColumn) e;
String tableAlias = exprCol.getOriginalTableAliasName();
String col = exprCol.getOriginalColumnName();
for (int j = 0; j < visible; j++) {
boolean found = false;
Expression ec = expressions.get(j);
if (ec instanceof ExpressionColumn) {
// select expression
ExpressionColumn c = (ExpressionColumn) ec;
found = db.equalsIdentifiers(col, c.getColumnName());
if (found && tableAlias != null) {
String ca = c.getOriginalTableAliasName();
if (ca == null) {
found = false;
if (filters != null) {
// select id from test order by test.id
for (TableFilter f : filters) {
if (db.equalsIdentifiers(f.getTableAlias(), tableAlias)) {
found = true;
break;
}
int idx = initExpression(session, expressions, expressionSQL, e, visible, mustBeInResult, filters);
o.columnIndexExpr = ValueExpression.get(ValueInt.get(idx + 1));
o.expression = expressions.get(idx).getNonAliasExpression();
}
}
static int initExpression(Session session, ArrayList<Expression> expressions,
ArrayList<String> expressionSQL, Expression e, int visible, boolean mustBeInResult,
ArrayList<TableFilter> filters) {
Database db = session.getDatabase();
// special case: SELECT 1 AS A FROM DUAL ORDER BY A
// (oracle supports it, but only in order by, not in group by and
// not in having):
// SELECT 1 AS A FROM DUAL ORDER BY -A
boolean isAlias = false;
int idx = expressions.size();
if (e instanceof ExpressionColumn) {
// order by expression
ExpressionColumn exprCol = (ExpressionColumn) e;
String tableAlias = exprCol.getOriginalTableAliasName();
String col = exprCol.getOriginalColumnName();
for (int j = 0; j < visible; j++) {
boolean found = false;
Expression ec = expressions.get(j);
if (ec instanceof ExpressionColumn) {
// select expression
ExpressionColumn c = (ExpressionColumn) ec;
found = db.equalsIdentifiers(col, c.getColumnName());
if (found && tableAlias != null) {
String ca = c.getOriginalTableAliasName();
if (ca == null) {
found = false;
if (filters != null) {
// select id from test order by test.id
for (TableFilter f : filters) {
if (db.equalsIdentifiers(f.getTableAlias(), tableAlias)) {
found = true;
break;
}
}
} else {
found = db.equalsIdentifiers(ca, tableAlias);
}
} else {
found = db.equalsIdentifiers(ca, tableAlias);
}
} else if (!(ec instanceof Alias)) {
continue;
} else if (tableAlias == null && db.equalsIdentifiers(col, ec.getAlias())) {
found = true;
} else {
Expression ec2 = ec.getNonAliasExpression();
if (ec2 instanceof ExpressionColumn) {
ExpressionColumn c2 = (ExpressionColumn) ec2;
String ta = exprCol.getSQL();
String tb = c2.getSQL();
String s2 = c2.getColumnName();
found = db.equalsIdentifiers(col, s2);
if (!db.equalsIdentifiers(ta, tb)) {
found = false;
}
}
} else if (!(ec instanceof Alias)) {
continue;
} else if (tableAlias == null && db.equalsIdentifiers(col, ec.getAlias())) {
found = true;
} else {
Expression ec2 = ec.getNonAliasExpression();
if (ec2 instanceof ExpressionColumn) {
ExpressionColumn c2 = (ExpressionColumn) ec2;
String ta = exprCol.getSQL();
String tb = c2.getSQL();
String s2 = c2.getColumnName();
found = db.equalsIdentifiers(col, s2);
if (!db.equalsIdentifiers(ta, tb)) {
found = false;
}
}
if (found) {
}
if (found) {
idx = j;
isAlias = true;
break;
}
}
} else {
String s = e.getSQL();
if (expressionSQL != null) {
for (int j = 0, size = expressionSQL.size(); j < size; j++) {
String s2 = expressionSQL.get(j);
if (db.equalsIdentifiers(s2, s)) {
idx = j;
isAlias = true;
break;
}
}
} else {
String s = e.getSQL();
if (expressionSQL != null) {
for (int j = 0, size = expressionSQL.size(); j < size; j++) {
String s2 = expressionSQL.get(j);
if (db.equalsIdentifiers(s2, s)) {
idx = j;
isAlias = true;
break;
}
}
}
}
if (!isAlias) {
if (mustBeInResult) {
if (session.getDatabase().getMode().getEnum() != ModeEnum.MySQL) {
if (!checkOrderOther(session, e, expressionSQL)) {
throw DbException.get(ErrorCode.ORDER_BY_NOT_IN_RESULT, e.getSQL());
}
}
if (!isAlias) {
if (mustBeInResult) {
if (session.getDatabase().getMode().getEnum() != ModeEnum.MySQL) {
if (!checkOrderOther(session, e, expressionSQL)) {
throw DbException.get(ErrorCode.ORDER_BY_NOT_IN_RESULT, e.getSQL());
}
}
expressions.add(e);
String sql = e.getSQL();
expressionSQL.add(sql);
}
o.columnIndexExpr = ValueExpression.get(ValueInt.get(idx + 1));
o.expression = expressions.get(idx).getNonAliasExpression();
expressions.add(e);
String sql = e.getSQL();
expressionSQL.add(sql);
}
return idx;
}
/**
......@@ -621,6 +650,14 @@ public abstract class Query extends Prepared {
return limitExpr;
}
public void setWithTies(boolean withTies) {
this.withTies = true;
}
public boolean isWithTies() {
return withTies;
}
/**
* Add a parameter to the parameter list.
*
......@@ -659,4 +696,22 @@ public abstract class Query extends Prepared {
isEverything(visitor);
return visitor.getMaxDataModificationId();
}
void appendLimitToSQL(StringBuilder buff) {
if (limitExpr != null) {
if (withTies) {
if (offsetExpr != null) {
buff.append("\nOFFSET ").append(StringUtils.unEnclose(offsetExpr.getSQL())).append(" ROWS");
}
buff.append("\nFETCH NEXT ").append(StringUtils.unEnclose(limitExpr.getSQL()))
.append(" ROWS WITH TIES");
} else {
buff.append("\nLIMIT ").append(StringUtils.unEnclose(limitExpr.getSQL()));
if (offsetExpr != null) {
buff.append("\nOFFSET ").append(StringUtils.unEnclose(offsetExpr.getSQL()));
}
}
}
}
}
......@@ -7,6 +7,7 @@ package org.h2.command.dml;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
......@@ -89,6 +90,13 @@ public class Select extends Query {
*/
int visibleColumnCount;
/**
* {@code DISTINCT ON(...)} expressions.
*/
private Expression[] distinctExpressions;
private int[] distinctIndexes;
private int distinctColumnCount;
private ArrayList<SelectOrderBy> orderList;
private ArrayList<Expression> group;
......@@ -247,6 +255,36 @@ public class Select extends Query {
return orderList != null || sort != null;
}
@Override
public void setDistinct() {
if (distinctExpressions != null) {
throw DbException.getUnsupportedException("DISTINCT ON together with DISTINCT");
}
distinct = true;
}
/**
* Set the distinct expressions.
*/
public void setDistinct(Expression[] distinctExpressions) {
if (distinct) {
throw DbException.getUnsupportedException("DISTINCT ON together with DISTINCT");
}
this.distinctExpressions = distinctExpressions;
}
@Override
public void setDistinctIfPossible() {
if (!isAnyDistinct() && offsetExpr == null && limitExpr == null) {
distinct = true;
}
}
@Override
public boolean isAnyDistinct() {
return distinct || distinctExpressions != null;
}
/**
* Add a condition to the list of conditions.
*
......@@ -548,7 +586,7 @@ public class Select extends Query {
return null;
}
private void queryDistinct(ResultTarget result, long limitRows) {
private void queryDistinct(ResultTarget result, long limitRows, boolean withTies) {
// limitRows must be long, otherwise we get an int overflow
// if limitRows is at or near Integer.MAX_VALUE
// limitRows is never 0 here
......@@ -580,7 +618,7 @@ public class Select extends Query {
result.addRow(row);
rowNumber++;
if ((sort == null || sortUsingIndex) && limitRows > 0 &&
rowNumber >= limitRows) {
rowNumber >= limitRows && !withTies) {
break;
}
if (sampleSize > 0 && rowNumber >= sampleSize) {
......@@ -589,7 +627,7 @@ public class Select extends Query {
}
}
private LazyResult queryFlat(int columnCount, ResultTarget result, long limitRows) {
private LazyResult queryFlat(int columnCount, ResultTarget result, long limitRows, boolean withTies) {
// limitRows must be long, otherwise we get an int overflow
// if limitRows is at or near Integer.MAX_VALUE
// limitRows is never 0 here
......@@ -606,7 +644,7 @@ public class Select extends Query {
if (result == null) {
return lazyResult;
}
if (sort != null && !sortUsingIndex || limitRows <= 0) {
if (sort != null && !sortUsingIndex || limitRows <= 0 || withTies) {
limitRows = Long.MAX_VALUE;
}
while (result.getRowCount() < limitRows && lazyResult.next()) {
......@@ -652,23 +690,25 @@ public class Select extends Query {
}
boolean lazy = session.isLazyQueryExecution() &&
target == null && !isForUpdate && !isQuickAggregateQuery &&
limitRows != 0 && offsetExpr == null && isReadOnly();
limitRows != 0 && !withTies && offsetExpr == null && isReadOnly();
int columnCount = expressions.size();
LocalResult result = null;
if (!lazy && (target == null ||
!session.getDatabase().getSettings().optimizeInsertFromSelect)) {
result = createLocalResult(result);
}
if (sort != null && (!sortUsingIndex || distinct)) {
if (sort != null && (!sortUsingIndex || isAnyDistinct() || withTies)) {
result = createLocalResult(result);
result.setSortOrder(sort);
}
if (distinct && !isDistinctQuery) {
result = createLocalResult(result);
result.setDistinct();
}
if (randomAccessResult) {
if (distinct) {
if (!isDistinctQuery) {
result = createLocalResult(result);
result.setDistinct();
}
} else if (distinctExpressions != null) {
result = createLocalResult(result);
result.setDistinct(distinctIndexes);
}
if (isGroupQuery && !isGroupSortedQuery) {
result = createLocalResult(result);
......@@ -683,7 +723,7 @@ public class Select extends Query {
if (isGroupQuery) {
throw DbException.getUnsupportedException(
"MVCC=TRUE && FOR UPDATE && GROUP");
} else if (distinct) {
} else if (isAnyDistinct()) {
throw DbException.getUnsupportedException(
"MVCC=TRUE && FOR UPDATE && DISTINCT");
} else if (isQuickAggregateQuery) {
......@@ -709,9 +749,9 @@ public class Select extends Query {
queryGroup(columnCount, result);
}
} else if (isDistinctQuery) {
queryDistinct(to, limitRows);
queryDistinct(to, limitRows, withTies);
} else {
lazyResult = queryFlat(columnCount, to, limitRows);
lazyResult = queryFlat(columnCount, to, limitRows, withTies);
}
} finally {
if (!lazy) {
......@@ -724,16 +764,24 @@ public class Select extends Query {
if (limitRows > 0) {
lazyResult.setLimit(limitRows);
}
return lazyResult;
if (randomAccessResult) {
return convertToDistinct(lazyResult);
} else {
return lazyResult;
}
}
if (offsetExpr != null) {
result.setOffset(offsetExpr.getValue(session).getInt());
}
if (limitRows >= 0) {
result.setLimit(limitRows);
result.setWithTies(withTies);
}
if (result != null) {
result.done();
if (randomAccessResult && !distinct) {
result = convertToDistinct(result);
}
if (target != null) {
while (result.next()) {
target.addRow(result.currentRow());
......@@ -761,6 +809,18 @@ public class Select extends Query {
visibleColumnCount);
}
private LocalResult convertToDistinct(ResultInterface result) {
LocalResult distinctResult = new LocalResult(session, expressionArray, visibleColumnCount);
distinctResult.setDistinct();
result.reset();
while (result.next()) {
distinctResult.addRow(result.currentRow());
}
result.close();
distinctResult.done();
return distinctResult;
}
private void expandColumnList() {
Database db = session.getDatabase();
......@@ -828,7 +888,7 @@ public class Select extends Query {
expandColumnList();
visibleColumnCount = expressions.size();
ArrayList<String> expressionSQL;
if (orderList != null || group != null) {
if (distinctExpressions != null || orderList != null || group != null) {
expressionSQL = new ArrayList<>(visibleColumnCount);
for (int i = 0; i < visibleColumnCount; i++) {
Expression expr = expressions.get(i);
......@@ -839,9 +899,23 @@ public class Select extends Query {
} else {
expressionSQL = null;
}
if (distinctExpressions != null) {
BitSet set = new BitSet();
for (Expression e : distinctExpressions) {
set.set(initExpression(session, expressions, expressionSQL, e, visibleColumnCount, false,
filters));
}
int idx = 0, cnt = set.cardinality();
distinctIndexes = new int[cnt];
for (int i = 0; i < cnt; i++) {
idx = set.nextSetBit(idx);
distinctIndexes[i] = idx;
idx++;
}
}
if (orderList != null) {
initOrder(session, expressions, expressionSQL, orderList,
visibleColumnCount, distinct, filters);
visibleColumnCount, isAnyDistinct(), filters);
}
distinctColumnCount = expressions.size();
if (having != null) {
......@@ -852,6 +926,10 @@ public class Select extends Query {
havingIndex = -1;
}
if (withTies && !hasOrder()) {
throw DbException.get(ErrorCode.WITH_TIES_WITHOUT_ORDER_BY);
}
Database db = session.getDatabase();
// first the select list (visible columns),
......@@ -1175,8 +1253,17 @@ public class Select extends Query {
}
buff.resetCount();
buff.append("SELECT");
if (distinct) {
if (isAnyDistinct()) {
buff.append(" DISTINCT");
if (distinctExpressions != null) {
buff.append(" ON(");
for (Expression distinctExpression: distinctExpressions) {
buff.appendExceptFirst(", ");
buff.append(distinctExpression.getSQL());
}
buff.append(')');
buff.resetCount();
}
}
for (int i = 0; i < visibleColumnCount; i++) {
buff.appendExceptFirst(",");
......@@ -1250,14 +1337,7 @@ public class Select extends Query {
buff.append(StringUtils.unEnclose(o.getSQL()));
}
}
if (limitExpr != null) {
buff.append("\nLIMIT ").append(
StringUtils.unEnclose(limitExpr.getSQL()));
if (offsetExpr != null) {
buff.append(" OFFSET ").append(
StringUtils.unEnclose(offsetExpr.getSQL()));
}
}
appendLimitToSQL(buff.builder());
if (sampleSizeExpr != null) {
buff.append("\nSAMPLE_SIZE ").append(
StringUtils.unEnclose(sampleSizeExpr.getSQL()));
......
......@@ -125,6 +125,11 @@ public class SelectUnion extends Query {
return orderList != null || sort != null;
}
@Override
public void setDistinctIfPossible() {
setDistinct();
}
private Value[] convert(Value[] values, int columnCount) {
Value[] newValues;
if (columnCount == values.length) {
......@@ -210,25 +215,22 @@ public class SelectUnion extends Query {
result.setSortOrder(sort);
}
if (distinct) {
left.setDistinct(true);
right.setDistinct(true);
left.setDistinctIfPossible();
right.setDistinctIfPossible();
result.setDistinct();
}
if (randomAccessResult) {
result.setRandomAccess();
}
switch (unionType) {
case UNION:
case EXCEPT:
left.setDistinct(true);
right.setDistinct(true);
left.setDistinctIfPossible();
right.setDistinctIfPossible();
result.setDistinct();
break;
case UNION_ALL:
break;
case INTERSECT:
left.setDistinct(true);
right.setDistinct(true);
left.setDistinctIfPossible();
right.setDistinctIfPossible();
break;
default:
DbException.throwInternalError("type=" + unionType);
......@@ -260,7 +262,6 @@ public class SelectUnion extends Query {
case INTERSECT: {
LocalResult temp = new LocalResult(session, expressionArray, columnCount);
temp.setDistinct();
temp.setRandomAccess();
while (l.next()) {
temp.addRow(convert(l.currentRow(), columnCount));
}
......@@ -283,6 +284,7 @@ public class SelectUnion extends Query {
Value v = limitExpr.getValue(session);
if (v != ValueNull.INSTANCE) {
result.setLimit(v.getInt());
result.setWithTies(withTies);
}
}
l.close();
......@@ -318,6 +320,9 @@ public class SelectUnion extends Query {
Expression l = le.get(i);
expressions.add(l);
}
if (withTies && !hasOrder()) {
throw DbException.get(ErrorCode.WITH_TIES_WITHOUT_ORDER_BY);
}
}
@Override
......@@ -445,14 +450,7 @@ public class SelectUnion extends Query {
if (sort != null) {
buff.append("\nORDER BY ").append(sort.getSQL(exprList, exprList.length));
}
if (limitExpr != null) {
buff.append("\nLIMIT ").append(
StringUtils.unEnclose(limitExpr.getSQL()));
if (offsetExpr != null) {
buff.append("\nOFFSET ").append(
StringUtils.unEnclose(offsetExpr.getSQL()));
}
}
appendLimitToSQL(buff);
if (sampleSizeExpr != null) {
buff.append("\nSAMPLE_SIZE ").append(
StringUtils.unEnclose(sampleSizeExpr.getSQL()));
......
......@@ -43,9 +43,7 @@ public class ConditionInSelect extends Condition {
@Override
public Value getValue(Session session) {
query.setSession(session);
if (!query.hasOrder()) {
query.setDistinct(true);
}
query.setDistinctIfPossible();
ResultInterface rows = query.query(0);
Value l = left.getValue(session);
if (!rows.hasNext()) {
......
......@@ -734,7 +734,8 @@ public class JdbcPreparedStatement extends JdbcStatement implements
if (x == null) {
setParameter(parameterIndex, ValueNull.INSTANCE);
} else {
setParameter(parameterIndex, DateTimeUtils.convertDate(x, calendar));
setParameter(parameterIndex,
calendar != null ? DateTimeUtils.convertDate(x, calendar) : ValueDate.get(x));
}
} catch (Exception e) {
throw logAndConvert(e);
......@@ -760,7 +761,8 @@ public class JdbcPreparedStatement extends JdbcStatement implements
if (x == null) {
setParameter(parameterIndex, ValueNull.INSTANCE);
} else {
setParameter(parameterIndex, DateTimeUtils.convertTime(x, calendar));
setParameter(parameterIndex,
calendar != null ? DateTimeUtils.convertTime(x, calendar) : ValueTime.get(x));
}
} catch (Exception e) {
throw logAndConvert(e);
......@@ -787,7 +789,8 @@ public class JdbcPreparedStatement extends JdbcStatement implements
if (x == null) {
setParameter(parameterIndex, ValueNull.INSTANCE);
} else {
setParameter(parameterIndex, DateTimeUtils.convertTimestamp(x, calendar));
setParameter(parameterIndex,
calendar != null ? DateTimeUtils.convertTimestamp(x, calendar) : ValueTimestamp.get(x));
}
} catch (Exception e) {
throw logAndConvert(e);
......
......@@ -2493,7 +2493,6 @@ public class JdbcResultSet extends TraceObject implements ResultSet, JdbcResultS
*
* @param columnIndex (1,2,...)
* @param x the value
* @param length the length
* @throws SQLException if the result set is closed or not updatable
*/
@Override
......@@ -2520,7 +2519,6 @@ public class JdbcResultSet extends TraceObject implements ResultSet, JdbcResultS
*
* @param columnLabel the column label
* @param x the value
* @param length the length
* @throws SQLException if the result set is closed or not updatable
*/
@Override
......@@ -3930,9 +3928,9 @@ public class JdbcResultSet extends TraceObject implements ResultSet, JdbcResultS
return type.cast(value == ValueNull.INSTANCE
? null : new JdbcSQLXML(conn, value, JdbcLob.State.WITH_VALUE, id));
} else if (type == TimestampWithTimeZone.class) {
return type.cast(value.getObject());
return type.cast(value.convertTo(Value.TIMESTAMP_TZ).getObject());
} else if (DataType.isGeometryClass(type)) {
return type.cast(value.getObject());
return type.cast(value.convertTo(Value.GEOMETRY).getObject());
} else if (type == LocalDateTimeUtils.LOCAL_DATE) {
return type.cast(LocalDateTimeUtils.valueToLocalDate(value));
} else if (type == LocalDateTimeUtils.LOCAL_TIME) {
......
......@@ -94,7 +94,7 @@ public class MVMap<K, V> extends AbstractMap<K, V>
@SuppressWarnings("unchecked")
private MVMap(MVStore store, DataType keyType, DataType valueType, int id, long createVersion,
AtomicReference<RootReference> root, int keysPerPage, boolean singleWriter) {
AtomicReference<RootReference> root, int keysPerPage, boolean singleWriter) {
this.store = store;
this.id = id;
this.createVersion = createVersion;
......@@ -1418,7 +1418,7 @@ public class MVMap<K, V> extends AbstractMap<K, V>
this.appendCounter = 0;
}
// This one is used for append buffer maintance
// This one is used for append buffer maintenance
RootReference(RootReference r, int appendCounter, int attempt) {
this.root = r.root;
this.version = r.version;
......
......@@ -5,8 +5,6 @@
*/
package org.h2.mvstore.db;
import java.util.Arrays;
import org.h2.engine.Database;
import org.h2.expression.Expression;
import org.h2.message.DbException;
......@@ -22,11 +20,6 @@ import org.h2.value.ValueArray;
*/
class MVPlainTempResult extends MVTempResult {
/**
* The type of the distinct values.
*/
private final ValueDataType distinctType;
/**
* Map with identities of rows as keys rows as values.
*/
......@@ -39,12 +32,6 @@ class MVPlainTempResult extends MVTempResult {
*/
private long counter;
/**
* Optional index. This index is created only if {@link #contains(Value[])}
* method is invoked. Only the root result should have an index if required.
*/
private MVMap<ValueArray, Boolean> index;
/**
* Cursor for the {@link #next()} method.
*/
......@@ -58,7 +45,6 @@ class MVPlainTempResult extends MVTempResult {
*/
private MVPlainTempResult(MVPlainTempResult parent) {
super(parent);
this.distinctType = null;
this.map = parent.map;
}
......@@ -75,46 +61,20 @@ class MVPlainTempResult extends MVTempResult {
MVPlainTempResult(Database database, Expression[] expressions, int visibleColumnCount) {
super(database, expressions.length, visibleColumnCount);
ValueDataType valueType = new ValueDataType(database.getCompareMode(), database, new int[columnCount]);
if (columnCount == visibleColumnCount) {
distinctType = valueType;
} else {
distinctType = new ValueDataType(database.getCompareMode(), database, new int[visibleColumnCount]);
}
Builder<Long, ValueArray> builder = new MVMap.Builder<Long, ValueArray>().valueType(valueType);
map = store.openMap("tmp", builder);
}
@Override
public int addRow(Value[] values) {
assert parent == null && index == null;
assert parent == null;
map.put(counter++, ValueArray.get(values));
return ++rowCount;
}
@Override
public boolean contains(Value[] values) {
// Only parent result maintains the index
if (parent != null) {
return parent.contains(values);
}
if (index == null) {
createIndex();
}
return index.containsKey(ValueArray.get(values));
}
private void createIndex() {
Builder<ValueArray, Boolean> builder = new MVMap.Builder<ValueArray, Boolean>().keyType(distinctType);
index = store.openMap("idx", builder);
Cursor<Long, ValueArray> c = map.cursor(null);
while (c.hasNext()) {
c.next();
ValueArray row = c.getValue();
if (columnCount != visibleColumnCount) {
row = ValueArray.get(Arrays.copyOf(row.getList(), visibleColumnCount));
}
index.putIfAbsent(row, true);
}
throw DbException.getUnsupportedException("contains()");
}
@Override
......
......@@ -29,10 +29,15 @@ import org.h2.value.ValueArray;
class MVSortedTempResult extends MVTempResult {
/**
* Whether this result is distinct.
* Whether this result is a standard distinct result.
*/
private final boolean distinct;
/**
* Distinct indexes for DISTINCT ON results.
*/
private final int[] distinctIndexes;
/**
* Mapping of indexes of columns to its positions in the store, or {@code null}
* if columns are not reordered.
......@@ -45,11 +50,6 @@ class MVSortedTempResult extends MVTempResult {
*/
private final MVMap<ValueArray, Long> map;
/**
* The type of the distinct values.
*/
private final ValueDataType distinctType;
/**
* Optional index. This index is created only if result is distinct and
* {@code columnCount != distinctColumnCount} or if
......@@ -84,9 +84,9 @@ class MVSortedTempResult extends MVTempResult {
private MVSortedTempResult(MVSortedTempResult parent) {
super(parent);
this.distinct = parent.distinct;
this.distinctIndexes = parent.distinctIndexes;
this.indexes = parent.indexes;
this.map = parent.map;
this.distinctType = null;
this.rowCount = parent.rowCount;
}
......@@ -99,16 +99,19 @@ class MVSortedTempResult extends MVTempResult {
* column expressions
* @param distinct
* whether this result should be distinct
* @param distinctIndexes
* indexes of distinct columns for DISTINCT ON results
* @param visibleColumnCount
* count of visible columns
* @param sort
* sort order, or {@code null} if this result does not need any
* sorting
*/
MVSortedTempResult(Database database, Expression[] expressions, boolean distinct, int visibleColumnCount,
SortOrder sort) {
MVSortedTempResult(Database database, Expression[] expressions, boolean distinct, int[] distinctIndexes,
int visibleColumnCount, SortOrder sort) {
super(database, expressions.length, visibleColumnCount);
this.distinct = distinct;
this.distinctIndexes = distinctIndexes;
int length = columnCount;
int[] sortTypes = new int[length];
int[] indexes;
......@@ -166,13 +169,11 @@ class MVSortedTempResult extends MVTempResult {
ValueDataType keyType = new ValueDataType(database.getCompareMode(), database, sortTypes);
Builder<ValueArray, Long> builder = new MVMap.Builder<ValueArray, Long>().keyType(keyType);
map = store.openMap("tmp", builder);
if (length == visibleColumnCount) {
distinctType = null;
} else {
distinctType = new ValueDataType(database.getCompareMode(), database, new int[visibleColumnCount]);
if (distinct) {
createIndex(false);
}
if (distinct && length != visibleColumnCount || distinctIndexes != null) {
int count = distinctIndexes != null ? distinctIndexes.length : visibleColumnCount;
ValueDataType distinctType = new ValueDataType(database.getCompareMode(), database, new int[count]);
Builder<ValueArray, Boolean> indexBuilder = new MVMap.Builder<ValueArray, Boolean>().keyType(distinctType);
index = store.openMap("idx", indexBuilder);
}
}
......@@ -180,8 +181,18 @@ class MVSortedTempResult extends MVTempResult {
public int addRow(Value[] values) {
assert parent == null;
ValueArray key = getKey(values);
if (distinct) {
if (columnCount != visibleColumnCount) {
if (distinct || distinctIndexes != null) {
if (distinctIndexes != null) {
int cnt = distinctIndexes.length;
Value[] newValues = new Value[cnt];
for (int i = 0; i < cnt; i++) {
newValues[i] = values[distinctIndexes[i]];
}
ValueArray distinctRow = ValueArray.get(newValues);
if (index.putIfAbsent(distinctRow, true) != null) {
return rowCount;
}
} else if (columnCount != visibleColumnCount) {
ValueArray distinctRow = ValueArray.get(Arrays.copyOf(values, visibleColumnCount));
if (index.putIfAbsent(distinctRow, true) != null) {
return rowCount;
......@@ -209,29 +220,13 @@ class MVSortedTempResult extends MVTempResult {
if (parent != null) {
return parent.contains(values);
}
assert distinct;
if (columnCount != visibleColumnCount) {
if (index == null) {
createIndex(true);
}
return index.containsKey(ValueArray.get(values));
}
return map.containsKey(getKey(values));
}
private void createIndex(boolean fill) {
Builder<ValueArray, Boolean> indexBuilder = new MVMap.Builder<ValueArray, Boolean>()
.keyType(distinctType);
index = store.openMap("idx", indexBuilder);
if (fill) {
Cursor<ValueArray, Long> c = map.cursor(null);
while (c.hasNext()) {
Value[] v = getValue(c.next().getList());
ValueArray distinctRow = ValueArray.get(Arrays.copyOf(v, visibleColumnCount));
index.putIfAbsent(distinctRow, true);
}
}
}
@Override
public synchronized ResultExternal createShallowCopy() {
if (parent != null) {
......
......@@ -66,6 +66,8 @@ public abstract class MVTempResult implements ResultExternal {
* expressions
* @param distinct
* is output distinct
* @param distinctIndexes
* indexes of distinct columns for DISTINCT ON results
* @param visibleColumnCount
* count of visible columns
* @param sort
......@@ -73,9 +75,9 @@ public abstract class MVTempResult implements ResultExternal {
* @return temporary result
*/
public static ResultExternal of(Database database, Expression[] expressions, boolean distinct,
int visibleColumnCount, SortOrder sort) {
return distinct || sort != null
? new MVSortedTempResult(database, expressions, distinct, visibleColumnCount, sort)
int[] distinctIndexes, int visibleColumnCount, SortOrder sort) {
return distinct || distinctIndexes != null || sort != null
? new MVSortedTempResult(database, expressions, distinct, distinctIndexes, visibleColumnCount, sort)
: new MVPlainTempResult(database, expressions, visibleColumnCount);
}
......
......@@ -152,7 +152,7 @@
90119=Uživatelský datový typ {0} již existuje
90120=Uživatelský datový typ {0} nenalezen
90121=Databáze byla již ukončena (pro deaktivaci automatického ukončení při zastavení virtuálního stroje přidejte parametr ";DB_CLOSE_ON_EXIT=FALSE" do URL databáze)
90122=Operace není podporována pro tabulku {0}, pokud na tabulku existují pohledy: {1}
90122=#The WITH TIES clause is not allowed without a corresponding ORDER BY clause.
90123=Nelze vzájemně míchat indexované a neindexované parametry
90124=Soubor nenalezen: {0}
90125=Neplatná třída, očekáváno {0}, ale obdrženo {1}
......
......@@ -152,7 +152,7 @@
90119=Benutzer-Datentyp {0} besteht bereits
90120=Benutzer-Datentyp {0} nicht gefunden
90121=Die Datenbank wurde bereits geschlossen (um das automatische Schliessen beim Stopp der VM zu deaktivieren, die Datenbank URL mit ";DB_CLOSE_ON_EXIT=FALSE" ergänzen)
90122=Funktion nicht unterstützt für Tabelle {0} wenn Views auf die Tabelle vorhanden sind: {1}
90122=#The WITH TIES clause is not allowed without a corresponding ORDER BY clause.
90123=Kann nicht indizierte und nicht indizierte Parameter mischen
90124=Datei nicht gefunden: {0}
90125=Ungültig Klasse, erwartet {0} erhalten {1}
......
......@@ -152,7 +152,7 @@
90119=User data type {0} already exists
90120=User data type {0} not found
90121=Database is already closed (to disable automatic closing at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL)
90122=Operation not supported for table {0} when there are views on the table: {1}
90122=The WITH TIES clause is not allowed without a corresponding ORDER BY clause.
90123=Cannot mix indexed and non-indexed parameters
90124=File not found: {0}
90125=Invalid class, expected {0} but got {1}
......
......@@ -152,7 +152,7 @@
90119=Tipo de dato de usuario {0} ya existe
90120=Tipo de dato de usuario {0} no encontrado
90121=La base de datos ya esta cerrada (para des-habilitar el cerrado automatico durante el shutdown de la VM, agregue ";DB_CLOSE_ON_EXIT=FALSE" a la URL de conexión)
90122=Operación no soportada para la tabla {0} cuando existen vistas sobre la tabla: {1}
90122=#The WITH TIES clause is not allowed without a corresponding ORDER BY clause.
90123=No se puede mezclar parametros indexados y no-indexados
90124=Archivo no encontrado: {0}
90125=Clase Invalida, se esperaba {0} pero se obtuvo {1}
......
......@@ -152,7 +152,7 @@
90119=Le type de données utilisateur {0} existe déjà
90120=Type de données utilisateur {0} non trouvé
90121=La base de données est déjà fermée (pour désactiver la fermeture automatique à l'arrêt de la VM, ajoutez "; DB_CLOSE_ON_EXIT = FALSE" à l'URL db)
90122=Opération non prise en charge pour la table {0} lorsqu'il existe des vues sur la table: {1}
90122=#The WITH TIES clause is not allowed without a corresponding ORDER BY clause.
90123=Impossible de mélanger des paramètres indexés et non-indexés
90124=Fichier non trouvé: {0}
90125=Classe invalide, attendue {0} mais obtenue {1}
......
......@@ -152,7 +152,7 @@
90119=ユーザデータ型 {0} はすでに存在します
90120=ユーザデータ型 {0} が見つかりません
90121=データベースはすでに閉じられています (VM終了時の自動データベースクローズを無効にするためには、db URLに ";DB_CLOSE_ON_EXIT=FALSE" を追加してください)
90122=ビューが存在するテーブル {0} に対する操作はサポートされていません: {1}
90122=#The WITH TIES clause is not allowed without a corresponding ORDER BY clause.
90123=インデックスの付いたパラメータと付いていないパラメータを混在させることはできません
90124=ファイルが見つかりません: {0}
90125=無効なクラス, {0} が期待されているにもかかわらず {1} を取得しました
......
......@@ -152,7 +152,7 @@
90119=Typ danych użytkownika {0} już istnieje
90120=Typ danych użytkownika {0} nie istnieje
90121=Baza danych jest już zamknięta (aby zablokować samoczynne zamykanie podczas zamknięcia VM dodaj ";DB_CLOSE_ON_EXIT=FALSE" do URL bazy danych)
90122=Operacja nie jest dozwolona dla tabeli {0} gdy istnieją widoki oparte na tabeli: {1}
90122=#The WITH TIES clause is not allowed without a corresponding ORDER BY clause.
90123=Nie można mieszać parametrów indeksowych z nieindeksowymi
90124=Plik nie istnieje: {0}
90125=Nieprawidłowa klasa, oczekiwano {0}, a jest {1}
......
......@@ -152,7 +152,7 @@
90119=Tipo de dados do usuário {0} já existe
90120=Tipo de dados do usuário {0} não foram encontrados
90121=Base de dados já está fechada (para desabilitar o fechamento automático quando a VM terminar, addicione ";DB_CLOSE_ON_EXIT=FALSE" na url da base de dados)
90122=Operação não suportada para a tabela {0} quando existe alguma vista sobre a tabela: {1}
90122=#The WITH TIES clause is not allowed without a corresponding ORDER BY clause.
90123=Não pode combinar parâmetros de índices com não índices
90124=Arquivo não encontrado: {0}
90125=Classe inválida, experada {0} mas está {1}
......
......@@ -152,7 +152,7 @@
90119=Объект с именем {0} уже существует
90120=Домен {0} не найден
90121=База данных уже закрыта (чтобы отключить автоматическое закрытие базы данных при останове JVM, добавьте ";DB_CLOSE_ON_EXIT=FALSE" в URL)
90122=Операция для таблицы {0} не поддерживается, пока существуют представления: {1}
90122=Ограничение WITH TIES использовано без соответствующего раздела ORDER BY.
90123=Одновременное использование индексированных и неиндексированных параметров в запросе не поддерживается
90124=Файл не найден: {0}
90125=Недопустимый класс, ожидался {0}, но получен {1}
......
......@@ -152,7 +152,7 @@
90119=Používateľský dátový typ {0} už existuje
90120=Používateľský dátový typ {0} nenájdený
90121=Databáza už je zatvorená (na zamedzenie automatického zatvárania pri ukončení VM, pridajte ";DB_CLOSE_ON_EXIT=FALSE" do DB URL)
90122=Operácia pre tabuľku {0} nie je podporovaná, kedže existujú na tabuľku pohľady (views): {1}
90122=#The WITH TIES clause is not allowed without a corresponding ORDER BY clause.
90123=Nemožno miešať indexované a neindexované parametre
90124=Súbor nenájdený: {0}
90125=Nesprávna trieda {1}, očakávana je {0}
......
......@@ -152,7 +152,7 @@
90119=用户数据类型 {0} 已存在
90120=找不到用户数据类型 {0}
90121=数据库已关闭 (若需要禁用在虚拟机关闭的时候同时关闭数据库,请加上 ";DB_CLOSE_ON_EXIT=FALSE" 到数据库连接的 URL)
90122={0}表不支持本操作,因为在表上存在视图: {1}
90122=#The WITH TIES clause is not allowed without a corresponding ORDER BY clause.
90123=不能混合已索引和未索引的参数
90124=文件 找不到: {0}
90125=无效的类, 取代找到 {0} 但得到 {1}
......
......@@ -41,9 +41,10 @@ public class LocalResult implements ResultInterface, ResultTarget {
private Value[] currentRow;
private int offset;
private int limit = -1;
private boolean withTies;
private ResultExternal external;
private boolean distinct;
private boolean randomAccess;
private int[] distinctIndexes;
private boolean closed;
private boolean containsLobs;
......@@ -151,7 +152,7 @@ public class LocalResult implements ResultInterface, ResultTarget {
copy.sort = this.sort;
copy.distinctRows = this.distinctRows;
copy.distinct = distinct;
copy.randomAccess = randomAccess;
copy.distinctIndexes = distinctIndexes;
copy.currentRow = null;
copy.offset = 0;
copy.limit = -1;
......@@ -172,15 +173,27 @@ public class LocalResult implements ResultInterface, ResultTarget {
* Remove duplicate rows.
*/
public void setDistinct() {
assert distinctIndexes == null;
distinct = true;
distinctRows = ValueHashMap.newInstance();
}
/**
* Random access is required (containsDistinct).
* Remove rows with duplicates in columns with specified indexes.
*
* @param distinctIndexes distinct indexes
*/
public void setDistinct(int[] distinctIndexes) {
assert !distinct;
this.distinctIndexes = distinctIndexes;
distinctRows = ValueHashMap.newInstance();
}
/**
* @return whether this result is a distinct result
*/
public void setRandomAccess() {
this.randomAccess = true;
public boolean isAnyDistinct() {
return distinct || distinctIndexes != null;
}
/**
......@@ -217,7 +230,7 @@ public class LocalResult implements ResultInterface, ResultTarget {
if (distinctRows == null) {
distinctRows = ValueHashMap.newInstance();
for (Value[] row : rows) {
ValueArray array = getArrayOfVisible(row);
ValueArray array = getArrayOfDistinct(row);
distinctRows.put(array, array.getList());
}
}
......@@ -278,8 +291,15 @@ public class LocalResult implements ResultInterface, ResultTarget {
}
}
private ValueArray getArrayOfVisible(Value[] values) {
if (values.length > visibleColumnCount) {
private ValueArray getArrayOfDistinct(Value[] values) {
if (distinctIndexes != null) {
int cnt = distinctIndexes.length;
Value[] newValues = new Value[cnt];
for (int i = 0; i < cnt; i++) {
newValues[i] = values[distinctIndexes[i]];
}
values = newValues;
} else if (values.length > visibleColumnCount) {
values = Arrays.copyOf(values, visibleColumnCount);
}
return ValueArray.get(values);
......@@ -288,7 +308,9 @@ public class LocalResult implements ResultInterface, ResultTarget {
private void createExternalResult() {
Database database = session.getDatabase();
external = database.isMVStore()
? MVTempResult.of(database, expressions, distinct, visibleColumnCount, sort)
|| /* not supported by ResultTempTable */ distinct && expressions.length != visibleColumnCount
|| distinctIndexes != null
? MVTempResult.of(database, expressions, distinct, distinctIndexes, visibleColumnCount, sort)
: new ResultTempTable(session, expressions, distinct, sort);
}
......@@ -300,10 +322,10 @@ public class LocalResult implements ResultInterface, ResultTarget {
@Override
public void addRow(Value[] values) {
cloneLobs(values);
if (distinct) {
if (isAnyDistinct()) {
if (distinctRows != null) {
ValueArray array = getArrayOfVisible(values);
distinctRows.put(array, values);
ValueArray array = getArrayOfDistinct(values);
distinctRows.putIfAbsent(array, values);
rowCount = distinctRows.size();
if (rowCount > maxMemoryRows) {
createExternalResult();
......@@ -342,12 +364,13 @@ public class LocalResult implements ResultInterface, ResultTarget {
if (external != null) {
addRowsToDisk();
} else {
if (distinct) {
if (isAnyDistinct()) {
rows = distinctRows.values();
}
if (sort != null) {
if (offset > 0 || limit > 0) {
sort.sort(rows, offset, limit < 0 ? rows.size() : limit);
if (sort != null && limit != 0) {
boolean withLimit = limit > 0 && !withTies;
if (offset > 0 || withLimit) {
sort.sort(rows, offset, withLimit ? limit : rows.size());
} else {
sort.sort(rows);
}
......@@ -380,8 +403,16 @@ public class LocalResult implements ResultInterface, ResultTarget {
rows.clear();
return;
}
int to = offset + limit;
if (withTies && sort != null) {
Value[] expected = rows.get(to - 1);
while (to < rows.size() && sort.compare(expected, rows.get(to)) == 0) {
to++;
rowCount++;
}
}
// avoid copying the whole array for each row
rows = new ArrayList<>(rows.subList(offset, offset + limit));
rows = new ArrayList<>(rows.subList(offset, to));
} else {
if (clearAll) {
external.close();
......@@ -399,12 +430,24 @@ public class LocalResult implements ResultInterface, ResultTarget {
while (--offset >= 0) {
temp.next();
}
Value[] row = null;
while (--limit >= 0) {
rows.add(temp.next());
row = temp.next();
rows.add(row);
if (rows.size() > maxMemoryRows) {
addRowsToDisk();
}
}
if (withTies && sort != null && row != null) {
Value[] expected = row;
while ((row = temp.next()) != null && sort.compare(expected, row) == 0) {
rows.add(row);
rowCount++;
if (rows.size() > maxMemoryRows) {
addRowsToDisk();
}
}
}
if (external != null) {
addRowsToDisk();
}
......@@ -430,6 +473,13 @@ public class LocalResult implements ResultInterface, ResultTarget {
this.limit = limit;
}
/**
* @param withTies whether tied rows should be included in result too
*/
public void setWithTies(boolean withTies) {
this.withTies = withTies;
}
@Override
public boolean needToClose() {
return external != null;
......
......@@ -144,13 +144,48 @@ public class ResultTempTable implements ResultExternal {
closeable = new CloseImpl(session, table);
fileRef = tempFileDeleter.addFile(closeable, this);
}
/*
* If ORDER BY or DISTINCT is specified create the index immediately. If
* they are not specified index still may be created later if required
* for IN (SELECT ...) etc.
*/
if (sort != null || distinct) {
getIndex();
IndexColumn[] indexCols;
if (sort != null) {
int[] colIndex = sort.getQueryColumnIndexes();
int len = colIndex.length;
if (distinct) {
BitSet used = new BitSet();
indexCols = new IndexColumn[columnCount];
for (int i = 0; i < len; i++) {
int idx = colIndex[i];
used.set(idx);
IndexColumn indexColumn = createIndexColumn(idx);
indexColumn.sortType = sort.getSortTypes()[i];
indexCols[i] = indexColumn;
}
int idx = 0;
for (int i = len; i < columnCount; i++) {
idx = used.nextClearBit(idx);
indexCols[i] = createIndexColumn(idx);
idx++;
}
} else {
indexCols = new IndexColumn[len];
for (int i = 0; i < len; i++) {
IndexColumn indexColumn = createIndexColumn(colIndex[i]);
indexColumn.sortType = sort.getSortTypes()[i];
indexCols[i] = indexColumn;
}
}
} else {
indexCols = new IndexColumn[columnCount];
for (int i = 0; i < columnCount; i++) {
indexCols[i] = createIndexColumn(i);
}
}
String indexName = table.getSchema().getUniqueIndexName(session, table, Constants.PREFIX_INDEX);
int indexId = session.getDatabase().allocateObjectId();
IndexType indexType = IndexType.createNonUnique(true);
index = table.addIndex(session, indexName, indexId, indexCols, indexType, true, null);
if (closeable != null) {
closeable.index = index;
}
}
}
......@@ -171,50 +206,6 @@ public class ResultTempTable implements ResultExternal {
if (parent != null) {
return parent.getIndex();
}
if (index != null) {
return index;
}
IndexColumn[] indexCols;
if (sort != null) {
int[] colIndex = sort.getQueryColumnIndexes();
int len = colIndex.length;
if (distinct) {
BitSet used = new BitSet();
indexCols = new IndexColumn[columnCount];
for (int i = 0; i < len; i++) {
int idx = colIndex[i];
used.set(idx);
IndexColumn indexColumn = createIndexColumn(idx);
indexColumn.sortType = sort.getSortTypes()[i];
indexCols[i] = indexColumn;
}
int idx = 0;
for (int i = len; i < columnCount; i++) {
idx = used.nextClearBit(idx);
indexCols[i] = createIndexColumn(idx);
idx++;
}
} else {
indexCols = new IndexColumn[len];
for (int i = 0; i < len; i++) {
IndexColumn indexColumn = createIndexColumn(colIndex[i]);
indexColumn.sortType = sort.getSortTypes()[i];
indexCols[i] = indexColumn;
}
}
} else {
indexCols = new IndexColumn[columnCount];
for (int i = 0; i < columnCount; i++) {
indexCols[i] = createIndexColumn(i);
}
}
String indexName = table.getSchema().getUniqueIndexName(session, table, Constants.PREFIX_INDEX);
int indexId = session.getDatabase().allocateObjectId();
IndexType indexType = IndexType.createNonUnique(true);
index = table.addIndex(session, indexName, indexId, indexCols, indexType, true, null);
if (closeable != null) {
closeable.index = index;
}
return index;
}
......
......@@ -13,7 +13,6 @@ import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import org.h2.engine.Mode;
import org.h2.message.DbException;
import org.h2.value.Value;
import org.h2.value.ValueDate;
import org.h2.value.ValueNull;
......@@ -278,9 +277,6 @@ public class DateTimeUtils {
* @return the date
*/
public static ValueDate convertDate(Date x, Calendar calendar) {
if (calendar == null) {
throw DbException.getInvalidValueException("calendar", null);
}
Calendar cal = (Calendar) calendar.clone();
cal.setTimeInMillis(x.getTime());
long dateValue = dateValueFromCalendar(cal);
......@@ -295,9 +291,6 @@ public class DateTimeUtils {
* @return the time
*/
public static ValueTime convertTime(Time x, Calendar calendar) {
if (calendar == null) {
throw DbException.getInvalidValueException("calendar", null);
}
Calendar cal = (Calendar) calendar.clone();
cal.setTimeInMillis(x.getTime());
long nanos = nanosFromCalendar(cal);
......@@ -313,9 +306,6 @@ public class DateTimeUtils {
*/
public static ValueTimestamp convertTimestamp(Timestamp x,
Calendar calendar) {
if (calendar == null) {
throw DbException.getInvalidValueException("calendar", null);
}
Calendar cal = (Calendar) calendar.clone();
cal.setTimeInMillis(x.getTime());
long dateValue = dateValueFromCalendar(cal);
......
......@@ -66,7 +66,7 @@ public class ValueHashMap<V> extends HashBase {
if (k != null && k != ValueNull.DELETED) {
// skip the checkSizePut so we don't end up
// accidentally recursing
internalPut(k, oldValues[i]);
internalPut(k, oldValues[i], false);
}
}
}
......@@ -88,10 +88,21 @@ public class ValueHashMap<V> extends HashBase {
*/
public void put(Value key, V value) {
checkSizePut();
internalPut(key, value);
internalPut(key, value, false);
}
private void internalPut(Value key, V value) {
/**
* Add a key value pair, values for existing keys are not replaced.
*
* @param key the key
* @param value the new value
*/
public void putIfAbsent(Value key, V value) {
checkSizePut();
internalPut(key, value, true);
}
private void internalPut(Value key, V value, boolean ifAbsent) {
int index = getIndex(key);
int plus = 1;
int deleted = -1;
......@@ -113,6 +124,9 @@ public class ValueHashMap<V> extends HashBase {
deleted = index;
}
} else if (k.equals(key)) {
if (ifAbsent) {
return;
}
// update existing
values[index] = value;
return;
......
......@@ -1246,7 +1246,8 @@ public class TestFunctions extends TestDb implements AggregateFunction {
Connection conn = getConnection("functions");
Statement stat = conn.createStatement();
stat.execute("CREATE TABLE TEST(S VARCHAR, TS TIMESTAMP, D DATE, T TIME, TZ TIMESTAMP WITH TIME ZONE)");
stat.execute("INSERT INTO TEST VALUES ('2010-01-01 10:11:12', '2010-01-01 10:11:12', '2010-01-01', '10:11:12', '2010-01-01 10:11:12Z')");
stat.execute("INSERT INTO TEST VALUES ('2010-01-01 10:11:12', '2010-01-01 10:11:12',"
+ " '2010-01-01', '10:11:12', '2010-01-01 10:11:12Z')");
ResultSetMetaData md = stat.executeQuery("SELECT DATE_TRUNC('HOUR', S), DATE_TRUNC('HOUR', TS),"
+ " DATE_TRUNC('HOUR', D), DATE_TRUNC('HOUR', T), DATE_TRUNC('HOUR', TZ) FROM TEST")
.getMetaData();
......
......@@ -149,6 +149,13 @@ public class TestSpatial extends TestDb {
new Coordinate(2, 2),
new Coordinate(1, 1) });
assertTrue(polygon.equals(rs.getObject(2)));
rs.close();
rs = stat.executeQuery("select id, cast(polygon as varchar) from test");
assertTrue(rs.next());
assertEquals(1, rs.getInt(1));
assertEquals("POLYGON ((1 1, 1 2, 2 2, 1 1))", rs.getObject(2));
assertTrue(polygon.equals(rs.getObject(2, Geometry.class)));
rs.close();
rs = stat.executeQuery("select * from test where polygon = " +
"'POLYGON ((1 1, 1 2, 2 2, 1 1))'");
......
......@@ -1497,6 +1497,12 @@ public class TestResultSet extends TestDb {
java.sql.Timestamp.valueOf("2107-08-09 10:11:12.131415"));
prep.execute();
prep.setInt(1, 5);
prep.setDate(2, java.sql.Date.valueOf("2101-02-03"), null);
prep.setTime(3, java.sql.Time.valueOf("14:05:06"), null);
prep.setTimestamp(4, java.sql.Timestamp.valueOf("2107-08-09 10:11:12.131415"), null);
prep.execute();
rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID");
assertResultSetMeta(rs, 4,
new String[] { "ID", "D", "T", "TS" },
......@@ -1545,6 +1551,13 @@ public class TestResultSet extends TestDb {
assertEquals("14:05:06", rs.getTime("T").toString());
assertEquals("2101-02-03", rs.getDate("D").toString());
rs.next();
assertEquals(5, rs.getInt("ID"));
assertEquals("2107-08-09 10:11:12.131415",
rs.getTimestamp("TS").toString());
assertEquals("14:05:06", rs.getTime("T").toString());
assertEquals("2101-02-03", rs.getDate("D").toString());
assertFalse(rs.next());
stat.execute("DROP TABLE TEST");
}
......
......@@ -129,7 +129,7 @@ public class TestScript extends TestDb {
testScript("ddl/" + s + ".sql");
}
for (String s : new String[] { "error_reporting", "insertIgnore", "merge", "mergeUsing", "replace",
"script", "show", "with" }) {
"script", "select", "show", "with" }) {
testScript("dml/" + s + ".sql");
}
for (String s : new String[] { "help" }) {
......
......@@ -103,3 +103,49 @@ DROP TABLE TEST;
DROP TABLE TEST2;
> ok
CREATE TABLE TEST(C1 INT, C2 INT, C3 INT, C4 INT, C5 INT);
> ok
INSERT INTO TEST VALUES(1, 2, 3, 4, 5), (1, 2, 3, 6, 7), (2, 1, 4, 8, 9), (3, 4, 5, 1, 1);
> update count: 4
SELECT DISTINCT ON(C1, C2) C1, C2, C3, C4, C5 FROM TEST;
> C1 C2 C3 C4 C5
> -- -- -- -- --
> 1 2 3 4 5
> 2 1 4 8 9
> 3 4 5 1 1
> rows: 3
SELECT DISTINCT ON(C1 + C2) C1, C2, C3, C4, C5 FROM TEST;
> C1 C2 C3 C4 C5
> -- -- -- -- --
> 1 2 3 4 5
> 3 4 5 1 1
> rows: 2
SELECT DISTINCT ON(C1 + C2, C3) C1, C2, C3, C4, C5 FROM TEST;
> C1 C2 C3 C4 C5
> -- -- -- -- --
> 1 2 3 4 5
> 2 1 4 8 9
> 3 4 5 1 1
> rows: 3
SELECT DISTINCT ON(C1) C2 FROM TEST ORDER BY C1;
> C2
> --
> 2
> 1
> 4
> rows (ordered): 3
EXPLAIN SELECT DISTINCT ON(C1) C2 FROM TEST ORDER BY C1;
>> SELECT DISTINCT ON(C1) C2 FROM PUBLIC.TEST /* PUBLIC.TEST.tableScan */ ORDER BY =C1
SELECT DISTINCT ON(C1) C2 FROM TEST ORDER BY C3;
> exception ORDER_BY_NOT_IN_RESULT
DROP TABLE TEST;
> ok
-- 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
--
CREATE TABLE TEST(A INT, B INT, C INT);
> ok
INSERT INTO TEST VALUES (1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 2, 1), (1, 2, 2), (1, 2, 3),
(2, 1, 1), (2, 1, 2), (2, 1, 3), (2, 2, 1), (2, 2, 2), (2, 2, 3);
> update count: 12
SELECT * FROM TEST ORDER BY A, B;
> A B C
> - - -
> 1 1 1
> 1 1 2
> 1 1 3
> 1 2 1
> 1 2 2
> 1 2 3
> 2 1 1
> 2 1 2
> 2 1 3
> 2 2 1
> 2 2 2
> 2 2 3
> rows (ordered): 12
SELECT * FROM TEST ORDER BY A, B, C FETCH FIRST 4 ROWS ONLY;
> A B C
> - - -
> 1 1 1
> 1 1 2
> 1 1 3
> 1 2 1
> rows (ordered): 4
SELECT * FROM TEST ORDER BY A, B, C FETCH FIRST 4 ROWS WITH TIES;
> A B C
> - - -
> 1 1 1
> 1 1 2
> 1 1 3
> 1 2 1
> rows: 4
SELECT * FROM TEST ORDER BY A, B FETCH FIRST 4 ROWS WITH TIES;
> A B C
> - - -
> 1 1 1
> 1 1 2
> 1 1 3
> 1 2 1
> 1 2 2
> 1 2 3
> rows: 6
SELECT * FROM TEST ORDER BY A FETCH FIRST 1 ROW WITH TIES;
> A B C
> - - -
> 1 1 1
> 1 1 2
> 1 1 3
> 1 2 1
> 1 2 2
> 1 2 3
> rows: 6
SELECT TOP (1) WITH TIES * FROM TEST ORDER BY A;
> A B C
> - - -
> 1 1 1
> 1 1 2
> 1 1 3
> 1 2 1
> 1 2 2
> 1 2 3
> rows: 6
SELECT * FROM TEST ORDER BY A, B OFFSET 3 ROWS FETCH NEXT 1 ROW WITH TIES;
> A B C
> - - -
> 1 2 1
> 1 2 2
> 1 2 3
> rows: 3
CREATE INDEX TEST_A_IDX ON TEST(A);
> ok
CREATE INDEX TEST_A_B_IDX ON TEST(A, B);
> ok
SELECT * FROM TEST ORDER BY A FETCH FIRST 1 ROW WITH TIES;
> A B C
> - - -
> 1 1 1
> 1 1 2
> 1 1 3
> 1 2 1
> 1 2 2
> 1 2 3
> rows: 6
SELECT * FROM TEST ORDER BY A, B OFFSET 3 ROWS FETCH NEXT 1 ROW WITH TIES;
> A B C
> - - -
> 1 2 1
> 1 2 2
> 1 2 3
> rows: 3
SELECT * FROM TEST FETCH FIRST 1 ROW WITH TIES;
> exception WITH_TIES_WITHOUT_ORDER_BY
(SELECT * FROM TEST) UNION (SELECT 1, 2, 4) ORDER BY A, B OFFSET 3 ROWS FETCH NEXT 1 ROW WITH TIES;
> A B C
> - - -
> 1 2 1
> 1 2 2
> 1 2 3
> 1 2 4
> rows: 4
(SELECT * FROM TEST) UNION (SELECT 1, 2, 4) FETCH NEXT 1 ROW WITH TIES;
> exception WITH_TIES_WITHOUT_ORDER_BY
EXPLAIN SELECT * FROM TEST ORDER BY A, B OFFSET 3 ROWS FETCH NEXT 1 ROW WITH TIES;
>> SELECT TEST.A, TEST.B, TEST.C FROM PUBLIC.TEST /* PUBLIC.TEST_A_B_IDX */ ORDER BY 1, 2 OFFSET 3 ROWS FETCH NEXT 1 ROWS WITH TIES /* index sorted */
DROP TABLE TEST;
> ok
CREATE TABLE TEST(A NUMERIC, B NUMERIC);
> ok
INSERT INTO TEST VALUES (0, 1), (0.0, 2), (0, 3), (1, 4);
> update count: 4
SELECT A, B FROM TEST ORDER BY A FETCH FIRST 1 ROW WITH TIES;
> A B
> --- -
> 0 1
> 0 3
> 0.0 2
> rows: 3
DROP TABLE TEST;
> ok
......@@ -69,7 +69,8 @@ public class TestTimeStampWithTimeZone extends TestDb {
assertEquals(1, ts.getMonth());
assertEquals(1, ts.getDay());
assertEquals(15, ts.getTimeZoneOffsetMins());
assertEquals(new TimestampWithTimeZone(1008673L, 43200000000000L, (short) 15), ts);
TimestampWithTimeZone firstExpected = new TimestampWithTimeZone(1008673L, 43200000000000L, (short) 15);
assertEquals(firstExpected, ts);
if (LocalDateTimeUtils.isJava8DateApiPresent()) {
assertEquals("1970-01-01T12:00+00:15", rs.getObject(1,
LocalDateTimeUtils.OFFSET_DATE_TIME).toString());
......@@ -125,6 +126,11 @@ public class TestTimeStampWithTimeZone extends TestDb {
assertEquals(2014, columnType);
rs.close();
rs = stat.executeQuery("select cast(t1 as varchar) from test");
assertTrue(rs.next());
assertEquals(firstExpected, rs.getObject(1, TimestampWithTimeZone.class));
stat.close();
conn.close();
}
......
......@@ -446,7 +446,8 @@ public class TestValue extends TestDb {
}
}
private static int testLobComparisonImpl(DataHandler dh, int type, int size1, int size2, int suffix1, int suffix2) {
private static int testLobComparisonImpl(DataHandler dh, int type, int size1, int size2, int suffix1,
int suffix2) {
byte[] bytes1 = new byte[size1];
byte[] bytes2 = new byte[size2];
if (size1 > 0) {
......
......@@ -784,3 +784,6 @@ ewkt ewkb informations authzpwd realms mappers jaxb realmname configurationfile
authenticators appname interrogate metatable barrier preliminary staticuser staticpassword unregistered inquiry
ldapexample remoteuser assignments djava validators mock relate mapid tighten
retried helpers unclean missed parsers sax myclass suppose mandatory testxml miao ciao
emptied titlecase ask snk dom xif transformer dbf stx stax xof descriptors
inconsistencies discover eliminated violates tweaks postpone leftovers
tied ties
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论