提交 42e33ea1 authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov

Return same datetime values only within a command in compatibility modes

上级 e03d7a25
...@@ -4496,7 +4496,8 @@ CALL TRANSLATE('Hello world', 'eo', 'EO') ...@@ -4496,7 +4496,8 @@ CALL TRANSLATE('Hello world', 'eo', 'EO')
{ CURRENT_DATE [ () ] | CURDATE() | SYSDATE | TODAY } { CURRENT_DATE [ () ] | CURDATE() | SYSDATE | TODAY }
"," ","
Returns the current date. Returns the current date.
This method always returns the same value within a transaction. These methods always return the same value within a transaction (default)
or within a command depending on database mode.
"," ","
CURRENT_DATE() CURRENT_DATE()
" "
...@@ -4509,7 +4510,8 @@ If fractional seconds precision is specified it should be from 0 to 9, 0 is defa ...@@ -4509,7 +4510,8 @@ If fractional seconds precision is specified it should be from 0 to 9, 0 is defa
The specified value can be used only to limit precision of a result. The specified value can be used only to limit precision of a result.
The actual maximum available precision depends on operating system and JVM and can be 3 (milliseconds) or higher. The actual maximum available precision depends on operating system and JVM and can be 3 (milliseconds) or higher.
Higher precision is not available before Java 9. Higher precision is not available before Java 9.
These methods always return the same value within a transaction. These methods always return the same value within a transaction (default)
or within a command depending on database mode.
"," ","
CURRENT_TIME() CURRENT_TIME()
" "
...@@ -4523,7 +4525,8 @@ If fractional seconds precision is specified it should be from 0 to 9, 6 is defa ...@@ -4523,7 +4525,8 @@ If fractional seconds precision is specified it should be from 0 to 9, 6 is defa
The specified value can be used only to limit precision of a result. The specified value can be used only to limit precision of a result.
The actual maximum available precision depends on operating system and JVM and can be 3 (milliseconds) or higher. The actual maximum available precision depends on operating system and JVM and can be 3 (milliseconds) or higher.
Higher precision is not available before Java 9. Higher precision is not available before Java 9.
This method always returns the same value within a transaction. This method always returns the same value within a transaction (default)
or within a command depending on database mode.
"," ","
CURRENT_TIMESTAMP() CURRENT_TIMESTAMP()
" "
...@@ -4536,7 +4539,8 @@ If fractional seconds precision is specified it should be from 0 to 9, 6 is defa ...@@ -4536,7 +4539,8 @@ If fractional seconds precision is specified it should be from 0 to 9, 6 is defa
The specified value can be used only to limit precision of a result. The specified value can be used only to limit precision of a result.
The actual maximum available precision depends on operating system and JVM and can be 3 (milliseconds) or higher. The actual maximum available precision depends on operating system and JVM and can be 3 (milliseconds) or higher.
Higher precision is not available before Java 9. Higher precision is not available before Java 9.
These methods always return the same value within a transaction. These methods always return the same value within a transaction (default)
or within a command depending on database mode.
"," ","
LOCALTIMESTAMP() LOCALTIMESTAMP()
" "
......
...@@ -21,6 +21,12 @@ Change Log ...@@ -21,6 +21,12 @@ Change Log
<h2>Next Version (unreleased)</h2> <h2>Next Version (unreleased)</h2>
<ul> <ul>
<li>Issue #1536: CURRENT_TIMESTAMP result doesn't change under Transactions
</li>
<li>Issue #239: Consider supporting Lucene 5 indexes
</li>
<li>PR #1520: Fixes bug in PutIfAbsentDecisionMaker
</li>
<li>Issue #1518: ENUM and VIEW with filtering on enum column <li>Issue #1518: ENUM and VIEW with filtering on enum column
</li> </li>
<li>Issue #1516: Array element reference array[index] should be 1-based <li>Issue #1516: Array element reference array[index] should be 1-based
......
...@@ -1056,6 +1056,7 @@ or the SQL statement <code>SET MODE DB2</code>. ...@@ -1056,6 +1056,7 @@ or the SQL statement <code>SET MODE DB2</code>.
results in the other value. results in the other value.
</li><li>Support the pseudo-table SYSIBM.SYSDUMMY1. </li><li>Support the pseudo-table SYSIBM.SYSDUMMY1.
</li><li>Timestamps with dash between date and time are supported. </li><li>Timestamps with dash between date and time are supported.
</li><li>Datetime value functions return the same value within a command.
</li></ul> </li></ul>
<h3>Derby Compatibility Mode</h3> <h3>Derby Compatibility Mode</h3>
...@@ -1071,6 +1072,7 @@ or the SQL statement <code>SET MODE Derby</code>. ...@@ -1071,6 +1072,7 @@ or the SQL statement <code>SET MODE Derby</code>.
</li><li>Concatenating <code>NULL</code> with another value </li><li>Concatenating <code>NULL</code> with another value
results in the other value. results in the other value.
</li><li>Support the pseudo-table SYSIBM.SYSDUMMY1. </li><li>Support the pseudo-table SYSIBM.SYSDUMMY1.
</li><li>Datetime value functions return the same value within a command.
</li></ul> </li></ul>
<h3>HSQLDB Compatibility Mode</h3> <h3>HSQLDB Compatibility Mode</h3>
...@@ -1086,6 +1088,7 @@ or the SQL statement <code>SET MODE HSQLDB</code>. ...@@ -1086,6 +1088,7 @@ or the SQL statement <code>SET MODE HSQLDB</code>.
</li><li>For unique indexes, <code>NULL</code> is distinct. </li><li>For unique indexes, <code>NULL</code> is distinct.
That means only one row with <code>NULL</code> in one of the columns is allowed. That means only one row with <code>NULL</code> in one of the columns is allowed.
</li><li>Text can be concatenated using '+'. </li><li>Text can be concatenated using '+'.
</li><li>Datetime value functions return the same value within a command.
</li></ul> </li></ul>
<h3>MS SQL Server Compatibility Mode</h3> <h3>MS SQL Server Compatibility Mode</h3>
...@@ -1106,6 +1109,7 @@ or the SQL statement <code>SET MODE MSSQLServer</code>. ...@@ -1106,6 +1109,7 @@ or the SQL statement <code>SET MODE MSSQLServer</code>.
data type. data type.
</li><li><code>IDENTITY</code> can be used for automatic id generation on column level. </li><li><code>IDENTITY</code> can be used for automatic id generation on column level.
</li><li>Table hints are discarded. Example: <code>SELECT * FROM table WITH (NOLOCK)</code>. </li><li>Table hints are discarded. Example: <code>SELECT * FROM table WITH (NOLOCK)</code>.
</li><li>Datetime value functions return the same value within a command.
</li></ul> </li></ul>
<h3>MySQL Compatibility Mode</h3> <h3>MySQL Compatibility Mode</h3>
...@@ -1128,6 +1132,7 @@ or the SQL statement <code>SET MODE MySQL</code>. Use this mode for compatibilit ...@@ -1128,6 +1132,7 @@ or the SQL statement <code>SET MODE MySQL</code>. Use this mode for compatibilit
</li><li>ON DUPLICATE KEY UPDATE is supported in INSERT statements. </li><li>ON DUPLICATE KEY UPDATE is supported in INSERT statements.
</li><li>INSERT IGNORE is partially supported and may be used to skip rows with duplicate keys if ON DUPLICATE KEY UPDATE is not specified. </li><li>INSERT IGNORE is partially supported and may be used to skip rows with duplicate keys if ON DUPLICATE KEY UPDATE is not specified.
</li><li>REGEXP_REPLACE() uses \ for back-references for compatibility with MariaDB. </li><li>REGEXP_REPLACE() uses \ for back-references for compatibility with MariaDB.
</li><li>Datetime value functions return the same value within a command.
</li></ul> </li></ul>
<p> <p>
Text comparison in MySQL is case insensitive by default, while in H2 it is case sensitive (as in most other databases). Text comparison in MySQL is case insensitive by default, while in H2 it is case sensitive (as in most other databases).
...@@ -1152,6 +1157,7 @@ or the SQL statement <code>SET MODE Oracle</code>. ...@@ -1152,6 +1157,7 @@ or the SQL statement <code>SET MODE Oracle</code>.
</li><li>Empty strings are treated like <code>NULL</code> values. </li><li>Empty strings are treated like <code>NULL</code> values.
</li><li>REGEXP_REPLACE() uses \ for back-references. </li><li>REGEXP_REPLACE() uses \ for back-references.
</li><li>DATE data type is treated like TIMESTAMP(0) data type. </li><li>DATE data type is treated like TIMESTAMP(0) data type.
</li><li>Datetime value functions return the same value within a command.
</li></ul> </li></ul>
<h3>PostgreSQL Compatibility Mode</h3> <h3>PostgreSQL Compatibility Mode</h3>
...@@ -1170,6 +1176,7 @@ or the SQL statement <code>SET MODE PostgreSQL</code>. ...@@ -1170,6 +1176,7 @@ or the SQL statement <code>SET MODE PostgreSQL</code>.
</li><li>REGEXP_REPLACE() uses \ for back-references. </li><li>REGEXP_REPLACE() uses \ for back-references.
</li><li>Fixed-width strings are padded with spaces. </li><li>Fixed-width strings are padded with spaces.
</li><li>MONEY data type is treated like NUMERIC(19, 2) data type. </li><li>MONEY data type is treated like NUMERIC(19, 2) data type.
</li><li>Datetime value functions return the same value within a transaction.
</li></ul> </li></ul>
<h3>Ignite Compatibility Mode</h3> <h3>Ignite Compatibility Mode</h3>
...@@ -1181,6 +1188,7 @@ or the SQL statement <code>SET MODE Ignite</code>. ...@@ -1181,6 +1188,7 @@ or the SQL statement <code>SET MODE Ignite</code>.
<code>INDEX(..)</code> or <code>KEY(..)</code>. <code>INDEX(..)</code> or <code>KEY(..)</code>.
Example: <code>create table test(id int primary key, name varchar(255), key idx_name(name));</code> Example: <code>create table test(id int primary key, name varchar(255), key idx_name(name));</code>
</li><li>AFFINITY KEY and SHARD KEY keywords may be used in index definition. </li><li>AFFINITY KEY and SHARD KEY keywords may be used in index definition.
</li><li>Datetime value functions return the same value within a transaction.
</li></ul> </li></ul>
<h2 id="auto_reconnect">Auto-Reconnect</h2> <h2 id="auto_reconnect">Auto-Reconnect</h2>
......
...@@ -202,6 +202,13 @@ public class Mode { ...@@ -202,6 +202,13 @@ public class Mode {
*/ */
public boolean charToBinaryInUtf8; public boolean charToBinaryInUtf8;
/**
* If {@code true}, datetime value function return the same value within a
* transaction, if {@code false} datetime value functions return the same
* value within a command.
*/
public boolean dateTimeValueWithinTransaction;
/** /**
* An optional Set of hidden/disallowed column types. * An optional Set of hidden/disallowed column types.
* Certain DBMSs don't support all column types provided by H2, such as * Certain DBMSs don't support all column types provided by H2, such as
...@@ -221,6 +228,7 @@ public class Mode { ...@@ -221,6 +228,7 @@ public class Mode {
static { static {
Mode mode = new Mode(ModeEnum.REGULAR); Mode mode = new Mode(ModeEnum.REGULAR);
mode.nullConcatIsNull = true; mode.nullConcatIsNull = true;
mode.dateTimeValueWithinTransaction = true;
add(mode); add(mode);
mode = new Mode(ModeEnum.DB2); mode = new Mode(ModeEnum.DB2);
...@@ -345,12 +353,14 @@ public class Mode { ...@@ -345,12 +353,14 @@ public class Mode {
dt.sqlType = Types.NUMERIC; dt.sqlType = Types.NUMERIC;
dt.name = "MONEY"; dt.name = "MONEY";
mode.typeByNameMap.put("MONEY", dt); mode.typeByNameMap.put("MONEY", dt);
mode.dateTimeValueWithinTransaction = true;
add(mode); add(mode);
mode = new Mode(ModeEnum.Ignite); mode = new Mode(ModeEnum.Ignite);
mode.nullConcatIsNull = true; mode.nullConcatIsNull = true;
mode.allowAffinityKey = true; mode.allowAffinityKey = true;
mode.indexDefinitionInCreateTable = true; mode.indexDefinitionInCreateTable = true;
mode.dateTimeValueWithinTransaction = true;
add(mode); add(mode);
} }
......
...@@ -117,7 +117,7 @@ public class Session extends SessionWithState implements TransactionStore.Rollba ...@@ -117,7 +117,7 @@ public class Session extends SessionWithState implements TransactionStore.Rollba
private boolean closed; private boolean closed;
private final long sessionStart = System.currentTimeMillis(); private final long sessionStart = System.currentTimeMillis();
private ValueTimestampTimeZone transactionStart; private ValueTimestampTimeZone transactionStart;
private long currentCommandStart; private ValueTimestampTimeZone currentCommandStart;
private HashMap<String, Value> variables; private HashMap<String, Value> variables;
private HashSet<ResultInterface> temporaryResults; private HashSet<ResultInterface> temporaryResults;
private int queryTimeout; private int queryTimeout;
...@@ -1205,8 +1205,8 @@ public class Session extends SessionWithState implements TransactionStore.Rollba ...@@ -1205,8 +1205,8 @@ public class Session extends SessionWithState implements TransactionStore.Rollba
* Wait for some time if this session is throttled (slowed down). * Wait for some time if this session is throttled (slowed down).
*/ */
public void throttle() { public void throttle() {
if (currentCommandStart == 0) { if (currentCommandStart == null) {
currentCommandStart = System.currentTimeMillis(); currentCommandStart = CurrentTimestamp.get();
} }
if (throttleNs == 0) { if (throttleNs == 0) {
return; return;
...@@ -1246,10 +1246,14 @@ public class Session extends SessionWithState implements TransactionStore.Rollba ...@@ -1246,10 +1246,14 @@ public class Session extends SessionWithState implements TransactionStore.Rollba
if (command != null && !command.isQuery()) { if (command != null && !command.isQuery()) {
getGeneratedKeys().clear(generatedKeysRequest); getGeneratedKeys().clear(generatedKeysRequest);
} }
if (queryTimeout > 0 && command != null) { if (command != null) {
currentCommandStart = System.currentTimeMillis(); if (queryTimeout > 0) {
long now = System.nanoTime(); currentCommandStart = CurrentTimestamp.get();
cancelAtNs = now + TimeUnit.MILLISECONDS.toNanos(queryTimeout); long now = System.nanoTime();
cancelAtNs = now + TimeUnit.MILLISECONDS.toNanos(queryTimeout);
} else {
currentCommandStart = null;
}
} }
state = command == null ? State.SLEEP : State.RUNNING; state = command == null ? State.SLEEP : State.RUNNING;
} }
...@@ -1285,7 +1289,10 @@ public class Session extends SessionWithState implements TransactionStore.Rollba ...@@ -1285,7 +1289,10 @@ public class Session extends SessionWithState implements TransactionStore.Rollba
return currentCommand; return currentCommand;
} }
public long getCurrentCommandStart() { public ValueTimestampTimeZone getCurrentCommandStart() {
if (currentCommandStart == null) {
currentCommandStart = CurrentTimestamp.get();
}
return currentCommandStart; return currentCommandStart;
} }
......
...@@ -831,22 +831,28 @@ public class Function extends Expression implements FunctionCall { ...@@ -831,22 +831,28 @@ public class Function extends Expression implements FunctionCall {
} }
case CURDATE: case CURDATE:
case CURRENT_DATE: { case CURRENT_DATE: {
result = session.getTransactionStart().convertTo(Value.DATE); result = (database.getMode().dateTimeValueWithinTransaction ? session.getTransactionStart()
: session.getCurrentCommandStart()).convertTo(Value.DATE);
break; break;
} }
case CURTIME: case CURTIME:
case CURRENT_TIME: { case CURRENT_TIME: {
ValueTime vt = (ValueTime) session.getTransactionStart().convertTo(Value.TIME); ValueTime vt = (ValueTime) (database.getMode().dateTimeValueWithinTransaction
? session.getTransactionStart()
: session.getCurrentCommandStart()).convertTo(Value.TIME);
result = vt.convertScale(false, v0 == null ? 0 : v0.getInt()); result = vt.convertScale(false, v0 == null ? 0 : v0.getInt());
break; break;
} }
case LOCALTIMESTAMP: { case LOCALTIMESTAMP: {
Value vt = session.getTransactionStart().convertTo(Value.TIMESTAMP); Value vt = (database.getMode().dateTimeValueWithinTransaction ? session.getTransactionStart()
: session.getCurrentCommandStart()).convertTo(Value.TIMESTAMP);
result = vt.convertScale(false, v0 == null ? 6 : v0.getInt()); result = vt.convertScale(false, v0 == null ? 6 : v0.getInt());
break; break;
} }
case CURRENT_TIMESTAMP: { case CURRENT_TIMESTAMP: {
ValueTimestampTimeZone vt = session.getTransactionStart(); ValueTimestampTimeZone vt = database.getMode().dateTimeValueWithinTransaction
? session.getTransactionStart()
: session.getCurrentCommandStart();
result = vt.convertScale(false, v0 == null ? 6 : v0.getInt()); result = vt.convertScale(false, v0 == null ? 6 : v0.getInt());
break; break;
} }
......
...@@ -250,15 +250,12 @@ public class DatabaseInfo implements DatabaseInfoMBean { ...@@ -250,15 +250,12 @@ public class DatabaseInfo implements DatabaseInfoMBean {
append('\n'); append('\n');
Command command = session.getCurrentCommand(); Command command = session.getCurrentCommand();
if (command != null) { if (command != null) {
buff.append("statement: "). buff.append("statement: ")
append(session.getCurrentCommand()). .append(command)
append('\n'); .append('\n')
long commandStart = session.getCurrentCommandStart(); .append("started: ")
if (commandStart != 0) { .append(session.getCurrentCommandStart().getString())
buff.append("started: ").append( .append('\n');
new Timestamp(commandStart)).
append('\n');
}
} }
Table[] t = session.getLocks(); Table[] t = session.getLocks();
if (t.length > 0) { if (t.length > 0) {
......
...@@ -362,13 +362,13 @@ public class Column { ...@@ -362,13 +362,13 @@ public class Column {
if (dt.decimal) { if (dt.decimal) {
value = ValueInt.get(0).convertTo(type); value = ValueInt.get(0).convertTo(type);
} else if (dt.type == Value.TIMESTAMP) { } else if (dt.type == Value.TIMESTAMP) {
value = session.getTransactionStart().convertTo(Value.TIMESTAMP); value = session.getCurrentCommandStart().convertTo(Value.TIMESTAMP);
} else if (dt.type == Value.TIMESTAMP_TZ) { } else if (dt.type == Value.TIMESTAMP_TZ) {
value = session.getTransactionStart(); value = session.getCurrentCommandStart();
} else if (dt.type == Value.TIME) { } else if (dt.type == Value.TIME) {
value = ValueTime.fromNanos(0); value = ValueTime.fromNanos(0);
} else if (dt.type == Value.DATE) { } else if (dt.type == Value.DATE) {
value = session.getTransactionStart().convertTo(Value.DATE); value = session.getCurrentCommandStart().convertTo(Value.DATE);
} else { } else {
value = ValueString.get("").convertTo(type); value = ValueString.get("").convertTo(type);
} }
......
...@@ -1848,14 +1848,9 @@ public class MetaTable extends Table { ...@@ -1848,14 +1848,9 @@ public class MetaTable extends Table {
break; break;
} }
case SESSIONS: { case SESSIONS: {
long now = System.currentTimeMillis();
for (Session s : database.getSessions(false)) { for (Session s : database.getSessions(false)) {
if (admin || s == session) { if (admin || s == session) {
Command command = s.getCurrentCommand(); Command command = s.getCurrentCommand();
long start = s.getCurrentCommandStart();
if (start == 0) {
start = now;
}
int blockingSessionId = s.getBlockingSessionId(); int blockingSessionId = s.getBlockingSessionId();
add(rows, add(rows,
// ID // ID
...@@ -1867,7 +1862,7 @@ public class MetaTable extends Table { ...@@ -1867,7 +1862,7 @@ public class MetaTable extends Table {
// STATEMENT // STATEMENT
command == null ? null : command.toString(), command == null ? null : command.toString(),
// STATEMENT_START // STATEMENT_START
DateTimeUtils.timestampTimeZoneFromMillis(start), command == null ? null : s.getCurrentCommandStart(),
// CONTAINS_UNCOMMITTED // CONTAINS_UNCOMMITTED
ValueBoolean.get(s.containsUncommitted()), ValueBoolean.get(s.containsUncommitted()),
// STATE // STATE
......
...@@ -11,3 +11,91 @@ SELECT CAST(CURRENT_TIMESTAMP(0) AS TIMESTAMP(9)) = LOCALTIMESTAMP(0); ...@@ -11,3 +11,91 @@ SELECT CAST(CURRENT_TIMESTAMP(0) AS TIMESTAMP(9)) = LOCALTIMESTAMP(0);
SELECT CAST(CURRENT_TIMESTAMP(9) AS TIMESTAMP(9)) = LOCALTIMESTAMP(9); SELECT CAST(CURRENT_TIMESTAMP(9) AS TIMESTAMP(9)) = LOCALTIMESTAMP(9);
>> TRUE >> TRUE
@reconnect off
SET AUTOCOMMIT OFF;
> ok
CREATE ALIAS SLEEP FOR "java.lang.Thread.sleep(long)";
> ok
CREATE TABLE TEST(I IDENTITY PRIMARY KEY, T TIMESTAMP(9) WITH TIME ZONE);
> ok
INSERT INTO TEST(T) VALUES (CURRENT_TIMESTAMP(9)), (CURRENT_TIMESTAMP(9));
> update count: 2
CALL SLEEP(10);
>> null
INSERT INTO TEST(T) VALUES (CURRENT_TIMESTAMP(9));
> update count: 1
COMMIT;
> ok
INSERT INTO TEST(T) VALUES (CURRENT_TIMESTAMP(9));
> update count: 1
COMMIT;
> ok
-- same statement
SELECT (SELECT T FROM TEST WHERE I = 1) = (SELECT T FROM TEST WHERE I = 2);
>> TRUE
-- same transaction
SELECT (SELECT T FROM TEST WHERE I = 2) = (SELECT T FROM TEST WHERE I = 3);
>> TRUE
-- another transaction
SELECT (SELECT T FROM TEST WHERE I = 3) = (SELECT T FROM TEST WHERE I = 4);
>> FALSE
SET MODE MySQL;
> ok
INSERT INTO TEST(T) VALUES (CURRENT_TIMESTAMP(9)), (CURRENT_TIMESTAMP(9));
> update count: 2
CALL SLEEP(10);
>> null
INSERT INTO TEST(T) VALUES (CURRENT_TIMESTAMP(9));
> update count: 1
COMMIT;
> ok
INSERT INTO TEST(T) VALUES (CURRENT_TIMESTAMP(9));
> update count: 1
COMMIT;
> ok
-- same statement
SELECT (SELECT T FROM TEST WHERE I = 5) = (SELECT T FROM TEST WHERE I = 6);
>> TRUE
-- same transaction
SELECT (SELECT T FROM TEST WHERE I = 6) = (SELECT T FROM TEST WHERE I = 7);
>> FALSE
-- another transaction
SELECT (SELECT T FROM TEST WHERE I = 7) = (SELECT T FROM TEST WHERE I = 8);
>> FALSE
SET MODE Regular;
> ok
DROP TABLE TEST;
> ok
DROP ALIAS SLEEP;
> ok
SET AUTOCOMMIT ON;
> ok
@reconnect on
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论