提交 566f4756 authored 作者: lukaseder's avatar lukaseder

Merge remote-tracking branch 'upstream/master'

# Conflicts:
#	h2/src/main/org/h2/res/help.csv
...@@ -31,7 +31,7 @@ Change Log ...@@ -31,7 +31,7 @@ Change Log
</li> </li>
<li>Issue #249: Clarify license declaration in Maven POM xml <li>Issue #249: Clarify license declaration in Maven POM xml
</li> </li>
<li>Fix NPE in querying spatial data through a sub-select. <li>Fix NullPointerException in querying spatial data through a sub-select.
</li> </li>
<li>Fix bug where a lock on the SYS table was not released when closing a session that contained a temp <li>Fix bug where a lock on the SYS table was not released when closing a session that contained a temp
table with an LOB column. table with an LOB column.
...@@ -71,7 +71,7 @@ table with an LOB column. ...@@ -71,7 +71,7 @@ table with an LOB column.
<li>Issue #231: Possible infinite loop when initializing the ObjectDataType class <li>Issue #231: Possible infinite loop when initializing the ObjectDataType class
when concurrently writing into MVStore. when concurrently writing into MVStore.
</li> </li>
<ul> </ul>
<h2>Version 1.4.191 Beta (2016-01-21)</h2> <h2>Version 1.4.191 Beta (2016-01-21)</h2>
<ul> <ul>
......
...@@ -644,8 +644,7 @@ To keep the content of an in-memory database as long as the virtual machine is a ...@@ -644,8 +644,7 @@ To keep the content of an in-memory database as long as the virtual machine is a
<h2 id="file_encryption">Database Files Encryption</h2> <h2 id="file_encryption">Database Files Encryption</h2>
<p> <p>
The database files can be encrypted. The database files can be encrypted.
<p> Three encryption algorithms are supported:
Three encryption algorithms are supported:
<ul> <ul>
<li>"AES" - also known as Rijndael, only AES-128 is implemented.</li> <li>"AES" - also known as Rijndael, only AES-128 is implemented.</li>
<li>"XTEA" - the 32 round version.</li> <li>"XTEA" - the 32 round version.</li>
......
...@@ -495,7 +495,7 @@ for (int i = 0; i &lt; 400; i++) { ...@@ -495,7 +495,7 @@ for (int i = 0; i &lt; 400; i++) {
} }
s.commit(); s.commit();
for (int i = 0; i &lt; 100; i++) { for (int i = 0; i &lt; 100; i++) {
map.put(0, "Hi"); map.put(i, "Hi");
} }
s.commit(); s.commit();
s.close(); s.close();
...@@ -511,7 +511,7 @@ will result in the following two chunks (excluding metadata): ...@@ -511,7 +511,7 @@ will result in the following two chunks (excluding metadata):
</p> </p>
<p> <p>
<b>Chunk 2:</b><br /> <b>Chunk 2:</b><br />
- Page 4: (root) node with 2 entries pointing to page 3 and 5<br /> - Page 4: (root) node with 2 entries pointing to page 5 and 3<br />
- Page 5: leaf with 140 entries (keys 0 - 139)<br /> - Page 5: leaf with 140 entries (keys 0 - 139)<br />
</p> </p>
<p> <p>
......
...@@ -98,6 +98,9 @@ toolbar.autoComplete=Auto complete ...@@ -98,6 +98,9 @@ toolbar.autoComplete=Auto complete
toolbar.autoComplete.full=Full toolbar.autoComplete.full=Full
toolbar.autoComplete.normal=Normal toolbar.autoComplete.normal=Normal
toolbar.autoComplete.off=Off toolbar.autoComplete.off=Off
toolbar.autoSelect=Auto select
toolbar.autoSelect.off=Off
toolbar.autoSelect.on=On
toolbar.cancelStatement=Cancel the current statement toolbar.cancelStatement=Cancel the current statement
toolbar.clear=Clear toolbar.clear=Clear
toolbar.commit=Commit toolbar.commit=Commit
......
...@@ -140,7 +140,8 @@ public class Mode { ...@@ -140,7 +140,8 @@ public class Mode {
public boolean onDuplicateKeyUpdate; public boolean onDuplicateKeyUpdate;
/** /**
* Pattern describing the keys the java.sql.Connection.setClientInfo() method accepts. * Pattern describing the keys the java.sql.Connection.setClientInfo()
* method accepts.
*/ */
public Pattern supportedClientInfoPropertiesRegEx; public Pattern supportedClientInfoPropertiesRegEx;
...@@ -161,9 +162,12 @@ public class Mode { ...@@ -161,9 +162,12 @@ public class Mode {
mode.supportOffsetFetch = true; mode.supportOffsetFetch = true;
mode.sysDummy1 = true; mode.sysDummy1 = true;
mode.isolationLevelInSelectOrInsertStatement = true; mode.isolationLevelInSelectOrInsertStatement = true;
// See https://www.ibm.com/support/knowledgecenter/SSEPEK_11.0.0/com.ibm.db2z11.doc.java/src/tpc/imjcc_r0052001.dita // See
// https://www.ibm.com/support/knowledgecenter/SSEPEK_11.0.0/
// com.ibm.db2z11.doc.java/src/tpc/imjcc_r0052001.dita
mode.supportedClientInfoPropertiesRegEx = mode.supportedClientInfoPropertiesRegEx =
Pattern.compile("ApplicationName|ClientAccountingInformation|ClientUser|ClientCorrelationToken"); Pattern.compile("ApplicationName|ClientAccountingInformation|" +
"ClientUser|ClientCorrelationToken");
add(mode); add(mode);
mode = new Mode("Derby"); mode = new Mode("Derby");
...@@ -183,7 +187,9 @@ public class Mode { ...@@ -183,7 +187,9 @@ public class Mode {
mode.uniqueIndexSingleNull = true; mode.uniqueIndexSingleNull = true;
mode.allowPlusForStringConcat = true; mode.allowPlusForStringConcat = true;
// HSQLDB does not support client info properties. See // HSQLDB does not support client info properties. See
// http://hsqldb.org/doc/apidocs/org/hsqldb/jdbc/JDBCConnection.html#setClientInfo%28java.lang.String,%20java.lang.String%29 // http://hsqldb.org/doc/apidocs/
// org/hsqldb/jdbc/JDBCConnection.html#
// setClientInfo%28java.lang.String,%20java.lang.String%29
mode.supportedClientInfoPropertiesRegEx = null; mode.supportedClientInfoPropertiesRegEx = null;
add(mode); add(mode);
...@@ -205,8 +211,11 @@ public class Mode { ...@@ -205,8 +211,11 @@ public class Mode {
mode.lowerCaseIdentifiers = true; mode.lowerCaseIdentifiers = true;
mode.onDuplicateKeyUpdate = true; mode.onDuplicateKeyUpdate = true;
// MySQL allows to use any key for client info entries. See // MySQL allows to use any key for client info entries. See
// http://grepcode.com/file/repo1.maven.org/maven2/mysql/mysql-connector-java/5.1.24/com/mysql/jdbc/JDBC4CommentClientInfoProvider.java // http://grepcode.com/file/repo1.maven.org/maven2/mysql/
mode.supportedClientInfoPropertiesRegEx = Pattern.compile(".*"); // mysql-connector-java/5.1.24/com/mysql/jdbc/
// JDBC4CommentClientInfoProvider.java
mode.supportedClientInfoPropertiesRegEx =
Pattern.compile(".*");
add(mode); add(mode);
mode = new Mode("Oracle"); mode = new Mode("Oracle");
...@@ -217,7 +226,8 @@ public class Mode { ...@@ -217,7 +226,8 @@ public class Mode {
mode.supportPoundSymbolForColumnNames = true; mode.supportPoundSymbolForColumnNames = true;
// Oracle accepts keys of the form <namespace>.*. See // Oracle accepts keys of the form <namespace>.*. See
// https://docs.oracle.com/database/121/JJDBC/jdbcvers.htm#JJDBC29006 // https://docs.oracle.com/database/121/JJDBC/jdbcvers.htm#JJDBC29006
mode.supportedClientInfoPropertiesRegEx = Pattern.compile(".*\\..*"); mode.supportedClientInfoPropertiesRegEx =
Pattern.compile(".*\\..*");
add(mode); add(mode);
mode = new Mode("PostgreSQL"); mode = new Mode("PostgreSQL");
...@@ -228,8 +238,10 @@ public class Mode { ...@@ -228,8 +238,10 @@ public class Mode {
mode.logIsLogBase10 = true; mode.logIsLogBase10 = true;
mode.serialColumnIsNotPK = true; mode.serialColumnIsNotPK = true;
// PostgreSQL only supports the ApplicationName property. See // PostgreSQL only supports the ApplicationName property. See
// https://github.com/hhru/postgres-jdbc/blob/master/postgresql-jdbc-9.2-1002.src/org/postgresql/jdbc4/AbstractJdbc4Connection.java // https://github.com/hhru/postgres-jdbc/blob/master/postgresql-jdbc-9.2-1002.src/
mode.supportedClientInfoPropertiesRegEx = Pattern.compile("ApplicationName"); // org/postgresql/jdbc4/AbstractJdbc4Connection.java
mode.supportedClientInfoPropertiesRegEx =
Pattern.compile("ApplicationName");
add(mode); add(mode);
} }
......
...@@ -815,7 +815,10 @@ public class Session extends SessionWithState { ...@@ -815,7 +815,10 @@ public class Session extends SessionWithState {
if (!closed) { if (!closed) {
try { try {
database.checkPowerOff(); database.checkPowerOff();
rollback(); // release any open table locks
// release any open table locks
rollback();
removeTemporaryLobs(false); removeTemporaryLobs(false);
cleanTempTables(true); cleanTempTables(true);
undoLog.clear(); undoLog.clear();
...@@ -934,13 +937,13 @@ public class Session extends SessionWithState { ...@@ -934,13 +937,13 @@ public class Session extends SessionWithState {
private void cleanTempTables(boolean closeSession) { private void cleanTempTables(boolean closeSession) {
if (localTempTables != null && localTempTables.size() > 0) { if (localTempTables != null && localTempTables.size() > 0) {
synchronized (database) { synchronized (database) {
Iterator<Table> itr = localTempTables.values().iterator(); Iterator<Table> it = localTempTables.values().iterator();
while (itr.hasNext()) { while (it.hasNext()) {
Table table = itr.next(); Table table = it.next();
if (closeSession || table.getOnCommitDrop()) { if (closeSession || table.getOnCommitDrop()) {
modificationId++; modificationId++;
table.setModified(); table.setModified();
itr.remove(); it.remove();
table.removeChildrenAndResources(this); table.removeChildrenAndResources(this);
if (closeSession) { if (closeSession) {
// need to commit, otherwise recovery might // need to commit, otherwise recovery might
...@@ -951,7 +954,8 @@ public class Session extends SessionWithState { ...@@ -951,7 +954,8 @@ public class Session extends SessionWithState {
table.truncate(this); table.truncate(this);
} }
} }
// sometimes Table#removeChildrenAndResources will take the meta lock // sometimes Table#removeChildrenAndResources
// will take the meta lock
if (closeSession) { if (closeSession) {
database.unlockMeta(this); database.unlockMeta(this);
} }
......
...@@ -259,7 +259,7 @@ public class CompareLike extends Condition { ...@@ -259,7 +259,7 @@ public class CompareLike extends Condition {
if (regexp) { if (regexp) {
result = patternRegexp.matcher(value).find(); result = patternRegexp.matcher(value).find();
} else if (shortcutToStartsWith) { } else if (shortcutToStartsWith) {
result = value.regionMatches(ignoreCase, 0, patternString, 0, patternLength-1); result = value.regionMatches(ignoreCase, 0, patternString, 0, patternLength - 1);
} else { } else {
result = compareAt(value, 0, 0, value.length(), patternChars, patternTypes); result = compareAt(value, 0, 0, value.length(), patternChars, patternTypes);
} }
...@@ -388,7 +388,7 @@ public class CompareLike extends Condition { ...@@ -388,7 +388,7 @@ public class CompareLike extends Condition {
while (maxMatch < patternLength && patternTypes[maxMatch] == MATCH) { while (maxMatch < patternLength && patternTypes[maxMatch] == MATCH) {
maxMatch++; maxMatch++;
} }
if (maxMatch == patternLength - 1 && patternTypes[patternLength-1] == ANY) { if (maxMatch == patternLength - 1 && patternTypes[patternLength - 1] == ANY) {
shortcutToStartsWith = true; shortcutToStartsWith = true;
} }
} }
......
...@@ -288,12 +288,18 @@ public class ExpressionVisitor { ...@@ -288,12 +288,18 @@ public class ExpressionVisitor {
return type; return type;
} }
/**
* Get the set of columns of all tables.
*
* @param filters the filters
* @return the set of columns
*/
public static HashSet<Column> allColumnsForTableFilters(TableFilter[] filters) { public static HashSet<Column> allColumnsForTableFilters(TableFilter[] filters) {
HashSet<Column> allColumnsSet = New.hashSet(); HashSet<Column> allColumnsSet = New.hashSet();
for (int i = 0; i < filters.length; i++) { for (int i = 0; i < filters.length; i++) {
if (filters[i].getSelect() != null) { if (filters[i].getSelect() != null) {
filters[i].getSelect().isEverything(ExpressionVisitor.getColumnsVisitor(allColumnsSet)); filters[i].getSelect().isEverything(
ExpressionVisitor.getColumnsVisitor(allColumnsSet));
} }
} }
return allColumnsSet; return allColumnsSet;
......
...@@ -156,6 +156,7 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index { ...@@ -156,6 +156,7 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index {
* @param filter the current table filter index * @param filter the current table filter index
* @param sortOrder the sort order * @param sortOrder the sort order
* @param isScanIndex whether this is a "table scan" index * @param isScanIndex whether this is a "table scan" index
* @param allColumnsSet the set of all columns
* @return the estimated cost * @return the estimated cost
*/ */
protected final long getCostRangeIndex(int[] masks, long rowCount, protected final long getCostRangeIndex(int[] masks, long rowCount,
...@@ -240,8 +241,8 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index { ...@@ -240,8 +241,8 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index {
} }
} }
// If we have two indexes with the same cost, and one of the indexes can // If we have two indexes with the same cost, and one of the indexes can
// satisfy the query without needing to read from the primary table (scan index), // satisfy the query without needing to read from the primary table
// make that one slightly lower cost. // (scan index), make that one slightly lower cost.
boolean needsToReadFromScanIndex = true; boolean needsToReadFromScanIndex = true;
if (!isScanIndex && allColumnsSet != null && !allColumnsSet.isEmpty()) { if (!isScanIndex && allColumnsSet != null && !allColumnsSet.isEmpty()) {
boolean foundAllColumnsWeNeed = true; boolean foundAllColumnsWeNeed = true;
......
...@@ -85,6 +85,7 @@ public interface Index extends SchemaObject { ...@@ -85,6 +85,7 @@ public interface Index extends SchemaObject {
* @param filters all joined table filters * @param filters all joined table filters
* @param filter the current table filter index * @param filter the current table filter index
* @param sortOrder the sort order * @param sortOrder the sort order
* @param allColumnsSet the set of all columns
* @return the estimated cost * @return the estimated cost
*/ */
double getCost(Session session, int[] masks, TableFilter[] filters, int filter, double getCost(Session session, int[] masks, TableFilter[] filters, int filter,
......
...@@ -20,10 +20,13 @@ public interface SpatialIndex extends Index { ...@@ -20,10 +20,13 @@ public interface SpatialIndex extends Index {
* *
* @param filter the table filter (which possibly knows about additional * @param filter the table filter (which possibly knows about additional
* conditions) * conditions)
* @param first the lower bound
* @param last the upper bound
* @param intersection the geometry which values should intersect with, or * @param intersection the geometry which values should intersect with, or
* null for anything * null for anything
* @return the cursor to iterate over the results * @return the cursor to iterate over the results
*/ */
Cursor findByGeometry(TableFilter filter, SearchRow first, SearchRow last, SearchRow intersection); Cursor findByGeometry(TableFilter filter, SearchRow first, SearchRow last,
SearchRow intersection);
} }
...@@ -168,7 +168,8 @@ public class SpatialTreeIndex extends BaseIndex implements SpatialIndex { ...@@ -168,7 +168,8 @@ public class SpatialTreeIndex extends BaseIndex implements SpatialIndex {
} }
@Override @Override
public Cursor findByGeometry(TableFilter filter, SearchRow first, SearchRow last, SearchRow intersection) { public Cursor findByGeometry(TableFilter filter, SearchRow first,
SearchRow last, SearchRow intersection) {
if (intersection == null) { if (intersection == null) {
return find(filter.getSession(), first, last); return find(filter.getSession(), first, last);
} }
......
...@@ -160,7 +160,8 @@ public class ViewIndex extends BaseIndex implements SpatialIndex { ...@@ -160,7 +160,8 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
} }
@Override @Override
public Cursor findByGeometry(TableFilter filter, SearchRow first, SearchRow last, SearchRow intersection) { public Cursor findByGeometry(TableFilter filter, SearchRow first,
SearchRow last, SearchRow intersection) {
return find(filter.getSession(), first, last, intersection); return find(filter.getSession(), first, last, intersection);
} }
......
...@@ -1675,16 +1675,21 @@ public class JdbcConnection extends TraceObject implements Connection { ...@@ -1675,16 +1675,21 @@ public class JdbcConnection extends TraceObject implements Connection {
} }
/** /**
* Set a client property. * Set a client property. This method always throws a SQLClientInfoException
* This method always throws a SQLClientInfoException in standard mode. * in standard mode. In compatibility mode the following properties are
* In compatibility mode the following properties are supported: * supported:
* <p><ul> * <ul>
* <li>DB2: The properties: ApplicationName, ClientAccountingInformation, ClientUser and ClientCorrelationToken * <li>DB2: The properties: ApplicationName, ClientAccountingInformation,
* are supported. * ClientUser and ClientCorrelationToken are supported.
* </li>
* <li>MySQL: All property names are supported. * <li>MySQL: All property names are supported.
* <li>Oracle: All properties in the form <namespace>.<key name> are supported. * </li>
* <li>Oracle: All properties in the form &lt;namespace&gt;.&lt;key name&gt;
* are supported.
* </li>
* <li>PostgreSQL: The ApplicationName property is supported. * <li>PostgreSQL: The ApplicationName property is supported.
* </ul><p> * </li>
* </ul>
* *
* For unsupported properties a SQLClientInfoException is thrown. * For unsupported properties a SQLClientInfoException is thrown.
* *
...@@ -1703,11 +1708,13 @@ public class JdbcConnection extends TraceObject implements Connection { ...@@ -1703,11 +1708,13 @@ public class JdbcConnection extends TraceObject implements Connection {
checkClosed(); checkClosed();
if (isInternalProperty(name)) { if (isInternalProperty(name)) {
throw new SQLClientInfoException("Property name '" + name + " is used internally by H2.", throw new SQLClientInfoException("Property name '" + name +
Collections.<String, ClientInfoStatus> emptyMap()); " is used internally by H2.",
Collections.<String, ClientInfoStatus> emptyMap());
} }
Pattern clientInfoNameRegEx = Mode.getInstance(getMode()).supportedClientInfoPropertiesRegEx; Pattern clientInfoNameRegEx =
Mode.getInstance(getMode()).supportedClientInfoPropertiesRegEx;
if (clientInfoNameRegEx != null && clientInfoNameRegEx.matcher(name).matches()) { if (clientInfoNameRegEx != null && clientInfoNameRegEx.matcher(name).matches()) {
if (clientInfo == null) { if (clientInfo == null) {
...@@ -1739,8 +1746,9 @@ public class JdbcConnection extends TraceObject implements Connection { ...@@ -1739,8 +1746,9 @@ public class JdbcConnection extends TraceObject implements Connection {
/** /**
* Set the client properties. This replaces all existing properties. * Set the client properties. This replaces all existing properties.
* *
* This method always throws a SQLClientInfoException in standard mode. In compatibility mode * This method always throws a SQLClientInfoException in standard mode. In
* some properties may be supported (see setProperty(String, String) for details). * compatibility mode some properties may be supported (see
* setProperty(String, String) for details).
* *
* @param properties the properties (ignored) * @param properties the properties (ignored)
*/ */
...@@ -1801,7 +1809,8 @@ public class JdbcConnection extends TraceObject implements Connection { ...@@ -1801,7 +1809,8 @@ public class JdbcConnection extends TraceObject implements Connection {
* Get a client property. * Get a client property.
* *
* @param name the client info name * @param name the client info name
* @return the property value or null if the property is not found or not supported. * @return the property value or null if the property is not found or not
* supported.
*/ */
@Override @Override
public String getClientInfo(String name) throws SQLException { public String getClientInfo(String name) throws SQLException {
......
...@@ -197,7 +197,8 @@ public class MVSpatialIndex extends BaseIndex implements SpatialIndex, MVIndex { ...@@ -197,7 +197,8 @@ public class MVSpatialIndex extends BaseIndex implements SpatialIndex, MVIndex {
} }
@Override @Override
public Cursor findByGeometry(TableFilter filter, SearchRow first, SearchRow last, SearchRow intersection) { public Cursor findByGeometry(TableFilter filter, SearchRow first,
SearchRow last, SearchRow intersection) {
Session session = filter.getSession(); Session session = filter.getSession();
if (intersection == null) { if (intersection == null) {
return find(session, first, last); return find(session, first, last);
......
...@@ -93,7 +93,11 @@ ALTER TABLE [ IF EXISTS ] tableName ADD constraint [ CHECK | NOCHECK ] ...@@ -93,7 +93,11 @@ ALTER TABLE [ IF EXISTS ] tableName ADD constraint [ CHECK | NOCHECK ]
"," ","
Adds a constraint to a table." Adds a constraint to a table."
"Commands (DDL)","ALTER TABLE RENAME CONSTRAINT"," "Commands (DDL)","ALTER TABLE RENAME CONSTRAINT","
<<<<<<< HEAD
ALTER TABLE [ IF EXISTS ] tableName RENAME oldConstraintName TO newConstraintName ALTER TABLE [ IF EXISTS ] tableName RENAME oldConstraintName TO newConstraintName
=======
ALTER TABLE tableName RENAME oldConstraintName TO newConstraintName
>>>>>>> upstream/master
"," ","
Renames a constraint." Renames a constraint."
"Commands (DDL)","ALTER TABLE ALTER COLUMN"," "Commands (DDL)","ALTER TABLE ALTER COLUMN","
......
...@@ -52,18 +52,20 @@ public class CipherFactory { ...@@ -52,18 +52,20 @@ public class CipherFactory {
*/ */
public static final String KEYSTORE_PASSWORD = public static final String KEYSTORE_PASSWORD =
"h2pass"; "h2pass";
/** /**
* The security property which can prevent anonymous TLS connections. * The security property which can prevent anonymous TLS connections.
* Introduced into Java 6,7,8 in updates from July 2015. * Introduced into Java 6, 7, 8 in updates from July 2015.
*/ */
public static final String LEGACY_ALGORITHMS_SECURITY_KEY = public static final String LEGACY_ALGORITHMS_SECURITY_KEY =
"jdk.tls.legacyAlgorithms"; "jdk.tls.legacyAlgorithms";
/** /**
* The value of {@value #LEGACY_ALGORITHMS_SECURITY_KEY} security * The value of {@value #LEGACY_ALGORITHMS_SECURITY_KEY} security
* property at the time of class initialization. * property at the time of class initialization.
* Null if it is not set. * Null if it is not set.
*/ */
public static final String DEFAULT_LEGACY_ALGORITHMS = getLegacyAlgoritmsSilently(); public static final String DEFAULT_LEGACY_ALGORITHMS = getLegacyAlgorithmsSilently();
private static final String KEYSTORE = private static final String KEYSTORE =
"~/.h2.keystore"; "~/.h2.keystore";
...@@ -167,23 +169,22 @@ public class CipherFactory { ...@@ -167,23 +169,22 @@ public class CipherFactory {
* Removes DH_anon and ECDH_anon from a comma separated list of ciphers. * Removes DH_anon and ECDH_anon from a comma separated list of ciphers.
* Only the first occurrence is removed. * Only the first occurrence is removed.
* If there is nothing to remove, returns the reference to the argument. * If there is nothing to remove, returns the reference to the argument.
* @param commaSepList a list of names separated by commas (and spaces) * @param list a list of names separated by commas (and spaces)
* @return a new string without DH_anon and ECDH_anon items, * @return a new string without DH_anon and ECDH_anon items,
* or the original if none were found * or the original if none were found
*/ */
public static String removeDhAnonFromCommaSepList(String commaSepList) { public static String removeDhAnonFromCommaSeparatedList(String list) {
if (commaSepList == null) { if (list == null) {
return commaSepList; return list;
} }
List<String> algos = new LinkedList<String>(Arrays.asList(commaSepList.split("\\s*,\\s*"))); List<String> algos = new LinkedList<String>(Arrays.asList(list.split("\\s*,\\s*")));
boolean dhAnonRemoved = algos.remove("DH_anon"); boolean dhAnonRemoved = algos.remove("DH_anon");
boolean ecdhAnonRemoved = algos.remove("ECDH_anon"); boolean ecdhAnonRemoved = algos.remove("ECDH_anon");
if (dhAnonRemoved || ecdhAnonRemoved) { if (dhAnonRemoved || ecdhAnonRemoved) {
String algosStr = Arrays.toString(algos.toArray(new String[algos.size()])); String algosStr = Arrays.toString(algos.toArray(new String[algos.size()]));
return (algos.size() > 0) ? algosStr.substring(1, algosStr.length() - 1): ""; return (algos.size() > 0) ? algosStr.substring(1, algosStr.length() - 1): "";
} else {
return commaSepList;
} }
return list;
} }
/** /**
...@@ -202,11 +203,11 @@ public class CipherFactory { ...@@ -202,11 +203,11 @@ public class CipherFactory {
* behavior. * behavior.
*/ */
public static synchronized void removeAnonFromLegacyAlgorithms() { public static synchronized void removeAnonFromLegacyAlgorithms() {
String legacyAlgosOrig = getLegacyAlgoritmsSilently(); String legacyAlgosOrig = getLegacyAlgorithmsSilently();
if (legacyAlgosOrig == null) { if (legacyAlgosOrig == null) {
return; return;
} }
String legacyAlgosNew = removeDhAnonFromCommaSepList(legacyAlgosOrig); String legacyAlgosNew = removeDhAnonFromCommaSeparatedList(legacyAlgosOrig);
if (!legacyAlgosOrig.equals(legacyAlgosNew)) { if (!legacyAlgosOrig.equals(legacyAlgosNew)) {
setLegacyAlgorithmsSilently(legacyAlgosNew); setLegacyAlgorithmsSilently(legacyAlgosNew);
} }
...@@ -228,10 +229,11 @@ public class CipherFactory { ...@@ -228,10 +229,11 @@ public class CipherFactory {
/** /**
* Returns the security property {@value #LEGACY_ALGORITHMS_SECURITY_KEY}. * Returns the security property {@value #LEGACY_ALGORITHMS_SECURITY_KEY}.
* Ignores security exceptions. * Ignores security exceptions.
*
* @return the value of the security property, or null if not set * @return the value of the security property, or null if not set
* or not accessible * or not accessible
*/ */
public static String getLegacyAlgoritmsSilently() { public static String getLegacyAlgorithmsSilently() {
String defaultLegacyAlgorithms = null; String defaultLegacyAlgorithms = null;
try { try {
defaultLegacyAlgorithms = Security.getProperty(LEGACY_ALGORITHMS_SECURITY_KEY); defaultLegacyAlgorithms = Security.getProperty(LEGACY_ALGORITHMS_SECURITY_KEY);
......
...@@ -98,6 +98,9 @@ toolbar.autoComplete=Automatické dokončování ...@@ -98,6 +98,9 @@ toolbar.autoComplete=Automatické dokončování
toolbar.autoComplete.full=Úplné toolbar.autoComplete.full=Úplné
toolbar.autoComplete.normal=Normální toolbar.autoComplete.normal=Normální
toolbar.autoComplete.off=Vypnuto toolbar.autoComplete.off=Vypnuto
toolbar.autoSelect=#Auto select
toolbar.autoSelect.off=Vypnuto
toolbar.autoSelect.on=#On
toolbar.cancelStatement=Zrušit prováděný příkaz toolbar.cancelStatement=Zrušit prováděný příkaz
toolbar.clear=Vyčistit toolbar.clear=Vyčistit
toolbar.commit=Vložit toolbar.commit=Vložit
......
...@@ -98,6 +98,9 @@ toolbar.autoComplete=Auto-Complete ...@@ -98,6 +98,9 @@ toolbar.autoComplete=Auto-Complete
toolbar.autoComplete.full=Alles toolbar.autoComplete.full=Alles
toolbar.autoComplete.normal=Normal toolbar.autoComplete.normal=Normal
toolbar.autoComplete.off=Aus toolbar.autoComplete.off=Aus
toolbar.autoSelect=#Auto select
toolbar.autoSelect.off=Aus
toolbar.autoSelect.on=#On
toolbar.cancelStatement=Laufenden Befehl abbrechen toolbar.cancelStatement=Laufenden Befehl abbrechen
toolbar.clear=Leeren toolbar.clear=Leeren
toolbar.commit=Commit (Abschliessen/Speichern) toolbar.commit=Commit (Abschliessen/Speichern)
......
...@@ -98,6 +98,9 @@ toolbar.autoComplete=Auto completado ...@@ -98,6 +98,9 @@ toolbar.autoComplete=Auto completado
toolbar.autoComplete.full=Completo toolbar.autoComplete.full=Completo
toolbar.autoComplete.normal=Normal toolbar.autoComplete.normal=Normal
toolbar.autoComplete.off=Desactivado toolbar.autoComplete.off=Desactivado
toolbar.autoSelect=#Auto select
toolbar.autoSelect.off=Desactivado
toolbar.autoSelect.on=#On
toolbar.cancelStatement=Cancelar la instrucción actual toolbar.cancelStatement=Cancelar la instrucción actual
toolbar.clear=Eliminar toolbar.clear=Eliminar
toolbar.commit=Commit toolbar.commit=Commit
......
...@@ -98,6 +98,9 @@ toolbar.autoComplete=Complètement automatique ...@@ -98,6 +98,9 @@ toolbar.autoComplete=Complètement automatique
toolbar.autoComplete.full=Exhaustif toolbar.autoComplete.full=Exhaustif
toolbar.autoComplete.normal=Normal toolbar.autoComplete.normal=Normal
toolbar.autoComplete.off=Désactivé toolbar.autoComplete.off=Désactivé
toolbar.autoSelect=#Auto select
toolbar.autoSelect.off=Désactivé
toolbar.autoSelect.on=#On
toolbar.cancelStatement=Annuler l'instruction en cours toolbar.cancelStatement=Annuler l'instruction en cours
toolbar.clear=Effacer toolbar.clear=Effacer
toolbar.commit=Valider toolbar.commit=Valider
......
...@@ -98,6 +98,9 @@ toolbar.autoComplete=Automatikus kiegészítés ...@@ -98,6 +98,9 @@ toolbar.autoComplete=Automatikus kiegészítés
toolbar.autoComplete.full=Teljes toolbar.autoComplete.full=Teljes
toolbar.autoComplete.normal=Normál toolbar.autoComplete.normal=Normál
toolbar.autoComplete.off=Kikapcsolva toolbar.autoComplete.off=Kikapcsolva
toolbar.autoSelect=#Auto select
toolbar.autoSelect.off=Kikapcsolva
toolbar.autoSelect.on=#On
toolbar.cancelStatement=Aktuális utasítás végrehajtásának megszakítása toolbar.cancelStatement=Aktuális utasítás végrehajtásának megszakítása
toolbar.clear=Törlés toolbar.clear=Törlés
toolbar.commit=Jóváhagyás toolbar.commit=Jóváhagyás
......
...@@ -98,6 +98,9 @@ toolbar.autoComplete=Auto-Complete ...@@ -98,6 +98,9 @@ toolbar.autoComplete=Auto-Complete
toolbar.autoComplete.full=Full toolbar.autoComplete.full=Full
toolbar.autoComplete.normal=Normal toolbar.autoComplete.normal=Normal
toolbar.autoComplete.off=Off toolbar.autoComplete.off=Off
toolbar.autoSelect=#Auto select
toolbar.autoSelect.off=Off
toolbar.autoSelect.on=#On
toolbar.cancelStatement=Batalkan pernyataan terkini toolbar.cancelStatement=Batalkan pernyataan terkini
toolbar.clear=Bersihkan toolbar.clear=Bersihkan
toolbar.commit=Laksanakan toolbar.commit=Laksanakan
......
...@@ -98,6 +98,9 @@ toolbar.autoComplete=Auto completamento ...@@ -98,6 +98,9 @@ toolbar.autoComplete=Auto completamento
toolbar.autoComplete.full=Pieno toolbar.autoComplete.full=Pieno
toolbar.autoComplete.normal=Normale toolbar.autoComplete.normal=Normale
toolbar.autoComplete.off=Disattivo toolbar.autoComplete.off=Disattivo
toolbar.autoSelect=#Auto select
toolbar.autoSelect.off=Disattivo
toolbar.autoSelect.on=#On
toolbar.cancelStatement=Annulla il seguente comando toolbar.cancelStatement=Annulla il seguente comando
toolbar.clear=Annulla toolbar.clear=Annulla
toolbar.commit=Esegui comando toolbar.commit=Esegui comando
......
...@@ -98,6 +98,9 @@ toolbar.autoComplete=オートコンプリート ...@@ -98,6 +98,9 @@ toolbar.autoComplete=オートコンプリート
toolbar.autoComplete.full=フル toolbar.autoComplete.full=フル
toolbar.autoComplete.normal=ノーマル toolbar.autoComplete.normal=ノーマル
toolbar.autoComplete.off=オフ toolbar.autoComplete.off=オフ
toolbar.autoSelect=#Auto select
toolbar.autoSelect.off=オフ
toolbar.autoSelect.on=#On
toolbar.cancelStatement=現在のステートメントをキャンセル toolbar.cancelStatement=現在のステートメントをキャンセル
toolbar.clear=クリア toolbar.clear=クリア
toolbar.commit=コミット toolbar.commit=コミット
......
...@@ -98,6 +98,9 @@ toolbar.autoComplete=자동 완성 ...@@ -98,6 +98,9 @@ toolbar.autoComplete=자동 완성
toolbar.autoComplete.full=전체 toolbar.autoComplete.full=전체
toolbar.autoComplete.normal=보통 toolbar.autoComplete.normal=보통
toolbar.autoComplete.off=안함 toolbar.autoComplete.off=안함
toolbar.autoSelect=#Auto select
toolbar.autoSelect.off=안함
toolbar.autoSelect.on=#On
toolbar.cancelStatement=현재 문 취소 toolbar.cancelStatement=현재 문 취소
toolbar.clear=지우기 toolbar.clear=지우기
toolbar.commit=커밋 toolbar.commit=커밋
......
...@@ -98,6 +98,9 @@ toolbar.autoComplete=Auto aanvullen ...@@ -98,6 +98,9 @@ toolbar.autoComplete=Auto aanvullen
toolbar.autoComplete.full=Volledig toolbar.autoComplete.full=Volledig
toolbar.autoComplete.normal=Normaal toolbar.autoComplete.normal=Normaal
toolbar.autoComplete.off=Uit toolbar.autoComplete.off=Uit
toolbar.autoSelect=#Auto select
toolbar.autoSelect.off=Uit
toolbar.autoSelect.on=#On
toolbar.cancelStatement=Annuleer het huidige statement toolbar.cancelStatement=Annuleer het huidige statement
toolbar.clear=Wissen toolbar.clear=Wissen
toolbar.commit=Commit toolbar.commit=Commit
......
...@@ -98,6 +98,9 @@ toolbar.autoComplete=Automatyczne uzupełnianie ...@@ -98,6 +98,9 @@ toolbar.autoComplete=Automatyczne uzupełnianie
toolbar.autoComplete.full=Pełny toolbar.autoComplete.full=Pełny
toolbar.autoComplete.normal=Normalny toolbar.autoComplete.normal=Normalny
toolbar.autoComplete.off=Wyłączony toolbar.autoComplete.off=Wyłączony
toolbar.autoSelect=#Auto select
toolbar.autoSelect.off=Wyłączony
toolbar.autoSelect.on=#On
toolbar.cancelStatement=Anuluj bieżące zapytanie toolbar.cancelStatement=Anuluj bieżące zapytanie
toolbar.clear=Wyczyść toolbar.clear=Wyczyść
toolbar.commit=Zatwierdź toolbar.commit=Zatwierdź
......
...@@ -98,6 +98,9 @@ toolbar.autoComplete=Auto complete ...@@ -98,6 +98,9 @@ toolbar.autoComplete=Auto complete
toolbar.autoComplete.full=Total toolbar.autoComplete.full=Total
toolbar.autoComplete.normal=Normal toolbar.autoComplete.normal=Normal
toolbar.autoComplete.off=Desligado toolbar.autoComplete.off=Desligado
toolbar.autoSelect=#Auto select
toolbar.autoSelect.off=Desligado
toolbar.autoSelect.on=#On
toolbar.cancelStatement=Cancelar o comando que se encontra em execução toolbar.cancelStatement=Cancelar o comando que se encontra em execução
toolbar.clear=Limpar toolbar.clear=Limpar
toolbar.commit=Commit toolbar.commit=Commit
......
...@@ -98,6 +98,9 @@ toolbar.autoComplete=Авто-завершение ...@@ -98,6 +98,9 @@ toolbar.autoComplete=Авто-завершение
toolbar.autoComplete.full=Все toolbar.autoComplete.full=Все
toolbar.autoComplete.normal=Нормальные toolbar.autoComplete.normal=Нормальные
toolbar.autoComplete.off=Выключено toolbar.autoComplete.off=Выключено
toolbar.autoSelect=#Auto select
toolbar.autoSelect.off=Выключено
toolbar.autoSelect.on=#On
toolbar.cancelStatement=Отменить текущий запрос toolbar.cancelStatement=Отменить текущий запрос
toolbar.clear=Очистить toolbar.clear=Очистить
toolbar.commit=Выполнить toolbar.commit=Выполнить
......
...@@ -98,6 +98,9 @@ toolbar.autoComplete=Auto dokončovanie ...@@ -98,6 +98,9 @@ toolbar.autoComplete=Auto dokončovanie
toolbar.autoComplete.full=Plné toolbar.autoComplete.full=Plné
toolbar.autoComplete.normal=Normálne toolbar.autoComplete.normal=Normálne
toolbar.autoComplete.off=Vypnuté toolbar.autoComplete.off=Vypnuté
toolbar.autoSelect=#Auto select
toolbar.autoSelect.off=Vypnuté
toolbar.autoSelect.on=#On
toolbar.cancelStatement=Zrušiť aktuálny príkaz toolbar.cancelStatement=Zrušiť aktuálny príkaz
toolbar.clear=Vyčistiť toolbar.clear=Vyčistiť
toolbar.commit=Commit (schváliť) toolbar.commit=Commit (schváliť)
......
...@@ -98,6 +98,9 @@ toolbar.autoComplete=Auto-Complete ...@@ -98,6 +98,9 @@ toolbar.autoComplete=Auto-Complete
toolbar.autoComplete.full=Hepsi toolbar.autoComplete.full=Hepsi
toolbar.autoComplete.normal=Normal toolbar.autoComplete.normal=Normal
toolbar.autoComplete.off=Kapalı toolbar.autoComplete.off=Kapalı
toolbar.autoSelect=#Auto select
toolbar.autoSelect.off=Kapalı
toolbar.autoSelect.on=#On
toolbar.cancelStatement=Yürütülen işlemi iptal et toolbar.cancelStatement=Yürütülen işlemi iptal et
toolbar.clear=Temizle toolbar.clear=Temizle
toolbar.commit=Degişiklikleri kaydet toolbar.commit=Degişiklikleri kaydet
......
...@@ -98,6 +98,9 @@ toolbar.autoComplete=Авто доповнення ...@@ -98,6 +98,9 @@ toolbar.autoComplete=Авто доповнення
toolbar.autoComplete.full=Повне toolbar.autoComplete.full=Повне
toolbar.autoComplete.normal=Нормальне toolbar.autoComplete.normal=Нормальне
toolbar.autoComplete.off=Виключене toolbar.autoComplete.off=Виключене
toolbar.autoSelect=#Auto select
toolbar.autoSelect.off=Виключене
toolbar.autoSelect.on=#On
toolbar.cancelStatement=Відмінити поточний запит toolbar.cancelStatement=Відмінити поточний запит
toolbar.clear=Очистити toolbar.clear=Очистити
toolbar.commit=Підтвердити зміни toolbar.commit=Підтвердити зміни
......
...@@ -98,6 +98,9 @@ toolbar.autoComplete=自动完成 ...@@ -98,6 +98,9 @@ toolbar.autoComplete=自动完成
toolbar.autoComplete.full=完全 toolbar.autoComplete.full=完全
toolbar.autoComplete.normal=正常 toolbar.autoComplete.normal=正常
toolbar.autoComplete.off=关闭 toolbar.autoComplete.off=关闭
toolbar.autoSelect=#Auto select
toolbar.autoSelect.off=关闭
toolbar.autoSelect.on=#On
toolbar.cancelStatement=取消当前的执行语句 toolbar.cancelStatement=取消当前的执行语句
toolbar.clear=清除 toolbar.clear=清除
toolbar.commit=提交 toolbar.commit=提交
......
...@@ -98,6 +98,9 @@ toolbar.autoComplete=自動完成 (complete) ...@@ -98,6 +98,9 @@ toolbar.autoComplete=自動完成 (complete)
toolbar.autoComplete.full=完整 toolbar.autoComplete.full=完整
toolbar.autoComplete.normal=標準 toolbar.autoComplete.normal=標準
toolbar.autoComplete.off=關閉 toolbar.autoComplete.off=關閉
toolbar.autoSelect=#Auto select
toolbar.autoSelect.off=關閉
toolbar.autoSelect.on=#On
toolbar.cancelStatement=取消目前的SQL述句 toolbar.cancelStatement=取消目前的SQL述句
toolbar.clear=清除 toolbar.clear=清除
toolbar.commit=提交 toolbar.commit=提交
......
...@@ -553,6 +553,7 @@ public class Data { ...@@ -553,6 +553,7 @@ public class Data {
writeVarInt(ts.getTimeZoneOffsetMins()); writeVarInt(ts.getTimeZoneOffsetMins());
} }
case Value.GEOMETRY: case Value.GEOMETRY:
// fall though
case Value.JAVA_OBJECT: { case Value.JAVA_OBJECT: {
writeByte((byte) type); writeByte((byte) type);
byte[] b = v.getBytesNoCopy(); byte[] b = v.getBytesNoCopy();
...@@ -792,7 +793,7 @@ public class Data { ...@@ -792,7 +793,7 @@ public class Data {
case Value.TIMESTAMP_TZ: { case Value.TIMESTAMP_TZ: {
long dateValue = readVarLong(); long dateValue = readVarLong();
long nanos = readVarLong(); long nanos = readVarLong();
short tz = (short)readVarInt(); short tz = (short) readVarInt();
return ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, nanos, tz); return ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, nanos, tz);
} }
case Value.BYTES: { case Value.BYTES: {
......
...@@ -298,7 +298,8 @@ public class Column { ...@@ -298,7 +298,8 @@ public class Column {
} else if (dt.type == Value.TIMESTAMP_UTC) { } else if (dt.type == Value.TIMESTAMP_UTC) {
value = ValueTimestampUtc.fromMillis(session.getTransactionStart()); value = ValueTimestampUtc.fromMillis(session.getTransactionStart());
} else if (dt.type == Value.TIMESTAMP_TZ) { } else if (dt.type == Value.TIMESTAMP_TZ) {
value = ValueTimestampTimeZone.fromMillis(session.getTransactionStart(), (short)0); value = ValueTimestampTimeZone.fromMillis(
session.getTransactionStart(), (short) 0);
} 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) {
......
...@@ -113,7 +113,8 @@ public class Plan { ...@@ -113,7 +113,8 @@ public class Plan {
} }
double cost = 1; double cost = 1;
boolean invalidPlan = false; boolean invalidPlan = false;
final HashSet<Column> allColumnsSet = ExpressionVisitor.allColumnsForTableFilters(allFilters); final HashSet<Column> allColumnsSet = ExpressionVisitor
.allColumnsForTableFilters(allFilters);
for (int i = 0; i < allFilters.length; i++) { for (int i = 0; i < allFilters.length; i++) {
TableFilter tableFilter = allFilters[i]; TableFilter tableFilter = allFilters[i];
if (t.isDebugEnabled()) { if (t.isDebugEnabled()) {
......
...@@ -248,6 +248,7 @@ public abstract class Table extends SchemaObjectBase { ...@@ -248,6 +248,7 @@ public abstract class Table extends SchemaObjectBase {
* @param filters the table filters * @param filters the table filters
* @param filter the filter index * @param filter the filter index
* @param sortOrder the sort order * @param sortOrder the sort order
* @param allColumnsSet all columns
* @return the scan index * @return the scan index
*/ */
public Index getScanIndex(Session session, int[] masks, public Index getScanIndex(Session session, int[] masks,
...@@ -702,6 +703,7 @@ public abstract class Table extends SchemaObjectBase { ...@@ -702,6 +703,7 @@ public abstract class Table extends SchemaObjectBase {
* @param filters all joined table filters * @param filters all joined table filters
* @param filter the current table filter index * @param filter the current table filter index
* @param sortOrder the sort order * @param sortOrder the sort order
* @param allColumnsSet the set of all columns
* @return the plan item * @return the plan item
*/ */
public PlanItem getBestPlanItem(Session session, int[] masks, public PlanItem getBestPlanItem(Session session, int[] masks,
...@@ -719,7 +721,8 @@ public abstract class Table extends SchemaObjectBase { ...@@ -719,7 +721,8 @@ public abstract class Table extends SchemaObjectBase {
if (indexes != null && masks != null) { if (indexes != null && masks != null) {
for (int i = 1, size = indexes.size(); i < size; i++) { for (int i = 1, size = indexes.size(); i < size; i++) {
Index index = indexes.get(i); Index index = indexes.get(i);
double cost = index.getCost(session, masks, filters, filter, sortOrder, allColumnsSet); double cost = index.getCost(session, masks, filters, filter,
sortOrder, allColumnsSet);
if (t.isDebugEnabled()) { if (t.isDebugEnabled()) {
t.debug("Table : potential plan item cost {0} index {1}", t.debug("Table : potential plan item cost {0} index {1}",
cost, index.getPlanSQL()); cost, index.getPlanSQL());
......
...@@ -184,6 +184,7 @@ public class TableFilter implements ColumnResolver { ...@@ -184,6 +184,7 @@ public class TableFilter implements ColumnResolver {
* @param s the session * @param s the session
* @param filters all joined table filters * @param filters all joined table filters
* @param filter the current table filter index * @param filter the current table filter index
* @param allColumnsSet the set of all columns
* @return the best plan item * @return the best plan item
*/ */
public PlanItem getBestPlanItem(Session s, TableFilter[] filters, int filter, public PlanItem getBestPlanItem(Session s, TableFilter[] filters, int filter,
...@@ -195,8 +196,10 @@ public class TableFilter implements ColumnResolver { ...@@ -195,8 +196,10 @@ public class TableFilter implements ColumnResolver {
} }
if (indexConditions.size() == 0) { if (indexConditions.size() == 0) {
item1 = new PlanItem(); item1 = new PlanItem();
item1.setIndex(table.getScanIndex(s, null, filters, filter, sortOrder, allColumnsSet)); item1.setIndex(table.getScanIndex(s, null, filters, filter,
item1.cost = item1.getIndex().getCost(s, null, filters, filter, sortOrder, allColumnsSet); sortOrder, allColumnsSet));
item1.cost = item1.getIndex().getCost(s, null, filters, filter,
sortOrder, allColumnsSet);
} }
int len = table.getColumns().length; int len = table.getColumns().length;
int[] masks = new int[len]; int[] masks = new int[len];
......
...@@ -557,6 +557,12 @@ public class TableView extends Table { ...@@ -557,6 +557,12 @@ public class TableView extends Table {
return 0; return 0;
} }
/**
* Get the index of the first parameter.
*
* @param additionalParameters additional parameters
* @return the index of the first parameter
*/
public int getParameterOffset(ArrayList<Parameter> additionalParameters) { public int getParameterOffset(ArrayList<Parameter> additionalParameters) {
int result = topQuery == null ? -1 : getMaxParameterIndex(topQuery.getParameters()); int result = topQuery == null ? -1 : getMaxParameterIndex(topQuery.getParameters());
if (additionalParameters != null) { if (additionalParameters != null) {
...@@ -565,7 +571,7 @@ public class TableView extends Table { ...@@ -565,7 +571,7 @@ public class TableView extends Table {
return result + 1; return result + 1;
} }
private int getMaxParameterIndex(ArrayList<Parameter> parameters) { private static int getMaxParameterIndex(ArrayList<Parameter> parameters) {
int result = -1; int result = -1;
for (Parameter p : parameters) { for (Parameter p : parameters) {
result = Math.max(result, p.getIndex()); result = Math.max(result, p.getIndex());
......
...@@ -314,7 +314,8 @@ public class DateTimeUtils { ...@@ -314,7 +314,8 @@ public class DateTimeUtils {
} }
/** /**
* Parse a time string. The format is: [-]hour:minute:second[.nanos] or alternatively [-]hour.minute.second[.nanos]. * Parse a time string. The format is: [-]hour:minute:second[.nanos] or
* alternatively [-]hour.minute.second[.nanos].
* *
* @param s the string to parse * @param s the string to parse
* @param start the parse index start * @param start the parse index start
...@@ -332,7 +333,8 @@ public class DateTimeUtils { ...@@ -332,7 +333,8 @@ public class DateTimeUtils {
int s2 = s.indexOf(':', s1 + 1); int s2 = s.indexOf(':', s1 + 1);
int s3 = s.indexOf('.', s2 + 1); int s3 = s.indexOf('.', s2 + 1);
if (s1 <= 0 || s2 <= s1) { if (s1 <= 0 || s2 <= s1) {
// if first try fails try to use IBM DB2 time format [-]hour.minute.second[.nanos] // if first try fails try to use IBM DB2 time format
// [-]hour.minute.second[.nanos]
s1 = s.indexOf('.', start); s1 = s.indexOf('.', start);
s2 = s.indexOf('.', s1 + 1); s2 = s.indexOf('.', s1 + 1);
s3 = s.indexOf('.', s2 + 1); s3 = s.indexOf('.', s2 + 1);
......
...@@ -148,8 +148,9 @@ class ToDateTokenizer { ...@@ -148,8 +148,9 @@ class ToDateTokenizer {
dateNr = Integer.parseInt(inputFragmentStr); dateNr = Integer.parseInt(inputFragmentStr);
// Gregorian calendar does not have a year 0. // Gregorian calendar does not have a year 0.
// 0 = 0001 BC, -1 = 0002 BC, ... so we adjust // 0 = 0001 BC, -1 = 0002 BC, ... so we adjust
if (dateNr==0) if (dateNr == 0) {
throwException(params, "Year may not be zero"); throwException(params, "Year may not be zero");
}
result.set(Calendar.YEAR, dateNr >= 0 ? dateNr : dateNr + 1); result.set(Calendar.YEAR, dateNr >= 0 ? dateNr : dateNr + 1);
break; break;
case YYY: case YYY:
...@@ -166,13 +167,15 @@ class ToDateTokenizer { ...@@ -166,13 +167,15 @@ class ToDateTokenizer {
PATTERN_TWO_TO_FOUR_DIGITS, params, formatTokenEnum); PATTERN_TWO_TO_FOUR_DIGITS, params, formatTokenEnum);
dateNr = Integer.parseInt(inputFragmentStr); dateNr = Integer.parseInt(inputFragmentStr);
if (inputFragmentStr.length() < 4) { if (inputFragmentStr.length() < 4) {
if (dateNr < 50) if (dateNr < 50) {
dateNr += 2000; dateNr += 2000;
else if (dateNr < 100) } else if (dateNr < 100) {
dateNr += 1900; dateNr += 1900;
}
} }
if (dateNr==0) if (dateNr == 0) {
throwException(params, "Year may not be zero"); throwException(params, "Year may not be zero");
}
result.set(Calendar.YEAR, dateNr); result.set(Calendar.YEAR, dateNr);
break; break;
case RR: case RR:
......
...@@ -589,7 +589,8 @@ public class Transfer { ...@@ -589,7 +589,8 @@ public class Transfer {
return ValueTimestampUtc.fromNanos(readLong()); return ValueTimestampUtc.fromNanos(readLong());
} }
case Value.TIMESTAMP_TZ: { case Value.TIMESTAMP_TZ: {
return ValueTimestampTimeZone.fromDateValueAndNanos(readLong(), readLong(), (short) readInt()); return ValueTimestampTimeZone.fromDateValueAndNanos(readLong(),
readLong(), (short) readInt());
} }
case Value.DECIMAL: case Value.DECIMAL:
return ValueDecimal.get(new BigDecimal(readString())); return ValueDecimal.get(new BigDecimal(readString()));
......
...@@ -20,7 +20,8 @@ import org.h2.util.StringUtils; ...@@ -20,7 +20,8 @@ import org.h2.util.StringUtils;
/** /**
* Implementation of the TIMESTAMP WITH TIMEZONE data type. * Implementation of the TIMESTAMP WITH TIMEZONE data type.
* *
* @see <a href="https://en.wikipedia.org/wiki/ISO_8601#Time_zone_designators">ISO 8601 Time zone designators</a> * @see <a href="https://en.wikipedia.org/wiki/ISO_8601#Time_zone_designators">
* ISO 8601 Time zone designators</a>
*/ */
public class ValueTimestampTimeZone extends Value { public class ValueTimestampTimeZone extends Value {
...@@ -54,12 +55,15 @@ public class ValueTimestampTimeZone extends Value { ...@@ -54,12 +55,15 @@ public class ValueTimestampTimeZone extends Value {
*/ */
private final short timeZoneOffsetMins; private final short timeZoneOffsetMins;
private ValueTimestampTimeZone(long dateValue, long timeNanos, short timeZoneOffsetMins) { private ValueTimestampTimeZone(long dateValue, long timeNanos,
short timeZoneOffsetMins) {
if (timeNanos < 0 || timeNanos >= 24L * 60 * 60 * 1000 * 1000 * 1000) { if (timeNanos < 0 || timeNanos >= 24L * 60 * 60 * 1000 * 1000 * 1000) {
throw new IllegalArgumentException("timeNanos out of range " + timeNanos); throw new IllegalArgumentException("timeNanos out of range " +
timeNanos);
} }
if (timeZoneOffsetMins < (-12*60) || timeZoneOffsetMins >= (12*60)) { if (timeZoneOffsetMins < (-12 * 60) || timeZoneOffsetMins >= (12 * 60)) {
throw new IllegalArgumentException("timeZoneOffsetMins out of range " + timeZoneOffsetMins); throw new IllegalArgumentException(
"timeZoneOffsetMins out of range " + timeZoneOffsetMins);
} }
this.dateValue = dateValue; this.dateValue = dateValue;
this.timeNanos = timeNanos; this.timeNanos = timeNanos;
...@@ -72,10 +76,13 @@ public class ValueTimestampTimeZone extends Value { ...@@ -72,10 +76,13 @@ public class ValueTimestampTimeZone extends Value {
* @param dateValue the date value, a bit field with bits for the year, * @param dateValue the date value, a bit field with bits for the year,
* month, and day * month, and day
* @param timeNanos the nanoseconds since midnight * @param timeNanos the nanoseconds since midnight
* @param timeZoneOffsetMins the timezone offset in minutes
* @return the value * @return the value
*/ */
public static ValueTimestampTimeZone fromDateValueAndNanos(long dateValue, long timeNanos, short timeZoneOffsetMins) { public static ValueTimestampTimeZone fromDateValueAndNanos(long dateValue,
return (ValueTimestampTimeZone) Value.cache(new ValueTimestampTimeZone(dateValue, timeNanos, timeZoneOffsetMins)); long timeNanos, short timeZoneOffsetMins) {
return (ValueTimestampTimeZone) Value.cache(new ValueTimestampTimeZone(
dateValue, timeNanos, timeZoneOffsetMins));
} }
/** /**
...@@ -89,7 +96,8 @@ public class ValueTimestampTimeZone extends Value { ...@@ -89,7 +96,8 @@ public class ValueTimestampTimeZone extends Value {
long nanos = timestamp.getNanos() % 1000000; long nanos = timestamp.getNanos() % 1000000;
long dateValue = DateTimeUtils.dateValueFromDate(ms); long dateValue = DateTimeUtils.dateValueFromDate(ms);
nanos += DateTimeUtils.nanosFromDate(ms); nanos += DateTimeUtils.nanosFromDate(ms);
return fromDateValueAndNanos(dateValue, nanos, timestamp.getTimeZoneOffsetMins()); return fromDateValueAndNanos(dateValue, nanos,
timestamp.getTimeZoneOffsetMins());
} }
/** /**
...@@ -97,9 +105,11 @@ public class ValueTimestampTimeZone extends Value { ...@@ -97,9 +105,11 @@ public class ValueTimestampTimeZone extends Value {
* *
* @param ms the milliseconds * @param ms the milliseconds
* @param nanos the nanoseconds * @param nanos the nanoseconds
* @param timeZoneOffsetMins the timezone offset in minutes
* @return the value * @return the value
*/ */
public static ValueTimestampTimeZone fromMillisNanos(long ms, int nanos, short timeZoneOffsetMins) { public static ValueTimestampTimeZone fromMillisNanos(long ms, int nanos,
short timeZoneOffsetMins) {
long dateValue = DateTimeUtils.dateValueFromDate(ms); long dateValue = DateTimeUtils.dateValueFromDate(ms);
long timeNanos = nanos + DateTimeUtils.nanosFromDate(ms); long timeNanos = nanos + DateTimeUtils.nanosFromDate(ms);
return fromDateValueAndNanos(dateValue, timeNanos, timeZoneOffsetMins); return fromDateValueAndNanos(dateValue, timeNanos, timeZoneOffsetMins);
...@@ -109,9 +119,11 @@ public class ValueTimestampTimeZone extends Value { ...@@ -109,9 +119,11 @@ public class ValueTimestampTimeZone extends Value {
* Get or create a timestamp value for the given date/time in millis. * Get or create a timestamp value for the given date/time in millis.
* *
* @param ms the milliseconds * @param ms the milliseconds
* @param timeZoneOffsetMins the timezone offset in minutes
* @return the value * @return the value
*/ */
public static ValueTimestampTimeZone fromMillis(long ms, short timeZoneOffsetMins) { public static ValueTimestampTimeZone fromMillis(long ms,
short timeZoneOffsetMins) {
long dateValue = DateTimeUtils.dateValueFromDate(ms); long dateValue = DateTimeUtils.dateValueFromDate(ms);
long nanos = DateTimeUtils.nanosFromDate(ms); long nanos = DateTimeUtils.nanosFromDate(ms);
return fromDateValueAndNanos(dateValue, nanos, timeZoneOffsetMins); return fromDateValueAndNanos(dateValue, nanos, timeZoneOffsetMins);
...@@ -149,7 +161,7 @@ public class ValueTimestampTimeZone extends Value { ...@@ -149,7 +161,7 @@ public class ValueTimestampTimeZone extends Value {
} }
long dateValue = DateTimeUtils.parseDateValue(s, 0, dateEnd); long dateValue = DateTimeUtils.parseDateValue(s, 0, dateEnd);
long nanos; long nanos;
short tz_mins = 0; short tzMinutes = 0;
if (timeStart < 0) { if (timeStart < 0) {
nanos = 0; nanos = 0;
} else { } else {
...@@ -183,12 +195,13 @@ public class ValueTimestampTimeZone extends Value { ...@@ -183,12 +195,13 @@ public class ValueTimestampTimeZone extends Value {
} }
if (tz != null) { if (tz != null) {
long millis = DateTimeUtils.convertDateValueToDate(dateValue).getTime(); long millis = DateTimeUtils.convertDateValueToDate(dateValue).getTime();
tz_mins = (short) (tz.getOffset(millis) / 1000 / 60); tzMinutes = (short) (tz.getOffset(millis) / 1000 / 60);
} }
} }
nanos = DateTimeUtils.parseTimeNanos(s, dateEnd + 1, timeEnd, true); nanos = DateTimeUtils.parseTimeNanos(s, dateEnd + 1, timeEnd, true);
} }
return ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, nanos, tz_mins); return ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, nanos,
tzMinutes);
} }
/** /**
...@@ -221,8 +234,10 @@ public class ValueTimestampTimeZone extends Value { ...@@ -221,8 +234,10 @@ public class ValueTimestampTimeZone extends Value {
@Override @Override
public Timestamp getTimestamp() { public Timestamp getTimestamp() {
Timestamp ts = DateTimeUtils.convertDateValueToTimestamp(dateValue, timeNanos); Timestamp ts = DateTimeUtils.convertDateValueToTimestamp(dateValue,
return new TimestampWithTimeZone(ts.getTime(), ts.getNanos(), getTimeZoneOffsetMins()); timeNanos);
return new TimestampWithTimeZone(ts.getTime(), ts.getNanos(),
getTimeZoneOffsetMins());
} }
@Override @Override
...@@ -250,7 +265,7 @@ public class ValueTimestampTimeZone extends Value { ...@@ -250,7 +265,7 @@ public class ValueTimestampTimeZone extends Value {
private static void appendTimeZone(StringBuilder buff, short tz) { private static void appendTimeZone(StringBuilder buff, short tz) {
if (tz < 0) { if (tz < 0) {
buff.append('-'); buff.append('-');
tz = (short)-tz; tz = (short) -tz;
} }
int hours = tz / 60; int hours = tz / 60;
tz -= hours * 60; tz -= hours * 60;
...@@ -324,12 +339,14 @@ public class ValueTimestampTimeZone extends Value { ...@@ -324,12 +339,14 @@ public class ValueTimestampTimeZone extends Value {
return false; return false;
} }
ValueTimestampTimeZone x = (ValueTimestampTimeZone) other; ValueTimestampTimeZone x = (ValueTimestampTimeZone) other;
return dateValue == x.dateValue && timeNanos == x.timeNanos && timeZoneOffsetMins == x.timeZoneOffsetMins; return dateValue == x.dateValue && timeNanos == x.timeNanos &&
timeZoneOffsetMins == x.timeZoneOffsetMins;
} }
@Override @Override
public int hashCode() { public int hashCode() {
return (int) (dateValue ^ (dateValue >>> 32) ^ timeNanos ^ (timeNanos >>> 32) ^ timeZoneOffsetMins); return (int) (dateValue ^ (dateValue >>> 32) ^ timeNanos ^
(timeNanos >>> 32) ^ timeZoneOffsetMins);
} }
@Override @Override
...@@ -345,12 +362,16 @@ public class ValueTimestampTimeZone extends Value { ...@@ -345,12 +362,16 @@ public class ValueTimestampTimeZone extends Value {
@Override @Override
public Value add(Value v) { public Value add(Value v) {
throw DbException.getUnsupportedException("manipulating TIMESTAMP WITH TIMEZONE values is unsupported"); throw DbException
.getUnsupportedException(
"manipulating TIMESTAMP WITH TIMEZONE values is unsupported");
} }
@Override @Override
public Value subtract(Value v) { public Value subtract(Value v) {
throw DbException.getUnsupportedException("manipulating TIMESTAMP WITH TIMEZONE values is unsupported"); throw DbException
.getUnsupportedException(
"manipulating TIMESTAMP WITH TIMEZONE values is unsupported");
} }
} }
...@@ -12,7 +12,10 @@ import org.junit.Test; ...@@ -12,7 +12,10 @@ import org.junit.Test;
* used by H2. * used by H2.
*/ */
public class TestAllJunit { public class TestAllJunit {
/**
* Run all the fast tests.
*/
@Test @Test
public void testFast() throws Exception { public void testFast() throws Exception {
TestAll.main("fast"); TestAll.main("fast");
......
...@@ -147,18 +147,24 @@ public class TestCompatibilityOracle extends TestBase { ...@@ -147,18 +147,24 @@ public class TestCompatibilityOracle extends TestBase {
Connection conn = getConnection("oracle;MODE=Oracle"); Connection conn = getConnection("oracle;MODE=Oracle");
Statement stat = conn.createStatement(); Statement stat = conn.createStatement();
stat.execute("CREATE TABLE DATETABLE (ID NUMBER PRIMARY KEY, TESTVAL TIMESTAMP)"); stat.execute("CREATE TABLE DATE_TABLE (ID NUMBER PRIMARY KEY, TEST_VAL TIMESTAMP)");
stat.execute("INSERT INTO DATETABLE VALUES (1, to_date('31-DEC-9999 23:59:59','DD-MON-RRRR HH24:MI:SS'))"); stat.execute("INSERT INTO DATE_TABLE VALUES (1, " +
stat.execute("INSERT INTO DATETABLE VALUES (2, to_date('01-JAN-0001 00:00:00','DD-MON-RRRR HH24:MI:SS'))"); "to_date('31-DEC-9999 23:59:59','DD-MON-RRRR HH24:MI:SS'))");
stat.execute("INSERT INTO DATE_TABLE VALUES (2, " +
"to_date('01-JAN-0001 00:00:00','DD-MON-RRRR HH24:MI:SS'))");
assertResultDate("9999-12-31T23:59:59", stat, "SELECT TESTVAL FROM DATETABLE WHERE ID=1"); assertResultDate("9999-12-31T23:59:59", stat,
assertResultDate("0001-01-01T00:00:00", stat, "SELECT TESTVAL FROM DATETABLE WHERE ID=2"); "SELECT TEST_VAL FROM DATE_TABLE WHERE ID=1");
assertResultDate("0001-01-01T00:00:00", stat,
"SELECT TEST_VAL FROM DATE_TABLE WHERE ID=2");
conn.close(); conn.close();
} }
private void assertResultDate(String expected, Statement stat, String sql) throws SQLException { private void assertResultDate(String expected, Statement stat, String sql)
SimpleDateFormat iso8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); throws SQLException {
SimpleDateFormat iso8601 = new SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ss");
ResultSet rs = stat.executeQuery(sql); ResultSet rs = stat.executeQuery(sql);
if (rs.next()) { if (rs.next()) {
assertEquals(expected, iso8601.format(rs.getTimestamp(1))); assertEquals(expected, iso8601.format(rs.getTimestamp(1)));
......
...@@ -1407,21 +1407,23 @@ public class TestFunctions extends TestBase implements AggregateFunction { ...@@ -1407,21 +1407,23 @@ public class TestFunctions extends TestBase implements AggregateFunction {
date = new SimpleDateFormat("yyyy-MM-dd").parse("2013-01-29"); date = new SimpleDateFormat("yyyy-MM-dd").parse("2013-01-29");
assertEquals(date, ToDateParser.toDate("113029", "J")); assertEquals(date, ToDateParser.toDate("113029", "J"));
date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").parse("9999-12-31T23:59:59"); date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss")
assertEquals(date, ToDateParser.toDate("31-DEC-9999 23:59:59","DD-MON-YYYY HH24:MI:SS")); .parse("9999-12-31T23:59:59");
assertEquals(date, ToDateParser.toDate("31-DEC-9999 23:59:59","DD-MON-RRRR HH24:MI:SS")); assertEquals(date, ToDateParser.toDate("31-DEC-9999 23:59:59",
"DD-MON-YYYY HH24:MI:SS"));
assertEquals(date, ToDateParser.toDate("31-DEC-9999 23:59:59",
"DD-MON-RRRR HH24:MI:SS"));
SimpleDateFormat ymd = new SimpleDateFormat("yyyy-MM-dd"); SimpleDateFormat ymd = new SimpleDateFormat("yyyy-MM-dd");
assertEquals(ymd.parse("0001-03-01"), ToDateParser.toDate("1-MAR-0001","DD-MON-RRRR")); assertEquals(ymd.parse("0001-03-01"), ToDateParser.toDate("1-MAR-0001", "DD-MON-RRRR"));
assertEquals(ymd.parse("9999-03-01"), ToDateParser.toDate("1-MAR-9999","DD-MON-RRRR")); assertEquals(ymd.parse("9999-03-01"), ToDateParser.toDate("1-MAR-9999", "DD-MON-RRRR"));
assertEquals(ymd.parse("2000-03-01"), ToDateParser.toDate("1-MAR-000","DD-MON-RRRR")); assertEquals(ymd.parse("2000-03-01"), ToDateParser.toDate("1-MAR-000", "DD-MON-RRRR"));
assertEquals(ymd.parse("1999-03-01"), ToDateParser.toDate("1-MAR-099","DD-MON-RRRR")); assertEquals(ymd.parse("1999-03-01"), ToDateParser.toDate("1-MAR-099", "DD-MON-RRRR"));
assertEquals(ymd.parse("0100-03-01"), ToDateParser.toDate("1-MAR-100","DD-MON-RRRR")); assertEquals(ymd.parse("0100-03-01"), ToDateParser.toDate("1-MAR-100", "DD-MON-RRRR"));
assertEquals(ymd.parse("2000-03-01"), ToDateParser.toDate("1-MAR-00","DD-MON-RRRR")); assertEquals(ymd.parse("2000-03-01"), ToDateParser.toDate("1-MAR-00", "DD-MON-RRRR"));
assertEquals(ymd.parse("2049-03-01"), ToDateParser.toDate("1-MAR-49","DD-MON-RRRR")); assertEquals(ymd.parse("2049-03-01"), ToDateParser.toDate("1-MAR-49", "DD-MON-RRRR"));
assertEquals(ymd.parse("1950-03-01"), ToDateParser.toDate("1-MAR-50","DD-MON-RRRR")); assertEquals(ymd.parse("1950-03-01"), ToDateParser.toDate("1-MAR-50", "DD-MON-RRRR"));
assertEquals(ymd.parse("1999-03-01"), ToDateParser.toDate("1-MAR-99","DD-MON-RRRR")); assertEquals(ymd.parse("1999-03-01"), ToDateParser.toDate("1-MAR-99", "DD-MON-RRRR"));
} }
private static void setMonth(Date date, int month) { private static void setMonth(Date date, int month) {
...@@ -1661,7 +1663,8 @@ public class TestFunctions extends TestBase implements AggregateFunction { ...@@ -1661,7 +1663,8 @@ public class TestFunctions extends TestBase implements AggregateFunction {
} }
private void testIfNull() throws SQLException { private void testIfNull() throws SQLException {
Connection conn = getConnection("functions"); Connection conn = getConnection("functions");
Statement stat = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); Statement stat = conn.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
stat.execute("CREATE TABLE T(f1 double)"); stat.execute("CREATE TABLE T(f1 double)");
stat.executeUpdate("INSERT INTO T VALUES( 1.2 )"); stat.executeUpdate("INSERT INTO T VALUES( 1.2 )");
stat.executeUpdate("INSERT INTO T VALUES( null )"); stat.executeUpdate("INSERT INTO T VALUES( null )");
......
...@@ -148,7 +148,8 @@ public class TestRecursiveQueries extends TestBase { ...@@ -148,7 +148,8 @@ public class TestRecursiveQueries extends TestBase {
null, null); null, null);
rs = stat.executeQuery("select x from system_range(1,5) " rs = stat.executeQuery("select x from system_range(1,5) "
+ "where x not in (with w(x) as (select 1 union all select x+1 from w where x<3) select x from w)"); + "where x not in (with w(x) as (select 1 union all select x+1 from w where x<3) "
+ "select x from w)");
assertResultSetOrdered(rs, new String[][]{{"4"}, {"5"}}); assertResultSetOrdered(rs, new String[][]{{"4"}, {"5"}});
conn.close(); conn.close();
......
...@@ -1512,9 +1512,11 @@ public class TestTableEngines extends TestBase { ...@@ -1512,9 +1512,11 @@ public class TestTableEngines extends TestBase {
@Override @Override
public double getCost(Session session, int[] masks, public double getCost(Session session, int[] masks,
TableFilter[] filters, int filter, SortOrder sortOrder, HashSet<Column> allColumnsSet) { TableFilter[] filters, int filter, SortOrder sortOrder,
HashSet<Column> allColumnsSet) {
doTests(session); doTests(session);
return getCostRangeIndex(masks, set.size(), filters, filter, sortOrder, false, allColumnsSet); return getCostRangeIndex(masks, set.size(), filters, filter,
sortOrder, false, allColumnsSet);
} }
@Override @Override
......
...@@ -37,7 +37,8 @@ public class TestConnection extends TestBase { ...@@ -37,7 +37,8 @@ public class TestConnection extends TestBase {
} }
private void testSetInternalProperty() throws SQLException { private void testSetInternalProperty() throws SQLException {
// Use MySQL-mode since this allows all property names (apart from h2 internal names). // Use MySQL-mode since this allows all property names
// (apart from h2 internal names).
Connection conn = getConnection("clientInfoMySQL;MODE=MySQL"); Connection conn = getConnection("clientInfoMySQL;MODE=MySQL");
assertThrows(SQLClientInfoException.class, conn).setClientInfo("numServers", "SomeValue"); assertThrows(SQLClientInfoException.class, conn).setClientInfo("numServers", "SomeValue");
...@@ -48,7 +49,7 @@ public class TestConnection extends TestBase { ...@@ -48,7 +49,7 @@ public class TestConnection extends TestBase {
Connection conn = getConnection("clientInfo"); Connection conn = getConnection("clientInfo");
Properties properties = new Properties(); Properties properties = new Properties();
properties.put("ClientUser", "someuser"); properties.put("ClientUser", "someUser");
assertThrows(SQLClientInfoException.class, conn).setClientInfo(properties); assertThrows(SQLClientInfoException.class, conn).setClientInfo(properties);
} }
...@@ -58,12 +59,12 @@ public class TestConnection extends TestBase { ...@@ -58,12 +59,12 @@ public class TestConnection extends TestBase {
conn.setClientInfo("ApplicationName", "Connection Test"); conn.setClientInfo("ApplicationName", "Connection Test");
Properties properties = new Properties(); Properties properties = new Properties();
properties.put("ClientUser", "someuser"); properties.put("ClientUser", "someUser");
conn.setClientInfo(properties); conn.setClientInfo(properties);
// old property should have been removed // old property should have been removed
assertNull(conn.getClientInfo("ApplicationName")); assertNull(conn.getClientInfo("ApplicationName"));
// new property has been set // new property has been set
assertEquals(conn.getClientInfo("ClientUser"), "someuser"); assertEquals(conn.getClientInfo("ClientUser"), "someUser");
} }
private void testSetSupportedClientInfo() throws SQLException { private void testSetSupportedClientInfo() throws SQLException {
...@@ -75,7 +76,8 @@ public class TestConnection extends TestBase { ...@@ -75,7 +76,8 @@ public class TestConnection extends TestBase {
private void testSetUnsupportedClientInfo() throws SQLException { private void testSetUnsupportedClientInfo() throws SQLException {
Connection conn = getConnection("clientInfoDB2;MODE=DB2"); Connection conn = getConnection("clientInfoDB2;MODE=DB2");
assertThrows(SQLClientInfoException.class, conn).setClientInfo("UnsupportedName", "SomeValue"); assertThrows(SQLClientInfoException.class, conn).setClientInfo(
"UnsupportedName", "SomeValue");
} }
private void testGetUnsupportedClientInfo() throws SQLException { private void testGetUnsupportedClientInfo() throws SQLException {
......
...@@ -132,7 +132,7 @@ public class CalculateHashConstantLong implements Runnable { ...@@ -132,7 +132,7 @@ public class CalculateHashConstantLong implements Runnable {
System.out.println("Collisions: " + collisions); System.out.println("Collisions: " + collisions);
} }
static void printQuality(CalculateHashConstantLong test, long[] randomValues) { private static void printQuality(CalculateHashConstantLong test, long[] randomValues) {
int finalCount = randomValues.length * 10; int finalCount = randomValues.length * 10;
System.out.println("Quality of " + test); System.out.println("Quality of " + test);
int[] minMax; int[] minMax;
......
...@@ -10212,13 +10212,13 @@ insert into test values(5, 'b'), (5, 'b'), (20, 'a'); ...@@ -10212,13 +10212,13 @@ insert into test values(5, 'b'), (5, 'b'), (20, 'a');
> update count: 3 > update count: 3
select 0 from (( select 0 from ((
select 0 as f from dual u1 where null in (?, ?, ?, ?, ?) select 0 as f from dual u1 where null in (?, ?, ?, ?, ?)
) union all ( ) union all (
select u2.f from ( select u2.f from (
select 0 as f from ( select 0 as f from (
select 0 from dual u2f1f1 where now() = ? select 0 from dual u2f1f1 where now() = ?
) u2f1 ) u2f1
) u2 ) u2
)) where f = 12345; )) where f = 12345;
{ {
11, 22, 33, 44, 55, null 11, 22, 33, 44, 55, null
......
...@@ -36,12 +36,17 @@ public class TestMode extends TestBase { ...@@ -36,12 +36,17 @@ public class TestMode extends TestBase {
private void testDb2ClientInfo() { private void testDb2ClientInfo() {
Mode db2Mode = Mode.getInstance("DB2"); Mode db2Mode = Mode.getInstance("DB2");
assertTrue(db2Mode.supportedClientInfoPropertiesRegEx.matcher("ApplicationName").matches()); assertTrue(db2Mode.supportedClientInfoPropertiesRegEx.matcher(
assertTrue(db2Mode.supportedClientInfoPropertiesRegEx.matcher("ClientAccountingInformation").matches()); "ApplicationName").matches());
assertTrue(db2Mode.supportedClientInfoPropertiesRegEx.matcher("ClientUser").matches()); assertTrue(db2Mode.supportedClientInfoPropertiesRegEx.matcher(
assertTrue(db2Mode.supportedClientInfoPropertiesRegEx.matcher("ClientCorrelationToken").matches()); "ClientAccountingInformation").matches());
assertTrue(db2Mode.supportedClientInfoPropertiesRegEx.matcher(
assertFalse(db2Mode.supportedClientInfoPropertiesRegEx.matcher("AnyOtherValue").matches()); "ClientUser").matches());
assertTrue(db2Mode.supportedClientInfoPropertiesRegEx.matcher(
"ClientCorrelationToken").matches());
assertFalse(db2Mode.supportedClientInfoPropertiesRegEx.matcher(
"AnyOtherValue").matches());
} }
private void testDerbyClientInfo() { private void testDerbyClientInfo() {
...@@ -61,22 +66,25 @@ public class TestMode extends TestBase { ...@@ -61,22 +66,25 @@ public class TestMode extends TestBase {
private void testMySqlClientInfo() { private void testMySqlClientInfo() {
Mode mySqlMode = Mode.getInstance("MySQL"); Mode mySqlMode = Mode.getInstance("MySQL");
assertTrue(mySqlMode.supportedClientInfoPropertiesRegEx.matcher("AnyString").matches()); assertTrue(mySqlMode.supportedClientInfoPropertiesRegEx.matcher(
"AnyString").matches());
} }
private void testOracleClientInfo() { private void testOracleClientInfo() {
Mode oracleMode = Mode.getInstance("Oracle"); Mode oracleMode = Mode.getInstance("Oracle");
assertTrue(oracleMode.supportedClientInfoPropertiesRegEx.matcher("anythingContaining.aDot").matches()); assertTrue(oracleMode.supportedClientInfoPropertiesRegEx.matcher(
assertFalse(oracleMode.supportedClientInfoPropertiesRegEx.matcher("anythingContainingNoDot").matches()); "anythingContaining.aDot").matches());
assertFalse(oracleMode.supportedClientInfoPropertiesRegEx.matcher(
"anythingContainingNoDot").matches());
} }
private void testPostgresqlClientInfo() { private void testPostgresqlClientInfo() {
Mode postgresqlMode = Mode.getInstance("PostgreSQL"); Mode postgresqlMode = Mode.getInstance("PostgreSQL");
assertTrue(postgresqlMode.supportedClientInfoPropertiesRegEx.matcher(
assertTrue(postgresqlMode.supportedClientInfoPropertiesRegEx.matcher("ApplicationName").matches()); "ApplicationName").matches());
assertFalse(postgresqlMode.supportedClientInfoPropertiesRegEx.matcher(
assertFalse(postgresqlMode.supportedClientInfoPropertiesRegEx.matcher("AnyOtherValue").matches()); "AnyOtherValue").matches());
} }
} }
...@@ -140,7 +140,7 @@ public class TestNetUtils extends TestBase { ...@@ -140,7 +140,7 @@ public class TestNetUtils extends TestBase {
private Task createServerSocketTask(final ServerSocket serverSocket) { private Task createServerSocketTask(final ServerSocket serverSocket) {
Task task = new Task() { Task task = new Task() {
@Override @Override
public void call() throws Exception { public void call() throws Exception {
Socket ss = null; Socket ss = null;
...@@ -148,14 +148,19 @@ public class TestNetUtils extends TestBase { ...@@ -148,14 +148,19 @@ public class TestNetUtils extends TestBase {
ss = serverSocket.accept(); ss = serverSocket.accept();
ss.getOutputStream().write(123); ss.getOutputStream().write(123);
} finally { } finally {
closeSilently(ss); closeSilently(ss);
} }
} }
}; };
return task; return task;
} }
private void closeSilently(Socket socket) { /**
* Close a socket, ignoring errors
*
* @param socket the socket
*/
void closeSilently(Socket socket) {
try { try {
socket.close(); socket.close();
} catch (Exception e) { } catch (Exception e) {
...@@ -163,7 +168,12 @@ public class TestNetUtils extends TestBase { ...@@ -163,7 +168,12 @@ public class TestNetUtils extends TestBase {
} }
} }
private void closeSilently(ServerSocket socket) { /**
* Close a server socket, ignoring errors
*
* @param socket the server socket
*/
void closeSilently(ServerSocket socket) {
try { try {
socket.close(); socket.close();
} catch (Exception e) { } catch (Exception e) {
......
...@@ -36,7 +36,7 @@ public class TestSecurity extends TestBase { ...@@ -36,7 +36,7 @@ public class TestSecurity extends TestBase {
testSHA(); testSHA();
testAES(); testAES();
testBlockCiphers(); testBlockCiphers();
testRemoveAnonFromLegacyAlgos(); testRemoveAnonFromLegacyAlgorithms();
//testResetLegacyAlgos(); //testResetLegacyAlgos();
} }
...@@ -254,7 +254,7 @@ public class TestSecurity extends TestBase { ...@@ -254,7 +254,7 @@ public class TestSecurity extends TestBase {
return len * r < len * 120; return len * r < len * 120;
} }
private void testRemoveAnonFromLegacyAlgos() { private void testRemoveAnonFromLegacyAlgorithms() {
String legacyAlgos = "K_NULL, C_NULL, M_NULL, DHE_DSS_EXPORT" + String legacyAlgos = "K_NULL, C_NULL, M_NULL, DHE_DSS_EXPORT" +
", DHE_RSA_EXPORT, DH_anon_EXPORT, DH_DSS_EXPORT, DH_RSA_EXPORT, RSA_EXPORT" + ", DHE_RSA_EXPORT, DH_anon_EXPORT, DH_DSS_EXPORT, DH_RSA_EXPORT, RSA_EXPORT" +
", DH_anon, ECDH_anon, RC4_128, RC4_40, DES_CBC, DES40_CBC"; ", DH_anon, ECDH_anon, RC4_128, RC4_40, DES_CBC, DES40_CBC";
...@@ -262,15 +262,15 @@ public class TestSecurity extends TestBase { ...@@ -262,15 +262,15 @@ public class TestSecurity extends TestBase {
", DHE_RSA_EXPORT, DH_anon_EXPORT, DH_DSS_EXPORT, DH_RSA_EXPORT, RSA_EXPORT" + ", DHE_RSA_EXPORT, DH_anon_EXPORT, DH_DSS_EXPORT, DH_RSA_EXPORT, RSA_EXPORT" +
", RC4_128, RC4_40, DES_CBC, DES40_CBC"; ", RC4_128, RC4_40, DES_CBC, DES40_CBC";
assertEquals(expectedLegacyAlgosWithoutDhAnon, assertEquals(expectedLegacyAlgosWithoutDhAnon,
CipherFactory.removeDhAnonFromCommaSepList(legacyAlgos)); CipherFactory.removeDhAnonFromCommaSeparatedList(legacyAlgos));
legacyAlgos = "ECDH_anon, DH_anon_EXPORT, DH_anon"; legacyAlgos = "ECDH_anon, DH_anon_EXPORT, DH_anon";
expectedLegacyAlgosWithoutDhAnon = "DH_anon_EXPORT"; expectedLegacyAlgosWithoutDhAnon = "DH_anon_EXPORT";
assertEquals(expectedLegacyAlgosWithoutDhAnon, assertEquals(expectedLegacyAlgosWithoutDhAnon,
CipherFactory.removeDhAnonFromCommaSepList(legacyAlgos)); CipherFactory.removeDhAnonFromCommaSeparatedList(legacyAlgos));
legacyAlgos = null; legacyAlgos = null;
assertNull(CipherFactory.removeDhAnonFromCommaSepList(legacyAlgos)); assertNull(CipherFactory.removeDhAnonFromCommaSeparatedList(legacyAlgos));
} }
/** /**
...@@ -282,13 +282,13 @@ public class TestSecurity extends TestBase { ...@@ -282,13 +282,13 @@ public class TestSecurity extends TestBase {
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
private void testResetLegacyAlgos() { private void testResetLegacyAlgos() {
String legacyAlgorithmsBefore = CipherFactory.getLegacyAlgoritmsSilently(); String legacyAlgorithmsBefore = CipherFactory.getLegacyAlgorithmsSilently();
assertEquals("Failed assumption: jdk.tls.legacyAlgorithms" + assertEquals("Failed assumption: jdk.tls.legacyAlgorithms" +
" has been modified from its initial setting", " has been modified from its initial setting",
CipherFactory.DEFAULT_LEGACY_ALGORITHMS, legacyAlgorithmsBefore); CipherFactory.DEFAULT_LEGACY_ALGORITHMS, legacyAlgorithmsBefore);
CipherFactory.removeAnonFromLegacyAlgorithms(); CipherFactory.removeAnonFromLegacyAlgorithms();
CipherFactory.resetDefaultLegacyAlgorithms(); CipherFactory.resetDefaultLegacyAlgorithms();
String legacyAlgorithmsAfter = CipherFactory.getLegacyAlgoritmsSilently(); String legacyAlgorithmsAfter = CipherFactory.getLegacyAlgorithmsSilently();
assertEquals(CipherFactory.DEFAULT_LEGACY_ALGORITHMS, legacyAlgorithmsAfter); assertEquals(CipherFactory.DEFAULT_LEGACY_ALGORITHMS, legacyAlgorithmsAfter);
} }
......
...@@ -39,7 +39,7 @@ public class TestTimeStampWithTimeZone extends TestBase { ...@@ -39,7 +39,7 @@ public class TestTimeStampWithTimeZone extends TestBase {
stat.execute("insert into test(t1) values('1970-01-01 12:00:00.00+00:15')"); stat.execute("insert into test(t1) values('1970-01-01 12:00:00.00+00:15')");
ResultSet rs = stat.executeQuery("select t1 from test"); ResultSet rs = stat.executeQuery("select t1 from test");
rs.next(); rs.next();
assertTrue(new TimestampWithTimeZone(36000000, 00, (short)15).equals(rs.getTimestamp(1))); assertTrue(new TimestampWithTimeZone(36000000, 00, (short) 15).equals(rs.getTimestamp(1)));
conn.close(); conn.close();
} }
......
...@@ -167,7 +167,7 @@ public class TestValueMemory extends TestBase implements DataHandler { ...@@ -167,7 +167,7 @@ public class TestValueMemory extends TestBase implements DataHandler {
case Value.TIMESTAMP_UTC: case Value.TIMESTAMP_UTC:
return ValueTimestampUtc.fromMillis(random.nextLong()); return ValueTimestampUtc.fromMillis(random.nextLong());
case Value.TIMESTAMP_TZ: case Value.TIMESTAMP_TZ:
return ValueTimestampTimeZone.fromMillis(random.nextLong(), (short)0); return ValueTimestampTimeZone.fromMillis(random.nextLong(), (short) 0);
case Value.BYTES: case Value.BYTES:
return ValueBytes.get(randomBytes(random.nextInt(1000))); return ValueBytes.get(randomBytes(random.nextInt(1000)));
case Value.STRING: case Value.STRING:
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
* *
* You can obtain a copy of the license at * You can obtain a copy of the license at
* glassfish/bootstrap/legal/CDDLv1.0.txt or * glassfish/bootstrap/legal/CDDLv1.0.txt or
* https://glassfish.dev.java.net/public/CDDLv1.0.html. * https://glassfish.dev.java.net/public/CDDLv1.0.html .
* See the License for the specific language governing * See the License for the specific language governing
* permissions and limitations under the License. * permissions and limitations under the License.
* *
......
...@@ -238,7 +238,8 @@ public class Build extends BuildBase { ...@@ -238,7 +238,8 @@ public class Build extends BuildBase {
if (!clientOnly) { if (!clientOnly) {
files = files("src/test"); files = files("src/test");
files.addAll(files("src/tools")); files.addAll(files("src/tools"));
files = files.exclude("src/test/org/h2/test/TestAllJunit.java"); //we don't use Junit for this test framework //we don't use Junit for this test framework
files = files.exclude("src/test/org/h2/test/TestAllJunit.java");
args = args("-Xlint:unchecked", "-Xlint:deprecation", args = args("-Xlint:unchecked", "-Xlint:deprecation",
"-d", "temp", "-sourcepath", "src/test" + File.pathSeparator + "src/tools", "-d", "temp", "-sourcepath", "src/test" + File.pathSeparator + "src/tools",
"-classpath", classpath); "-classpath", classpath);
......
...@@ -57,7 +57,7 @@ public class BuildBase { ...@@ -57,7 +57,7 @@ public class BuildBase {
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
@Documented @Documented
public static @interface Description { public static @interface Description {
String summary() default ""; String summary() default "";
} }
/** /**
...@@ -317,7 +317,8 @@ public class BuildBase { ...@@ -317,7 +317,8 @@ public class BuildBase {
if (!Modifier.isStatic(mod) && Modifier.isPublic(mod) if (!Modifier.isStatic(mod) && Modifier.isPublic(mod)
&& m.getParameterTypes().length == 0) { && m.getParameterTypes().length == 0) {
if (m.isAnnotationPresent(Description.class)) { if (m.isAnnotationPresent(Description.class)) {
description = String.format("%1$-20s %2$s", m.getName(), m.getAnnotation(Description.class).summary()); description = String.format("%1$-20s %2$s",
m.getName(), m.getAnnotation(Description.class).summary());
} else { } else {
description = m.getName(); description = m.getName();
} }
...@@ -355,7 +356,8 @@ public class BuildBase { ...@@ -355,7 +356,8 @@ public class BuildBase {
} }
/** /**
* Execute java in a separate process, but using the java executable of the current JRE. * Execute java in a separate process, but using the java executable of the
* current JRE.
* *
* @param args the command line parameters for the java command * @param args the command line parameters for the java command
* @return the exit value * @return the exit value
......
...@@ -99,8 +99,9 @@ public class CheckJavadoc { ...@@ -99,8 +99,9 @@ public class CheckJavadoc {
} }
if (inComment) { if (inComment) {
if (rawLine.length() > MAX_COMMENT_LINE_SIZE if (rawLine.length() > MAX_COMMENT_LINE_SIZE
&& !line.trim().startsWith("* http://")) { && !line.trim().startsWith("* http://")
System.out.println("Long line : " + file.getAbsolutePath() && !line.trim().startsWith("* https://")) {
System.out.println("Long line: " + file.getAbsolutePath()
+ " (" + file.getName() + ":" + lineNumber + ")"); + " (" + file.getName() + ":" + lineNumber + ")");
errorCount++; errorCount++;
} }
...@@ -108,12 +109,14 @@ public class CheckJavadoc { ...@@ -108,12 +109,14 @@ public class CheckJavadoc {
inComment = false; inComment = false;
} }
} }
if (!inComment && line.startsWith("//") if (!inComment && line.startsWith("//")) {
&& rawLine.length() > MAX_COMMENT_LINE_SIZE if (rawLine.length() > MAX_COMMENT_LINE_SIZE
&& !line.trim().startsWith("// http://")) { && !line.trim().startsWith("// http://")
System.out.println("Long line: " + file.getAbsolutePath() && !line.trim().startsWith("// https://")) {
+ " (" + file.getName() + ":" + lineNumber + ")"); System.out.println("Long line: " + file.getAbsolutePath()
errorCount++; + " (" + file.getName() + ":" + lineNumber + ")");
errorCount++;
}
} else if (!inComment && rawLine.length() > MAX_SOURCE_LINE_SIZE) { } else if (!inComment && rawLine.length() > MAX_SOURCE_LINE_SIZE) {
System.out.println("Long line: " + file.getAbsolutePath() System.out.println("Long line: " + file.getAbsolutePath()
+ " (" + file.getName() + ":" + lineNumber + ")"); + " (" + file.getName() + ":" + lineNumber + ")");
......
...@@ -11,6 +11,8 @@ import java.util.Arrays; ...@@ -11,6 +11,8 @@ import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.h2.build.BuildBase; import org.h2.build.BuildBase;
import org.h2.util.StringUtils; import org.h2.util.StringUtils;
...@@ -72,6 +74,11 @@ public class SpellChecker { ...@@ -72,6 +74,11 @@ public class SpellChecker {
private void run(String dictionaryFileName, String dir) throws IOException { private void run(String dictionaryFileName, String dir) throws IOException {
process(new File(dictionaryFileName)); process(new File(dictionaryFileName));
process(new File(dir)); process(new File(dir));
HashSet<String> unused = new HashSet<String>();
unused.addAll(dictionary);
unused.removeAll(used);
// System.out.println("UNUSED WORDS");
// System.out.println(unused);
if (printDictionary) { if (printDictionary) {
System.out.println("USED WORDS"); System.out.println("USED WORDS");
String[] list = new String[used.size()]; String[] list = new String[used.size()];
...@@ -190,19 +197,20 @@ public class SpellChecker { ...@@ -190,19 +197,20 @@ public class SpellChecker {
} }
private String removeLinks(String fileName, String text) { private String removeLinks(String fileName, String text) {
Pattern linkPattern = Pattern.compile("http[s]?://");
StringBuilder buff = new StringBuilder(text.length()); StringBuilder buff = new StringBuilder(text.length());
int pos = 0, last = 0; int pos = 0, last = 0;
if (fileName.endsWith(".properties")) { if (fileName.endsWith(".properties")) {
text = StringUtils.replaceAll(text, "\\:", ":"); text = StringUtils.replaceAll(text, "\\:", ":");
} }
while (true) { while (true) {
pos = text.indexOf("http://", pos); Matcher m = linkPattern.matcher(text.substring(pos));
if (pos < 0) { if (!m.find()) {
break; break;
} }
int start = pos; int start = m.start() + pos;
pos = m.end() + pos;
buff.append(text.substring(last, start)); buff.append(text.substring(last, start));
pos += "http://".length();
while (true) { while (true) {
char c = text.charAt(pos); char c = text.charAt(pos);
if (!Character.isJavaIdentifierPart(c) && if (!Character.isJavaIdentifierPart(c) &&
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -538,10 +538,13 @@ public class ArchiveTool { ...@@ -538,10 +538,13 @@ public class ArchiveTool {
long inPos = 0; long inPos = 0;
int bufferTotal = 64 * 1024 * 1024; int bufferTotal = 64 * 1024 * 1024;
int bufferPerStream = bufferTotal / segmentStart.size(); int bufferPerStream = bufferTotal / segmentStart.size();
// FileChannel fc = new RandomAccessFile(tempFileName, "r").getChannel(); // FileChannel fc = new RandomAccessFile(tempFileName, "r").
// getChannel();
for (int i = 0; i < segmentStart.size(); i++) { for (int i = 0; i < segmentStart.size(); i++) {
// long end = i < segmentStart.size() - 1 ? segmentStart.get(i+1) : fc.size(); // long end = i < segmentStart.size() - 1 ?
// InputStream in = new SharedInputStream(fc, segmentStart.get(i), end); // segmentStart.get(i+1) : fc.size();
// InputStream in =
// new SharedInputStream(fc, segmentStart.get(i), end);
InputStream in = new FileInputStream(tempFileName); InputStream in = new FileInputStream(tempFileName);
in.skip(segmentStart.get(i)); in.skip(segmentStart.get(i));
ChunkStream s = new ChunkStream(i); ChunkStream s = new ChunkStream(i);
...@@ -1062,6 +1065,9 @@ public class ArchiveTool { ...@@ -1062,6 +1065,9 @@ public class ArchiveTool {
return x; return x;
} }
/**
* An input stream that uses a shared file channel.
*/
static class SharedInputStream extends InputStream { static class SharedInputStream extends InputStream {
private final FileChannel channel; private final FileChannel channel;
private final long endPosition; private final long endPosition;
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论