未验证 提交 29f2f452 编写于 作者: A Alex Pelagenko 提交者: GitHub

fix(sql): make SQL functions case insensitive (#1219)

上级 2d8bc4cf
......@@ -118,7 +118,7 @@ public final class ColumnType {
public static int overloadDistance(int from, int to) {
// Functions cannot accept UNDEFINED type (signature is not supported)
// this check is just in case
assert to >= 0;
assert to >= 0 : "Undefined not supported in overloads";
return overloadPriorityMatrix[OVERLOAD_MATRIX_SIZE * (from + 1) + to];
}
......
......@@ -281,6 +281,9 @@ public class PGConnectionContext implements IOContext, Mutable, WriterSource {
namedPortalMap.clear();
bindVariableService.clear();
bindVariableTypes.clear();
resumeProcessor = null;
completed = true;
clearCursorAndFactory();
}
public void clearWriters() {
......@@ -2092,19 +2095,7 @@ public class PGConnectionContext implements IOContext, Mutable, WriterSource {
completed = maxRows <= 0 || rowCount < maxRows;
if (completed) {
resumeProcessor = null;
currentCursor = Misc.free(currentCursor);
// do not free factory, it will be cached
currentFactory = null;
// we we resumed the cursor send the typeAndSelect will be null
// we do not want to overwrite cache entries and potentially
// leak memory
if (typesAndSelect != null) {
typesAndSelectCache.put(queryText, typesAndSelect);
// clear selectAndTypes so that context doesn't accidentally
// free the factory when context finishes abnormally
this.typesAndSelect = null;
}
clearCursorAndFactory();
// at this point buffer can contain unsent data
// and it may not have enough space for the command
if (sendBufferLimit - sendBufferPtr < PROTOCOL_TAIL_COMMAND_LENGTH) {
......@@ -2117,6 +2108,22 @@ public class PGConnectionContext implements IOContext, Mutable, WriterSource {
}
}
private void clearCursorAndFactory() {
resumeProcessor = null;
currentCursor = Misc.free(currentCursor);
// do not free factory, it will be cached
currentFactory = null;
// we we resumed the cursor send the typeAndSelect will be null
// we do not want to overwrite cache entries and potentially
// leak memory
if (typesAndSelect != null) {
typesAndSelectCache.put(queryText, typesAndSelect);
// clear selectAndTypes so that context doesn't accidentally
// free the factory when context finishes abnormally
this.typesAndSelect = null;
}
}
private void sendReadyForNewQuery() throws PeerDisconnectedException, PeerIsSlowToReadException {
prepareReadyForQuery();
sendAndReset();
......
......@@ -36,10 +36,10 @@ public class FunctionFactoryCache {
static final IntHashSet invalidFunctionNameChars = new IntHashSet();
static final CharSequenceHashSet invalidFunctionNames = new CharSequenceHashSet();
private static final Log LOG = LogFactory.getLog(FunctionFactoryCache.class);
private final CharSequenceObjHashMap<ObjList<FunctionFactoryDescriptor>> factories = new CharSequenceObjHashMap<>();
private final CharSequenceHashSet groupByFunctionNames = new CharSequenceHashSet();
private final CharSequenceHashSet cursorFunctionNames = new CharSequenceHashSet();
private final CharSequenceHashSet runtimeConstantFunctionNames = new CharSequenceHashSet();
private final LowerCaseCharSequenceObjHashMap<ObjList<FunctionFactoryDescriptor>> factories = new LowerCaseCharSequenceObjHashMap<>();
private final LowerCaseCharSequenceHashSet groupByFunctionNames = new LowerCaseCharSequenceHashSet();
private final LowerCaseCharSequenceHashSet cursorFunctionNames = new LowerCaseCharSequenceHashSet();
private final LowerCaseCharSequenceHashSet runtimeConstantFunctionNames = new LowerCaseCharSequenceHashSet();
public FunctionFactoryCache(CairoConfiguration configuration, Iterable<FunctionFactory> functionFactories) {
boolean enableTestFactories = configuration.enableTestFactories();
......@@ -112,11 +112,11 @@ public class FunctionFactoryCache {
return runtimeConstantFunctionNames.contains(name);
}
private void addFactoryToList(CharSequenceObjHashMap<ObjList<FunctionFactoryDescriptor>> list, FunctionFactory factory) throws SqlException {
private void addFactoryToList(LowerCaseCharSequenceObjHashMap<ObjList<FunctionFactoryDescriptor>> list, FunctionFactory factory) throws SqlException {
addFactoryToList(list, new FunctionFactoryDescriptor(factory));
}
private void addFactoryToList(CharSequenceObjHashMap<ObjList<FunctionFactoryDescriptor>> list, FunctionFactoryDescriptor descriptor) {
private void addFactoryToList(LowerCaseCharSequenceObjHashMap<ObjList<FunctionFactoryDescriptor>> list, FunctionFactoryDescriptor descriptor) {
String name = descriptor.getName();
int index = list.keyIndex(name);
ObjList<FunctionFactoryDescriptor> overload;
......
......@@ -466,6 +466,7 @@ io.questdb.griffin.engine.functions.catalogue.PrefixedNamespaceCatalogueFunction
io.questdb.griffin.engine.functions.catalogue.IsTableVisibleCatalogueFunctionFactory
io.questdb.griffin.engine.functions.catalogue.UserByIdCatalogueFunctionFactory
io.questdb.griffin.engine.functions.catalogue.TypeCatalogueFunctionFactory
io.questdb.griffin.engine.functions.catalogue.VersionFunctionFactory
io.questdb.griffin.engine.functions.catalogue.NamespaceCatalogueFunctionFactory
io.questdb.griffin.engine.functions.catalogue.PrefixedCurrentSchemasFunctionFactory
io.questdb.griffin.engine.functions.catalogue.CursorDereferenceFunctionFactory
......
......@@ -3746,6 +3746,44 @@ nodejs code:
);
}
@Test
public void testRowLimitNotResumed() throws Exception {
assertMemoryLeak(() -> {
try (final PGWireServer ignored = createPGServer(1)) {
try (final Connection connection = getConnection(false
, true)) {
try (CallableStatement st1 = connection.prepareCall("create table y as (" +
"select timestamp_sequence(0, 1000000000) timestamp," +
" rnd_symbol('a','b',null) symbol1 " +
" from long_sequence(10)" +
") timestamp (timestamp)")) {
st1.execute();
}
}
}
try (final PGWireServer ignored = createPGServer(1)) {
for (int i = 0; i < 3; i++) {
try (final Connection connection = getConnection(false, true)) {
try (PreparedStatement select1 = connection.prepareStatement("select version()")) {
ResultSet rs0 = select1.executeQuery();
sink.clear();
assertResultSet("version[VARCHAR]\n" +
"PostgreSQL 12.3, compiled by Visual C++ build 1914, 64-bit\n", sink, rs0);
rs0.close();
}
try (PreparedStatement select2 = connection.prepareStatement("select timestamp from y")) {
select2.setMaxRows(1);
ResultSet rs2 = select2.executeQuery();
rs2.next();
rs2.close();
}
}
}
}
});
}
@Test
public void testStaleQueryCacheOnTableDroppedSimple() throws Exception {
testStaleQueryCacheOnTableDropped(true);
......@@ -3756,13 +3794,13 @@ nodejs code:
testStaleQueryCacheOnTableDropped(false);
}
public void testStaleQueryCacheOnTableDropped(boolean simple) throws Exception {
private void testStaleQueryCacheOnTableDropped(boolean simple) throws Exception {
assertMemoryLeak(() -> {
try (
final PGWireServer ignored = createPGServer(2);
final Connection connection = getConnection(simple, true)
) {
try(CallableStatement st1 = connection.prepareCall("create table y as (" +
try (CallableStatement st1 = connection.prepareCall("create table y as (" +
"select timestamp_sequence(0, 1000000000) timestamp," +
" rnd_symbol('a','b',null) symbol1 " +
" from long_sequence(10)" +
......
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* 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;
import org.junit.Ignore;
import org.junit.Test;
public class DataGripTest extends AbstractGriffinTest {
@Test
@Ignore
public void testStartUpUnknowDBMS() throws SqlException {
assertQuery(
"",
"SELECT NULL AS TABLE_CAT, n.nspname AS TABLE_SCHEM," +
" ct.relname AS TABLE_NAME," +
" NOT i.indisunique AS NON_UNIQUE," +
" NULL AS INDEX_QUALIFIER, ci.relname AS INDEX_NAME," +
" CASE i.indisclustered" +
" WHEN true THEN 1" +
" ELSE CASE am.amname" +
" WHEN 'hash' THEN 2" +
" ELSE 3" +
" END" +
" END AS TYPE," +
" (i.keys).n AS ORDINAL_POSITION," +
" trim(both '\"' from pg_catalog.pg_get_indexdef(ci.oid, (i.keys).n, false)) AS COLUMN_NAME," +
" CASE am.amname" +
" WHEN 'btree' THEN CASE i.indoption[(i.keys).n - 1] & 1" +
" WHEN 1 THEN 'D'" +
" ELSE 'A'" +
" END" +
" ELSE NULL" +
" END AS ASC_OR_DESC," +
" ci.reltuples AS CARDINALITY," +
" ci.relpages AS PAGES," +
" pg_catalog.pg_get_expr(i.indpred, i.indrelid) AS FILTER_CONDITION" +
" FROM pg_catalog.pg_class ct" +
" JOIN pg_catalog.pg_namespace n ON (ct.relnamespace = n.oid)" +
" JOIN (SELECT i.indexrelid, i.indrelid, i.indoption," +
" i.indisunique, i.indisclustered, i.indpred," +
" i.indexprs," +
" information_schema._pg_expandarray(i.indkey) AS keys" +
" FROM pg_catalog.pg_index i) i" +
" ON (ct.oid = i.indrelid)" +
" JOIN pg_catalog.pg_class ci ON (ci.oid = i.indexrelid)" +
" JOIN pg_catalog.pg_am am ON (ci.relam = am.oid)" +
" WHERE true" +
" AND n.nspname = E'\"public\"' AND ct.relname = E'\"xx\"'" +
" ORDER BY NON_UNIQUE, TYPE, INDEX_NAME, ORDINAL_POSITION",
null,
true,
sqlExecutionContext,
false,
false
);
}
@Test
public void testUpperCaseCount() throws Exception {
assertMemoryLeak(() -> {
compiler.compile("create table y as (select x from long_sequence(10))", sqlExecutionContext);
assertSql(
"select COUNT(*) from y",
"count\n" +
"10\n"
);
});
}
@Test
public void testLowerCaseCount() throws Exception {
assertMemoryLeak(() -> {
compiler.compile("create table y as (select x from long_sequence(10))", sqlExecutionContext);
assertSql(
"select COUNT(*) from y",
"count\n" +
"10\n"
);
});
}
}
......@@ -41,10 +41,7 @@ import io.questdb.griffin.engine.functions.date.ToStrTimestampFunctionFactory;
import io.questdb.griffin.engine.functions.eq.EqDoubleFunctionFactory;
import io.questdb.griffin.engine.functions.eq.EqIntFunctionFactory;
import io.questdb.griffin.engine.functions.eq.EqLongFunctionFactory;
import io.questdb.griffin.engine.functions.groupby.CountLong256GroupByFunctionFactory;
import io.questdb.griffin.engine.functions.groupby.MinDateGroupByFunctionFactory;
import io.questdb.griffin.engine.functions.groupby.MinFloatGroupByFunctionFactory;
import io.questdb.griffin.engine.functions.groupby.MinTimestampGroupByFunctionFactory;
import io.questdb.griffin.engine.functions.groupby.*;
import io.questdb.griffin.engine.functions.math.*;
import io.questdb.griffin.engine.functions.str.LengthStrFunctionFactory;
import io.questdb.griffin.engine.functions.str.LengthSymbolFunctionFactory;
......@@ -81,6 +78,7 @@ public class FunctionParserTest extends BaseFunctionFactoryTest {
ColumnType.overloadDistance(ColumnType.INT, ColumnType.UNDEFINED);
Assert.fail();
} catch (AssertionError e) {
TestUtils.assertContains(e.getMessage(), "Undefined not supported in overloads");
}
}
}
......@@ -1110,6 +1108,19 @@ public class FunctionParserTest extends BaseFunctionFactoryTest {
});
}
@Test
public void testCountUpperCase() throws SqlException {
functions.add(new CountGroupByFunctionFactory());
final GenericRecordMetadata metadata = new GenericRecordMetadata();
metadata.add(new TableColumnMetadata("a", ColumnType.INT, null));
FunctionParser functionParser = createFunctionParser();
Function function = parseFunction("COUNT()", metadata, functionParser);
Assert.assertEquals(ColumnType.LONG, function.getType());
Assert.assertEquals(
"io.questdb.griffin.engine.functions.groupby.CountGroupByFunction",
function.getClass().getCanonicalName());
}
@Test
public void testSymbolFunction() throws SqlException {
functions.add(new LengthStrFunctionFactory());
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册