Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ private static Optional<RexNode> replaceWithNullLiteralInCoalesce(CalcitePlanCon
if (context.isInCoalesceFunction()) {
return Optional.of(
context.rexBuilder.makeNullLiteral(
context.rexBuilder.getTypeFactory().createSqlType(SqlTypeName.VARCHAR)));
context.rexBuilder.getTypeFactory().createSqlType(SqlTypeName.NULL)));
}
return Optional.empty();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,11 @@ public SqlReturnTypeInference getReturnTypeInference() {

// Let Calcite determine the least restrictive common type
var commonType = opBinding.getTypeFactory().leastRestrictive(operandTypes);
return commonType != null
? commonType
: opBinding.getTypeFactory().createSqlType(SqlTypeName.VARCHAR);
if (commonType == null || commonType.getSqlTypeName() == SqlTypeName.NULL) {
// Fall back to VARCHAR when all operands are NULL or no common type exists
return opBinding.getTypeFactory().createSqlType(SqlTypeName.VARCHAR);
}
return commonType;
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,48 @@ public void testLeastRestrictiveDelegatesToSuperForPlainTypes() {
assertNotNull(result);
assertEquals(SqlTypeName.INTEGER, result.getSqlTypeName());
}

@Test
public void testLeastRestrictiveNullAndIntegerReturnsInteger() {
RelDataType nullType = TYPE_FACTORY.createSqlType(SqlTypeName.NULL);
RelDataType intType = TYPE_FACTORY.createSqlType(SqlTypeName.INTEGER);

RelDataType result = TYPE_FACTORY.leastRestrictive(List.of(nullType, intType));

assertNotNull(result);
assertEquals(SqlTypeName.INTEGER, result.getSqlTypeName());
}

@Test
public void testLeastRestrictiveIntegerAndNullReturnsInteger() {
RelDataType intType = TYPE_FACTORY.createSqlType(SqlTypeName.INTEGER);
RelDataType nullType = TYPE_FACTORY.createSqlType(SqlTypeName.NULL);

RelDataType result = TYPE_FACTORY.leastRestrictive(List.of(intType, nullType));

assertNotNull(result);
assertEquals(SqlTypeName.INTEGER, result.getSqlTypeName());
}

@Test
public void testLeastRestrictiveNullAndDoubleReturnsDouble() {
RelDataType nullType = TYPE_FACTORY.createSqlType(SqlTypeName.NULL);
RelDataType doubleType = TYPE_FACTORY.createSqlType(SqlTypeName.DOUBLE);

RelDataType result = TYPE_FACTORY.leastRestrictive(List.of(nullType, doubleType));

assertNotNull(result);
assertEquals(SqlTypeName.DOUBLE, result.getSqlTypeName());
}

@Test
public void testLeastRestrictiveNullAndVarcharReturnsVarchar() {
RelDataType nullType = TYPE_FACTORY.createSqlType(SqlTypeName.NULL);
RelDataType varcharType = TYPE_FACTORY.createSqlType(SqlTypeName.VARCHAR);

RelDataType result = TYPE_FACTORY.leastRestrictive(List.of(nullType, varcharType));

assertNotNull(result);
assertEquals(SqlTypeName.VARCHAR, result.getSqlTypeName());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,45 @@ public void testCoalesceTypeCoercionWithMixedTypes() throws IOException {
verifyDataRows(actual, rows(70, "70"), rows(30, "30"));
}

@Test
public void testCoalesceNullLiteralWithIntegerReturnsIntType() throws IOException {
// Regression test for https://github.com/opensearch-project/sql/issues/5175
JSONObject actual =
executeQuery(
String.format(
"source=%s | eval x = coalesce(null, 42) | fields x | head 1",
TEST_INDEX_STATE_COUNTRY_WITH_NULL));

verifySchema(actual, schema("x", "int"));
verifyDataRows(actual, rows(42));
}

@Test
public void testCoalesceIntegerWithNullLiteralReturnsIntType() throws IOException {
// Regression test for https://github.com/opensearch-project/sql/issues/5175
JSONObject actual =
executeQuery(
String.format(
"source=%s | eval x = coalesce(42, null) | fields x | head 1",
TEST_INDEX_STATE_COUNTRY_WITH_NULL));

verifySchema(actual, schema("x", "int"));
verifyDataRows(actual, rows(42));
}

@Test
public void testCoalesceNonExistentFieldWithIntegerReturnsIntType() throws IOException {
// Regression test for https://github.com/opensearch-project/sql/issues/5175
JSONObject actual =
executeQuery(
String.format(
"source=%s | eval x = coalesce(nonexistent_field, 42) | fields x | head 1",
TEST_INDEX_STATE_COUNTRY_WITH_NULL));

verifySchema(actual, schema("x", "int"));
verifyDataRows(actual, rows(42));
}

@Test
public void testCoalesceWithCompatibleNumericAndTemporalTypes() throws IOException {
JSONObject actual =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
setup:
- do:
query.settings:
body:
transient:
plugins.calcite.enabled: true

- do:
indices.create:
index: issue5175
body:
settings:
number_of_shards: 1
number_of_replicas: 0
mappings:
properties:
dummy:
type: keyword

- do:
bulk:
refresh: true
body:
- '{"index": {"_index": "issue5175", "_id": "1"}}'
- '{"dummy": "row"}'

---
teardown:
- do:
indices.delete:
index: issue5175
ignore_unavailable: true
- do:
query.settings:
body:
transient:
plugins.calcite.enabled: false

---
"Issue 5175: COALESCE(null, 42) returns integer type":
- skip:
features:
- headers
- do:
headers:
Content-Type: 'application/json'
ppl:
body:
query: source=issue5175 | eval x = coalesce(null, 42) | fields x | head 1

- match: { total: 1 }
- match: { schema: [ { name: x, type: int } ] }
- match: { datarows: [ [ 42 ] ] }

---
"Issue 5175: COALESCE(42, null) returns integer type":
- skip:
features:
- headers
- do:
headers:
Content-Type: 'application/json'
ppl:
body:
query: source=issue5175 | eval x = coalesce(42, null) | fields x | head 1

- match: { total: 1 }
- match: { schema: [ { name: x, type: int } ] }
- match: { datarows: [ [ 42 ] ] }

---
"Issue 5175: COALESCE(nonexistent_field, 42) returns integer type":
- skip:
features:
- headers
- do:
headers:
Content-Type: 'application/json'
ppl:
body:
query: source=issue5175 | eval x = coalesce(nonexistent_field, 42) | fields x | head 1

- match: { total: 1 }
- match: { schema: [ { name: x, type: int } ] }
- match: { datarows: [ [ 42 ] ] }

---
"Issue 5175: COALESCE(null, 3.14) returns double type":
- skip:
features:
- headers
- do:
headers:
Content-Type: 'application/json'
ppl:
body:
query: source=issue5175 | eval x = coalesce(null, 3.14) | fields x | head 1

- match: { total: 1 }
- match: { schema: [ { name: x, type: double } ] }
- match: { datarows: [ [ 3.14 ] ] }
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@

package org.opensearch.sql.ppl.calcite;

import static org.junit.Assert.assertEquals;

import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.test.CalciteAssert;
import org.junit.Test;

Expand Down Expand Up @@ -138,7 +142,7 @@ public void testCoalesceWithNonExistentField() {
RelNode root = getRelNode(ppl);
String expectedLogical =
"LogicalSort(fetch=[2])\n"
+ " LogicalProject(EMPNO=[$0], result=[COALESCE(null:VARCHAR, $1)])\n"
+ " LogicalProject(EMPNO=[$0], result=[COALESCE(null:NULL, $1)])\n"
+ " LogicalTableScan(table=[[scott, EMP]])\n";
verifyLogical(root, expectedLogical);

Expand All @@ -155,7 +159,7 @@ public void testCoalesceWithMultipleNonExistentFields() {
RelNode root = getRelNode(ppl);
String expectedLogical =
"LogicalSort(fetch=[1])\n"
+ " LogicalProject(EMPNO=[$0], result=[COALESCE(null:VARCHAR, null:VARCHAR, $1,"
+ " LogicalProject(EMPNO=[$0], result=[COALESCE(null:NULL, null:NULL, $1,"
+ " 'fallback':VARCHAR)])\n"
+ " LogicalTableScan(table=[[scott, EMP]])\n";
verifyLogical(root, expectedLogical);
Expand All @@ -175,8 +179,8 @@ public void testCoalesceWithAllNonExistentFields() {
RelNode root = getRelNode(ppl);
String expectedLogical =
"LogicalSort(fetch=[1])\n"
+ " LogicalProject(EMPNO=[$0], result=[COALESCE(null:VARCHAR, null:VARCHAR,"
+ " null:VARCHAR)])\n"
+ " LogicalProject(EMPNO=[$0], result=[COALESCE(null:NULL, null:NULL,"
+ " null:NULL)])\n"
+ " LogicalTableScan(table=[[scott, EMP]])\n";
verifyLogical(root, expectedLogical);

Expand Down Expand Up @@ -235,4 +239,30 @@ public void testCoalesceTypeInferenceWithNonNullableOperands() {
+ "LIMIT 2";
verifyPPLToSparkSQL(root, expectedSparkSql);
}

@Test
public void testCoalesceNullLiteralWithInteger() {
// Reproducer for https://github.com/opensearch-project/sql/issues/5175
// COALESCE(null, 42) should return INTEGER type, not VARCHAR
String ppl = "source=EMP | eval x = coalesce(null, 42) | fields x | head 1";
RelNode root = getRelNode(ppl);
// The COALESCE return type should be INTEGER, not VARCHAR
RelDataType resultType = root.getRowType().getFieldList().get(0).getType();
assertEquals(
"COALESCE(null, 42) should infer INTEGER type",
SqlTypeName.INTEGER,
resultType.getSqlTypeName());
}

@Test
public void testCoalesceIntegerWithNullLiteral() {
// COALESCE(42, null) should also return INTEGER type
String ppl = "source=EMP | eval x = coalesce(42, null) | fields x | head 1";
RelNode root = getRelNode(ppl);
RelDataType resultType = root.getRowType().getFieldList().get(0).getType();
assertEquals(
"COALESCE(42, null) should infer INTEGER type",
SqlTypeName.INTEGER,
resultType.getSqlTypeName());
}
}
Loading