未验证 提交 a9e36982 编写于 作者: M marregui 提交者: GitHub

feat(sql): 'is null' is an alias for '= null' and 'is not null' for '!= null' (#1967)

上级 ff6359e0
......@@ -55,6 +55,8 @@ import static io.questdb.std.datetime.millitime.DateFormatUtils.PG_DATE_Z_FORMAT
*/
public class PGConnectionContext implements IOContext, Mutable, WriterSource {
private final static Log LOG = LogFactory.getLog(PGConnectionContext.class);
public static final String TAG_SET = "SET";
public static final String TAG_BEGIN = "BEGIN";
public static final String TAG_COMMIT = "COMMIT";
......@@ -63,10 +65,11 @@ public class PGConnectionContext implements IOContext, Mutable, WriterSource {
public static final String TAG_OK = "OK";
public static final String TAG_COPY = "COPY";
public static final String TAG_INSERT = "INSERT";
public static final char STATUS_IN_TRANSACTION = 'T';
public static final char STATUS_IN_ERROR = 'E';
public static final char STATUS_IDLE = 'I';
private final static Log LOG = LogFactory.getLog(PGConnectionContext.class);
private static final int INT_BYTES_X = Numbers.bswap(Integer.BYTES);
private static final int INT_NULL_X = Numbers.bswap(-1);
......@@ -1686,12 +1689,21 @@ public class PGConnectionContext implements IOContext, Mutable, WriterSource {
validateParameterCounts(parameterFormatCount, parameterValueCount, parsePhaseBindVariableCount);
lo += Short.BYTES;
if (parameterValueCount > 0) {
if (this.parsePhaseBindVariableCount == parameterValueCount) {
lo = bindValuesUsingSetters(lo, msgLimit, parameterValueCount);
} else {
lo = bindValuesAsStrings(lo, msgLimit, parameterValueCount);
try {
if (parameterValueCount > 0) {
if (this.parsePhaseBindVariableCount == parameterValueCount) {
lo = bindValuesUsingSetters(lo, msgLimit, parameterValueCount);
} else {
lo = bindValuesAsStrings(lo, msgLimit, parameterValueCount);
}
}
} catch (SqlException e) {
if (typesAndSelect != null) {
Misc.free(typesAndSelect);
typesAndSelect = null;
}
throw e;
}
if (typesAndSelect != null) {
......@@ -2019,7 +2031,7 @@ public class PGConnectionContext implements IOContext, Mutable, WriterSource {
//query text
lo = hi + 1;
hi = getStringLength(lo, msgLimit, "bad query text length");
//TODO: parsePhaseBindVariableCount have to be checked before parseQueryText and fed into it to serve as type hints !
parseQueryText(lo, hi, compiler);
//parameter type count
......
......@@ -35,8 +35,8 @@ class ExpressionParser {
private static final IntHashSet nonLiteralBranches = new IntHashSet();
private static final int BRANCH_NONE = 0;
private static final int BRANCH_COMMA = 1;
private static final int BRANCH_LEFT_BRACE = 2;
private static final int BRANCH_RIGHT_BRACE = 3;
private static final int BRANCH_LEFT_PARENTHESIS = 2;
private static final int BRANCH_RIGHT_PARENTHESIS = 3;
private static final int BRANCH_CONSTANT = 4;
private static final int BRANCH_OPERATOR = 5;
private static final int BRANCH_LITERAL = 6;
......@@ -206,14 +206,14 @@ class ExpressionParser {
throw SqlException.$(position, "too many dots");
}
if (prevBranch == BRANCH_RIGHT_BRACE) {
if (prevBranch == BRANCH_RIGHT_PARENTHESIS) {
thisBranch = BRANCH_DOT_DEREFERENCE;
} else {
thisBranch = BRANCH_DOT;
}
break;
case ',':
if (prevBranch == BRANCH_COMMA || prevBranch == BRANCH_LEFT_BRACE) {
if (prevBranch == BRANCH_COMMA || prevBranch == BRANCH_LEFT_PARENTHESIS) {
throw missingArgs(position);
}
thisBranch = BRANCH_COMMA;
......@@ -399,11 +399,11 @@ class ExpressionParser {
break;
case '(':
if (prevBranch == BRANCH_RIGHT_BRACE) {
if (prevBranch == BRANCH_RIGHT_PARENTHESIS) {
throw SqlException.$(position, "not a method call");
}
thisBranch = BRANCH_LEFT_BRACE;
thisBranch = BRANCH_LEFT_PARENTHESIS;
// If the token is a left parenthesis, then push it onto the stack.
paramCountStack.push(paramCount);
paramCount = 0;
......@@ -435,8 +435,8 @@ class ExpressionParser {
break OUT;
}
thisBranch = BRANCH_RIGHT_BRACE;
int localParamCount = (prevBranch == BRANCH_LEFT_BRACE ? 0 : paramCount + 1);
thisBranch = BRANCH_RIGHT_PARENTHESIS;
int localParamCount = (prevBranch == BRANCH_LEFT_PARENTHESIS ? 0 : paramCount + 1);
final boolean thisWasCast;
if (castBraceCountStack.size() > 0 && castBraceCountStack.peek() == braceCount) {
......@@ -757,6 +757,40 @@ class ExpressionParser {
}
processDefaultBranch = true;
break;
case 'i':
case 'I':
if (SqlKeywords.isIsKeyword(tok)) {
// replace:
// <literal or constant> IS NULL -> <literal or constant> = NULL
// <literal or constant> IS NOT NULL -> <literal or constant> != NULL
if (prevBranch == BRANCH_LITERAL || prevBranch == BRANCH_CONSTANT || prevBranch == BRANCH_RIGHT_PARENTHESIS) {
final CharSequence isTok = GenericLexer.immutableOf(tok);
tok = SqlUtil.fetchNext(lexer);
if (tok == null) {
throw SqlException.$(position, "IS must be followed by [NOT] NULL");
}
if (SqlKeywords.isNotKeyword(tok)) {
final int notTokPosition = lexer.lastTokenPosition();
final CharSequence notTok = GenericLexer.immutableOf(tok);
tok = SqlUtil.fetchNext(lexer);
if (tok != null && SqlKeywords.isNullKeyword(tok)) {
lexer.backTo(notTokPosition + 3, notTok);
tok = "!=";
} else {
throw SqlException.$(position, "IS NOT must be followed by NULL");
}
} else if (SqlKeywords.isNullKeyword(tok)) {
lexer.backTo(position + 2, isTok);
tok = "=";
} else {
throw SqlException.$(position, "IS must be followed by NULL");
}
} else {
throw SqlException.$(position, "IS [NOT] not allowed here");
}
}
processDefaultBranch = true;
break;
case '*':
// special case for tab.*
if (prevBranch == BRANCH_DOT) {
......@@ -808,7 +842,7 @@ class ExpressionParser {
if (thisChar == '-' || thisChar == '~') {
switch (prevBranch) {
case BRANCH_OPERATOR:
case BRANCH_LEFT_BRACE:
case BRANCH_LEFT_PARENTHESIS:
case BRANCH_COMMA:
case BRANCH_NONE:
case BRANCH_CASE_CONTROL:
......@@ -1024,7 +1058,7 @@ class ExpressionParser {
tok = SqlUtil.fetchNext(lexer);
// Next token is string literal, or we are in 'as' part of cast function
boolean isInActiveCastAs = (castBraceCountStack.size() > 0 && (castBraceCountStack.size() == castAsCount));
if (tok != null && (isInActiveCastAs|| tok.charAt(0) == '\'')) {
if (tok != null && (isInActiveCastAs || tok.charAt(0) == '\'')) {
lexer.backTo(zoneTokPosition, zoneTok);
continue;
}
......@@ -1122,7 +1156,7 @@ class ExpressionParser {
}
static {
nonLiteralBranches.add(BRANCH_RIGHT_BRACE);
nonLiteralBranches.add(BRANCH_RIGHT_PARENTHESIS);
nonLiteralBranches.add(BRANCH_CONSTANT);
nonLiteralBranches.add(BRANCH_LITERAL);
nonLiteralBranches.add(BRANCH_LAMBDA);
......
......@@ -662,6 +662,16 @@ public class SqlKeywords {
&& (tok.charAt(i) | 32) == 't';
}
public static boolean isIsKeyword(CharSequence tok) {
if (tok.length() != 2) {
return false;
}
int i = 0;
return (tok.charAt(i++) | 32) == 'i'
&& (tok.charAt(i) | 32) == 's';
}
public static boolean isInto(CharSequence tok) {
if (tok.length() != 4) {
return false;
......
......@@ -59,42 +59,43 @@ public class EqDoubleFunctionFactory implements FunctionFactory {
Function left = args.getQuick(0);
Function right = args.getQuick(1);
if (left.isConstant() && ColumnType.isDouble(left.getType()) && Double.isNaN(left.getDouble(null))) {
switch (ColumnType.tagOf(right.getType())) {
case ColumnType.INT:
return new FuncIntIsNaN(right);
case ColumnType.LONG:
return new FuncLongIsNaN(right);
case ColumnType.DATE:
return new FuncDateIsNaN(right);
case ColumnType.TIMESTAMP:
return new FuncTimestampIsNaN(right);
case ColumnType.FLOAT:
return new FuncFloatIsNaN(right);
default:
// double
return new FuncDoubleIsNaN(right);
}
} else if (right.isConstant() && ColumnType.isDouble(right.getType()) && Double.isNaN(right.getDouble(null))) {
switch (ColumnType.tagOf(left.getType())) {
case ColumnType.INT:
return new FuncIntIsNaN(left);
case ColumnType.LONG:
return new FuncLongIsNaN(left);
case ColumnType.DATE:
return new FuncDateIsNaN(left);
case ColumnType.TIMESTAMP:
return new FuncTimestampIsNaN(left);
case ColumnType.FLOAT:
return new FuncFloatIsNaN(left);
default:
// double
return new FuncDoubleIsNaN(left);
}
int leftType = left.getType();
int rightType = right.getType();
if (isNullConstant(left, leftType)) {
return dispatchUnaryFunc(right, rightType);
}
if (isNullConstant(right, rightType)) {
return dispatchUnaryFunc(left, leftType);
}
return new Func(args.getQuick(0), args.getQuick(1));
}
private static boolean isNullConstant(Function operand, int operandType) {
return operand.isConstant() &&
(ColumnType.isDouble(operandType) && Double.isNaN(operand.getDouble(null))
||
operandType == ColumnType.NULL);
}
private static Function dispatchUnaryFunc(Function operand, int operandType) {
switch (ColumnType.tagOf(operandType)) {
case ColumnType.INT:
return new FuncIntIsNaN(operand);
case ColumnType.LONG:
return new FuncLongIsNaN(operand);
case ColumnType.DATE:
return new FuncDateIsNaN(operand);
case ColumnType.TIMESTAMP:
return new FuncTimestampIsNaN(operand);
case ColumnType.FLOAT:
return new FuncFloatIsNaN(operand);
default:
// double
return new FuncDoubleIsNaN(operand);
}
}
protected static class Func extends NegatableBooleanFunction implements BinaryFunction {
protected final Function left;
protected final Function right;
......
......@@ -1319,6 +1319,26 @@ public class PGJobContextTest extends BasePGTest {
});
}
@Test
public void testBindVariableIsNullBinaryTransfer() throws Exception {
testBindVariableIsNull(true);
}
@Test
public void testBindVariableIsNullStringTransfer() throws Exception {
testBindVariableIsNull(false);
}
@Test
public void testBindVariableIsNotNullBinaryTransfer() throws Exception {
testBindVariableIsNotNull(true);
}
@Test
public void testBindVariableIsNotNullStringTransfer() throws Exception {
testBindVariableIsNotNull(false);
}
@Test
public void testBindVariableInFilterBinaryTransfer() throws Exception {
testBindVariableInFilter(true);
......@@ -6251,6 +6271,325 @@ create table tab as (
});
}
private void testBindVariableIsNull(boolean binary) throws Exception {
assertMemoryLeak(() -> {
try (
final PGWireServer ignored = createPGServer(1);
final Connection connection = getConnection(false, binary)
) {
connection.setAutoCommit(false);
connection.prepareStatement("create table tab1 (value int, ts timestamp) timestamp(ts)").execute();
connection.prepareStatement("insert into tab1 (value, ts) values (100, 0)").execute();
connection.prepareStatement("insert into tab1 (value, ts) values (null, 1)").execute();
connection.commit();
connection.setAutoCommit(true);
sink.clear();
try (PreparedStatement ps = connection.prepareStatement("tab1 where null is null")) {
try (ResultSet rs = ps.executeQuery()) {
// all rows, null = null is always true
assertResultSet(
"value[INTEGER],ts[TIMESTAMP]\n" +
"100,1970-01-01 00:00:00.0\n" +
"null,1970-01-01 00:00:00.000001\n",
sink,
rs
);
}
}
sink.clear();
try (PreparedStatement ps = connection.prepareStatement("tab1 where (? | null) is null")) {
ps.setLong(1, 1066);
try (ResultSet rs = ps.executeQuery()) {
assertResultSet(
"value[INTEGER],ts[TIMESTAMP]\n" +
"100,1970-01-01 00:00:00.0\n" +
"null,1970-01-01 00:00:00.000001\n",
sink,
rs
);
}
}
sink.clear();
try (PreparedStatement ps = connection.prepareStatement("tab1 where ? is null")) {
// 'is' is an alias for '=', the matching type for this operator, with null
// on the right, is DOUBLE (EqDoubleFunctionFactory)
ps.setDouble(1, Double.NaN);
try (ResultSet rs = ps.executeQuery()) {
assertResultSet(
"value[INTEGER],ts[TIMESTAMP]\n" +
"100,1970-01-01 00:00:00.0\n" +
"null,1970-01-01 00:00:00.000001\n",
sink,
rs
);
}
}
sink.clear();
try (PreparedStatement ps = connection.prepareStatement("tab1 where ? is null")) {
// INTEGER fits in a DOUBLE, however it is interpreted differently depending on
// transfer type (binary, string)
ps.setInt(1, Numbers.INT_NaN);
try (ResultSet rs = ps.executeQuery()) {
if (binary) {
// in binary protocol DOUBLE.null == INT.null
assertResultSet(
"value[INTEGER],ts[TIMESTAMP]\n" +
"100,1970-01-01 00:00:00.0\n" +
"null,1970-01-01 00:00:00.000001\n",
sink,
rs
);
} else {
// in string protocol DOUBLE.null != INT.null
assertResultSet(
"value[INTEGER],ts[TIMESTAMP]\n",
sink,
rs
);
}
}
}
sink.clear();
try (PreparedStatement ps = connection.prepareStatement("tab1 where ? is null")) {
// 'is' is an alias for '=', the matching type for this operator
// (with null on the right) is DOUBLE, and thus INT is a valid
// value type
ps.setInt(1, 21);
try (ResultSet rs = ps.executeQuery()) {
assertResultSet(
"value[INTEGER],ts[TIMESTAMP]\n",
sink,
rs
);
}
}
try (PreparedStatement ps = connection.prepareStatement("tab1 where ? is null")) {
ps.setString(1, "");
try (ResultSet ignore1 = ps.executeQuery()) {
Assert.fail();
} catch (PSQLException e) {
TestUtils.assertContains(e.getMessage(), "could not parse [value='', as=DOUBLE, index=0]");
}
}
try (PreparedStatement ps = connection.prepareStatement("tab1 where ? is null")) {
ps.setString(1, "cha-cha-cha");
try (ResultSet ignore1 = ps.executeQuery()) {
Assert.fail();
} catch (PSQLException e) {
TestUtils.assertContains(e.getMessage(), "could not parse [value='cha-cha-cha', as=DOUBLE, index=0]");
}
}
try (PreparedStatement ps = connection.prepareStatement("tab1 where value is ?")) {
ps.setString(1, "NULL");
try (ResultSet ignore1 = ps.executeQuery()) {
Assert.fail();
} catch (PSQLException e) {
TestUtils.assertContains(e.getMessage(), "IS must be followed by NULL");
}
}
try (PreparedStatement ps = connection.prepareStatement("tab1 where null is ?")) {
ps.setDouble(1, Double.NaN);
try (ResultSet ignore1 = ps.executeQuery()) {
Assert.fail();
} catch (PSQLException e) {
TestUtils.assertContains(e.getMessage(), "IS must be followed by NULL");
}
}
try (PreparedStatement ps = connection.prepareStatement("tab1 where null is ?")) {
ps.setNull(1, Types.NULL);
try (ResultSet ignored1 = ps.executeQuery()) {
Assert.fail();
} catch (PSQLException e) {
TestUtils.assertContains(e.getMessage(), "IS must be followed by NULL");
}
}
try (PreparedStatement ps = connection.prepareStatement("tab1 where value is ?")) {
ps.setString(1, "NULL");
try (ResultSet ignored1 = ps.executeQuery()) {
Assert.fail();
} catch (PSQLException e) {
TestUtils.assertContains(e.getMessage(), "IS must be followed by NULL");
}
}
}
});
}
private void testBindVariableIsNotNull(boolean binary) throws Exception {
assertMemoryLeak(() -> {
try (
final PGWireServer ignored = createPGServer(1);
final Connection connection = getConnection(false, binary)
) {
connection.setAutoCommit(false);
connection.prepareStatement("create table tab1 (value int, ts timestamp) timestamp(ts)").execute();
connection.prepareStatement("insert into tab1 (value, ts) values (100, 0)").execute();
connection.prepareStatement("insert into tab1 (value, ts) values (null, 1)").execute();
connection.commit();
connection.setAutoCommit(true);
sink.clear();
try (PreparedStatement ps = connection.prepareStatement("tab1 where 3 is not null")) {
try (ResultSet rs = ps.executeQuery()) {
assertResultSet(
"value[INTEGER],ts[TIMESTAMP]\n" +
"100,1970-01-01 00:00:00.0\n" +
"null,1970-01-01 00:00:00.000001\n",
sink,
rs
);
}
}
sink.clear();
try (PreparedStatement ps = connection.prepareStatement("tab1 where coalesce(?, 12.37) is not null")) {
// 'is not' is an alias for '!=', the matching type for this operator
// (with null on the right) is DOUBLE
ps.setDouble(1, 3.14);
try (ResultSet rs = ps.executeQuery()) {
assertResultSet(
"value[INTEGER],ts[TIMESTAMP]\n" +
"100,1970-01-01 00:00:00.0\n" +
"null,1970-01-01 00:00:00.000001\n",
sink,
rs
);
}
}
sink.clear();
try (PreparedStatement ps = connection.prepareStatement("tab1 where ? is not null")) {
// 'is not' is an alias for '!=', the matching type for this operator
// (with null on the right) is DOUBLE
ps.setDouble(1, 3.14);
try (ResultSet rs = ps.executeQuery()) {
assertResultSet(
"value[INTEGER],ts[TIMESTAMP]\n" +
"100,1970-01-01 00:00:00.0\n" +
"null,1970-01-01 00:00:00.000001\n",
sink,
rs
);
}
}
sink.clear();
try (PreparedStatement ps = connection.prepareStatement("tab1 where ? is not null")) {
ps.setDouble(1, Double.NaN);
try (ResultSet rs = ps.executeQuery()) {
assertResultSet(
"value[INTEGER],ts[TIMESTAMP]\n",
sink,
rs
);
}
}
sink.clear();
try (PreparedStatement ps = connection.prepareStatement("tab1 where ? is not null")) {
ps.setInt(1, Numbers.INT_NaN);
try (ResultSet rs = ps.executeQuery()) {
if (binary) {
assertResultSet(
"value[INTEGER],ts[TIMESTAMP]\n",
sink,
rs
);
} else {
assertResultSet(
"value[INTEGER],ts[TIMESTAMP]\n" +
"100,1970-01-01 00:00:00.0\n" +
"null,1970-01-01 00:00:00.000001\n",
sink,
rs
);
}
}
}
sink.clear();
try (PreparedStatement ps = connection.prepareStatement("tab1 where ? is not null")) {
ps.setInt(1, 12);
try (ResultSet rs = ps.executeQuery()) {
assertResultSet(
"value[INTEGER],ts[TIMESTAMP]\n" +
"100,1970-01-01 00:00:00.0\n" +
"null,1970-01-01 00:00:00.000001\n",
sink,
rs
);
}
}
try (PreparedStatement ps = connection.prepareStatement("tab1 where ? is not null")) {
ps.setString(1, "");
try (ResultSet ignore1 = ps.executeQuery()) {
Assert.fail();
} catch (PSQLException e) {
TestUtils.assertContains(e.getMessage(), "could not parse [value='', as=DOUBLE, index=0]");
}
}
try (PreparedStatement ps = connection.prepareStatement("tab1 where ? is not null")) {
ps.setString(1, "cah-cha-cha");
try (ResultSet ignore1 = ps.executeQuery()) {
Assert.fail();
} catch (PSQLException e) {
TestUtils.assertContains(e.getMessage(), "could not parse [value='cah-cha-cha', as=DOUBLE, index=0]");
}
}
try (PreparedStatement ps = connection.prepareStatement("tab1 where null is not ?")) {
ps.setString(1, "NULL");
try (ResultSet ignore1 = ps.executeQuery()) {
Assert.fail();
} catch (PSQLException e) {
TestUtils.assertContains(e.getMessage(), "IS NOT must be followed by NULL");
}
}
try (PreparedStatement ps = connection.prepareStatement("tab1 where null is not ?")) {
ps.setDouble(1, Double.NaN);
try (ResultSet ignore1 = ps.executeQuery()) {
Assert.fail();
} catch (PSQLException e) {
TestUtils.assertContains(e.getMessage(), "IS NOT must be followed by NULL");
}
}
try (PreparedStatement ps = connection.prepareStatement("tab1 where null is not ?")) {
ps.setNull(1, Types.NULL);
try (ResultSet ignored1 = ps.executeQuery()) {
Assert.fail();
} catch (PSQLException e) {
TestUtils.assertContains(e.getMessage(), "IS NOT must be followed by NULL");
}
}
try (PreparedStatement ps = connection.prepareStatement("tab1 where value is not ?")) {
ps.setString(1, "NULL");
try (ResultSet ignored1 = ps.executeQuery()) {
Assert.fail();
} catch (PSQLException e) {
TestUtils.assertContains(e.getMessage(), "IS NOT must be followed by NULL");
}
}
}
});
}
private void testBindVariableInFilter(boolean binary) throws Exception {
assertMemoryLeak(() -> {
try (
......
......@@ -215,6 +215,36 @@ public class ExpressionParserTest extends AbstractCairoTest {
x("ax'a''b'innotand", "a and not x in ('a','b')");
}
@Test
public void testIsNull() throws SqlException {
x("aNULL=", "a IS NULL");
x("tab.aNULL=", "tab.a IS NULL");
x("3NULL=", "3 IS NULL");
x("nullNULL=", "null IS NULL");
x("NULLNULL=", "NULL IS NULL");
x("'null'NULL=", "'null' IS NULL");
x("''null|NULL=", "('' | null) IS NULL");
assertFail("column is 3", 7, "IS must be followed by NULL");
assertFail(". is great", 2, "IS [NOT] not allowed here");
assertFail("column is $1", 7, "IS must be followed by NULL");
assertFail("column is", 7, "IS must be followed by [NOT] NULL");
}
@Test
public void testIsNotNull() throws SqlException {
x("aNULL!=", "a IS NOT NULL");
x("tab.aNULL!=", "tab.a IS NOT NULL");
x("3NULL!=", "3 IS NOT NULL");
x("nullNULL!=", "null IS NOT NULL");
x("NULLNULL!=", "NULL IS NOT NULL");
x("'null'NULL!=", "'null' IS NOT NULL");
x("''null||NULL!=", "('' || null) IS NOT NULL");
assertFail("column is not 3", 7, "IS NOT must be followed by NULL");
assertFail(". is not great", 2, "IS [NOT] not allowed here");
assertFail("column is not $1", 7, "IS NOT must be followed by NULL");
assertFail("column is not", 7, "IS NOT must be followed by NULL");
}
@Test
public void testBug1() throws SqlException {
x("2022.yyyy", "'2022'.'yyyy'");
......
......@@ -49,7 +49,11 @@ public class InsertNullTest extends AbstractGriffinTest {
{"string", ""},
{"symbol", ""},
{"long256", ""},
{"binary", ""}
{"binary", ""},
{"geohash(5b)", ""},
{"geohash(15b)", ""},
{"geohash(31b)", ""},
{"geohash(60b)", ""}
};
@Test
......@@ -171,6 +175,56 @@ public class InsertNullTest extends AbstractGriffinTest {
});
}
@Test
public void testInsertNullThenFilterIsNull() throws Exception {
for (int i = 0; i < TYPES.length; i++) {
if (i > 0) {
setUp();
}
try {
final String[] type = TYPES[i];
assertQuery(
"value\n",
"x where value is null",
String.format("create table x (value %s)", type[0]),
null,
String.format("insert into x select null from long_sequence(%d)", NULL_INSERTS),
expectedNullInserts("value\n", type[1], NULL_INSERTS),
true,
true,
type[0].equals("long256")
);
} finally {
tearDown();
}
}
}
@Test
public void testInsertNullThenFilterIsNotNull() throws Exception {
for (int i = 0; i < TYPES.length; i++) {
if (i > 0) {
setUp();
}
try {
final String[] type = TYPES[i];
assertQuery(
"value\n",
"x where value is not null",
String.format("create table x (value %s)", type[0]),
null,
String.format("insert into x select null from long_sequence(%d)", NULL_INSERTS),
"value\n",
!type[0].equals("long256"),
true,
false
);
} finally {
tearDown();
}
}
}
static String expectedNullInserts(String header, String nullValue, int count) {
StringSink sb = Misc.getThreadLocalBuilder();
sb.put(header);
......
......@@ -2990,6 +2990,124 @@ public class SqlParserTest extends AbstractSqlParserTest {
modelOf("orders").col("customerId", ColumnType.INT));
}
@Test
public void testInvalidIsNull() throws Exception {
assertSyntaxError(
"a where x is 12",
10,
"IS must be followed by NULL",
modelOf("a").col("x", ColumnType.INT)
);
assertSyntaxError(
"a where a.x is 12",
12,
"IS must be followed by NULL",
modelOf("a").col("x", ColumnType.INT)
);
assertSyntaxError(
"a where x is",
10,
"IS must be followed by [NOT] NULL",
modelOf("a").col("x", ColumnType.INT)
);
}
@Test
public void testInvalidIsNotNull() throws Exception {
assertSyntaxError(
"a where x is not 12",
10,
"IS NOT must be followed by NULL",
modelOf("a").col("x", ColumnType.INT)
);
assertSyntaxError(
"a where a.x is not 12",
12,
"IS NOT must be followed by NULL",
modelOf("a").col("x", ColumnType.INT)
);
assertSyntaxError(
"a where x is not",
10,
"IS NOT must be followed by NULL",
modelOf("a").col("x", ColumnType.INT)
);
}
@Test
public void testExpressionIsNull() throws Exception {
assertQuery(
"select-choose tab1.ts ts, tab1.x x, tab2.y y from (select [ts, x] from tab1 timestamp (ts) join select [y] from tab2 on tab2.y = tab1.x where coalesce(x,42) = null)",
"tab1 join tab2 on tab1.x = tab2.y where coalesce(tab1.x, 42) is null",
modelOf("tab1").timestamp("ts").col("x", ColumnType.INT),
modelOf("tab2").col("y", ColumnType.INT)
);
}
@Test
public void testExpressionIsNotNull() throws Exception {
assertQuery(
"select-choose tab1.ts ts, tab1.x x, tab2.y y from (select [ts, x] from tab1 timestamp (ts) join select [y] from tab2 on tab2.y = tab1.x where coalesce(x,42) != null)",
"tab1 join tab2 on tab1.x = tab2.y where coalesce(tab1.x, 42) is not null",
modelOf("tab1").timestamp("ts").col("x", ColumnType.INT),
modelOf("tab2").col("y", ColumnType.INT)
);
}
@Test
public void testLiteralIsNull() throws Exception {
assertQuery(
"select-choose tab1.ts ts, tab1.x x, tab2.y y from (select [ts, x] from tab1 timestamp (ts) join select [y] from tab2 on tab2.y = tab1.x where x = null)",
"tab1 join tab2 on tab1.x = tab2.y where tab1.x is null",
modelOf("tab1").timestamp("ts").col("x", ColumnType.INT),
modelOf("tab2").col("y", ColumnType.INT)
);
}
@Test
public void testLiteralIsNotNull() throws Exception {
assertQuery(
"select-choose tab1.ts ts, tab1.x x, tab2.y y from (select [ts, x] from tab1 timestamp (ts) join select [y] from tab2 on tab2.y = tab1.x where x != null)",
"tab1 join tab2 on tab1.x = tab2.y where tab1.x is not null",
modelOf("tab1").timestamp("ts").col("x", ColumnType.INT),
modelOf("tab2").col("y", ColumnType.INT)
);
}
@Test
public void testConstantIsNull() throws Exception {
assertQuery(
"select-choose tab1.ts ts, tab1.x x, tab2.y y from (select [ts, x] from tab1 timestamp (ts) join select [y] from tab2 on tab2.y = tab1.x const-where null = null)",
"tab1 join tab2 on tab1.x = tab2.y where null is null",
modelOf("tab1").timestamp("ts").col("x", ColumnType.INT),
modelOf("tab2").col("y", ColumnType.INT)
);
assertQuery(
"select-choose tab1.ts ts, tab1.x x, tab2.y y from (select [ts, x] from tab1 timestamp (ts) join select [y] from tab2 on tab2.y = tab1.x const-where 'null' = null)",
"tab1 join tab2 on tab1.x = tab2.y where 'null' is null",
modelOf("tab1").timestamp("ts").col("x", ColumnType.INT),
modelOf("tab2").col("y", ColumnType.INT)
);
}
@Test
public void testConstantIsNotNull() throws Exception {
assertQuery(
"select-choose tab1.ts ts, tab1.x x, tab2.y y from (select [ts, x] from tab1 timestamp (ts) join select [y] from tab2 on tab2.y = tab1.x const-where null != null)",
"tab1 join tab2 on tab1.x = tab2.y where null is not null",
modelOf("tab1").timestamp("ts").col("x", ColumnType.INT),
modelOf("tab2").col("y", ColumnType.INT)
);
assertQuery(
"select-choose tab1.ts ts, tab1.x x, tab2.y y from (select [ts, x] from tab1 timestamp (ts) join select [y] from tab2 on tab2.y = tab1.x const-where 'null' != null)",
"tab1 join tab2 on tab1.x = tab2.y where 'null' is not null",
modelOf("tab1").timestamp("ts").col("x", ColumnType.INT),
modelOf("tab2").col("y", ColumnType.INT)
);
}
@Test
public void testJoin1() throws Exception {
assertQuery(
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册