提交 16c4500c authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov

Map all remaining error codes to custom exception classes

上级 2235d228
...@@ -533,7 +533,6 @@ See also <a href="build.html#providing_patches">Providing Patches</a>. ...@@ -533,7 +533,6 @@ See also <a href="build.html#providing_patches">Providing Patches</a>.
</li><li>Long running transactions: log session id when detected. </li><li>Long running transactions: log session id when detected.
</li><li>Optimization: "select id from test" should use the index on id even without "order by". </li><li>Optimization: "select id from test" should use the index on id even without "order by".
</li><li>Sybase SQL Anywhere compatibility: SELECT TOP ... START AT ... </li><li>Sybase SQL Anywhere compatibility: SELECT TOP ... START AT ...
</li><li>Use Java 6 SQLException subclasses for more kinds of errors.
</li><li>Issue 390: RUNSCRIPT FROM '...' CONTINUE_ON_ERROR </li><li>Issue 390: RUNSCRIPT FROM '...' CONTINUE_ON_ERROR
</li></ul> </li></ul>
......
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.jdbc;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.sql.SQLNonTransientException;
import org.h2.message.DbException;
/**
* Represents a database exception.
*/
public class JdbcSQLNonTransientException extends SQLNonTransientException implements JdbcException {
private static final long serialVersionUID = 1L;
private final String originalMessage;
private final String stackTrace;
private String message;
private String sql;
/**
* Creates a SQLNonTransientException.
*
* @param message the reason
* @param sql the SQL statement
* @param state the SQL state
* @param errorCode the error code
* @param cause the exception that was the reason for this exception
* @param stackTrace the stack trace
*/
public JdbcSQLNonTransientException(String message, String sql, String state,
int errorCode, Throwable cause, String stackTrace) {
super(message, state, errorCode);
this.originalMessage = message;
this.stackTrace = stackTrace;
// setSQL() also generates message
setSQL(sql);
initCause(cause);
}
@Override
public String getMessage() {
return message;
}
@Override
public String getOriginalMessage() {
return originalMessage;
}
@Override
public void printStackTrace(PrintWriter s) {
super.printStackTrace(s);
DbException.printNextExceptions(this, s);
}
@Override
public void printStackTrace(PrintStream s) {
super.printStackTrace(s);
DbException.printNextExceptions(this, s);
}
@Override
public String getSQL() {
return sql;
}
@Override
public void setSQL(String sql) {
this.sql = sql;
message = DbException.buildMessageForException(this);
}
@Override
public String toString() {
if (stackTrace == null) {
return super.toString();
}
return stackTrace;
}
}
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.jdbc;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.sql.SQLTimeoutException;
import org.h2.message.DbException;
/**
* Represents a database exception.
*/
public class JdbcSQLTimeoutException extends SQLTimeoutException implements JdbcException {
private static final long serialVersionUID = 1L;
private final String originalMessage;
private final String stackTrace;
private String message;
private String sql;
/**
* Creates a SQLTimeoutException.
*
* @param message the reason
* @param sql the SQL statement
* @param state the SQL state
* @param errorCode the error code
* @param cause the exception that was the reason for this exception
* @param stackTrace the stack trace
*/
public JdbcSQLTimeoutException(String message, String sql, String state,
int errorCode, Throwable cause, String stackTrace) {
super(message, state, errorCode);
this.originalMessage = message;
this.stackTrace = stackTrace;
// setSQL() also generates message
setSQL(sql);
initCause(cause);
}
@Override
public String getMessage() {
return message;
}
@Override
public String getOriginalMessage() {
return originalMessage;
}
@Override
public void printStackTrace(PrintWriter s) {
super.printStackTrace(s);
DbException.printNextExceptions(this, s);
}
@Override
public void printStackTrace(PrintStream s) {
super.printStackTrace(s);
DbException.printNextExceptions(this, s);
}
@Override
public String getSQL() {
return sql;
}
@Override
public void setSQL(String sql) {
this.sql = sql;
message = DbException.buildMessageForException(this);
}
@Override
public String toString() {
if (stackTrace == null) {
return super.toString();
}
return stackTrace;
}
}
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.jdbc;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.sql.SQLTransientException;
import org.h2.message.DbException;
/**
* Represents a database exception.
*/
public class JdbcSQLTransientException extends SQLTransientException implements JdbcException {
private static final long serialVersionUID = 1L;
private final String originalMessage;
private final String stackTrace;
private String message;
private String sql;
/**
* Creates a SQLTransientException.
*
* @param message the reason
* @param sql the SQL statement
* @param state the SQL state
* @param errorCode the error code
* @param cause the exception that was the reason for this exception
* @param stackTrace the stack trace
*/
public JdbcSQLTransientException(String message, String sql, String state,
int errorCode, Throwable cause, String stackTrace) {
super(message, state, errorCode);
this.originalMessage = message;
this.stackTrace = stackTrace;
// setSQL() also generates message
setSQL(sql);
initCause(cause);
}
@Override
public String getMessage() {
return message;
}
@Override
public String getOriginalMessage() {
return originalMessage;
}
@Override
public void printStackTrace(PrintWriter s) {
super.printStackTrace(s);
DbException.printNextExceptions(this, s);
}
@Override
public void printStackTrace(PrintStream s) {
super.printStackTrace(s);
DbException.printNextExceptions(this, s);
}
@Override
public String getSQL() {
return sql;
}
@Override
public void setSQL(String sql) {
this.sql = sql;
message = DbException.buildMessageForException(this);
}
@Override
public String toString() {
if (stackTrace == null) {
return super.toString();
}
return stackTrace;
}
}
...@@ -17,6 +17,7 @@ import java.text.MessageFormat; ...@@ -17,6 +17,7 @@ import java.text.MessageFormat;
import java.util.Locale; import java.util.Locale;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Properties; import java.util.Properties;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.jdbc.JdbcException; import org.h2.jdbc.JdbcException;
...@@ -26,8 +27,11 @@ import org.h2.jdbc.JdbcSQLFeatureNotSupportedException; ...@@ -26,8 +27,11 @@ import org.h2.jdbc.JdbcSQLFeatureNotSupportedException;
import org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException; import org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException;
import org.h2.jdbc.JdbcSQLInvalidAuthorizationSpecException; import org.h2.jdbc.JdbcSQLInvalidAuthorizationSpecException;
import org.h2.jdbc.JdbcSQLNonTransientConnectionException; import org.h2.jdbc.JdbcSQLNonTransientConnectionException;
import org.h2.jdbc.JdbcSQLNonTransientException;
import org.h2.jdbc.JdbcSQLSyntaxErrorException; import org.h2.jdbc.JdbcSQLSyntaxErrorException;
import org.h2.jdbc.JdbcSQLTimeoutException;
import org.h2.jdbc.JdbcSQLTransactionRollbackException; import org.h2.jdbc.JdbcSQLTransactionRollbackException;
import org.h2.jdbc.JdbcSQLTransientException;
import org.h2.util.SortedProperties; import org.h2.util.SortedProperties;
import org.h2.util.StringUtils; import org.h2.util.StringUtils;
import org.h2.util.Utils; import org.h2.util.Utils;
...@@ -440,6 +444,12 @@ public class DbException extends RuntimeException { ...@@ -440,6 +444,12 @@ public class DbException extends RuntimeException {
sql = filterSQL(sql); sql = filterSQL(sql);
// Use SQLState class value to detect type // Use SQLState class value to detect type
switch (errorCode / 1_000) { switch (errorCode / 1_000) {
case 2:
return new JdbcSQLNonTransientException(message, sql, state, errorCode, cause, stackTrace);
case 7:
case 21:
case 42:
return new JdbcSQLSyntaxErrorException(message, sql, state, errorCode, cause, stackTrace);
case 8: case 8:
return new JdbcSQLNonTransientConnectionException(message, sql, state, errorCode, cause, stackTrace); return new JdbcSQLNonTransientConnectionException(message, sql, state, errorCode, cause, stackTrace);
case 22: case 22:
...@@ -450,20 +460,163 @@ public class DbException extends RuntimeException { ...@@ -450,20 +460,163 @@ public class DbException extends RuntimeException {
return new JdbcSQLInvalidAuthorizationSpecException(message, sql, state, errorCode, cause, stackTrace); return new JdbcSQLInvalidAuthorizationSpecException(message, sql, state, errorCode, cause, stackTrace);
case 40: case 40:
return new JdbcSQLTransactionRollbackException(message, sql, state, errorCode, cause, stackTrace); return new JdbcSQLTransactionRollbackException(message, sql, state, errorCode, cause, stackTrace);
case 42:
return new JdbcSQLSyntaxErrorException(message, sql, state, errorCode, cause, stackTrace);
} }
// Check error code // Check error code
switch (errorCode){ switch (errorCode){
case ErrorCode.GENERAL_ERROR_1:
case ErrorCode.UNKNOWN_DATA_TYPE_1:
case ErrorCode.METHOD_NOT_ALLOWED_FOR_QUERY:
case ErrorCode.METHOD_ONLY_ALLOWED_FOR_QUERY:
case ErrorCode.SEQUENCE_EXHAUSTED:
case ErrorCode.OBJECT_CLOSED:
case ErrorCode.CANNOT_DROP_CURRENT_USER:
case ErrorCode.UNSUPPORTED_SETTING_COMBINATION:
case ErrorCode.FILE_RENAME_FAILED_2:
case ErrorCode.FILE_DELETE_FAILED_1:
case ErrorCode.IO_EXCEPTION_1:
case ErrorCode.NOT_ON_UPDATABLE_ROW:
case ErrorCode.IO_EXCEPTION_2:
case ErrorCode.TRACE_FILE_ERROR_2:
case ErrorCode.ADMIN_RIGHTS_REQUIRED:
case ErrorCode.ERROR_EXECUTING_TRIGGER_3:
case ErrorCode.COMMIT_ROLLBACK_NOT_ALLOWED:
case ErrorCode.FILE_CREATION_FAILED_1:
case ErrorCode.SAVEPOINT_IS_INVALID_1:
case ErrorCode.SAVEPOINT_IS_UNNAMED:
case ErrorCode.SAVEPOINT_IS_NAMED:
case ErrorCode.NOT_ENOUGH_RIGHTS_FOR_1:
case ErrorCode.DATABASE_IS_READ_ONLY:
case ErrorCode.WRONG_XID_FORMAT_1:
case ErrorCode.UNSUPPORTED_COMPRESSION_OPTIONS_1:
case ErrorCode.UNSUPPORTED_COMPRESSION_ALGORITHM_1:
case ErrorCode.COMPRESSION_ERROR:
case ErrorCode.EXCEPTION_IN_FUNCTION_1:
case ErrorCode.ERROR_ACCESSING_LINKED_TABLE_2:
case ErrorCode.FILE_NOT_FOUND_1:
case ErrorCode.INVALID_CLASS_2:
case ErrorCode.DATABASE_IS_NOT_PERSISTENT:
case ErrorCode.RESULT_SET_NOT_UPDATABLE:
case ErrorCode.RESULT_SET_NOT_SCROLLABLE:
case ErrorCode.METHOD_NOT_ALLOWED_FOR_PREPARED_STATEMENT:
case ErrorCode.ACCESS_DENIED_TO_CLASS_1:
case ErrorCode.RESULT_SET_READONLY:
return new JdbcSQLNonTransientException(message, sql, state, errorCode, cause, stackTrace);
case ErrorCode.FEATURE_NOT_SUPPORTED_1: case ErrorCode.FEATURE_NOT_SUPPORTED_1:
return new JdbcSQLFeatureNotSupportedException(message, sql, state, errorCode, cause, stackTrace); return new JdbcSQLFeatureNotSupportedException(message, sql, state, errorCode, cause, stackTrace);
case ErrorCode.LOCK_TIMEOUT_1:
case ErrorCode.STATEMENT_WAS_CANCELED:
case ErrorCode.LOB_CLOSED_ON_TIMEOUT_1:
return new JdbcSQLTimeoutException(message, sql, state, errorCode, cause, stackTrace);
case ErrorCode.FUNCTION_MUST_RETURN_RESULT_SET_1:
case ErrorCode.TRIGGER_SELECT_AND_ROW_BASED_NOT_SUPPORTED:
case ErrorCode.SUM_OR_AVG_ON_WRONG_DATATYPE_1:
case ErrorCode.MUST_GROUP_BY_COLUMN_1:
case ErrorCode.SECOND_PRIMARY_KEY:
case ErrorCode.FUNCTION_NOT_FOUND_1:
case ErrorCode.COLUMN_MUST_NOT_BE_NULLABLE_1:
case ErrorCode.USER_NOT_FOUND_1:
case ErrorCode.USER_ALREADY_EXISTS_1:
case ErrorCode.SEQUENCE_ALREADY_EXISTS_1:
case ErrorCode.SEQUENCE_NOT_FOUND_1:
case ErrorCode.VIEW_NOT_FOUND_1:
case ErrorCode.VIEW_ALREADY_EXISTS_1:
case ErrorCode.TRIGGER_ALREADY_EXISTS_1:
case ErrorCode.TRIGGER_NOT_FOUND_1:
case ErrorCode.ERROR_CREATING_TRIGGER_OBJECT_3:
case ErrorCode.CONSTRAINT_ALREADY_EXISTS_1:
case ErrorCode.INVALID_VALUE_SCALE_PRECISION:
case ErrorCode.SUBQUERY_IS_NOT_SINGLE_COLUMN:
case ErrorCode.INVALID_USE_OF_AGGREGATE_FUNCTION_1:
case ErrorCode.CONSTRAINT_NOT_FOUND_1:
case ErrorCode.AMBIGUOUS_COLUMN_NAME_1:
case ErrorCode.ORDER_BY_NOT_IN_RESULT:
case ErrorCode.ROLE_ALREADY_EXISTS_1:
case ErrorCode.ROLE_NOT_FOUND_1:
case ErrorCode.USER_OR_ROLE_NOT_FOUND_1:
case ErrorCode.ROLES_AND_RIGHT_CANNOT_BE_MIXED:
case ErrorCode.METHODS_MUST_HAVE_DIFFERENT_PARAMETER_COUNTS_2:
case ErrorCode.ROLE_ALREADY_GRANTED_1:
case ErrorCode.COLUMN_IS_PART_OF_INDEX_1:
case ErrorCode.FUNCTION_ALIAS_ALREADY_EXISTS_1:
case ErrorCode.FUNCTION_ALIAS_NOT_FOUND_1:
case ErrorCode.SCHEMA_ALREADY_EXISTS_1:
case ErrorCode.SCHEMA_NOT_FOUND_1:
case ErrorCode.SCHEMA_NAME_MUST_MATCH:
case ErrorCode.COLUMN_CONTAINS_NULL_VALUES_1:
case ErrorCode.SEQUENCE_BELONGS_TO_A_TABLE_1:
case ErrorCode.COLUMN_IS_REFERENCED_1:
case ErrorCode.CANNOT_DROP_LAST_COLUMN:
case ErrorCode.INDEX_BELONGS_TO_CONSTRAINT_2:
case ErrorCode.CLASS_NOT_FOUND_1:
case ErrorCode.METHOD_NOT_FOUND_1:
case ErrorCode.COLLATION_CHANGE_WITH_DATA_TABLE_1:
case ErrorCode.SCHEMA_CAN_NOT_BE_DROPPED_1:
case ErrorCode.ROLE_CAN_NOT_BE_DROPPED_1:
case ErrorCode.CANNOT_TRUNCATE_1:
case ErrorCode.CANNOT_DROP_2:
case ErrorCode.VIEW_IS_INVALID_2:
case ErrorCode.COMPARING_ARRAY_TO_SCALAR:
case ErrorCode.CONSTANT_ALREADY_EXISTS_1:
case ErrorCode.CONSTANT_NOT_FOUND_1:
case ErrorCode.LITERALS_ARE_NOT_ALLOWED:
case ErrorCode.CANNOT_DROP_TABLE_1:
case ErrorCode.USER_DATA_TYPE_ALREADY_EXISTS_1:
case ErrorCode.USER_DATA_TYPE_NOT_FOUND_1:
case ErrorCode.WITH_TIES_WITHOUT_ORDER_BY:
case ErrorCode.CANNOT_MIX_INDEXED_AND_UNINDEXED_PARAMS:
case ErrorCode.TRANSACTION_NOT_FOUND_1:
case ErrorCode.AGGREGATE_NOT_FOUND_1:
case ErrorCode.CAN_ONLY_ASSIGN_TO_VARIABLE_1:
case ErrorCode.PUBLIC_STATIC_JAVA_METHOD_NOT_FOUND_1:
case ErrorCode.JAVA_OBJECT_SERIALIZER_CHANGE_WITH_DATA_TABLE:
return new JdbcSQLSyntaxErrorException(message, sql, state, errorCode, cause, stackTrace);
case ErrorCode.HEX_STRING_ODD_1: case ErrorCode.HEX_STRING_ODD_1:
case ErrorCode.HEX_STRING_WRONG_1: case ErrorCode.HEX_STRING_WRONG_1:
case ErrorCode.INVALID_VALUE_2: case ErrorCode.INVALID_VALUE_2:
case ErrorCode.SEQUENCE_ATTRIBUTES_INVALID:
case ErrorCode.INVALID_TO_CHAR_FORMAT:
case ErrorCode.PARAMETER_NOT_SET_1:
case ErrorCode.PARSE_ERROR_1: case ErrorCode.PARSE_ERROR_1:
case ErrorCode.INVALID_TO_DATE_FORMAT: case ErrorCode.INVALID_TO_DATE_FORMAT:
case ErrorCode.STRING_FORMAT_ERROR_1: case ErrorCode.STRING_FORMAT_ERROR_1:
case ErrorCode.SERIALIZATION_FAILED_1:
case ErrorCode.DESERIALIZATION_FAILED_1:
case ErrorCode.SCALAR_SUBQUERY_CONTAINS_MORE_THAN_ONE_ROW:
case ErrorCode.STEP_SIZE_MUST_NOT_BE_ZERO:
return new JdbcSQLDataException(message, sql, state, errorCode, cause, stackTrace); return new JdbcSQLDataException(message, sql, state, errorCode, cause, stackTrace);
case ErrorCode.URL_RELATIVE_TO_CWD:
case ErrorCode.DATABASE_NOT_FOUND_1:
case ErrorCode.TRACE_CONNECTION_NOT_CLOSED:
case ErrorCode.DATABASE_ALREADY_OPEN_1:
case ErrorCode.FILE_CORRUPTED_1:
case ErrorCode.URL_FORMAT_ERROR_2:
case ErrorCode.DRIVER_VERSION_ERROR_2:
case ErrorCode.FILE_VERSION_ERROR_1:
case ErrorCode.FILE_ENCRYPTION_ERROR_1:
case ErrorCode.WRONG_PASSWORD_FORMAT:
case ErrorCode.UNSUPPORTED_CIPHER:
case ErrorCode.UNSUPPORTED_LOCK_METHOD_1:
case ErrorCode.EXCEPTION_OPENING_PORT_2:
case ErrorCode.DUPLICATE_PROPERTY_1:
case ErrorCode.CONNECTION_BROKEN_1:
case ErrorCode.UNKNOWN_MODE_1:
case ErrorCode.CLUSTER_ERROR_DATABASE_RUNS_ALONE:
case ErrorCode.CLUSTER_ERROR_DATABASE_RUNS_CLUSTERED_1:
case ErrorCode.DATABASE_IS_CLOSED:
case ErrorCode.ERROR_SETTING_DATABASE_EVENT_LISTENER_2:
case ErrorCode.OUT_OF_MEMORY:
case ErrorCode.UNSUPPORTED_SETTING_1:
case ErrorCode.REMOTE_CONNECTION_NOT_ALLOWED:
case ErrorCode.DATABASE_CALLED_AT_SHUTDOWN:
case ErrorCode.CANNOT_CHANGE_SETTING_WHEN_OPEN_1:
case ErrorCode.DATABASE_IS_IN_EXCLUSIVE_MODE:
case ErrorCode.INVALID_DATABASE_NAME_1:
case ErrorCode.AUTHENTICATOR_NOT_AVAILABLE:
return new JdbcSQLNonTransientConnectionException(message, sql, state, errorCode, cause, stackTrace);
case ErrorCode.ROW_NOT_FOUND_WHEN_DELETING_1:
case ErrorCode.CONCURRENT_UPDATE_1:
case ErrorCode.ROW_NOT_FOUND_IN_PRIMARY_INDEX:
return new JdbcSQLTransientException(message, sql, state, errorCode, cause, stackTrace);
} }
// Default // Default
return new JdbcSQLException(message, sql, state, errorCode, cause, stackTrace); return new JdbcSQLException(message, sql, state, errorCode, cause, stackTrace);
......
...@@ -180,6 +180,7 @@ import org.h2.test.unit.TestDataPage; ...@@ -180,6 +180,7 @@ import org.h2.test.unit.TestDataPage;
import org.h2.test.unit.TestDate; import org.h2.test.unit.TestDate;
import org.h2.test.unit.TestDateIso8601; import org.h2.test.unit.TestDateIso8601;
import org.h2.test.unit.TestDateTimeUtils; import org.h2.test.unit.TestDateTimeUtils;
import org.h2.test.unit.TestDbException;
import org.h2.test.unit.TestExit; import org.h2.test.unit.TestExit;
import org.h2.test.unit.TestFile; import org.h2.test.unit.TestFile;
import org.h2.test.unit.TestFileLock; import org.h2.test.unit.TestFileLock;
...@@ -956,6 +957,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1` ...@@ -956,6 +957,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
addTest(new TestClearReferences()); addTest(new TestClearReferences());
addTest(new TestDataPage()); addTest(new TestDataPage());
addTest(new TestDateIso8601()); addTest(new TestDateIso8601());
addTest(new TestDbException());
addTest(new TestFile()); addTest(new TestFile());
addTest(new TestFtp()); addTest(new TestFtp());
addTest(new TestInterval()); addTest(new TestInterval());
......
/*
* Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.unit;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.sql.SQLException;
import org.h2.api.ErrorCode;
import org.h2.jdbc.JdbcException;
import org.h2.jdbc.JdbcSQLException;
import org.h2.message.DbException;
import org.h2.test.TestBase;
/**
* Tests DbException class.
*/
public class TestDbException extends TestBase {
/**
* Run just this test.
*
* @param a
* ignored
*/
public static void main(String... a) throws Exception {
TestBase.createCaller().init().test();
}
@Override
public void test() throws Exception {
testGetJdbcSQLException();
}
private void testGetJdbcSQLException() throws Exception {
for (Field field : ErrorCode.class.getDeclaredFields()) {
if (field.getModifiers() == (Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL)) {
int errorCode = field.getInt(null);
SQLException exception = DbException.getJdbcSQLException(errorCode);
if (exception instanceof JdbcSQLException) {
fail("Custom exception expected for " + ErrorCode.class.getName() + '.' + field.getName() + " ("
+ errorCode + ')');
}
if (!(exception instanceof JdbcException)) {
fail("Custom exception for " + ErrorCode.class.getName() + '.' + field.getName() + " (" + errorCode
+ ") should implement JdbcException");
}
}
}
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论