Unverified 提交 46ac2ec7 authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov 提交者: GitHub

Merge pull request #1714 from katzyn/value

Assorted changes
......@@ -21,6 +21,12 @@ Change Log
<h2>Next Version (unreleased)</h2>
<ul>
<li>Issue #1715: Postgres mode: Domain "regproc" already exists
</li>
<li>PR #1714: Assorted changes
</li>
<li>PR #1713: Remove DataType.defaultDisplaySize and fix display size in TypeInfo
</li>
<li>PR #1711: Add QUALIFY clause to SELECT command
</li>
<li>Issue #1708: CREATE TABLE AS doesn't support column lists without data types
......
......@@ -279,12 +279,12 @@ public class Mode {
// MS SQL Server does not support client info properties. See
// https://msdn.microsoft.com/en-Us/library/dd571296%28v=sql.110%29.aspx
mode.supportedClientInfoPropertiesRegEx = null;
DataType dt = DataType.createDecimal(19, 19, 4, 21, false, false);
DataType dt = DataType.createNumeric(19, 4, false);
dt.type = Value.DECIMAL;
dt.sqlType = Types.NUMERIC;
dt.name = "MONEY";
mode.typeByNameMap.put("MONEY", dt);
dt = DataType.createDecimal(10, 10, 4, 12, false, false);
dt = DataType.createNumeric(10, 4, false);
dt.type = Value.DECIMAL;
dt.sqlType = Types.NUMERIC;
dt.name = "SMALLMONEY";
......@@ -348,7 +348,7 @@ public class Mode {
disallowedTypes.add("TINYINT");
disallowedTypes.add("BLOB");
mode.disallowedTypes = disallowedTypes;
dt = DataType.createDecimal(19, 19, 2, 21, false, false);
dt = DataType.createNumeric(19, 2, false);
dt.type = Value.DECIMAL;
dt.sqlType = Types.NUMERIC;
dt.name = "MONEY";
......
......@@ -15,7 +15,7 @@ import org.h2.table.ColumnResolver;
import org.h2.table.TableFilter;
import org.h2.value.TypeInfo;
import org.h2.value.Value;
import org.h2.value.ValueArray;
import org.h2.value.ValueCollectionBase;
/**
* An expression is a operation, a value, or a function in a query.
......@@ -370,7 +370,7 @@ public abstract class Expression {
* @param value the value to extract columns from
* @return array of expression columns
*/
protected static Expression[] getExpressionColumns(Session session, ValueArray value) {
protected static Expression[] getExpressionColumns(Session session, ValueCollectionBase value) {
Value[] list = value.getList();
ExpressionColumn[] expr = new ExpressionColumn[list.length];
for (int i = 0, len = list.length; i < len; i++) {
......
......@@ -13,8 +13,8 @@ import org.h2.table.ColumnResolver;
import org.h2.table.TableFilter;
import org.h2.value.TypeInfo;
import org.h2.value.Value;
import org.h2.value.ValueArray;
import org.h2.value.ValueBoolean;
import org.h2.value.ValueCollectionBase;
import org.h2.value.ValueNull;
/**
......@@ -163,8 +163,9 @@ public class ValueExpression extends Expression {
@Override
public Expression[] getExpressionColumns(Session session) {
if (getType().getValueType() == Value.ARRAY) {
return getExpressionColumns(session, (ValueArray) getValue(session));
int valueType = getType().getValueType();
if (valueType == Value.ARRAY || valueType == Value.ROW) {
return getExpressionColumns(session, (ValueCollectionBase) getValue(session));
}
return super.getExpressionColumns(session);
}
......
......@@ -161,6 +161,7 @@ public class JavaFunction extends Expression implements FunctionCall {
ValueResultSet rs = getValueForColumnList(session, getArgs());
return getExpressionColumns(session, rs.getResult());
case Value.ARRAY:
case Value.ROW:
return getExpressionColumns(session, (ValueArray) getValue(session));
}
return super.getExpressionColumns(session);
......
......@@ -107,6 +107,7 @@ merge into pg_catalog.pg_type values(
null
);
drop domain if exists regproc cascade;
create domain regproc as varchar_ignorecase;
create view pg_catalog.pg_class -- (oid, relname, relnamespace, relkind, relam, reltuples, reltablespace, relpages, relhasindex, relhasrules, relhasoids, relchecks, reltriggers)
......
......@@ -456,8 +456,8 @@ public final class JTSUtils {
int d = sequence.getDimension();
if (M_IS_SUPPORTED) {
d -= measures;
z = d > 2 ? sequence.getOrdinate(index, Z) : Double.NaN;
m = measures >= 1 ? sequence.getOrdinate(index, d) : Double.NaN;
z = d > 2 ? toCanonicalDouble(sequence.getOrdinate(index, Z)) : Double.NaN;
m = measures >= 1 ? toCanonicalDouble(sequence.getOrdinate(index, d)) : Double.NaN;
} else {
z = d >= 3 ? toCanonicalDouble(sequence.getOrdinate(index, Z)) : Double.NaN;
m = d >= 4 ? toCanonicalDouble(sequence.getOrdinate(index, M)) : Double.NaN;
......
......@@ -163,11 +163,6 @@ public class DataType {
*/
public boolean hidden;
/**
* The number of bytes required for an object.
*/
public int memory;
static {
Class<?> g;
try {
......@@ -182,77 +177,52 @@ public class DataType {
dataType.defaultPrecision = dataType.maxPrecision = ValueNull.PRECISION;
add(Value.NULL, Types.NULL,
dataType,
new String[]{"NULL"},
// the value is always in the cache
0
new String[]{"NULL"}
);
add(Value.STRING, Types.VARCHAR,
createString(true),
new String[]{"VARCHAR", "CHARACTER VARYING", "VARCHAR2", "NVARCHAR", "NVARCHAR2",
"VARCHAR_CASESENSITIVE", "TID"},
// 24 for ValueString, 24 for String
48
"VARCHAR_CASESENSITIVE", "TID"}
);
add(Value.STRING, Types.LONGVARCHAR,
createString(true),
new String[]{"LONGVARCHAR", "LONGNVARCHAR"},
48
new String[]{"LONGVARCHAR", "LONGNVARCHAR"}
);
add(Value.STRING_FIXED, Types.CHAR,
createString(true),
new String[]{"CHAR", "CHARACTER", "NCHAR"},
48
new String[]{"CHAR", "CHARACTER", "NCHAR"}
);
add(Value.STRING_IGNORECASE, Types.VARCHAR,
createString(false),
new String[]{"VARCHAR_IGNORECASE"},
48
new String[]{"VARCHAR_IGNORECASE"}
);
add(Value.BOOLEAN, Types.BOOLEAN,
createDecimal(ValueBoolean.PRECISION, ValueBoolean.PRECISION,
0, ValueBoolean.DISPLAY_SIZE, false, false),
new String[]{"BOOLEAN", "BIT", "BOOL"},
// the value is always in the cache
0
createNumeric(ValueBoolean.PRECISION, 0, false),
new String[]{"BOOLEAN", "BIT", "BOOL"}
);
add(Value.BYTE, Types.TINYINT,
createDecimal(ValueByte.PRECISION, ValueByte.PRECISION, 0,
ValueByte.DISPLAY_SIZE, false, false),
new String[]{"TINYINT"},
// the value is almost always in the cache
1
createNumeric(ValueByte.PRECISION, 0, false),
new String[]{"TINYINT"}
);
add(Value.SHORT, Types.SMALLINT,
createDecimal(ValueShort.PRECISION, ValueShort.PRECISION, 0,
ValueShort.DISPLAY_SIZE, false, false),
new String[]{"SMALLINT", "YEAR", "INT2"},
// in many cases the value is in the cache
20
createNumeric(ValueShort.PRECISION, 0, false),
new String[]{"SMALLINT", "YEAR", "INT2"}
);
add(Value.INT, Types.INTEGER,
createDecimal(ValueInt.PRECISION, ValueInt.PRECISION, 0,
ValueInt.DISPLAY_SIZE, false, false),
new String[]{"INTEGER", "INT", "MEDIUMINT", "INT4", "SIGNED"},
// in many cases the value is in the cache
20
createNumeric(ValueInt.PRECISION, 0, false),
new String[]{"INTEGER", "INT", "MEDIUMINT", "INT4", "SIGNED"}
);
add(Value.INT, Types.INTEGER,
createDecimal(ValueInt.PRECISION, ValueInt.PRECISION, 0,
ValueInt.DISPLAY_SIZE, false, true),
new String[]{"SERIAL"},
20
createNumeric(ValueInt.PRECISION, 0, true),
new String[]{"SERIAL"}
);
add(Value.LONG, Types.BIGINT,
createDecimal(ValueLong.PRECISION, ValueLong.PRECISION, 0,
ValueLong.DISPLAY_SIZE, false, false),
new String[]{"BIGINT", "INT8", "LONG"},
24
createNumeric(ValueLong.PRECISION, 0, false),
new String[]{"BIGINT", "INT8", "LONG"}
);
add(Value.LONG, Types.BIGINT,
createDecimal(ValueLong.PRECISION, ValueLong.PRECISION, 0,
ValueLong.DISPLAY_SIZE, false, true),
new String[]{"IDENTITY", "BIGSERIAL"},
24
createNumeric(ValueLong.PRECISION, 0, true),
new String[]{"IDENTITY", "BIGSERIAL"}
);
if (SysProperties.BIG_DECIMAL_IS_DECIMAL) {
addDecimal();
......@@ -262,44 +232,32 @@ public class DataType {
addDecimal();
}
add(Value.FLOAT, Types.REAL,
createDecimal(ValueFloat.PRECISION, ValueFloat.PRECISION,
0, ValueFloat.DISPLAY_SIZE, false, false),
new String[] {"REAL", "FLOAT4"},
24
createNumeric(ValueFloat.PRECISION, 0, false),
new String[] {"REAL", "FLOAT4"}
);
add(Value.DOUBLE, Types.DOUBLE,
createDecimal(ValueDouble.PRECISION, ValueDouble.PRECISION,
0, ValueDouble.DISPLAY_SIZE, false, false),
new String[] { "DOUBLE", "DOUBLE PRECISION" },
24
createNumeric(ValueDouble.PRECISION, 0, false),
new String[] { "DOUBLE", "DOUBLE PRECISION" }
);
add(Value.DOUBLE, Types.FLOAT,
createDecimal(ValueDouble.PRECISION, ValueDouble.PRECISION,
0, ValueDouble.DISPLAY_SIZE, false, false),
new String[] {"FLOAT", "FLOAT8" },
24
createNumeric(ValueDouble.PRECISION, 0, false),
new String[] {"FLOAT", "FLOAT8" }
);
add(Value.TIME, Types.TIME,
createDate(ValueTime.MAXIMUM_PRECISION, ValueTime.DEFAULT_PRECISION,
"TIME", true, ValueTime.DEFAULT_SCALE, ValueTime.MAXIMUM_SCALE),
new String[]{"TIME", "TIME WITHOUT TIME ZONE"},
// 24 for ValueTime, 32 for java.sql.Time
56
new String[]{"TIME", "TIME WITHOUT TIME ZONE"}
);
add(Value.DATE, Types.DATE,
createDate(ValueDate.PRECISION, ValueDate.PRECISION,
"DATE", false, 0, 0),
new String[]{"DATE"},
// 24 for ValueDate, 32 for java.sql.Date
56
new String[]{"DATE"}
);
add(Value.TIMESTAMP, Types.TIMESTAMP,
createDate(ValueTimestamp.MAXIMUM_PRECISION, ValueTimestamp.DEFAULT_PRECISION,
"TIMESTAMP", true, ValueTimestamp.DEFAULT_SCALE, ValueTimestamp.MAXIMUM_SCALE),
new String[]{"TIMESTAMP", "TIMESTAMP WITHOUT TIME ZONE",
"DATETIME", "DATETIME2", "SMALLDATETIME"},
// 24 for ValueTimestamp, 32 for java.sql.Timestamp
56
"DATETIME", "DATETIME2", "SMALLDATETIME"}
);
// 2014 is the value of Types.TIMESTAMP_WITH_TIMEZONE
// use the value instead of the reference because the code has to
......@@ -309,24 +267,19 @@ public class DataType {
createDate(ValueTimestampTimeZone.MAXIMUM_PRECISION, ValueTimestampTimeZone.DEFAULT_PRECISION,
"TIMESTAMP_TZ", true, ValueTimestampTimeZone.DEFAULT_SCALE,
ValueTimestampTimeZone.MAXIMUM_SCALE),
new String[]{"TIMESTAMP WITH TIME ZONE"},
// 26 for ValueTimestampTimeZone, 32 for java.sql.Timestamp
58
new String[]{"TIMESTAMP WITH TIME ZONE"}
);
add(Value.BYTES, Types.VARBINARY,
createString(false),
new String[]{"VARBINARY", "BINARY VARYING"},
32
new String[]{"VARBINARY", "BINARY VARYING"}
);
add(Value.BYTES, Types.BINARY,
createString(false),
new String[]{"BINARY", "RAW", "BYTEA", "LONG RAW"},
32
new String[]{"BINARY", "RAW", "BYTEA", "LONG RAW"}
);
add(Value.BYTES, Types.LONGVARBINARY,
createString(false),
new String[]{"LONGVARBINARY"},
32
new String[]{"LONGVARBINARY"}
);
dataType = new DataType();
dataType.prefix = dataType.suffix = "'";
......@@ -334,55 +287,45 @@ public class DataType {
add(Value.UUID, Types.BINARY,
createString(false),
// UNIQUEIDENTIFIER is the MSSQL mode equivalent
new String[]{"UUID", "UNIQUEIDENTIFIER"},
32
new String[]{"UUID", "UNIQUEIDENTIFIER"}
);
add(Value.JAVA_OBJECT, Types.OTHER,
createString(false),
new String[]{"OTHER", "OBJECT", "JAVA_OBJECT"},
24
new String[]{"OTHER", "OBJECT", "JAVA_OBJECT"}
);
add(Value.BLOB, Types.BLOB,
createLob(),
new String[]{"BLOB", "BINARY LARGE OBJECT", "TINYBLOB", "MEDIUMBLOB",
"LONGBLOB", "IMAGE", "OID"},
// 80 for ValueLob, 24 for String
104
"LONGBLOB", "IMAGE", "OID"}
);
add(Value.CLOB, Types.CLOB,
createLob(),
new String[]{"CLOB", "CHARACTER LARGE OBJECT", "TINYTEXT", "TEXT", "MEDIUMTEXT",
"LONGTEXT", "NTEXT", "NCLOB"},
// 80 for ValueLob, 24 for String
104
"LONGTEXT", "NTEXT", "NCLOB"}
);
add(Value.GEOMETRY, Types.OTHER,
createGeometry(),
new String[]{"GEOMETRY"},
32
new String[]{"GEOMETRY"}
);
dataType = new DataType();
dataType.prefix = "ARRAY[";
dataType.suffix = "]";
add(Value.ARRAY, Types.ARRAY,
dataType,
new String[]{"ARRAY"},
32
new String[]{"ARRAY"}
);
dataType = new DataType();
dataType.maxPrecision = dataType.defaultPrecision = Integer.MAX_VALUE;
add(Value.RESULT_SET, DataType.TYPE_RESULT_SET,
dataType,
new String[]{"RESULT_SET"},
400
new String[]{"RESULT_SET"}
);
dataType = createString(false);
dataType.supportsPrecision = false;
dataType.supportsScale = false;
add(Value.ENUM, Types.OTHER,
dataType,
new String[]{"ENUM"},
48
new String[]{"ENUM"}
);
for (int i = Value.INTERVAL_YEAR; i <= Value.INTERVAL_MINUTE_TO_SECOND; i++) {
addInterval(i);
......@@ -399,24 +342,15 @@ public class DataType {
private static void addDecimal() {
add(Value.DECIMAL, Types.DECIMAL,
createDecimal(Integer.MAX_VALUE,
ValueDecimal.DEFAULT_PRECISION,
ValueDecimal.DEFAULT_SCALE,
ValueDecimal.DEFAULT_DISPLAY_SIZE, true, false),
new String[]{"DECIMAL", "DEC"},
// 40 for ValueDecimal,
64
createNumeric(Integer.MAX_VALUE, ValueDecimal.DEFAULT_PRECISION, ValueDecimal.DEFAULT_SCALE),
new String[]{"DECIMAL", "DEC"}
);
}
private static void addNumeric() {
add(Value.DECIMAL, Types.NUMERIC,
createDecimal(Integer.MAX_VALUE,
ValueDecimal.DEFAULT_PRECISION,
ValueDecimal.DEFAULT_SCALE,
ValueDecimal.DEFAULT_DISPLAY_SIZE, true, false),
new String[]{"NUMERIC", "NUMBER"},
64
createNumeric(Integer.MAX_VALUE, ValueDecimal.DEFAULT_PRECISION, ValueDecimal.DEFAULT_SCALE),
new String[]{"NUMERIC", "NUMBER"}
);
}
......@@ -433,15 +367,17 @@ public class DataType {
dataType.supportsScale = true;
dataType.defaultScale = ValueInterval.DEFAULT_SCALE;
dataType.maxScale = ValueInterval.MAXIMUM_SCALE;
dataType.params = "PRECISION,SCALE";
} else {
dataType.params = "PRECISION";
}
add(type, Types.OTHER, dataType,
new String[]{("INTERVAL " + name).intern()},
36
new String[]{("INTERVAL " + name).intern()}
);
}
private static void add(int type, int sqlType,
DataType dataType, String[] names, int memory) {
DataType dataType, String[] names) {
for (int i = 0; i < names.length; i++) {
DataType dt = new DataType();
dt.type = type;
......@@ -461,7 +397,6 @@ public class DataType {
dt.defaultScale = dataType.defaultScale;
dt.caseSensitive = dataType.caseSensitive;
dt.hidden = i > 0;
dt.memory = memory;
for (DataType t2 : TYPES) {
if (t2.sqlType == dt.sqlType) {
dt.sqlTypePos++;
......@@ -475,32 +410,41 @@ public class DataType {
}
}
/**
* Create a width numeric data type without parameters.
*
* @param precision precision
* @param scale scale
* @param autoInc whether the data type is an auto-increment type
* @return data type
*/
public static DataType createNumeric(int precision, int scale, boolean autoInc) {
DataType dataType = new DataType();
dataType.defaultPrecision = dataType.maxPrecision = precision;
dataType.defaultScale = dataType.maxScale = dataType.minScale = scale;
dataType.decimal = true;
dataType.autoIncrement = autoInc;
return dataType;
}
/**
* Create a numeric data type.
*
* @param maxPrecision maximum supported precision
* @param defaultPrecision default precision
* @param defaultScale default scale
* @param defaultDisplaySize default display size
* @param needsPrecisionAndScale where precision and scale are supported
* @param autoInc whether the data type is an auto-increment type
* @return data type
*/
public static DataType createDecimal(int maxPrecision,
int defaultPrecision, int defaultScale, int defaultDisplaySize,
boolean needsPrecisionAndScale, boolean autoInc) {
public static DataType createNumeric(int maxPrecision, int defaultPrecision, int defaultScale) {
DataType dataType = new DataType();
dataType.maxPrecision = maxPrecision;
dataType.defaultPrecision = defaultPrecision;
dataType.defaultScale = defaultScale;
if (needsPrecisionAndScale) {
dataType.params = "PRECISION,SCALE";
dataType.supportsPrecision = true;
dataType.supportsScale = true;
dataType.maxScale = maxPrecision;
}
dataType.decimal = true;
dataType.autoIncrement = autoInc;
return dataType;
}
......@@ -521,10 +465,13 @@ public class DataType {
dataType.prefix = prefix + " '";
dataType.suffix = "'";
dataType.maxPrecision = maxPrecision;
dataType.supportsScale = supportsScale;
dataType.maxScale = maxScale;
dataType.defaultPrecision = precision;
if (supportsScale) {
dataType.params = "SCALE";
dataType.supportsScale = true;
dataType.maxScale = maxScale;
dataType.defaultScale = scale;
}
return dataType;
}
......
......@@ -314,7 +314,11 @@ public abstract class Value extends VersionedValue {
* @return the memory used in bytes
*/
public int getMemory() {
return DataType.getDataType(getValueType()).memory;
/*
* Java 11 with -XX:-UseCompressedOops for all values up to ValueLong
* and ValueDouble.
*/
return 24;
}
/**
......
......@@ -50,6 +50,12 @@ public class ValueBoolean extends Value {
return BOOLEAN;
}
@Override
public int getMemory() {
// Singleton TRUE and FALSE values
return 0;
}
@Override
public StringBuilder getSQL(StringBuilder builder) {
return builder.append(getString());
......
......@@ -123,7 +123,7 @@ public abstract class ValueCollectionBase extends Value {
@Override
public int getMemory() {
int memory = 32;
int memory = 72;
for (Value v : values) {
memory += v.getMemory() + Constants.MEMORY_POINTER;
}
......
......@@ -103,6 +103,11 @@ public class ValueEnumBase extends Value {
return ENUM;
}
@Override
public int getMemory() {
return 120;
}
@Override
public int hashCode() {
int results = 31;
......
......@@ -171,6 +171,12 @@ public class ValueInterval extends Value {
return valueType;
}
@Override
public int getMemory() {
// Java 11 with -XX:-UseCompressedOops
return 48;
}
@Override
public Value convertScale(boolean onlyToSmallerScale, int targetScale) {
if (targetScale >= MAXIMUM_SCALE) {
......
......@@ -183,9 +183,9 @@ public class ValueJavaObject extends ValueBytes {
@Override
public int getMemory() {
if (value == null) {
return DataType.getDataType(getValueType()).memory;
return 40;
}
int mem = super.getMemory();
int mem = 40;
if (javaObject != null) {
mem *= 2;
}
......
......@@ -515,7 +515,12 @@ public class ValueLobDb extends Value {
@Override
public int getMemory() {
if (small != null) {
return small.length + 104;
/*
* Java 11 with -XX:-UseCompressedOops
* 0 bytes: 120 bytes
* 1 byte: 128 bytes
*/
return small.length + 127;
}
return 140;
}
......
......@@ -63,6 +63,12 @@ public class ValueNull extends Value {
return NULL;
}
@Override
public int getMemory() {
// Singleton value
return 0;
}
@Override
public String getString() {
return null;
......
......@@ -106,6 +106,11 @@ public class ValueResultSet extends Value {
return RESULT_SET;
}
@Override
public int getMemory() {
return result.getRowCount() * result.getVisibleColumnCount() * 32 + 400;
}
@Override
public String getString() {
StringBuilder buff = new StringBuilder("(");
......
......@@ -69,7 +69,12 @@ public class ValueString extends Value {
@Override
public int getMemory() {
return value.length() * 2 + 48;
/*
* Java 11 with -XX:-UseCompressedOops
* Empty string: 88 bytes
* 1 to 4 UTF-16 chars: 96 bytes
*/
return value.length() * 2 + 94;
}
@Override
......
......@@ -176,6 +176,11 @@ public class ValueTimestamp extends Value {
return TIMESTAMP;
}
@Override
public int getMemory() {
return 32;
}
@Override
public String getString() {
StringBuilder buff = new StringBuilder(MAXIMUM_PRECISION);
......
......@@ -168,6 +168,12 @@ public class ValueTimestampTimeZone extends Value {
return TIMESTAMP_TZ;
}
@Override
public int getMemory() {
// Java 11 with -XX:-UseCompressedOops
return 40;
}
@Override
public String getString() {
StringBuilder builder = new StringBuilder(ValueTimestampTimeZone.MAXIMUM_PRECISION);
......
......@@ -136,6 +136,11 @@ public class ValueUuid extends Value {
return TypeInfo.TYPE_UUID;
}
@Override
public int getMemory() {
return 32;
}
@Override
public int getValueType() {
return UUID;
......
......@@ -637,7 +637,7 @@ public class TestMVTableEngine extends TestDb {
String readCount = plan.substring(plan.indexOf("reads: "));
readCount = readCount.substring("reads: ".length(), readCount.indexOf('\n'));
int rc = Integer.parseInt(readCount);
assertTrue(plan, rc >= 60 && rc <= 70);
assertTrue(plan, rc >= 60 && rc <= 80);
// assertTrue(plan, rc >= 1000 && rc <= 1200);
conn.close();
}
......
......@@ -806,4 +806,4 @@ econd irst bcef ordinality nord unnest
analyst occupation distributive josaph aor engineer sajeewa isuru randil kevin doctor businessman artist ashan
corrupts splitted disruption unintentional octets preconditions predicates subq objectweb insn opcodes
preserves masking holder unboxing avert iae transformed subtle reevaluate exclusions subclause ftbl rgr
presorted inclusion contexts aax mwd percentile cont interpolate mwa hypothetical
presorted inclusion contexts aax mwd percentile cont interpolate mwa hypothetical regproc
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论