提交 08ba2f01 编写于 作者: V Vlad Ilyushchenko

GRIFFIN: distinct implementation. Bugfix - filter was not applied to group-by...

GRIFFIN: distinct implementation. Bugfix - filter was not applied to group-by queries. Bugfix - join sub-queries did not compile correctly.
上级 dd554f03
......@@ -28,6 +28,7 @@ import io.questdb.cairo.sql.Record;
import io.questdb.cairo.sql.RecordCursor;
import io.questdb.std.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class FastMap implements Map {
......@@ -62,7 +63,7 @@ public class FastMap implements Map {
public FastMap(int pageSize,
@Transient @NotNull ColumnTypes keyTypes,
@Transient @NotNull ColumnTypes valueTypes,
@Transient @Nullable ColumnTypes valueTypes,
int keyCapacity,
double loadFactor
) {
......
......@@ -752,9 +752,14 @@ public class SqlCodeGenerator {
} else {
return generateTableQuery(model, executionContext);
}
}
final RecordCursorFactory factory = generateSubQuery(model, executionContext);
final ExpressionNode filter = model.getWhereClause();
if (filter != null) {
return new FilteredRecordCursorFactory(factory, functionParser.parseFunction(filter, factory.getMetadata(), executionContext));
}
return generateSubQuery(model, executionContext);
return factory;
}
private RecordCursorFactory generateOrderBy(RecordCursorFactory recordCursorFactory, QueryModel model) throws SqlException {
......@@ -975,6 +980,8 @@ public class SqlCodeGenerator {
return generateSelectVirtual(model, executionContext);
case QueryModel.SELECT_MODEL_ANALYTIC:
return generateSelectAnalytic(model, executionContext);
case QueryModel.SELECT_MODEL_DISTINCT:
return generateSelectDistinct(model, executionContext);
default:
if (model.getJoinModels().size() > 1 && processJoins) {
return generateJoins(model, executionContext);
......@@ -1079,6 +1086,22 @@ public class SqlCodeGenerator {
}
}
private RecordCursorFactory generateSelectDistinct(QueryModel model, SqlExecutionContext executionContext) throws SqlException {
final RecordCursorFactory factory = generateSubQuery(model, executionContext);
try {
return new DistinctRecordCursorFactory(
configuration,
factory,
entityColumnFilter,
asm
);
} catch (CairoException e) {
factory.close();
throw e;
}
}
private RecordCursorFactory generateSelectVirtual(QueryModel model, SqlExecutionContext executionContext) throws SqlException {
assert model.getNestedModel() != null;
final RecordCursorFactory factory = generateSubQuery(model, executionContext);
......
......@@ -244,7 +244,16 @@ class SqlOptimiser {
deletedContexts.add(idx);
}
private void addFunction(QueryColumn qc, QueryModel baseModel, QueryModel groupByModel, QueryModel outerModel, QueryModel innerModel, QueryModel analyticModel, QueryModel translatingModel) throws SqlException {
private void addFunction(
QueryColumn qc,
QueryModel baseModel,
QueryModel translatingModel,
QueryModel innerModel,
QueryModel analyticModel,
QueryModel groupByModel,
QueryModel outerModel,
QueryModel distinctModel
) throws SqlException {
// there were no aggregation functions emitted therefore
// this is just a function that goes into virtual model
innerModel.addColumn(qc);
......@@ -261,6 +270,7 @@ class SqlOptimiser {
groupByModel.addColumn(innerColumn);
analyticModel.addColumn(innerColumn);
outerModel.addColumn(innerColumn);
distinctModel.addColumn(innerColumn);
}
private void addJoinContext(QueryModel parent, JoinContext context) {
......@@ -629,9 +639,10 @@ class SqlOptimiser {
QueryModel validatingModel,
QueryModel translatingModel,
QueryModel innerModel,
QueryModel groupByModel,
QueryModel analyticModel,
QueryModel outerModel
QueryModel groupByModel,
QueryModel outerModel,
QueryModel distinctModel
) throws SqlException {
// add duplicate column names only to group-by model
// taking into account that column is pre-aliased, e.g.
......@@ -644,10 +655,11 @@ class SqlOptimiser {
final CharSequence translatedColumnName = translatingAliasMap.valueAt(index);
final CharSequence alias = createColumnAlias(columnName, groupByModel);
final QueryColumn translatedColumn = nextColumn(alias, translatedColumnName);
innerModel.addColumn(translatedColumn);
groupByModel.addColumn(translatedColumn);
analyticModel.addColumn(translatedColumn);
outerModel.addColumn(translatedColumn);
innerModel.addColumn(translatedColumn);
distinctModel.addColumn(translatedColumn);
} else {
final CharSequence alias = createColumnAlias(columnName, translatingModel);
addColumnToTranslatingModel(
......@@ -663,21 +675,23 @@ class SqlOptimiser {
// create column that references inner alias we just created
innerModel.addColumn(translatedColumn);
groupByModel.addColumn(translatedColumn);
analyticModel.addColumn(translatedColumn);
groupByModel.addColumn(translatedColumn);
outerModel.addColumn(translatedColumn);
distinctModel.addColumn(translatedColumn);
}
}
private void createSelectColumnsForWildcard(
QueryColumn qc,
boolean hasJoins,
QueryModel baseModel,
QueryModel translatingModel,
QueryModel innerModel,
QueryModel groupByModel,
QueryModel analyticModel,
QueryModel groupByModel,
QueryModel outerModel,
boolean hasJoins
QueryModel distinctModel
) throws SqlException {
// this could be a wildcard, such as '*' or 'a.*'
int dot = Chars.indexOf(qc.getAst().token, '.');
......@@ -690,26 +704,28 @@ class SqlOptimiser {
// we are targeting single table
createSelectColumnsForWildcard0(
baseModel.getJoinModels().getQuick(index),
hasJoins,
qc.getAst().position,
translatingModel,
innerModel,
groupByModel,
analyticModel,
groupByModel,
outerModel,
hasJoins
distinctModel
);
} else {
ObjList<QueryModel> models = baseModel.getJoinModels();
for (int j = 0, z = models.size(); j < z; j++) {
createSelectColumnsForWildcard0(
models.getQuick(j),
hasJoins,
qc.getAst().position,
translatingModel,
innerModel,
groupByModel,
analyticModel,
groupByModel,
outerModel,
hasJoins
distinctModel
);
}
}
......@@ -717,13 +733,14 @@ class SqlOptimiser {
private void createSelectColumnsForWildcard0(
QueryModel srcModel,
boolean hasJoins,
int wildcardPosition,
QueryModel translatingModel,
QueryModel innerModel,
QueryModel groupByModel,
QueryModel analyticModel,
QueryModel groupByModel,
QueryModel outerModel,
boolean hasJoins
QueryModel distinctModel
) throws SqlException {
final ObjList<CharSequence> columnNames = srcModel.getColumnNames();
for (int j = 0, z = columnNames.size(); j < z; j++) {
......@@ -744,9 +761,10 @@ class SqlOptimiser {
null, // do not validate
translatingModel,
innerModel,
groupByModel,
analyticModel,
outerModel
groupByModel,
outerModel,
distinctModel
);
}
}
......@@ -1866,6 +1884,22 @@ class SqlOptimiser {
}
}
}
QueryModel nested = base.getNestedModel();
if (nested != null) {
rewriteOrderByPosition(nested);
}
ObjList<QueryModel> joinModels = base.getJoinModels();
for (int i = 1, n = joinModels.size(); i < n; i++) {
// we can ignore result of order by rewrite for because
// 1. when join model is not a sub-query it will always have all the fields, so order by wouldn't
// introduce synthetic model (no column needs to be hidden)
// 2. when join model is a sub-query it will have nested model, which can be rewritten. Parent model
// would remain the same again.
rewriteOrderByPosition(joinModels.getQuick(i));
}
return model;
}
......@@ -1880,7 +1914,7 @@ class SqlOptimiser {
*
* @param model inbound model
* @return outbound model
* @throws SqlException when column names are ambiguos or not found at all.
* @throws SqlException when column names are ambiguous or not found at all.
*/
private QueryModel rewriteOrderBy(QueryModel model) throws SqlException {
// find base model and check if there is "group-by" model in between
......@@ -1900,7 +1934,7 @@ class SqlOptimiser {
final int modelColumnCount = model.getColumns().size();
boolean groupBy = false;
while (base.getColumns().size() > 0) {
while (base.getColumns().size() > 0 && !base.isNestedModelIsSubQuery()) {
baseParent = base;
base = base.getNestedModel();
groupBy = groupBy || baseParent.getSelectModelType() == QueryModel.SELECT_MODEL_GROUP_BY;
......@@ -2004,7 +2038,7 @@ class SqlOptimiser {
}
}
}
if (ascendColumns && base != model) {
if (ascendColumns && base != baseParent) {
model.addOrderBy(orderBy, base.getOrderByDirection().getQuick(i));
}
}
......@@ -2070,6 +2104,8 @@ class SqlOptimiser {
QueryModel groupByModel = queryModelPool.next();
groupByModel.setSelectModelType(QueryModel.SELECT_MODEL_GROUP_BY);
QueryModel distinctModel = queryModelPool.next();
distinctModel.setSelectModelType(QueryModel.SELECT_MODEL_DISTINCT);
QueryModel outerModel = queryModelPool.next();
outerModel.setSelectModelType(QueryModel.SELECT_MODEL_VIRTUAL);
QueryModel innerModel = queryModelPool.next();
......@@ -2082,10 +2118,10 @@ class SqlOptimiser {
boolean useAnalyticModel = false;
boolean useGroupByModel = false;
boolean useOuterModel = false;
boolean useDistinctModel = model.isDistinct();
final ObjList<QueryColumn> columns = model.getColumns();
final QueryModel baseModel = model.getNestedModel();
// baseModel.moveLimitFrom(model);
final boolean hasJoins = baseModel.getJoinModels().size() > 1;
// sample by clause should be promoted to all of the models as well as validated
......@@ -2112,13 +2148,32 @@ class SqlOptimiser {
if (qc.getAst().type == ExpressionNode.LITERAL) {
if (!isNotBindVariable(qc.getAst().token)) {
addFunction(qc, baseModel, groupByModel, outerModel, innerModel, analyticModel, translatingModel);
addFunction(
qc,
baseModel,
translatingModel,
innerModel,
analyticModel,
groupByModel,
outerModel,
distinctModel
);
useInnerModel = true;
} else if (Chars.endsWith(qc.getAst().token, '*')) {
// in general sense we need to create new column in case
// there is change of alias, for example we may have something as simple as
// select a.f, b.f from ....
createSelectColumnsForWildcard(qc, baseModel, translatingModel, innerModel, groupByModel, analyticModel, outerModel, hasJoins);
createSelectColumnsForWildcard(
qc,
hasJoins,
baseModel,
translatingModel,
innerModel,
analyticModel,
groupByModel,
outerModel,
distinctModel
);
} else {
createSelectColumn(
qc.getAlias(),
......@@ -2126,9 +2181,10 @@ class SqlOptimiser {
baseModel,
translatingModel,
innerModel,
groupByModel,
analyticModel,
outerModel
groupByModel,
outerModel,
distinctModel
);
}
} else {
......@@ -2152,7 +2208,9 @@ class SqlOptimiser {
// group-by column references might be needed when we have
// outer model supporting arithmetic such as:
// select sum(a)+sum(b) ....
outerModel.addColumn(nextColumn(qc.getAlias()));
QueryColumn aggregateRef = nextColumn(qc.getAlias());
outerModel.addColumn(aggregateRef);
distinctModel.addColumn(aggregateRef);
// pull out literals
emitLiterals(qc.getAst(), translatingModel, innerModel, baseModel);
useGroupByModel = true;
......@@ -2167,6 +2225,7 @@ class SqlOptimiser {
emitAggregates(qc.getAst(), groupByModel);
if (beforeSplit < groupByModel.getColumns().size()) {
outerModel.addColumn(qc);
distinctModel.addColumn(nextColumn(qc.getAlias()));
// pull literals from newly created group-by columns into both of underlying models
for (int j = beforeSplit, n = groupByModel.getColumns().size(); j < n; j++) {
......@@ -2176,7 +2235,16 @@ class SqlOptimiser {
useGroupByModel = true;
useOuterModel = true;
} else {
addFunction(qc, baseModel, groupByModel, outerModel, innerModel, analyticModel, translatingModel);
addFunction(
qc,
baseModel,
translatingModel,
innerModel,
analyticModel,
groupByModel,
outerModel,
distinctModel
);
useInnerModel = true;
}
}
......@@ -2245,6 +2313,11 @@ class SqlOptimiser {
root = outerModel;
}
if (useDistinctModel) {
distinctModel.setNestedModel(root);
root = distinctModel;
}
if (!useGroupByModel && groupByModel.getSampleBy() != null) {
throw SqlException.$(groupByModel.getSampleBy().position, "at least one aggregation function must be present in 'select' clause");
}
......
......@@ -559,12 +559,6 @@ public final class SqlParser {
parseSelectClause(lexer, model);
} else {
lexer.unparse();
// do not default to wildcard column selection when
// dealing with sub-queries
if (subQueryMode) {
parseFromClause(lexer, model, model);
return model;
}
model.addColumn(SqlUtil.nextColumn(queryColumnPool, sqlNodePool, "*", "*"));
}
QueryModel nestedModel = queryModelPool.next();
......@@ -573,22 +567,8 @@ public final class SqlParser {
model.setLimit(nestedModel.getLimitLo(), nestedModel.getLimitHi());
nestedModel.setLimit(null, null);
}
if (
nestedModel.getColumns().size() == 0
&& nestedModel.getNestedModel() != null
&& nestedModel.getWhereClause() == null
&& nestedModel.getSampleBy() == null
&& nestedModel.getJoinModels().size() == 1
&& nestedModel.getTimestamp() == null
&& nestedModel.getOrderBy().size() == 0
&& nestedModel.getAlias() == null
) {
nestedModel = nestedModel.getNestedModel();
} else {
model.setSelectModelType(QueryModel.SELECT_MODEL_CHOOSE);
}
model.setSelectModelType(QueryModel.SELECT_MODEL_CHOOSE);
model.setNestedModel(nestedModel);
return model;
}
......@@ -598,6 +578,7 @@ public final class SqlParser {
if (Chars.equals(tok, '(')) {
model.setNestedModel(parseSubQuery(lexer));
model.setNestedModelIsSubQuery(true);
tok = optTok(lexer);
......@@ -886,7 +867,12 @@ public final class SqlParser {
private void parseSelectClause(GenericLexer lexer, QueryModel model) throws SqlException {
while (true) {
CharSequence tok = tok(lexer, "column");
CharSequence tok = tok(lexer, "[distinct] column");
if (Chars.equalsLowerCaseAscii(tok, "distinct")) {
model.setDistinct(true);
tok = tok(lexer, "column");
}
final ExpressionNode expr;
// this is quite dramatic workaround for lexer
......
......@@ -87,7 +87,7 @@ public class AbstractSampleByRecordCursorFactory implements RecordCursorFactory
recordFunctions,
groupByMetadata,
keyTypes,
valueTypes,
valueTypes.getColumnCount(),
symbolTableIndex,
false
);
......
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (C) 2014-2019 Appsicle
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
******************************************************************************/
package io.questdb.griffin.engine.groupby;
import io.questdb.cairo.*;
import io.questdb.cairo.map.Map;
import io.questdb.cairo.map.MapFactory;
import io.questdb.cairo.map.MapKey;
import io.questdb.cairo.sql.*;
import io.questdb.griffin.SqlExecutionContext;
import io.questdb.std.BytecodeAssembler;
import io.questdb.std.Misc;
import io.questdb.std.Transient;
import org.jetbrains.annotations.NotNull;
public class DistinctRecordCursorFactory implements RecordCursorFactory {
protected final RecordCursorFactory base;
private final Map dataMap;
private final DistinctRecordCursor cursor;
private final RecordSink mapSink;
// this sink is used to copy recordKeyMap keys to dataMap
private final RecordMetadata metadata;
public DistinctRecordCursorFactory(
CairoConfiguration configuration,
RecordCursorFactory base,
@Transient @NotNull EntityColumnFilter columnFilter,
@Transient @NotNull BytecodeAssembler asm
) {
final RecordMetadata metadata = base.getMetadata();
// sink will be storing record columns to map key
columnFilter.of(metadata.getColumnCount());
this.mapSink = RecordSinkFactory.getInstance(asm, metadata, columnFilter, false);
this.dataMap = MapFactory.createMap(configuration, metadata);
this.base = base;
this.metadata = metadata;
this.cursor = new DistinctRecordCursor();
}
@Override
public void close() {
dataMap.close();
base.close();
}
@Override
public RecordCursor getCursor(SqlExecutionContext executionContext) {
dataMap.clear();
final RecordCursor baseCursor = base.getCursor(executionContext);
try {
cursor.of(baseCursor, dataMap, mapSink);
return cursor;
} catch (CairoException e) {
baseCursor.close();
throw e;
}
}
@Override
public RecordMetadata getMetadata() {
return metadata;
}
@Override
public boolean isRandomAccessCursor() {
return base.isRandomAccessCursor();
}
private static class DistinctRecordCursor implements RecordCursor {
private RecordCursor baseCursor;
private Map dataMap;
private RecordSink recordSink;
private Record record;
public DistinctRecordCursor() {
}
@Override
public void close() {
Misc.free(baseCursor);
}
@Override
public Record getRecord() {
return record;
}
@Override
public SymbolTable getSymbolTable(int columnIndex) {
return baseCursor.getSymbolTable(columnIndex);
}
@Override
public boolean hasNext() {
while (baseCursor.hasNext()) {
MapKey key = dataMap.withKey();
recordSink.copy(record, key);
if (key.create()) {
return true;
}
}
return false;
}
@Override
public Record newRecord() {
return baseCursor.newRecord();
}
@Override
public void recordAt(Record record, long atRowId) {
baseCursor.recordAt(record, atRowId);
}
@Override
public void recordAt(long rowId) {
baseCursor.recordAt(rowId);
}
@Override
public void toTop() {
baseCursor.toTop();
dataMap.clear();
}
public void of(RecordCursor baseCursor, Map dataMap, RecordSink recordSink) {
this.baseCursor = baseCursor;
this.dataMap = dataMap;
this.recordSink = recordSink;
this.record = baseCursor.getRecord();
}
}
}
......@@ -83,7 +83,7 @@ public class GroupByRecordCursorFactory implements RecordCursorFactory {
recordFunctions,
groupByMetadata,
keyTypes,
valueTypes,
valueTypes.getColumnCount(),
symbolTableIndex,
true
);
......
......@@ -48,7 +48,8 @@ class GroupByUtils {
FunctionParser functionParser,
SqlExecutionContext executionContext,
ObjList<GroupByFunction> groupByFunctions,
ArrayColumnTypes valueTypes) throws SqlException {
ArrayColumnTypes valueTypes
) throws SqlException {
final ObjList<QueryColumn> columns = model.getColumns();
for (int i = 0, n = columns.size(); i < n; i++) {
......@@ -82,7 +83,7 @@ class GroupByUtils {
ObjList<Function> recordFunctions,
GenericRecordMetadata groupByMetadata,
ArrayColumnTypes keyTypes,
ArrayColumnTypes valueTypes,
int keyColumnIndex,
IntIntHashMap symbolTableIndex,
boolean timestampUnimportant
) {
......@@ -96,7 +97,6 @@ class GroupByUtils {
final ObjList<QueryColumn> columns = model.getColumns();
// assert timestampIndex != -1;
int keyColumnIndex = valueTypes.getColumnCount();
int valueColumnIndex = 0;
// when we have same column several times in a row
......
......@@ -97,7 +97,7 @@ public class SampleByInterpolateRecordCursorFactory implements RecordCursorFacto
recordFunctions,
groupByMetadata,
keyTypes,
valueTypes,
valueTypes.getColumnCount(),
symbolTableIndex,
false
);
......
......@@ -45,6 +45,18 @@ public class QueryModel implements Mutable, ExecutionModel, AliasTranslator, Sin
public static final int SELECT_MODEL_VIRTUAL = 2;
public static final int SELECT_MODEL_ANALYTIC = 3;
public static final int SELECT_MODEL_GROUP_BY = 4;
public static final int SELECT_MODEL_DISTINCT = 5;
private static final ObjList<String> modelTypeName = new ObjList<>();
static {
modelTypeName.extendAndSet(SELECT_MODEL_NONE, "select");
modelTypeName.extendAndSet(SELECT_MODEL_CHOOSE, "select-choose");
modelTypeName.extendAndSet(SELECT_MODEL_VIRTUAL, "select-virtual");
modelTypeName.extendAndSet(SELECT_MODEL_ANALYTIC, "select-analytic");
modelTypeName.extendAndSet(SELECT_MODEL_GROUP_BY, "select-group-by");
modelTypeName.extendAndSet(SELECT_MODEL_DISTINCT, "select-distinct");
}
private final ObjList<QueryColumn> columns = new ObjList<>();
private final CharSequenceObjHashMap<CharSequence> aliasToColumnMap = new CharSequenceObjHashMap<>();
private final CharSequenceObjHashMap<CharSequence> columnToAliasMap = new CharSequenceObjHashMap<>();
......@@ -89,7 +101,8 @@ public class QueryModel implements Mutable, ExecutionModel, AliasTranslator, Sin
private ExpressionNode limitLo;
private ExpressionNode limitHi;
private int selectModelType = SELECT_MODEL_NONE;
private boolean nestedModelIsSubQuery = false;
private boolean distinct = false;
private QueryModel() {
joinModels.add(this);
......@@ -105,6 +118,14 @@ public class QueryModel implements Mutable, ExecutionModel, AliasTranslator, Sin
}
}
public boolean isDistinct() {
return distinct;
}
public void setDistinct(boolean distinct) {
this.distinct = distinct;
}
public boolean addAliasIndex(ExpressionNode node, int index) {
return aliasIndexes.put(node.token, index);
}
......@@ -199,6 +220,8 @@ public class QueryModel implements Mutable, ExecutionModel, AliasTranslator, Sin
tableVersion = -1;
columnNames.clear();
expressionModels.clear();
distinct = false;
nestedModelIsSubQuery = false;
}
public void clearOrderBy() {
......@@ -247,6 +270,14 @@ public class QueryModel implements Mutable, ExecutionModel, AliasTranslator, Sin
return -1;
}
public boolean isNestedModelIsSubQuery() {
return nestedModelIsSubQuery;
}
public void setNestedModelIsSubQuery(boolean nestedModelIsSubQuery) {
this.nestedModelIsSubQuery = nestedModelIsSubQuery;
}
public CharSequenceObjHashMap<CharSequence> getAliasToColumnMap() {
return aliasToColumnMap;
}
......@@ -536,16 +567,7 @@ public class QueryModel implements Mutable, ExecutionModel, AliasTranslator, Sin
}
private String getSelectModelTypeText() {
switch (selectModelType) {
case SELECT_MODEL_CHOOSE:
return "select-choose";
case SELECT_MODEL_VIRTUAL:
return "select-virtual";
case SELECT_MODEL_ANALYTIC:
return "select-analytic";
default:
return "select-group-by";
}
return modelTypeName.get(selectModelType);
}
private void toSink0(CharSink sink, boolean joinSlave) {
......
......@@ -118,9 +118,9 @@ public class FilesTest {
@Test
public void testListNonExistingDir() {
String temp = temporaryFolder.getRoot().getAbsolutePath();
try (Path path = new Path().of(temp).concat("xyz")) {
try (Path path = new Path().of(temp).concat("xyz").$()) {
long pFind = Files.findFirst(path);
Assert.assertEquals(0, pFind);
Assert.assertEquals("failed os=" + Os.errno(), 0, pFind);
}
}
......
......@@ -2264,6 +2264,114 @@ public class SqlCodeGeneratorTest extends AbstractGriffinTest {
expected2);
}
@Test
public void testSelectDistinct() throws Exception {
final String expected = "a\n" +
"0\n" +
"8\n" +
"3\n" +
"1\n" +
"9\n" +
"2\n" +
"6\n" +
"4\n" +
"7\n" +
"5\n";
assertQuery(expected,
"select distinct a from x",
"create table x as " +
"(" +
"select" +
" abs(rnd_int())%10 a" +
" from" +
" long_sequence(20)" +
")",
null,
"insert into x select * from (" +
"select" +
" abs(rnd_int())%10 a" +
" from long_sequence(1000000)" +
") ",
expected, true);
}
@Test
public void testSelectDistinctSymbol() throws Exception {
final String expected = "a\n" +
"EHNRX\n" +
"\n" +
"BHFOW\n" +
"QULOF\n" +
"RUEDR\n" +
"SZSRY\n" +
"YYQE\n" +
"IBBTGP\n" +
"TJWC\n" +
"ZSXU\n" +
"CCXZ\n" +
"KGHVUV\n" +
"SWHYRX\n" +
"OUOJS\n" +
"PDXYSB\n" +
"OOZZ\n" +
"WFFYUD\n" +
"DZJMY\n" +
"GETJ\n" +
"FBVTMH\n" +
"UICW\n";
final String expected2 = "a\n" +
"EHNRX\n" +
"\n" +
"BHFOW\n" +
"QULOF\n" +
"RUEDR\n" +
"SZSRY\n" +
"YYQE\n" +
"IBBTGP\n" +
"TJWC\n" +
"ZSXU\n" +
"CCXZ\n" +
"KGHVUV\n" +
"SWHYRX\n" +
"OUOJS\n" +
"PDXYSB\n" +
"OOZZ\n" +
"WFFYUD\n" +
"DZJMY\n" +
"GETJ\n" +
"FBVTMH\n" +
"UICW\n" +
"SSCL\n" +
"HLDN\n" +
"IIB\n" +
"ROGHY\n" +
"CJFT\n" +
"WNX\n" +
"VZKE\n" +
"NDMRS\n" +
"SVNVD\n" +
"ILQP\n";
assertQuery(expected,
"select distinct a from x",
"create table x as " +
"(" +
"select" +
" rnd_symbol(20,4,6,2) a" +
" from" +
" long_sequence(10000)" +
")",
null,
"insert into x select * from (" +
"select" +
" rnd_symbol(10,3,5,0) a" +
" from long_sequence(1000000)" +
") ",
expected2, true);
}
@Test
public void testOrderByAllSupported() throws Exception {
final String expected = "a\tb\tc\td\te\tf\tg\ti\tj\tk\tl\tm\tn\n" +
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册