未验证 提交 86062c80 编写于 作者: J Joan Augsburger 提交者: GitHub

chore(sql): column names are no longer case-sensitive

上级 f1fc3935
...@@ -25,15 +25,16 @@ ...@@ -25,15 +25,16 @@
package io.questdb.cairo; package io.questdb.cairo;
import io.questdb.cairo.sql.RecordMetadata; import io.questdb.cairo.sql.RecordMetadata;
import io.questdb.std.CharSequenceIntHashMap; import io.questdb.std.LowerCaseCharSequenceIntHashMap;
import io.questdb.std.ObjList; import io.questdb.std.ObjList;
public class GenericRecordMetadata extends BaseRecordMetadata { public class GenericRecordMetadata extends BaseRecordMetadata {
public static final GenericRecordMetadata EMPTY = new GenericRecordMetadata(); public static final GenericRecordMetadata EMPTY = new GenericRecordMetadata();
private final LowerCaseCharSequenceIntHashMap columnNameIndexMap;
public GenericRecordMetadata() { public GenericRecordMetadata() {
this.columnMetadata = new ObjList<>(); this.columnMetadata = new ObjList<>();
this.columnNameIndexMap = new CharSequenceIntHashMap(); this.columnNameIndexMap = new LowerCaseCharSequenceIntHashMap();
this.timestampIndex = -1; this.timestampIndex = -1;
} }
...@@ -49,6 +50,15 @@ public class GenericRecordMetadata extends BaseRecordMetadata { ...@@ -49,6 +50,15 @@ public class GenericRecordMetadata extends BaseRecordMetadata {
} }
} }
@Override
public int getColumnIndexQuiet(CharSequence columnName, int lo, int hi) {
final int index = columnNameIndexMap.keyIndex(columnName, lo, hi);
if (index < 0) {
return columnNameIndexMap.valueAt(index);
}
return -1;
}
public static GenericRecordMetadata copyOf(RecordMetadata that) { public static GenericRecordMetadata copyOf(RecordMetadata that) {
GenericRecordMetadata metadata = copyOfSansTimestamp(that); GenericRecordMetadata metadata = copyOfSansTimestamp(that);
metadata.setTimestampIndex(that.getTimestampIndex()); metadata.setTimestampIndex(that.getTimestampIndex());
......
...@@ -1050,7 +1050,7 @@ public class SqlCodeGenerator implements Mutable { ...@@ -1050,7 +1050,7 @@ public class SqlCodeGenerator implements Mutable {
return recordCursorFactory; return recordCursorFactory;
} }
try { try {
final CharSequenceIntHashMap orderBy = model.getOrderHash(); final LowerCaseCharSequenceIntHashMap orderBy = model.getOrderHash();
final ObjList<CharSequence> columnNames = orderBy.keys(); final ObjList<CharSequence> columnNames = orderBy.keys();
final int size = columnNames.size(); final int size = columnNames.size();
......
...@@ -668,7 +668,7 @@ class SqlOptimiser { ...@@ -668,7 +668,7 @@ class SqlOptimiser {
// order hash is used to determine redundant order when parsing analytic function definition // order hash is used to determine redundant order when parsing analytic function definition
private void createOrderHash(QueryModel model) { private void createOrderHash(QueryModel model) {
CharSequenceIntHashMap hash = model.getOrderHash(); LowerCaseCharSequenceIntHashMap hash = model.getOrderHash();
hash.clear(); hash.clear();
final ObjList<ExpressionNode> orderBy = model.getOrderBy(); final ObjList<ExpressionNode> orderBy = model.getOrderBy();
...@@ -687,7 +687,7 @@ class SqlOptimiser { ...@@ -687,7 +687,7 @@ class SqlOptimiser {
if (nestedModel != null) { if (nestedModel != null) {
createOrderHash(nestedModel); createOrderHash(nestedModel);
if (m > 0) { if (m > 0) {
CharSequenceIntHashMap thatHash = nestedModel.getOrderHash(); LowerCaseCharSequenceIntHashMap thatHash = nestedModel.getOrderHash();
if (thatHash.size() > 0) { if (thatHash.size() > 0) {
for (int i = 0; i < m; i++) { for (int i = 0; i < m; i++) {
QueryColumn column = columns.getQuick(i); QueryColumn column = columns.getQuick(i);
...@@ -729,7 +729,7 @@ class SqlOptimiser { ...@@ -729,7 +729,7 @@ class SqlOptimiser {
// taking into account that column is pre-aliased, e.g. // taking into account that column is pre-aliased, e.g.
// "col, col" will look like "col, col col1" // "col, col" will look like "col, col col1"
CharSequenceObjHashMap<CharSequence> translatingAliasMap = translatingModel.getColumnNameToAliasMap(); LowerCaseCharSequenceObjHashMap<CharSequence> translatingAliasMap = translatingModel.getColumnNameToAliasMap();
int index = translatingAliasMap.keyIndex(columnAst.token); int index = translatingAliasMap.keyIndex(columnAst.token);
if (index < 0) { if (index < 0) {
// column is already being referenced by translating model // column is already being referenced by translating model
...@@ -914,7 +914,7 @@ class SqlOptimiser { ...@@ -914,7 +914,7 @@ class SqlOptimiser {
} }
private ExpressionNode doReplaceLiteral(@Transient ExpressionNode node, QueryModel translatingModel, QueryModel innerModel, QueryModel validatingModel) throws SqlException { private ExpressionNode doReplaceLiteral(@Transient ExpressionNode node, QueryModel translatingModel, QueryModel innerModel, QueryModel validatingModel) throws SqlException {
final CharSequenceObjHashMap<CharSequence> map = translatingModel.getColumnNameToAliasMap(); final LowerCaseCharSequenceObjHashMap<CharSequence> map = translatingModel.getColumnNameToAliasMap();
int index = map.keyIndex(node.token); int index = map.keyIndex(node.token);
if (index > -1) { if (index > -1) {
// there is a possibility that column references join table, but in a different way // there is a possibility that column references join table, but in a different way
...@@ -1210,7 +1210,7 @@ class SqlOptimiser { ...@@ -1210,7 +1210,7 @@ class SqlOptimiser {
return orderByAdvice; return orderByAdvice;
} }
CharSequenceObjHashMap<QueryColumn> map = model.getAliasToColumnMap(); LowerCaseCharSequenceObjHashMap<QueryColumn> map = model.getAliasToColumnMap();
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
QueryColumn queryColumn = map.get(orderBy.getQuick(i).token); QueryColumn queryColumn = map.get(orderBy.getQuick(i).token);
if (queryColumn.getAst().type == ExpressionNode.LITERAL) { if (queryColumn.getAst().type == ExpressionNode.LITERAL) {
...@@ -2277,7 +2277,7 @@ class SqlOptimiser { ...@@ -2277,7 +2277,7 @@ class SqlOptimiser {
if (ascendColumns && base != model) { if (ascendColumns && base != model) {
// check if column is aliased as either // check if column is aliased as either
// "x y" or "tab.x y" or "t.x y", where "t" is alias of table "tab" // "x y" or "tab.x y" or "t.x y", where "t" is alias of table "tab"
final CharSequenceObjHashMap<CharSequence> map = baseParent.getColumnNameToAliasMap(); final LowerCaseCharSequenceObjHashMap<CharSequence> map = baseParent.getColumnNameToAliasMap();
CharSequence alias = null; CharSequence alias = null;
int index = map.keyIndex(column); int index = map.keyIndex(column);
if (index > -1 && dot > -1) { if (index > -1 && dot > -1) {
...@@ -2496,7 +2496,7 @@ class SqlOptimiser { ...@@ -2496,7 +2496,7 @@ class SqlOptimiser {
if (flatModel) { if (flatModel) {
if (flatParent && m.getSampleBy() != null) { if (flatParent && m.getSampleBy() != null) {
throw SqlException.$(m.getSampleBy().position, "'sample by' must be used with 'select' clause, which contains aggerate expression(s)"); throw SqlException.$(m.getSampleBy().position, "'sample by' must be used with 'select' clause, which contains aggregate expression(s)");
} }
} else { } else {
model.replaceJoinModel(i, rewriteSelectClause0(m)); model.replaceJoinModel(i, rewriteSelectClause0(m));
...@@ -2828,7 +2828,7 @@ class SqlOptimiser { ...@@ -2828,7 +2828,7 @@ class SqlOptimiser {
} }
private static class LiteralCheckingVisitor implements PostOrderTreeTraversalAlgo.Visitor { private static class LiteralCheckingVisitor implements PostOrderTreeTraversalAlgo.Visitor {
private CharSequenceObjHashMap<QueryColumn> nameTypeMap; private LowerCaseCharSequenceObjHashMap<QueryColumn> nameTypeMap;
@Override @Override
public void visit(ExpressionNode node) { public void visit(ExpressionNode node) {
...@@ -2846,14 +2846,14 @@ class SqlOptimiser { ...@@ -2846,14 +2846,14 @@ class SqlOptimiser {
} }
} }
PostOrderTreeTraversalAlgo.Visitor of(CharSequenceObjHashMap<QueryColumn> nameTypeMap) { PostOrderTreeTraversalAlgo.Visitor of(LowerCaseCharSequenceObjHashMap<QueryColumn> nameTypeMap) {
this.nameTypeMap = nameTypeMap; this.nameTypeMap = nameTypeMap;
return this; return this;
} }
} }
private static class LiteralRewritingVisitor implements PostOrderTreeTraversalAlgo.Visitor { private static class LiteralRewritingVisitor implements PostOrderTreeTraversalAlgo.Visitor {
private CharSequenceObjHashMap<CharSequence> aliasToColumnMap; private LowerCaseCharSequenceObjHashMap<CharSequence> aliasToColumnMap;
@Override @Override
public void visit(ExpressionNode node) { public void visit(ExpressionNode node) {
...@@ -2873,7 +2873,7 @@ class SqlOptimiser { ...@@ -2873,7 +2873,7 @@ class SqlOptimiser {
} }
} }
PostOrderTreeTraversalAlgo.Visitor of(CharSequenceObjHashMap<CharSequence> aliasToColumnMap) { PostOrderTreeTraversalAlgo.Visitor of(LowerCaseCharSequenceObjHashMap<CharSequence> aliasToColumnMap) {
this.aliasToColumnMap = aliasToColumnMap; this.aliasToColumnMap = aliasToColumnMap;
return this; return this;
} }
......
...@@ -328,7 +328,7 @@ public final class SqlParser { ...@@ -328,7 +328,7 @@ public final class SqlParser {
return parseSelect(lexer); return parseSelect(lexer);
} }
QueryModel parseAsSubQuery(GenericLexer lexer, @Nullable CharSequenceObjHashMap<WithClauseModel> withClauses) throws SqlException { QueryModel parseAsSubQuery(GenericLexer lexer, @Nullable LowerCaseCharSequenceObjHashMap<WithClauseModel> withClauses) throws SqlException {
QueryModel model; QueryModel model;
this.subQueryMode = true; this.subQueryMode = true;
try { try {
...@@ -339,7 +339,7 @@ public final class SqlParser { ...@@ -339,7 +339,7 @@ public final class SqlParser {
return model; return model;
} }
private QueryModel parseAsSubQueryAndExpectClosingBrace(GenericLexer lexer, CharSequenceObjHashMap<WithClauseModel> withClauses) throws SqlException { private QueryModel parseAsSubQueryAndExpectClosingBrace(GenericLexer lexer, LowerCaseCharSequenceObjHashMap<WithClauseModel> withClauses) throws SqlException {
final QueryModel model = parseAsSubQuery(lexer, withClauses); final QueryModel model = parseAsSubQuery(lexer, withClauses);
expectTok(lexer, ')'); expectTok(lexer, ')');
return model; return model;
...@@ -655,7 +655,7 @@ public final class SqlParser { ...@@ -655,7 +655,7 @@ public final class SqlParser {
return null; return null;
} }
private QueryModel parseDml(GenericLexer lexer, @Nullable CharSequenceObjHashMap<WithClauseModel> withClauses) throws SqlException { private QueryModel parseDml(GenericLexer lexer, @Nullable LowerCaseCharSequenceObjHashMap<WithClauseModel> withClauses) throws SqlException {
QueryModel model = null; QueryModel model = null;
QueryModel prevModel = null; QueryModel prevModel = null;
while (true) { while (true) {
...@@ -823,7 +823,7 @@ public final class SqlParser { ...@@ -823,7 +823,7 @@ public final class SqlParser {
} }
@NotNull @NotNull
private QueryModel parseDml0(GenericLexer lexer, @Nullable CharSequenceObjHashMap<WithClauseModel> parentWithClauses) throws SqlException { private QueryModel parseDml0(GenericLexer lexer, @Nullable LowerCaseCharSequenceObjHashMap<WithClauseModel> parentWithClauses) throws SqlException {
CharSequence tok; CharSequence tok;
final int modelPosition = lexer.getPosition(); final int modelPosition = lexer.getPosition();
...@@ -1274,7 +1274,7 @@ public final class SqlParser { ...@@ -1274,7 +1274,7 @@ public final class SqlParser {
return null; return null;
} }
private QueryModel parseWith(GenericLexer lexer, WithClauseModel wcm, CharSequenceObjHashMap<WithClauseModel> withClauses) throws SqlException { private QueryModel parseWith(GenericLexer lexer, WithClauseModel wcm, LowerCaseCharSequenceObjHashMap<WithClauseModel> withClauses) throws SqlException {
QueryModel m = wcm.popModel(); QueryModel m = wcm.popModel();
if (m != null) { if (m != null) {
return m; return m;
......
...@@ -81,7 +81,7 @@ public class SqlUtil { ...@@ -81,7 +81,7 @@ public class SqlUtil {
CharacterStore store, CharacterStore store,
CharSequence base, CharSequence base,
int indexOfDot, int indexOfDot,
CharSequenceObjHashMap<QueryColumn> aliasToColumnMap LowerCaseCharSequenceObjHashMap<QueryColumn> aliasToColumnMap
) { ) {
final boolean disallowed = disallowedAliases.contains(base); final boolean disallowed = disallowedAliases.contains(base);
......
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
package io.questdb.griffin.engine.functions.catalogue;
import io.questdb.cairo.*;
import io.questdb.cairo.sql.*;
import io.questdb.griffin.FunctionFactory;
import io.questdb.griffin.SqlExecutionContext;
import io.questdb.griffin.engine.functions.CursorFunction;
import io.questdb.std.*;
import io.questdb.std.str.NativeLPSZ;
import io.questdb.std.str.Path;
public class AttributeCatalogueFunctionFactory implements FunctionFactory {
private static final RecordMetadata METADATA;
@Override
public String getSignature() {
return "pg_catalog.pg_attribute()";
}
@Override
public Function newInstance(ObjList<Function> args, int position, CairoConfiguration configuration) {
return new CursorFunction(
position,
new AttributeCatalogueCursorFactory(
configuration,
METADATA
)
);
}
private static class AttributeCatalogueCursorFactory extends AbstractRecordCursorFactory {
private final Path path = new Path();
private final ReadOnlyColumn metaMem = new OnePageMemory();
private final AttributeClassCatalogueCursor cursor;
public AttributeCatalogueCursorFactory(CairoConfiguration configuration, RecordMetadata metadata) {
super(metadata);
this.cursor = new AttributeClassCatalogueCursor(configuration, path, metaMem);
}
@Override
public void close() {
Misc.free(path);
Misc.free(metaMem);
}
@Override
public RecordCursor getCursor(SqlExecutionContext executionContext) {
cursor.toTop();
return cursor;
}
@Override
public boolean recordCursorSupportsRandomAccess() {
return false;
}
}
private static class AttributeClassCatalogueCursor implements NoRandomAccessRecordCursor {
private final Path path;
private final FilesFacade ff;
private final DiskReadingRecord diskReadingRecord = new DiskReadingRecord();
private final NativeLPSZ nativeLPSZ = new NativeLPSZ();
private final int plimit;
private final ReadOnlyColumn metaMem;
private long findFileStruct = 0;
private int columnIndex = 0;
private int tableId = 1000;
private boolean readNextFileFromDisk = true;
private int columnCount;
private boolean hasNextFile = true;
private boolean foundMetadataFile = false;
public AttributeClassCatalogueCursor(CairoConfiguration configuration, Path path, ReadOnlyColumn metaMem) {
this.ff = configuration.getFilesFacade();
this.path = path;
this.path.of(configuration.getRoot()).$();
this.plimit = this.path.length();
this.metaMem = metaMem;
}
@Override
public void close() {
if (findFileStruct != 0) {
ff.findClose(findFileStruct);
findFileStruct = 0;
}
metaMem.close();
}
@Override
public Record getRecord() {
return diskReadingRecord;
}
@Override
public boolean hasNext() {
if (findFileStruct == 0) {
findFileStruct = ff.findFirst(path.trimTo(plimit).$());
if (findFileStruct > 0) {
return next0();
}
findFileStruct = 0;
return false;
}
return next0();
}
@Override
public void toTop() {
if (findFileStruct != 0) {
ff.findClose(findFileStruct);
findFileStruct = 0;
}
}
@Override
public long size() {
return -1;
}
private boolean next0() {
do {
if (readNextFileFromDisk) {
foundMetadataFile = false;
final long pname = ff.findName(findFileStruct);
if (hasNextFile) {
nativeLPSZ.of(pname);
if (
ff.findType(findFileStruct) == Files.DT_DIR && Chars.notDots(nativeLPSZ)
) {
path.trimTo(plimit);
path.concat(pname);
if (ff.exists(path.concat(TableUtils.META_FILE_NAME).$())) {
foundMetadataFile = true;
metaMem.of(ff, path, ff.getPageSize(), ff.length(path));
columnCount = metaMem.getInt(TableUtils.META_OFFSET_COUNT);
tableId = metaMem.getInt(TableUtils.META_OFFSET_TABLE_ID);
}
}
hasNextFile = ff.findNext(findFileStruct) > 0;
}
}
if (foundMetadataFile) {
long offset = TableUtils.getColumnNameOffset(columnCount);
for (int i = 0; i < columnCount; i++) {
CharSequence name = metaMem.getStr(offset);
if (columnIndex == i) {
diskReadingRecord.name = name;
diskReadingRecord.columnNumber = (short) (i + 1);
diskReadingRecord.tableId = tableId;
columnIndex++;
if (columnIndex == columnCount) {
readNextFileFromDisk = true;
columnIndex = 0;
} else {
readNextFileFromDisk = false;
}
return true;
}
offset += ReadOnlyMemory.getStorageLength(name);
}
}
} while (hasNextFile);
ff.findClose(findFileStruct);
findFileStruct = 0;
hasNextFile = true;
foundMetadataFile = false;
return false;
}
private static class DiskReadingRecord implements Record {
public CharSequence name = null;
public short columnNumber = 0;
public int tableId = 0;
@Override
public short getShort(int col) {
return columnNumber;
}
@Override
public int getInt(int col) {
return tableId;
}
@Override
public CharSequence getStr(int col) {
return name;
}
@Override
public CharSequence getStrB(int col) {
return name;
}
@Override
public int getStrLen(int col) {
return getStr(col).length();
}
}
}
static {
final GenericRecordMetadata metadata = new GenericRecordMetadata();
metadata.add(new TableColumnMetadata("attrelid", ColumnType.INT));
metadata.add(new TableColumnMetadata("attname", ColumnType.STRING));
metadata.add(new TableColumnMetadata("attnum", ColumnType.SHORT));
METADATA = metadata;
}
}
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
package io.questdb.griffin.engine.functions.catalogue;
import io.questdb.cairo.ColumnType;
import io.questdb.cairo.GenericRecordMetadata;
import io.questdb.cairo.TableColumnMetadata;
import io.questdb.cairo.sql.NoRandomAccessRecordCursor;
import io.questdb.cairo.sql.Record;
import io.questdb.cairo.sql.RecordMetadata;
class IndexCatalogueCursor implements NoRandomAccessRecordCursor {
static final RecordMetadata METADATA;
private static final IndexCatalogueRecord record = new IndexCatalogueRecord();
@Override
public void close() {
}
@Override
public Record getRecord() {
return record;
}
@Override
public boolean hasNext() {
return false;
}
@Override
public void toTop() {
}
@Override
public long size() {
return 0;
}
private static class IndexCatalogueRecord implements Record {
}
static {
final GenericRecordMetadata metadata = new GenericRecordMetadata();
metadata.add(new TableColumnMetadata("indkey", ColumnType.INT));
metadata.add(new TableColumnMetadata("indrelid", ColumnType.INT));
metadata.add(new TableColumnMetadata("indexrelid", ColumnType.INT));
metadata.add(new TableColumnMetadata("indisprimary", ColumnType.BOOLEAN));
METADATA = metadata;
}
}
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
package io.questdb.griffin.engine.functions.catalogue;
import io.questdb.cairo.CairoConfiguration;
import io.questdb.cairo.sql.Function;
import io.questdb.griffin.FunctionFactory;
import io.questdb.griffin.engine.functions.CursorFunction;
import io.questdb.griffin.engine.functions.GenericRecordCursorFactory;
import io.questdb.std.ObjList;
public class IndexCatalogueFunctionFactory implements FunctionFactory {
@Override
public String getSignature() {
return "pg_catalog.pg_index()";
}
public Function newInstance(ObjList<Function> args, int position, CairoConfiguration configuration) {
return new CursorFunction(
position,
new GenericRecordCursorFactory(
IndexCatalogueCursor.METADATA,
new IndexCatalogueCursor(),
false
)
);
}
}
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
package io.questdb.griffin.engine.functions.catalogue;
import io.questdb.cairo.ColumnType;
import io.questdb.cairo.GenericRecordMetadata;
import io.questdb.cairo.TableColumnMetadata;
import io.questdb.cairo.sql.NoRandomAccessRecordCursor;
import io.questdb.cairo.sql.Record;
import io.questdb.cairo.sql.RecordMetadata;
class InformationSchemaCursor implements NoRandomAccessRecordCursor {
static final RecordMetadata METADATA;
private static final InformationSchemaRecord record = new InformationSchemaRecord();
@Override
public void close() {
}
@Override
public Record getRecord() {
return record;
}
@Override
public boolean hasNext() {
return false;
}
@Override
public void toTop() {
}
@Override
public long size() {
return 0;
}
private static class InformationSchemaRecord implements Record {
}
static {
final GenericRecordMetadata metadata = new GenericRecordMetadata();
metadata.add(new TableColumnMetadata("x", ColumnType.INT));
metadata.add(new TableColumnMetadata("n", ColumnType.INT));
METADATA = metadata;
}
}
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
package io.questdb.griffin.engine.functions.catalogue;
import io.questdb.cairo.CairoConfiguration;
import io.questdb.cairo.sql.Function;
import io.questdb.griffin.FunctionFactory;
import io.questdb.griffin.engine.functions.CursorFunction;
import io.questdb.griffin.engine.functions.GenericRecordCursorFactory;
import io.questdb.std.ObjList;
public class InformationSchemaFunctionFactory implements FunctionFactory {
@Override
public String getSignature() {
return "information_schema._pg_expandarray(I)";
}
public Function newInstance(ObjList<Function> args, int position, CairoConfiguration configuration) {
return new CursorFunction(
position,
new GenericRecordCursorFactory(
InformationSchemaCursor.METADATA,
new InformationSchemaCursor(),
false
)
);
}
}
...@@ -56,11 +56,11 @@ public class QueryModel implements Mutable, ExecutionModel, AliasTranslator, Sin ...@@ -56,11 +56,11 @@ public class QueryModel implements Mutable, ExecutionModel, AliasTranslator, Sin
public static final int SET_OPERATION_INTERSECT = 3; public static final int SET_OPERATION_INTERSECT = 3;
private static final ObjList<String> modelTypeName = new ObjList<>(); private static final ObjList<String> modelTypeName = new ObjList<>();
private final ObjList<QueryColumn> bottomUpColumns = new ObjList<>(); private final ObjList<QueryColumn> bottomUpColumns = new ObjList<>();
private final CharSequenceHashSet topDownNameSet = new CharSequenceHashSet(); private final LowerCaseCharSequenceHashSet topDownNameSet = new LowerCaseCharSequenceHashSet();
private final ObjList<QueryColumn> topDownColumns = new ObjList<>(); private final ObjList<QueryColumn> topDownColumns = new ObjList<>();
private final CharSequenceObjHashMap<CharSequence> aliasToColumnNameMap = new CharSequenceObjHashMap<>(); private final LowerCaseCharSequenceObjHashMap<CharSequence> aliasToColumnNameMap = new LowerCaseCharSequenceObjHashMap<>();
private final CharSequenceObjHashMap<CharSequence> columnNameToAliasMap = new CharSequenceObjHashMap<>(); private final LowerCaseCharSequenceObjHashMap<CharSequence> columnNameToAliasMap = new LowerCaseCharSequenceObjHashMap<>();
private final CharSequenceObjHashMap<QueryColumn> aliasToColumnMap = new CharSequenceObjHashMap<>(); private final LowerCaseCharSequenceObjHashMap<QueryColumn> aliasToColumnMap = new LowerCaseCharSequenceObjHashMap<>();
private final ObjList<CharSequence> bottomUpColumnNames = new ObjList<>(); private final ObjList<CharSequence> bottomUpColumnNames = new ObjList<>();
private final ObjList<QueryModel> joinModels = new ObjList<>(); private final ObjList<QueryModel> joinModels = new ObjList<>();
private final ObjList<ExpressionNode> orderBy = new ObjList<>(); private final ObjList<ExpressionNode> orderBy = new ObjList<>();
...@@ -69,7 +69,7 @@ public class QueryModel implements Mutable, ExecutionModel, AliasTranslator, Sin ...@@ -69,7 +69,7 @@ public class QueryModel implements Mutable, ExecutionModel, AliasTranslator, Sin
private final IntHashSet dependencies = new IntHashSet(); private final IntHashSet dependencies = new IntHashSet();
private final IntList orderedJoinModels1 = new IntList(); private final IntList orderedJoinModels1 = new IntList();
private final IntList orderedJoinModels2 = new IntList(); private final IntList orderedJoinModels2 = new IntList();
private final CharSequenceIntHashMap aliasIndexes = new CharSequenceIntHashMap(); private final LowerCaseCharSequenceIntHashMap aliasIndexes = new LowerCaseCharSequenceIntHashMap();
private final ObjList<ExpressionNode> expressionModels = new ObjList<>(); private final ObjList<ExpressionNode> expressionModels = new ObjList<>();
// collect frequency of column names from each join model // collect frequency of column names from each join model
// and check if any of columns with frequency > 0 are selected // and check if any of columns with frequency > 0 are selected
...@@ -79,9 +79,9 @@ public class QueryModel implements Mutable, ExecutionModel, AliasTranslator, Sin ...@@ -79,9 +79,9 @@ public class QueryModel implements Mutable, ExecutionModel, AliasTranslator, Sin
private final ObjList<ExpressionNode> parsedWhere = new ObjList<>(); private final ObjList<ExpressionNode> parsedWhere = new ObjList<>();
private final IntHashSet parsedWhereConsts = new IntHashSet(); private final IntHashSet parsedWhereConsts = new IntHashSet();
private final ArrayDeque<ExpressionNode> sqlNodeStack = new ArrayDeque<>(); private final ArrayDeque<ExpressionNode> sqlNodeStack = new ArrayDeque<>();
private final CharSequenceIntHashMap orderHash = new CharSequenceIntHashMap(4, 0.5, -1); private final LowerCaseCharSequenceIntHashMap orderHash = new LowerCaseCharSequenceIntHashMap(4, 0.5, -1);
private final ObjList<ExpressionNode> joinColumns = new ObjList<>(4); private final ObjList<ExpressionNode> joinColumns = new ObjList<>(4);
private final CharSequenceObjHashMap<WithClauseModel> withClauses = new CharSequenceObjHashMap<>(); private final LowerCaseCharSequenceObjHashMap<WithClauseModel> withClauses = new LowerCaseCharSequenceObjHashMap<>();
private final ObjList<ExpressionNode> sampleByFill = new ObjList<>(); private final ObjList<ExpressionNode> sampleByFill = new ObjList<>();
private final ObjList<ExpressionNode> latestBy = new ObjList<>(); private final ObjList<ExpressionNode> latestBy = new ObjList<>();
private final ObjList<ExpressionNode> orderByAdvice = new ObjList<>(); private final ObjList<ExpressionNode> orderByAdvice = new ObjList<>();
...@@ -182,7 +182,7 @@ public class QueryModel implements Mutable, ExecutionModel, AliasTranslator, Sin ...@@ -182,7 +182,7 @@ public class QueryModel implements Mutable, ExecutionModel, AliasTranslator, Sin
withClauses.put(name, model); withClauses.put(name, model);
} }
public void addWithClauses(CharSequenceObjHashMap<WithClauseModel> parentWithClauses) { public void addWithClauses(LowerCaseCharSequenceObjHashMap<WithClauseModel> parentWithClauses) {
withClauses.putAll(parentWithClauses); withClauses.putAll(parentWithClauses);
} }
...@@ -288,11 +288,11 @@ public class QueryModel implements Mutable, ExecutionModel, AliasTranslator, Sin ...@@ -288,11 +288,11 @@ public class QueryModel implements Mutable, ExecutionModel, AliasTranslator, Sin
return -1; return -1;
} }
public CharSequenceObjHashMap<QueryColumn> getAliasToColumnMap() { public LowerCaseCharSequenceObjHashMap<QueryColumn> getAliasToColumnMap() {
return aliasToColumnMap; return aliasToColumnMap;
} }
public CharSequenceObjHashMap<CharSequence> getAliasToColumnNameMap() { public LowerCaseCharSequenceObjHashMap<CharSequence> getAliasToColumnNameMap() {
return aliasToColumnNameMap; return aliasToColumnNameMap;
} }
...@@ -304,7 +304,7 @@ public class QueryModel implements Mutable, ExecutionModel, AliasTranslator, Sin ...@@ -304,7 +304,7 @@ public class QueryModel implements Mutable, ExecutionModel, AliasTranslator, Sin
return bottomUpColumns; return bottomUpColumns;
} }
public CharSequenceObjHashMap<CharSequence> getColumnNameToAliasMap() { public LowerCaseCharSequenceObjHashMap<CharSequence> getColumnNameToAliasMap() {
return columnNameToAliasMap; return columnNameToAliasMap;
} }
...@@ -441,7 +441,7 @@ public class QueryModel implements Mutable, ExecutionModel, AliasTranslator, Sin ...@@ -441,7 +441,7 @@ public class QueryModel implements Mutable, ExecutionModel, AliasTranslator, Sin
return orderByDirectionAdvice; return orderByDirectionAdvice;
} }
public CharSequenceIntHashMap getOrderHash() { public LowerCaseCharSequenceIntHashMap getOrderHash() {
return orderHash; return orderHash;
} }
...@@ -550,7 +550,7 @@ public class QueryModel implements Mutable, ExecutionModel, AliasTranslator, Sin ...@@ -550,7 +550,7 @@ public class QueryModel implements Mutable, ExecutionModel, AliasTranslator, Sin
return withClauses.get(name); return withClauses.get(name);
} }
public CharSequenceObjHashMap<WithClauseModel> getWithClauses() { public LowerCaseCharSequenceObjHashMap<WithClauseModel> getWithClauses() {
return withClauses; return withClauses;
} }
......
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
package io.questdb.std;
import java.util.Arrays;
public abstract class AbstractLowerCaseCharSequenceHashSet implements Mutable {
protected static final CharSequence noEntryKey = null;
protected static final int MIN_INITIAL_CAPACITY = 16;
protected final double loadFactor;
protected int mask;
protected int free;
protected int capacity;
// exposed for testing only
protected CharSequence[] keys;
public AbstractLowerCaseCharSequenceHashSet(int initialCapacity, double loadFactor) {
if (loadFactor <= 0d || loadFactor >= 1d) {
throw new IllegalArgumentException("0 < loadFactor < 1");
}
free = this.capacity = Math.max(initialCapacity, MIN_INITIAL_CAPACITY);
this.loadFactor = loadFactor;
keys = new CharSequence[Numbers.ceilPow2((int) (this.capacity / loadFactor))];
mask = keys.length - 1;
}
@Override
public void clear() {
Arrays.fill(keys, noEntryKey);
free = this.capacity;
}
public boolean excludes(CharSequence key) {
return keyIndex(key) > -1;
}
public boolean excludes(CharSequence key, int lo, int hi) {
return keyIndex(key, lo, hi) > -1;
}
public int keyIndex(CharSequence key) {
int index = Chars.lowerCaseHashCode(key) & mask;
if (keys[index] == noEntryKey) {
return index;
}
if (Chars.equalsLowerCase(key, keys[index])) {
return -index - 1;
}
return probe(key, index);
}
public int keyIndex(CharSequence key, int lo, int hi) {
int index = Chars.lowerCaseHashCode(key, lo, hi) & mask;
if (keys[index] == noEntryKey) {
return index;
}
CharSequence cs = keys[index];
if (Chars.equalsLowerCase(key, lo, hi, cs, 0, cs.length())) {
return -index - 1;
}
return probe(key, lo, hi, index);
}
public int remove(CharSequence key) {
int index = keyIndex(key);
if (index < 0) {
removeAt(index);
return -index - 1;
}
return -1;
}
public void removeAt(int index) {
if (index < 0) {
int from = -index - 1;
erase(from);
free++;
// after we have freed up a slot
// consider non-empty keys directly below
// they may have been a direct hit but because
// directly hit slot wasn't empty these keys would
// have moved.
//
// After slot if freed these keys require re-hash
from = (from + 1) & mask;
for (
CharSequence key = keys[from];
key != noEntryKey;
from = (from + 1) & mask, key = keys[from]
) {
int idealHit = Chars.lowerCaseHashCode(key) & mask;
if (idealHit != from) {
int to;
if (keys[idealHit] != noEntryKey) {
to = probe(key, idealHit);
} else {
to = idealHit;
}
if (to > -1) {
move(from, to);
}
}
}
}
}
public int size() {
return capacity - free;
}
/**
* Erases entry in array.
*
* @param index always positive, no arithmetic required.
*/
abstract protected void erase(int index);
abstract protected void move(int from, int to);
private int probe(CharSequence key, int index) {
do {
index = (index + 1) & mask;
if (keys[index] == noEntryKey) {
return index;
}
if (Chars.equalsLowerCase(key, keys[index])) {
return -index - 1;
}
} while (true);
}
private int probe(CharSequence key, int lo, int hi, int index) {
do {
index = (index + 1) & mask;
if (keys[index] == noEntryKey) {
return index;
}
CharSequence cs = keys[index];
if (Chars.equalsLowerCase(key, lo, hi, cs, 0, cs.length())) {
return -index - 1;
}
} while (true);
}
}
...@@ -230,6 +230,39 @@ public final class Chars { ...@@ -230,6 +230,39 @@ public final class Chars {
return l != null && equalsIgnoreCase(l, r); return l != null && equalsIgnoreCase(l, r);
} }
public static boolean equalsLowerCase(CharSequence l, int lLo, int lHi, CharSequence r, int rLo, int rHi) {
if (l == r) {
return true;
}
int ll = lHi - lLo;
if (ll != rHi - rLo) {
return false;
}
for (int i = 0; i < ll; i++) {
if (Character.toLowerCase(l.charAt(i + lLo)) != Character.toLowerCase(r.charAt(i + rLo))) {
return false;
}
}
return true;
}
public static boolean equalsLowerCase(@NotNull CharSequence l, CharSequence r) {
int ll;
if ((ll = l.length()) != r.length()) {
return false;
}
for (int i = 0; i < ll; i++) {
if (Character.toLowerCase(l.charAt(i)) != Character.toLowerCase(r.charAt(i))) {
return false;
}
}
return true;
}
public static boolean equalsLowerCaseAscii(CharSequence l, int lLo, int lHi, CharSequence r, int rLo, int rHi) { public static boolean equalsLowerCaseAscii(CharSequence l, int lLo, int lHi, CharSequence r, int rLo, int rHi) {
if (l == r) { if (l == r) {
return true; return true;
...@@ -241,7 +274,7 @@ public final class Chars { ...@@ -241,7 +274,7 @@ public final class Chars {
} }
for (int i = 0; i < ll; i++) { for (int i = 0; i < ll; i++) {
if (toLowerCaseAscii(l.charAt(i + lLo)) != r.charAt(i + rLo)) { if (toLowerCaseAscii(l.charAt(i + lLo)) != toLowerCaseAscii(r.charAt(i + rLo))) {
return false; return false;
} }
} }
...@@ -255,7 +288,7 @@ public final class Chars { ...@@ -255,7 +288,7 @@ public final class Chars {
} }
for (int i = 0; i < ll; i++) { for (int i = 0; i < ll; i++) {
if (toLowerCaseAscii(l.charAt(i)) != r.charAt(i)) { if (toLowerCaseAscii(l.charAt(i)) != toLowerCaseAscii(r.charAt(i))) {
return false; return false;
} }
} }
...@@ -389,6 +422,31 @@ public final class Chars { ...@@ -389,6 +422,31 @@ public final class Chars {
return h; return h;
} }
public static int lowerCaseHashCode(CharSequence value, int lo, int hi) {
if (hi == lo) {
return 0;
}
int h = 0;
for (int p = lo; p < hi; p++) {
h = 31 * h + Character.toLowerCase(value.charAt(p));
}
return h;
}
public static int lowerCaseHashCode(CharSequence value) {
int len = value.length();
if (len == 0) {
return 0;
}
int h = 0;
for (int p = 0; p < len; p++) {
h = 31 * h + Character.toLowerCase(value.charAt(p));
}
return h;
}
public static boolean noMatch(CharSequence l, int llo, int lhi, CharSequence r, int rlo, int rhi) { public static boolean noMatch(CharSequence l, int llo, int lhi, CharSequence r, int rlo, int rhi) {
int lp = llo; int lp = llo;
int rp = rlo; int rp = rlo;
...@@ -498,8 +556,6 @@ public final class Chars { ...@@ -498,8 +556,6 @@ public final class Chars {
b.put(toLowerCaseAscii(value.charAt(i))); b.put(toLowerCaseAscii(value.charAt(i)));
} }
return b.toString(); return b.toString();
} }
public static char toLowerCaseAscii(char character) { public static char toLowerCaseAscii(char character) {
...@@ -765,4 +821,15 @@ public final class Chars { ...@@ -765,4 +821,15 @@ public final class Chars {
sink.put((char) (b1 << 6 ^ b2 ^ 3968)); sink.put((char) (b1 << 6 ^ b2 ^ 3968));
return 2; return 2;
} }
public static String toLowerCase(CharSequence str) {
final CharSink sink = Misc.getThreadLocalBuilder();
if (str != null) {
final int len = str.length();
for (int i = 0; i < len; i++) {
sink.put(Character.toLowerCase(str.charAt(i)));
}
}
return sink.toString();
}
} }
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
package io.questdb.std;
public class LowerCaseCharSequenceHashSet extends AbstractLowerCaseCharSequenceHashSet {
private static final int MIN_INITIAL_CAPACITY = 16;
public LowerCaseCharSequenceHashSet() {
this(MIN_INITIAL_CAPACITY);
}
private LowerCaseCharSequenceHashSet(int initialCapacity) {
this(initialCapacity, 0.4);
}
private LowerCaseCharSequenceHashSet(int initialCapacity, double loadFactor) {
super(initialCapacity, loadFactor);
clear();
}
/**
* Adds key to hash set preserving key uniqueness.
*
* @param key immutable sequence of characters.
* @return false if key is already in the set and true otherwise.
*/
public boolean add(CharSequence key) {
int index = keyIndex(key);
if (index < 0) {
return false;
}
addAt(index, key);
return true;
}
public void addAt(int index, CharSequence key) {
keys[index] = key;
if (--free < 1) {
rehash();
}
}
public boolean contains(CharSequence key) {
return keyIndex(key) < 0;
}
public CharSequence keyAt(int index) {
return keys[-index - 1];
}
@Override
protected void erase(int index) {
keys[index] = noEntryKey;
}
@Override
protected void move(int from, int to) {
keys[to] = keys[from];
erase(from);
}
private void rehash() {
int newCapacity = capacity * 2;
final int size = size();
free = capacity = newCapacity;
int len = Numbers.ceilPow2((int) (newCapacity / loadFactor));
CharSequence[] newKeys = new CharSequence[len];
CharSequence[] oldKeys = keys;
mask = len - 1;
this.keys = newKeys;
free -= size;
for (int i = 0, n = oldKeys.length; i < n; i++) {
CharSequence key = oldKeys[i];
if (key != null) {
keys[keyIndex(key)] = key;
}
}
}
}
\ No newline at end of file
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
package io.questdb.std;
import java.util.Arrays;
public class LowerCaseCharSequenceIntHashMap extends AbstractLowerCaseCharSequenceHashSet {
private static final int NO_ENTRY_VALUE = -1;
private final int noEntryValue;
protected int[] values;
private final ObjList<CharSequence> list;
public LowerCaseCharSequenceIntHashMap() {
this(8);
}
public LowerCaseCharSequenceIntHashMap(int initialCapacity) {
this(initialCapacity, 0.5, NO_ENTRY_VALUE);
}
public LowerCaseCharSequenceIntHashMap(int initialCapacity, double loadFactor, int noEntryValue) {
super(initialCapacity, loadFactor);
this.noEntryValue = noEntryValue;
this.list = new ObjList<>(capacity);
values = new int[keys.length];
clear();
}
public void clear() {
super.clear();
list.clear();
Arrays.fill(values, noEntryValue);
}
@Override
protected void erase(int index) {
keys[index] = noEntryKey;
values[index] = noEntryValue;
}
public void removeAt(int index) {
if (index < 0) {
int index1 = -index - 1;
CharSequence key = keys[index1];
super.removeAt(index);
list.remove(key);
}
}
public int valueAt(int index) {
return index < 0 ? values[-index - 1] : noEntryValue;
}
public boolean contains(CharSequence key) {
return keyIndex(key) < 0;
}
public int get(CharSequence key) {
return valueAt(keyIndex(key));
}
public boolean put(CharSequence key, int value) {
return putAt(keyIndex(key), key, value);
}
public boolean putAt(int index, CharSequence key, int value) {
if (index < 0) {
values[-index - 1] = value;
return false;
}
putAt0(index, key, value);
list.add(key);
return true;
}
public void putIfAbsent(CharSequence key, int value) {
int index = keyIndex(key);
if (index > -1) {
putAt0(index, key, value);
}
}
@Override
protected void move(int from, int to) {
keys[to] = keys[from];
values[to] = values[from];
erase(from);
}
protected void putAt0(int index, CharSequence key, int value) {
keys[index] = key;
values[index] = value;
if (--free == 0) {
rehash();
}
}
private void rehash() {
int size = size();
int newCapacity = capacity * 2;
free = capacity = newCapacity;
int len = Numbers.ceilPow2((int) (newCapacity / loadFactor));
int[] oldValues = values;
CharSequence[] oldKeys = keys;
this.keys = new CharSequence[len];
this.values = new int[len];
Arrays.fill(keys, null);
mask = len - 1;
free -= size;
for (int i = oldKeys.length; i-- > 0; ) {
CharSequence key = oldKeys[i];
if (key != null) {
final int index = keyIndex(key);
keys[index] = key;
values[index] = oldValues[i];
}
}
}
public ObjList<CharSequence> keys() {
return list;
}
}
\ No newline at end of file
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
package io.questdb.std;
import java.util.Arrays;
public class LowerCaseCharSequenceObjHashMap<T> extends AbstractLowerCaseCharSequenceHashSet {
private final ObjList<CharSequence> list;
private T[] values;
public LowerCaseCharSequenceObjHashMap() {
this(8);
}
public LowerCaseCharSequenceObjHashMap(int initialCapacity) {
this(initialCapacity, 0.5);
}
@SuppressWarnings("unchecked")
public LowerCaseCharSequenceObjHashMap(int initialCapacity, double loadFactor) {
super(initialCapacity, loadFactor);
values = (T[]) new Object[keys.length];
this.list = new ObjList<>(capacity);
clear();
}
public final void clear() {
super.clear();
list.clear();
Arrays.fill(values, null);
}
public ObjList<CharSequence> keys() {
return list;
}
@Override
protected void erase(int index) {
keys[index] = noEntryKey;
values[index] = null;
}
@Override
public void removeAt(int index) {
if (index < 0) {
CharSequence key = keys[-index - 1];
super.removeAt(index);
list.remove(key);
}
}
public boolean contains(CharSequence key) {
return keyIndex(key) < 0;
}
public T get(CharSequence key) {
return valueAt(keyIndex(key));
}
@Override
protected void move(int from, int to) {
keys[to] = keys[from];
values[to] = values[from];
erase(from);
}
public boolean put(CharSequence key, T value) {
return putAt(keyIndex(key), key, value);
}
public boolean putAt(int index, CharSequence key, T value) {
if (index < 0) {
values[-index - 1] = value;
return false;
}
putAt0(index, key, value);
list.add(key);
return true;
}
public void putIfAbsent(CharSequence key, T value) {
int index = keyIndex(key);
if (index > -1) {
putAt0(index, key, value);
}
}
public T valueAt(int index) {
return index < 0 ? valueAtQuick(index) : null;
}
public T valueAtQuick(int index) {
return values[-index - 1];
}
private void putAt0(int index, CharSequence key, T value) {
keys[index] = key;
values[index] = value;
if (--free == 0) {
rehash();
}
}
@SuppressWarnings("unchecked")
private void rehash() {
int size = size();
int newCapacity = capacity * 2;
free = capacity = newCapacity;
int arrayCapacity = Numbers.ceilPow2((int) (newCapacity / loadFactor));
T[] oldValues = values;
CharSequence[] oldKeys = keys;
this.keys = new CharSequence[arrayCapacity];
this.values = (T[]) new Object[arrayCapacity];
Arrays.fill(keys, null);
mask = arrayCapacity - 1;
free -= size;
for (int i = oldKeys.length; i-- > 0; ) {
CharSequence key = oldKeys[i];
if (key != null) {
final int index = keyIndex(key);
keys[index] = key;
values[index] = oldValues[i];
}
}
}
public void putAll(LowerCaseCharSequenceObjHashMap<T> other) {
CharSequence[] otherKeys = other.keys;
T[] otherValues = other.values;
for (int i = 0, n = otherKeys.length; i < n; i++) {
if (otherKeys[i] != noEntryKey) {
put(otherKeys[i], otherValues[i]);
}
}
}
}
\ No newline at end of file
...@@ -455,7 +455,10 @@ open module io.questdb { ...@@ -455,7 +455,10 @@ open module io.questdb {
io.questdb.griffin.engine.functions.conditional.CaseFunctionFactory, io.questdb.griffin.engine.functions.conditional.CaseFunctionFactory,
io.questdb.griffin.engine.functions.conditional.SwitchFunctionFactory, io.questdb.griffin.engine.functions.conditional.SwitchFunctionFactory,
// PostgeSQL catalogue functions // PostgeSQL catalogue functions
io.questdb.griffin.engine.functions.catalogue.AttributeCatalogueFunctionFactory,
io.questdb.griffin.engine.functions.catalogue.ClassCatalogueFunctionFactory, io.questdb.griffin.engine.functions.catalogue.ClassCatalogueFunctionFactory,
io.questdb.griffin.engine.functions.catalogue.IndexCatalogueFunctionFactory,
io.questdb.griffin.engine.functions.catalogue.InformationSchemaFunctionFactory,
io.questdb.griffin.engine.functions.catalogue.PrefixedNamespaceCatalogueFunctionFactory, io.questdb.griffin.engine.functions.catalogue.PrefixedNamespaceCatalogueFunctionFactory,
io.questdb.griffin.engine.functions.catalogue.NamespaceCatalogueFunctionFactory, io.questdb.griffin.engine.functions.catalogue.NamespaceCatalogueFunctionFactory,
io.questdb.griffin.engine.functions.catalogue.IsTableVisibleCatalogueFunctionFactory, io.questdb.griffin.engine.functions.catalogue.IsTableVisibleCatalogueFunctionFactory,
......
...@@ -22,6 +22,30 @@ ...@@ -22,6 +22,30 @@
# #
################################################################################ ################################################################################
################################################################################
# ___ _ ____ ____
# / _ \ _ _ ___ ___| |_| _ \| __ )
# | | | | | | |/ _ \/ __| __| | | | _ \
# | |_| | |_| | __/\__ \ |_| |_| | |_) |
# \__\_\\__,_|\___||___/\__|____/|____/
#
# Copyright (c) 2014-2019 Appsicle
# Copyright (c) 2019-2020 QuestDB
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
################################################################################
################################################################################ ################################################################################
# ___ _ ____ ____ # ___ _ ____ ____
...@@ -463,8 +487,11 @@ io.questdb.griffin.engine.functions.math.RoundHalfEvenDoubleFunctionFactory ...@@ -463,8 +487,11 @@ io.questdb.griffin.engine.functions.math.RoundHalfEvenDoubleFunctionFactory
io.questdb.griffin.engine.functions.conditional.CaseFunctionFactory io.questdb.griffin.engine.functions.conditional.CaseFunctionFactory
io.questdb.griffin.engine.functions.conditional.SwitchFunctionFactory io.questdb.griffin.engine.functions.conditional.SwitchFunctionFactory
# PostgeSQL catalogue functions # PostgreSQL catalogue functions
io.questdb.griffin.engine.functions.catalogue.AttributeCatalogueFunctionFactory
io.questdb.griffin.engine.functions.catalogue.ClassCatalogueFunctionFactory io.questdb.griffin.engine.functions.catalogue.ClassCatalogueFunctionFactory
io.questdb.griffin.engine.functions.catalogue.IndexCatalogueFunctionFactory
io.questdb.griffin.engine.functions.catalogue.InformationSchemaFunctionFactory
io.questdb.griffin.engine.functions.catalogue.PrefixedNamespaceCatalogueFunctionFactory io.questdb.griffin.engine.functions.catalogue.PrefixedNamespaceCatalogueFunctionFactory
io.questdb.griffin.engine.functions.catalogue.IsTableVisibleCatalogueFunctionFactory io.questdb.griffin.engine.functions.catalogue.IsTableVisibleCatalogueFunctionFactory
io.questdb.griffin.engine.functions.catalogue.UserByIdCatalogueFunctionFactory io.questdb.griffin.engine.functions.catalogue.UserByIdCatalogueFunctionFactory
......
...@@ -78,6 +78,31 @@ public class GenericRecordMetadataTest { ...@@ -78,6 +78,31 @@ public class GenericRecordMetadataTest {
TestUtils.assertEquals(expected, sink); TestUtils.assertEquals(expected, sink);
} }
@Test
public void testDuplicateColumn2() {
final String expected = "{\"columnCount\":2,\"columns\":[{\"index\":0,\"name\":\"abc\",\"type\":\"INT\"},{\"index\":1,\"name\":\"cde\",\"type\":\"INT\"}],\"timestampIndex\":-1}";
GenericRecordMetadata metadata = new GenericRecordMetadata();
metadata.add(new TableColumnMetadata("abc", ColumnType.INT));
metadata.add(new TableColumnMetadata("cde", ColumnType.INT));
sink.clear();
metadata.toJson(sink);
TestUtils.assertEquals(expected, sink);
try {
metadata.add(new TableColumnMetadata("ABC", ColumnType.FLOAT));
Assert.fail();
} catch (CairoException e) {
TestUtils.assertContains(e.getMessage(), "Duplicate column");
}
sink.clear();
metadata.toJson(sink);
TestUtils.assertEquals(expected, sink);
}
@Test @Test
public void testReuse() { public void testReuse() {
String expected1 = "{\"columnCount\":3,\"columns\":[{\"index\":0,\"name\":\"abc\",\"type\":\"INT\"},{\"index\":1,\"name\":\"cde\",\"type\":\"INT\"},{\"index\":2,\"name\":\"timestamp\",\"type\":\"TIMESTAMP\"}],\"timestampIndex\":2}"; String expected1 = "{\"columnCount\":3,\"columns\":[{\"index\":0,\"name\":\"abc\",\"type\":\"INT\"},{\"index\":1,\"name\":\"cde\",\"type\":\"INT\"},{\"index\":2,\"name\":\"timestamp\",\"type\":\"TIMESTAMP\"}],\"timestampIndex\":2}";
......
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
package io.questdb.griffin.engine.functions.catalogue;
import io.questdb.griffin.AbstractGriffinTest;
import org.junit.Test;
public class AttributeCatalogueFunctionFactoryTest extends AbstractGriffinTest {
@Test
public void testPgAttributeFunc() throws Exception {
assertQuery(
"attrelid\tattname\tattnum\n" +
"1\ta\t1\n",
"pg_catalog.pg_attribute;",
"create table x(a int)",
null,
false,
false
);
}
@Test
public void testPgAttributeFuncNoTables() throws Exception {
assertQuery(
"attrelid\tattname\tattnum\n",
"pg_catalog.pg_attribute;",
null,
null,
false,
false
);
}
@Test
public void testPgAttributeFuncWith2Tables() throws Exception {
assertQuery(
"attrelid\tattname\tattnum\n" +
"1\ta\t1\n",
"pg_catalog.pg_attribute order by 1;",
"create table x(a int)",
null,
"create table y(a double, b string)",
"attrelid\tattname\tattnum\n" +
"1\ta\t1\n" +
"2\ta\t1\n" +
"2\tb\t2\n",
true,
false,
false
);
}
@Test
public void testPgAttributeFuncWith2TablesLimit1() throws Exception {
assertQuery(
"attrelid\tattname\tattnum\n" +
"1\ta\t1\n",
"pg_catalog.pg_attribute order by 1 limit 1;",
"create table x(a int)",
null,
"create table y(a double, b string)",
"attrelid\tattname\tattnum\n" +
"1\ta\t1\n",
true,
false,
true
);
}
@Test
public void testKafkaMetadataQuery() throws Exception {
String query = "\n" +
"SELECT\n" +
" result.TABLE_CAT, \n" +
" result.TABLE_SCHEM, \n" +
" result.TABLE_NAME, \n" +
" result.COLUMN_NAME, \n" +
" result.KEY_SEQ, \n" +
" result.PK_NAME,\n" +
" result.KEYS,\n" +
" result.A_ATTNUM,\n" +
" RAW \n" +
"FROM\n" +
" (SELECT \n" +
" NULL AS TABLE_CAT, \n" +
" n.nspname AS TABLE_SCHEM, \n" +
" ct.relname AS TABLE_NAME, \n" +
" a.attname AS COLUMN_NAME, \n" +
" (information_schema._pg_expandarray(i.indkey)).n AS KEY_SEQ, \n" +
" ci.relname AS PK_NAME, \n" +
" information_schema._pg_expandarray(i.indkey) AS KEYS, \n" +
" a.attnum AS A_ATTNUM,\n" +
" i.indkey AS RAW \n" +
" FROM pg_catalog.pg_class ct\n" +
" JOIN pg_catalog.pg_attribute a ON (ct.oid = a.attrelid) \n" +
" JOIN pg_catalog.pg_namespace n ON (ct.relnamespace = n.oid) \n" +
" JOIN pg_catalog.pg_index i ON ( a.attrelid = i.indrelid) \n" +
" JOIN pg_catalog.pg_class ci ON (ci.oid = i.indexrelid) \n" +
" WHERE \n" +
" true \n" +
" AND ct.relname = E'po_items' \n" +
" AND i.indisprimary \n" +
" ) result; \n";
assertQuery(
"TABLE_CAT\tTABLE_SCHEM\tTABLE_NAME\tCOLUMN_NAME\tKEY_SEQ\tPK_NAME\tKEYS\tA_ATTNUM\tRAW\n",
query,
"create table x(a int)",
null,
false,
false
);
}
@Test
public void testKafkaMetadataQueryCaseInsensitivity1() throws Exception {
String query = "SELECT\n" +
" result.TABLE_CAT, \n" +
" result.TABLE_SCHEM, \n" +
" result.TABLE_NAME, \n" +
" result.COLUMN_NAME, \n" +
" result.KEY_SEQ, \n" +
" result.PK_NAME,\n" +
" -- result.KEYS,\n" +
" result.A_ATTNUM,\n" +
" RAW \n" +
"FROM\n" +
" (SELECT \n" +
" NULL AS TABLE_CAT, \n" +
" n.nspname AS TABLE_SCHEM, \n" +
" ct.relname AS TABLE_NAME, \n" +
" a.attname AS COLUMN_NAME, \n" +
" (information_schema._pg_expandarray(i.indkey)).n AS KEY_SEQ, \n" +
" ci.relname AS PK_NAME, \n" +
" -- information_schema._pg_expandarray(i.indkey) AS KEYS, \n" +
" a.attnum AS A_ATTNUM,\n" +
" i.indkey AS RAW \n" +
" FROM pg_catalog.pg_class ct\n" +
" JOIN pg_catalog.pg_attribute a ON (ct.oid = a.attrelid) \n" +
" JOIN pg_catalog.pg_namespace n ON (ct.relnamespace = n.oid) \n" +
" JOIN pg_catalog.pg_index i ON ( a.attrelid = i.indrelid) \n" +
" JOIN pg_catalog.pg_class ci ON (ci.oid = i.indexrelid) \n" +
" WHERE \n" +
" true \n" +
" AND ct.relname = E'po_items' \n" +
" AND i.indisprimary \n" +
" ) result \n" +
"--WHERE A_ATTNUM = (result.KEYS).x \n" +
"ORDER BY result.table_name, result.PK_NAME, result.KEY_SEQ;";
assertQuery(
"TABLE_CAT\tTABLE_SCHEM\tTABLE_NAME\tCOLUMN_NAME\tKEY_SEQ\tPK_NAME\tA_ATTNUM\tRAW\n",
query,
"create table x(a int)",
null,
true,
false
);
}
@Test
public void testKafkaMetadataQueryCaseInsensitivity2() throws Exception {
String query = "SELECT\n" +
" result.TABLE_CAT, \n" +
" result.TABLE_SCHEM, \n" +
" result.TABLE_NAME, \n" +
" result.COLUMN_NAME, \n" +
" result.KEY_SEQ, \n" +
" result.PK_NAME,\n" +
" -- result.KEYS,\n" +
" result.A_ATTNUM,\n" +
" RAW \n" +
"FROM\n" +
" (SELECT \n" +
" NULL AS TABLE_CAT, \n" +
" n.nspname AS TABLE_SCHEM, \n" +
" ct.relname AS TABLE_NAME, \n" +
" a.attname AS COLUMN_NAME, \n" +
" (information_schema._pg_expandarray(i.indkey)).n AS KEY_SEQ, \n" +
" ci.relname AS PK_NAME, \n" +
" -- information_schema._pg_expandarray(i.indkey) AS KEYS, \n" +
" a.attnum AS A_ATTNUM,\n" +
" i.indkey AS RAW \n" +
" FROM pg_catalog.pg_class ct\n" +
" JOIN pg_catalog.pg_attribute a ON (ct.oid = a.attrelid) \n" +
" JOIN pg_catalog.pg_namespace n ON (ct.relnamespace = n.oid) \n" +
" JOIN pg_catalog.pg_index i ON ( a.attrelid = i.indrelid) \n" +
" JOIN pg_catalog.pg_class ci ON (ci.oid = i.indexrelid) \n" +
" WHERE \n" +
" true \n" +
" AND ct.relname = E'po_items' \n" +
" AND i.indisprimary \n" +
" ) result \n" +
"--WHERE A_ATTNUM = (result.KEYS).x \n" +
"ORDER BY result.TABLE_NAME, result.pk_name, result.KEY_SEQ;";
assertQuery(
"TABLE_CAT\tTABLE_SCHEM\tTABLE_NAME\tCOLUMN_NAME\tKEY_SEQ\tPK_NAME\tA_ATTNUM\tRAW\n",
query,
"create table x(a int)",
null,
true,
false
);
}
}
\ No newline at end of file
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
package io.questdb.griffin.engine.functions.catalogue;
import io.questdb.griffin.AbstractGriffinTest;
import org.junit.Test;
public class IndexCatalogueFunctionFactoryTest extends AbstractGriffinTest {
@Test
public void testPgIndexFunc() throws Exception {
assertQuery(
"indkey\tindrelid\tindexrelid\tindisprimary\n",
"pg_catalog.pg_index;",
"create table x(a int)",
null,
false,
false,
true
);
}
}
\ No newline at end of file
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
package io.questdb.griffin.engine.functions.catalogue;
import io.questdb.griffin.AbstractGriffinTest;
import org.junit.Test;
public class InformationSchemaFunctionFactoryTest extends AbstractGriffinTest {
@Test
public void testInformationSchemaPivotFunc() throws Exception {
assertQuery(
"x\tn\n",
"information_schema._pg_expandarray(5);",
"create table x(a int)",
null,
false,
false,
true
);
}
}
\ No newline at end of file
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
package io.questdb.std;
import org.junit.Assert;
import org.junit.Test;
import java.util.HashSet;
public class LowerCaseCharSequenceHashSetTest {
@Test
public void testSaturation() {
final int N = 10_000;
final Rnd rnd = new Rnd();
final LowerCaseCharSequenceHashSet lowerCaseSet = new LowerCaseCharSequenceHashSet();
final HashSet<String> referenceSet = new HashSet<>();
for (int i = 0; i < N; i++) {
String str = rnd.nextString(4);
int keyIndex = lowerCaseSet.keyIndex(str, 0, str.length());
if (lowerCaseSet.add(str)) {
Assert.assertTrue("at " + i, referenceSet.add(str));
Assert.assertTrue(keyIndex > -1);
} else {
Assert.assertTrue(keyIndex < 0);
}
}
// verify
for (String s : referenceSet) {
Assert.assertTrue(lowerCaseSet.contains(s));
Assert.assertFalse(lowerCaseSet.excludes(s));
Assert.assertFalse(lowerCaseSet.excludes(s, 0, s.length()));
int keyIndex = lowerCaseSet.keyIndex(s);
Assert.assertTrue(keyIndex < 0);
Assert.assertTrue(Chars.equals(s, lowerCaseSet.keyAt(keyIndex)));
keyIndex = lowerCaseSet.keyIndex(s, 0, s.length());
Assert.assertTrue(Chars.equals(s, lowerCaseSet.keyAt(keyIndex)));
}
for (int i = 0, n = lowerCaseSet.keys.length; i < n; i++) {
CharSequence v = lowerCaseSet.keys[i];
if (v != null) {
Assert.assertTrue(referenceSet.contains(v.toString()));
}
}
// remove every forth key
// make sure rnd generates the same keys again
rnd.reset();
for (int i = 0; i < N; i++) {
String s = rnd.nextString(4);
if (i % 4 == 0) {
lowerCaseSet.remove(s);
referenceSet.remove(s);
}
}
// verify
for (String s : referenceSet) {
Assert.assertTrue(s, lowerCaseSet.contains(s));
}
for (int i = 0, n = lowerCaseSet.keys.length; i < n; i++) {
CharSequence v = lowerCaseSet.keys[i];
if (v != null) {
Assert.assertTrue(referenceSet.contains(v.toString()));
}
}
}
}
\ No newline at end of file
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
package io.questdb.std;
import org.junit.Assert;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
public class LowerCaseCharSequenceIntHashMapTest {
@Test
public void testSaturation() {
final int N = 10_000;
final Rnd rnd = new Rnd();
final LowerCaseCharSequenceIntHashMap lowerCaseMap = new LowerCaseCharSequenceIntHashMap();
final HashMap<String, Integer> referenceMap = new HashMap<>();
for (int i = 0; i < N; i++) {
String str = rnd.nextString(4);
int value = rnd.nextInt();
int keyIndex = lowerCaseMap.keyIndex(str, 0, str.length());
if (lowerCaseMap.put(str, value)) {
Assert.assertNull("at " + i, referenceMap.put(str, value));
Assert.assertTrue(keyIndex > -1);
} else {
Assert.assertTrue(keyIndex < 0);
// this should fail to put
lowerCaseMap.putIfAbsent(str, value);
lowerCaseMap.putAt(keyIndex, str, referenceMap.get(str));
}
}
// verify
for (Map.Entry<String, Integer> e : referenceMap.entrySet()) {
Assert.assertTrue(lowerCaseMap.contains(e.getKey()));
Assert.assertFalse(lowerCaseMap.excludes(e.getKey()));
Assert.assertFalse(lowerCaseMap.excludes(e.getKey(), 0, e.getKey().length()));
int keyIndex = lowerCaseMap.keyIndex(e.getKey());
Assert.assertTrue(keyIndex < 0);
Assert.assertEquals(e.getKey(), (int) e.getValue(), lowerCaseMap.valueAt(keyIndex));
keyIndex = lowerCaseMap.keyIndex(e.getKey(), 0, e.getKey().length());
Assert.assertEquals((int) e.getValue(), lowerCaseMap.valueAt(keyIndex));
}
for (int i = 0, n = lowerCaseMap.keys.length; i < n; i++) {
CharSequence v = lowerCaseMap.keys[i];
if (v != null) {
Assert.assertTrue(referenceMap.containsKey(v.toString()));
}
}
// remove every forth key
// make sure rnd generates the same keys again
rnd.reset();
for (int i = 0; i < N; i++) {
String s = rnd.nextString(4);
if (i % 4 == 0) {
lowerCaseMap.remove(s);
referenceMap.remove(s);
}
}
// verify
for (Map.Entry<String, Integer> e : referenceMap.entrySet()) {
Assert.assertTrue(e.getKey(), lowerCaseMap.contains(e.getKey()));
}
for (int i = 0, n = lowerCaseMap.keys.length; i < n; i++) {
CharSequence v = lowerCaseMap.keys[i];
if (v != null) {
Assert.assertTrue(referenceMap.containsKey(v.toString()));
}
}
}
}
\ No newline at end of file
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2020 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
package io.questdb.std;
import org.junit.Assert;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
public class LowerCaseCharSequenceObjHashMapTest {
@Test
public void testSaturation() {
final int N = 10_000;
final Rnd rnd = new Rnd();
final LowerCaseCharSequenceObjHashMap<Integer> lowerCaseMap = new LowerCaseCharSequenceObjHashMap<>();
final HashMap<String, Integer> referenceMap = new HashMap<>();
for (int i = 0; i < N; i++) {
String str = rnd.nextString(4);
int value = rnd.nextInt();
int keyIndex = lowerCaseMap.keyIndex(str, 0, str.length());
if (lowerCaseMap.put(str, value)) {
Assert.assertNull("at " + i, referenceMap.put(str, value));
Assert.assertTrue(keyIndex > -1);
} else {
Assert.assertTrue(keyIndex < 0);
// this should fail to put
lowerCaseMap.putIfAbsent(str, value);
lowerCaseMap.putAt(keyIndex, str, referenceMap.get(str));
}
}
// verify
for (Map.Entry<String, Integer> e : referenceMap.entrySet()) {
Assert.assertTrue(lowerCaseMap.contains(e.getKey()));
Assert.assertFalse(lowerCaseMap.excludes(e.getKey()));
Assert.assertFalse(lowerCaseMap.excludes(e.getKey(), 0, e.getKey().length()));
int keyIndex = lowerCaseMap.keyIndex(e.getKey());
Assert.assertTrue(keyIndex < 0);
Assert.assertEquals(e.getKey(), e.getValue(), lowerCaseMap.valueAt(keyIndex));
keyIndex = lowerCaseMap.keyIndex(e.getKey(), 0, e.getKey().length());
Assert.assertEquals(e.getValue(), lowerCaseMap.valueAt(keyIndex));
}
for (int i = 0, n = lowerCaseMap.keys.length; i < n; i++) {
CharSequence v = lowerCaseMap.keys[i];
if (v != null) {
Assert.assertTrue(referenceMap.containsKey(v.toString()));
}
}
// remove every forth key
// make sure rnd generates the same keys again
rnd.reset();
for (int i = 0; i < N; i++) {
String s = rnd.nextString(4);
if (i % 4 == 0) {
lowerCaseMap.remove(s);
referenceMap.remove(s);
}
}
// verify
for (Map.Entry<String, Integer> e : referenceMap.entrySet()) {
Assert.assertTrue(e.getKey(), lowerCaseMap.contains(e.getKey()));
}
for (int i = 0, n = lowerCaseMap.keys.length; i < n; i++) {
CharSequence v = lowerCaseMap.keys[i];
if (v != null) {
Assert.assertTrue(referenceMap.containsKey(v.toString()));
}
}
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册