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

Merge pull request #1306 from katzyn/ties

Add initial implementation of WITH TIES clause
......@@ -3,7 +3,8 @@
# Initial Developer: H2 Group
"SECTION","TOPIC","SYNTAX","TEXT","EXAMPLE"
"Commands (DML)","SELECT","
SELECT [ TOP term ] [ DISTINCT [ ON ( expression [,...] ) ] | ALL ]
SELECT [ TOP term [ WITH TIES ] ]
[ DISTINCT [ ON ( expression [,...] ) ] | ALL ]
selectExpression [,...]
FROM tableExpression [,...] [ WHERE expression ]
[ GROUP BY expression [,...] ] [ HAVING expression ]
......@@ -11,7 +12,8 @@ FROM tableExpression [,...] [ WHERE expression ]
[ ORDER BY order [,...] ]
[ { LIMIT expression [ OFFSET expression ] [ SAMPLE_SIZE rowCountInt ] }
| { [ OFFSET expression { ROW | ROWS } ]
[ { FETCH { FIRST | NEXT } expression { ROW | ROWS } ONLY } ] } ]
[ FETCH { FIRST | NEXT } expression { ROW | ROWS }
{ ONLY | WITH TIES } ] } ]
[ FOR UPDATE ]
","
Selects data from a table or multiple tables.
......
......@@ -152,7 +152,7 @@
90119=User data type {0} already exists
90120=User data type {0} not found
90121=Database is already closed (to disable automatic closing at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL)
90122=Operation not supported for table {0} when there are views on the table: {1}
90122=The WITH TIES clause is not allowed without a corresponding ORDER BY clause.
90123=Cannot mix indexed and non-indexed parameters
90124=File not found: {0}
90125=Invalid class, expected {0} but got {1}
......
......@@ -1756,6 +1756,12 @@ public class ErrorCode {
*/
public static final int DATABASE_CALLED_AT_SHUTDOWN = 90121;
/**
* The error with code <code>90122</code> is thrown when
* WITH TIES clause is used without ORDER BY clause.
*/
public static final int WITH_TIES_WITHOUT_ORDER_BY = 90122;
/**
* The error with code <code>90123</code> is thrown when
* trying mix regular parameters and indexed parameters in the same
......@@ -2002,7 +2008,7 @@ public class ErrorCode {
public static final int AUTHENTICATOR_NOT_AVAILABLE = 90144;
// next are 90122, 90145
// next is 90145
private ErrorCode() {
// utility class
......
......@@ -2381,7 +2381,12 @@ public class Parser {
read("ROWS");
}
}
read("ONLY");
if (readIf(WITH)) {
read("TIES");
command.setWithTies(true);
} else {
read("ONLY");
}
}
currentSelect = temp;
if (readIf(LIMIT)) {
......@@ -2527,6 +2532,10 @@ public class Parser {
// SELECT TOP 1 (+?) AS A FROM TEST
Expression limit = readTerm().optimize(session);
command.setLimit(limit);
if (readIf(WITH)) {
read("TIES");
command.setWithTies(true);
}
} else if (readIf(LIMIT)) {
Expression offset = readTerm().optimize(session);
command.setOffset(offset);
......
......@@ -32,6 +32,7 @@ import org.h2.result.SortOrder;
import org.h2.table.ColumnResolver;
import org.h2.table.Table;
import org.h2.table.TableFilter;
import org.h2.util.StringUtils;
import org.h2.util.Utils;
import org.h2.value.Value;
import org.h2.value.ValueInt;
......@@ -47,6 +48,11 @@ public abstract class Query extends Prepared {
*/
protected Expression limitExpr;
/**
* Whether tied rows should be included in result too.
*/
protected boolean withTies;
/**
* The offset expression as specified in the LIMIT .. OFFSET clause.
*/
......@@ -644,6 +650,14 @@ public abstract class Query extends Prepared {
return limitExpr;
}
public void setWithTies(boolean withTies) {
this.withTies = true;
}
public boolean isWithTies() {
return withTies;
}
/**
* Add a parameter to the parameter list.
*
......@@ -682,4 +696,21 @@ public abstract class Query extends Prepared {
isEverything(visitor);
return visitor.getMaxDataModificationId();
}
void appendLimitToSQL(StringBuilder buff) {
if (limitExpr != null) {
if (withTies) {
if (offsetExpr != null) {
buff.append("\nOFFSET ").append(StringUtils.unEnclose(offsetExpr.getSQL())).append(" ROWS");
}
buff.append("\nFETCH NEXT ").append(StringUtils.unEnclose(limitExpr.getSQL())).append(" ROWS WITH TIES");
} else {
buff.append("\nLIMIT ").append(StringUtils.unEnclose(limitExpr.getSQL()));
if (offsetExpr != null) {
buff.append("\nOFFSET ").append(StringUtils.unEnclose(offsetExpr.getSQL()));
}
}
}
}
}
......@@ -586,7 +586,7 @@ public class Select extends Query {
return null;
}
private void queryDistinct(ResultTarget result, long limitRows) {
private void queryDistinct(ResultTarget result, long limitRows, boolean withTies) {
// limitRows must be long, otherwise we get an int overflow
// if limitRows is at or near Integer.MAX_VALUE
// limitRows is never 0 here
......@@ -618,7 +618,7 @@ public class Select extends Query {
result.addRow(row);
rowNumber++;
if ((sort == null || sortUsingIndex) && limitRows > 0 &&
rowNumber >= limitRows) {
rowNumber >= limitRows && !withTies) {
break;
}
if (sampleSize > 0 && rowNumber >= sampleSize) {
......@@ -627,7 +627,7 @@ public class Select extends Query {
}
}
private LazyResult queryFlat(int columnCount, ResultTarget result, long limitRows) {
private LazyResult queryFlat(int columnCount, ResultTarget result, long limitRows, boolean withTies) {
// limitRows must be long, otherwise we get an int overflow
// if limitRows is at or near Integer.MAX_VALUE
// limitRows is never 0 here
......@@ -644,7 +644,7 @@ public class Select extends Query {
if (result == null) {
return lazyResult;
}
if (sort != null && !sortUsingIndex || limitRows <= 0) {
if (sort != null && !sortUsingIndex || limitRows <= 0 || withTies) {
limitRows = Long.MAX_VALUE;
}
while (result.getRowCount() < limitRows && lazyResult.next()) {
......@@ -690,14 +690,14 @@ public class Select extends Query {
}
boolean lazy = session.isLazyQueryExecution() &&
target == null && !isForUpdate && !isQuickAggregateQuery &&
limitRows != 0 && offsetExpr == null && isReadOnly();
limitRows != 0 && !withTies && offsetExpr == null && isReadOnly();
int columnCount = expressions.size();
LocalResult result = null;
if (!lazy && (target == null ||
!session.getDatabase().getSettings().optimizeInsertFromSelect)) {
result = createLocalResult(result);
}
if (sort != null && (!sortUsingIndex || isAnyDistinct())) {
if (sort != null && (!sortUsingIndex || isAnyDistinct() || withTies)) {
result = createLocalResult(result);
result.setSortOrder(sort);
}
......@@ -749,9 +749,9 @@ public class Select extends Query {
queryGroup(columnCount, result);
}
} else if (isDistinctQuery) {
queryDistinct(to, limitRows);
queryDistinct(to, limitRows, withTies);
} else {
lazyResult = queryFlat(columnCount, to, limitRows);
lazyResult = queryFlat(columnCount, to, limitRows, withTies);
}
} finally {
if (!lazy) {
......@@ -775,6 +775,7 @@ public class Select extends Query {
}
if (limitRows >= 0) {
result.setLimit(limitRows);
result.setWithTies(withTies);
}
if (result != null) {
result.done();
......@@ -925,6 +926,10 @@ public class Select extends Query {
havingIndex = -1;
}
if (withTies && !hasOrder()) {
throw DbException.get(ErrorCode.WITH_TIES_WITHOUT_ORDER_BY);
}
Database db = session.getDatabase();
// first the select list (visible columns),
......@@ -1332,14 +1337,7 @@ public class Select extends Query {
buff.append(StringUtils.unEnclose(o.getSQL()));
}
}
if (limitExpr != null) {
buff.append("\nLIMIT ").append(
StringUtils.unEnclose(limitExpr.getSQL()));
if (offsetExpr != null) {
buff.append(" OFFSET ").append(
StringUtils.unEnclose(offsetExpr.getSQL()));
}
}
appendLimitToSQL(buff.builder());
if (sampleSizeExpr != null) {
buff.append("\nSAMPLE_SIZE ").append(
StringUtils.unEnclose(sampleSizeExpr.getSQL()));
......
......@@ -284,6 +284,7 @@ public class SelectUnion extends Query {
Value v = limitExpr.getValue(session);
if (v != ValueNull.INSTANCE) {
result.setLimit(v.getInt());
result.setWithTies(withTies);
}
}
l.close();
......@@ -319,6 +320,9 @@ public class SelectUnion extends Query {
Expression l = le.get(i);
expressions.add(l);
}
if (withTies && !hasOrder()) {
throw DbException.get(ErrorCode.WITH_TIES_WITHOUT_ORDER_BY);
}
}
@Override
......@@ -446,14 +450,7 @@ public class SelectUnion extends Query {
if (sort != null) {
buff.append("\nORDER BY ").append(sort.getSQL(exprList, exprList.length));
}
if (limitExpr != null) {
buff.append("\nLIMIT ").append(
StringUtils.unEnclose(limitExpr.getSQL()));
if (offsetExpr != null) {
buff.append("\nOFFSET ").append(
StringUtils.unEnclose(offsetExpr.getSQL()));
}
}
appendLimitToSQL(buff);
if (sampleSizeExpr != null) {
buff.append("\nSAMPLE_SIZE ").append(
StringUtils.unEnclose(sampleSizeExpr.getSQL()));
......
......@@ -152,7 +152,7 @@
90119=Uživatelský datový typ {0} již existuje
90120=Uživatelský datový typ {0} nenalezen
90121=Databáze byla již ukončena (pro deaktivaci automatického ukončení při zastavení virtuálního stroje přidejte parametr ";DB_CLOSE_ON_EXIT=FALSE" do URL databáze)
90122=Operace není podporována pro tabulku {0}, pokud na tabulku existují pohledy: {1}
90122=#The WITH TIES clause is not allowed without a corresponding ORDER BY clause.
90123=Nelze vzájemně míchat indexované a neindexované parametry
90124=Soubor nenalezen: {0}
90125=Neplatná třída, očekáváno {0}, ale obdrženo {1}
......
......@@ -152,7 +152,7 @@
90119=Benutzer-Datentyp {0} besteht bereits
90120=Benutzer-Datentyp {0} nicht gefunden
90121=Die Datenbank wurde bereits geschlossen (um das automatische Schliessen beim Stopp der VM zu deaktivieren, die Datenbank URL mit ";DB_CLOSE_ON_EXIT=FALSE" ergänzen)
90122=Funktion nicht unterstützt für Tabelle {0} wenn Views auf die Tabelle vorhanden sind: {1}
90122=#The WITH TIES clause is not allowed without a corresponding ORDER BY clause.
90123=Kann nicht indizierte und nicht indizierte Parameter mischen
90124=Datei nicht gefunden: {0}
90125=Ungültig Klasse, erwartet {0} erhalten {1}
......
......@@ -152,7 +152,7 @@
90119=User data type {0} already exists
90120=User data type {0} not found
90121=Database is already closed (to disable automatic closing at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL)
90122=Operation not supported for table {0} when there are views on the table: {1}
90122=The WITH TIES clause is not allowed without a corresponding ORDER BY clause.
90123=Cannot mix indexed and non-indexed parameters
90124=File not found: {0}
90125=Invalid class, expected {0} but got {1}
......
......@@ -152,7 +152,7 @@
90119=Tipo de dato de usuario {0} ya existe
90120=Tipo de dato de usuario {0} no encontrado
90121=La base de datos ya esta cerrada (para des-habilitar el cerrado automatico durante el shutdown de la VM, agregue ";DB_CLOSE_ON_EXIT=FALSE" a la URL de conexión)
90122=Operación no soportada para la tabla {0} cuando existen vistas sobre la tabla: {1}
90122=#The WITH TIES clause is not allowed without a corresponding ORDER BY clause.
90123=No se puede mezclar parametros indexados y no-indexados
90124=Archivo no encontrado: {0}
90125=Clase Invalida, se esperaba {0} pero se obtuvo {1}
......
......@@ -152,7 +152,7 @@
90119=Le type de données utilisateur {0} existe déjà
90120=Type de données utilisateur {0} non trouvé
90121=La base de données est déjà fermée (pour désactiver la fermeture automatique à l'arrêt de la VM, ajoutez "; DB_CLOSE_ON_EXIT = FALSE" à l'URL db)
90122=Opération non prise en charge pour la table {0} lorsqu'il existe des vues sur la table: {1}
90122=#The WITH TIES clause is not allowed without a corresponding ORDER BY clause.
90123=Impossible de mélanger des paramètres indexés et non-indexés
90124=Fichier non trouvé: {0}
90125=Classe invalide, attendue {0} mais obtenue {1}
......
......@@ -152,7 +152,7 @@
90119=ユーザデータ型 {0} はすでに存在します
90120=ユーザデータ型 {0} が見つかりません
90121=データベースはすでに閉じられています (VM終了時の自動データベースクローズを無効にするためには、db URLに ";DB_CLOSE_ON_EXIT=FALSE" を追加してください)
90122=ビューが存在するテーブル {0} に対する操作はサポートされていません: {1}
90122=#The WITH TIES clause is not allowed without a corresponding ORDER BY clause.
90123=インデックスの付いたパラメータと付いていないパラメータを混在させることはできません
90124=ファイルが見つかりません: {0}
90125=無効なクラス, {0} が期待されているにもかかわらず {1} を取得しました
......
......@@ -152,7 +152,7 @@
90119=Typ danych użytkownika {0} już istnieje
90120=Typ danych użytkownika {0} nie istnieje
90121=Baza danych jest już zamknięta (aby zablokować samoczynne zamykanie podczas zamknięcia VM dodaj ";DB_CLOSE_ON_EXIT=FALSE" do URL bazy danych)
90122=Operacja nie jest dozwolona dla tabeli {0} gdy istnieją widoki oparte na tabeli: {1}
90122=#The WITH TIES clause is not allowed without a corresponding ORDER BY clause.
90123=Nie można mieszać parametrów indeksowych z nieindeksowymi
90124=Plik nie istnieje: {0}
90125=Nieprawidłowa klasa, oczekiwano {0}, a jest {1}
......
......@@ -152,7 +152,7 @@
90119=Tipo de dados do usuário {0} já existe
90120=Tipo de dados do usuário {0} não foram encontrados
90121=Base de dados já está fechada (para desabilitar o fechamento automático quando a VM terminar, addicione ";DB_CLOSE_ON_EXIT=FALSE" na url da base de dados)
90122=Operação não suportada para a tabela {0} quando existe alguma vista sobre a tabela: {1}
90122=#The WITH TIES clause is not allowed without a corresponding ORDER BY clause.
90123=Não pode combinar parâmetros de índices com não índices
90124=Arquivo não encontrado: {0}
90125=Classe inválida, experada {0} mas está {1}
......
......@@ -152,7 +152,7 @@
90119=Объект с именем {0} уже существует
90120=Домен {0} не найден
90121=База данных уже закрыта (чтобы отключить автоматическое закрытие базы данных при останове JVM, добавьте ";DB_CLOSE_ON_EXIT=FALSE" в URL)
90122=Операция для таблицы {0} не поддерживается, пока существуют представления: {1}
90122=Ограничение WITH TIES использовано без соответствующего раздела ORDER BY.
90123=Одновременное использование индексированных и неиндексированных параметров в запросе не поддерживается
90124=Файл не найден: {0}
90125=Недопустимый класс, ожидался {0}, но получен {1}
......
......@@ -152,7 +152,7 @@
90119=Používateľský dátový typ {0} už existuje
90120=Používateľský dátový typ {0} nenájdený
90121=Databáza už je zatvorená (na zamedzenie automatického zatvárania pri ukončení VM, pridajte ";DB_CLOSE_ON_EXIT=FALSE" do DB URL)
90122=Operácia pre tabuľku {0} nie je podporovaná, kedže existujú na tabuľku pohľady (views): {1}
90122=#The WITH TIES clause is not allowed without a corresponding ORDER BY clause.
90123=Nemožno miešať indexované a neindexované parametre
90124=Súbor nenájdený: {0}
90125=Nesprávna trieda {1}, očakávana je {0}
......
......@@ -152,7 +152,7 @@
90119=用户数据类型 {0} 已存在
90120=找不到用户数据类型 {0}
90121=数据库已关闭 (若需要禁用在虚拟机关闭的时候同时关闭数据库,请加上 ";DB_CLOSE_ON_EXIT=FALSE" 到数据库连接的 URL)
90122={0}表不支持本操作,因为在表上存在视图: {1}
90122=#The WITH TIES clause is not allowed without a corresponding ORDER BY clause.
90123=不能混合已索引和未索引的参数
90124=文件 找不到: {0}
90125=无效的类, 取代找到 {0} 但得到 {1}
......
......@@ -41,6 +41,7 @@ public class LocalResult implements ResultInterface, ResultTarget {
private Value[] currentRow;
private int offset;
private int limit = -1;
private boolean withTies;
private ResultExternal external;
private boolean distinct;
private int[] distinctIndexes;
......@@ -366,9 +367,10 @@ public class LocalResult implements ResultInterface, ResultTarget {
if (isAnyDistinct()) {
rows = distinctRows.values();
}
if (sort != null) {
if (offset > 0 || limit > 0) {
sort.sort(rows, offset, limit < 0 ? rows.size() : limit);
if (sort != null && limit != 0) {
boolean withLimit = limit > 0 && !withTies;
if (offset > 0 || withLimit) {
sort.sort(rows, offset, withLimit ? limit : rows.size());
} else {
sort.sort(rows);
}
......@@ -401,8 +403,23 @@ public class LocalResult implements ResultInterface, ResultTarget {
rows.clear();
return;
}
int to = offset + limit;
if (withTies && sort != null) {
int[] indexes = sort.getQueryColumnIndexes();
Value[] expected = rows.get(to - 1);
loop: while (to < rows.size()) {
Value[] current = rows.get(to);
for (int idx : indexes) {
if (!expected[idx].equals(current[idx])) {
break loop;
}
}
to++;
rowCount++;
}
}
// avoid copying the whole array for each row
rows = new ArrayList<>(rows.subList(offset, offset + limit));
rows = new ArrayList<>(rows.subList(offset, to));
} else {
if (clearAll) {
external.close();
......@@ -420,12 +437,30 @@ public class LocalResult implements ResultInterface, ResultTarget {
while (--offset >= 0) {
temp.next();
}
Value[] row = null;
while (--limit >= 0) {
rows.add(temp.next());
row = temp.next();
rows.add(row);
if (rows.size() > maxMemoryRows) {
addRowsToDisk();
}
}
if (withTies && sort != null && row != null) {
int[] indexes = sort.getQueryColumnIndexes();
Value[] expected = row;
loop: while ((row = temp.next()) != null) {
for (int idx : indexes) {
if (!expected[idx].equals(row[idx])) {
break loop;
}
}
rows.add(row);
rowCount++;
if (rows.size() > maxMemoryRows) {
addRowsToDisk();
}
}
}
if (external != null) {
addRowsToDisk();
}
......@@ -451,6 +486,13 @@ public class LocalResult implements ResultInterface, ResultTarget {
this.limit = limit;
}
/**
* @param withTies whether tied rows should be included in result too
*/
public void setWithTies(boolean withTies) {
this.withTies = withTies;
}
@Override
public boolean needToClose() {
return external != null;
......
......@@ -129,7 +129,7 @@ public class TestScript extends TestDb {
testScript("ddl/" + s + ".sql");
}
for (String s : new String[] { "error_reporting", "insertIgnore", "merge", "mergeUsing", "replace",
"script", "show", "with" }) {
"script", "select", "show", "with" }) {
testScript("dml/" + s + ".sql");
}
for (String s : new String[] { "help" }) {
......
-- Copyright 2004-2018 H2 Group. Multiple-Licensed under the MPL 2.0,
-- and the EPL 1.0 (http://h2database.com/html/license.html).
-- Initial Developer: H2 Group
--
CREATE TABLE TEST(A INT, B INT, C INT);
> ok
INSERT INTO TEST VALUES (1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 2, 1), (1, 2, 2), (1, 2, 3),
(2, 1, 1), (2, 1, 2), (2, 1, 3), (2, 2, 1), (2, 2, 2), (2, 2, 3);
> update count: 12
SELECT * FROM TEST ORDER BY A, B;
> A B C
> - - -
> 1 1 1
> 1 1 2
> 1 1 3
> 1 2 1
> 1 2 2
> 1 2 3
> 2 1 1
> 2 1 2
> 2 1 3
> 2 2 1
> 2 2 2
> 2 2 3
> rows (ordered): 12
SELECT * FROM TEST ORDER BY A, B, C FETCH FIRST 4 ROWS ONLY;
> A B C
> - - -
> 1 1 1
> 1 1 2
> 1 1 3
> 1 2 1
> rows (ordered): 4
SELECT * FROM TEST ORDER BY A, B, C FETCH FIRST 4 ROWS WITH TIES;
> A B C
> - - -
> 1 1 1
> 1 1 2
> 1 1 3
> 1 2 1
> rows: 4
SELECT * FROM TEST ORDER BY A, B FETCH FIRST 4 ROWS WITH TIES;
> A B C
> - - -
> 1 1 1
> 1 1 2
> 1 1 3
> 1 2 1
> 1 2 2
> 1 2 3
> rows: 6
SELECT * FROM TEST ORDER BY A FETCH FIRST 1 ROW WITH TIES;
> A B C
> - - -
> 1 1 1
> 1 1 2
> 1 1 3
> 1 2 1
> 1 2 2
> 1 2 3
> rows: 6
SELECT TOP (1) WITH TIES * FROM TEST ORDER BY A;
> A B C
> - - -
> 1 1 1
> 1 1 2
> 1 1 3
> 1 2 1
> 1 2 2
> 1 2 3
> rows: 6
SELECT * FROM TEST ORDER BY A, B OFFSET 3 ROWS FETCH NEXT 1 ROW WITH TIES;
> A B C
> - - -
> 1 2 1
> 1 2 2
> 1 2 3
> rows: 3
CREATE INDEX TEST_A_IDX ON TEST(A);
> ok
CREATE INDEX TEST_A_B_IDX ON TEST(A, B);
> ok
SELECT * FROM TEST ORDER BY A FETCH FIRST 1 ROW WITH TIES;
> A B C
> - - -
> 1 1 1
> 1 1 2
> 1 1 3
> 1 2 1
> 1 2 2
> 1 2 3
> rows: 6
SELECT * FROM TEST ORDER BY A, B OFFSET 3 ROWS FETCH NEXT 1 ROW WITH TIES;
> A B C
> - - -
> 1 2 1
> 1 2 2
> 1 2 3
> rows: 3
SELECT * FROM TEST FETCH FIRST 1 ROW WITH TIES;
> exception WITH_TIES_WITHOUT_ORDER_BY
(SELECT * FROM TEST) UNION (SELECT 1, 2, 4) ORDER BY A, B OFFSET 3 ROWS FETCH NEXT 1 ROW WITH TIES;
> A B C
> - - -
> 1 2 1
> 1 2 2
> 1 2 3
> 1 2 4
> rows: 4
(SELECT * FROM TEST) UNION (SELECT 1, 2, 4) FETCH NEXT 1 ROW WITH TIES;
> exception WITH_TIES_WITHOUT_ORDER_BY
EXPLAIN SELECT * FROM TEST ORDER BY A, B OFFSET 3 ROWS FETCH NEXT 1 ROW WITH TIES;
>> SELECT TEST.A, TEST.B, TEST.C FROM PUBLIC.TEST /* PUBLIC.TEST_A_B_IDX */ ORDER BY 1, 2 OFFSET 3 ROWS FETCH NEXT 1 ROWS WITH TIES /* index sorted */
DROP TABLE TEST;
> ok
......@@ -786,3 +786,4 @@ ldapexample remoteuser assignments djava validators mock relate mapid tighten
retried helpers unclean missed parsers sax myclass suppose mandatory testxml miao ciao
emptied titlecase ask snk dom xif transformer dbf stx stax xof descriptors
inconsistencies discover eliminated violates tweaks postpone leftovers
tied ties
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论