Skip to content

[CALCITE-7316] The POSITION function in SQLite is missing the FROM clause#4854

Open
macroguo-ghy wants to merge 2 commits intoapache:mainfrom
macroguo-ghy:calcite-7316-sqlite-position
Open

[CALCITE-7316] The POSITION function in SQLite is missing the FROM clause#4854
macroguo-ghy wants to merge 2 commits intoapache:mainfrom
macroguo-ghy:calcite-7316-sqlite-position

Conversation

@macroguo-ghy
Copy link
Copy Markdown
Contributor

@macroguo-ghy macroguo-ghy commented Mar 27, 2026

Jira Link

CALCITE-7316

Changes Proposed

This PR fixes SQLite SQL generation for the 3-argument POSITION form so that the FROM start position is preserved instead of being dropped.

Reproduction:

select position(''C'' IN ''ABCABC'' FROM 4) from "product";

Before this change, the generated SQLite SQL lost the FROM 4 semantics.

This change rewrites the SQLite form to an equivalent expression based on SUBSTR and INSTR, and adds SQLite rel-to-sql tests covering both a hit and a miss case for POSITION(... FROM ...).

@macroguo-ghy macroguo-ghy force-pushed the calcite-7316-sqlite-position branch 2 times, most recently from ca0c597 to 434840d Compare March 27, 2026 17:21
@macroguo-ghy macroguo-ghy force-pushed the calcite-7316-sqlite-position branch from 434840d to ae31f91 Compare March 27, 2026 17:45
+ "FROM \"foodmart\".\"product\"";
sql(query).withSQLite().ok(expected);

final String query1 = "select position('C' IN 'ABCABC' FROM 4) from \"product\"";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add jira link

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes SQLite SQL generation for the 3-argument POSITION(... FROM ...) form so the start-position semantics are preserved during unparsing.

Changes:

  • Update SqliteSqlDialect to unparse 3-arg POSITION into a CASE expression based on SUBSTR + INSTR.
  • Add rel-to-sql tests for SQLite covering both a match and a no-match case for POSITION(... FROM ...).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
core/src/main/java/org/apache/calcite/sql/dialect/SqliteSqlDialect.java Adds operand-count-sensitive POSITION unparsing and a helper to rewrite the 3-arg form for SQLite.
core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java Adds SQLite rel-to-sql assertions for POSITION(... FROM ...) (hit + miss).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

final SqlNode thenResult =
SqlStdOperatorTable.PLUS.createCall(pos, relativePosition, startAdjustment);
return new SqlCase(pos, null, SqlNodeList.of(whenCondition),
SqlNodeList.of(thenResult), SqlLiteral.createExactNumeric("0", pos));
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the 3-arg POSITION rewrite, the CASE expression uses ELSE 0. If any operand is NULL, INSTR(...) (and therefore relativePosition) becomes NULL; the WHEN relativePosition > 0 condition then evaluates to NULL and the CASE falls through to ELSE 0, changing the result from NULL to 0. To preserve POSITION/INSTR null-propagation semantics, make the ELSE branch return relativePosition (so it yields 0 on “not found” and NULL on NULL inputs), or add an explicit WHEN relativePosition IS NULL THEN NULL branch.

Suggested change
SqlNodeList.of(thenResult), SqlLiteral.createExactNumeric("0", pos));
SqlNodeList.of(thenResult), SqlNode.clone(relativePosition));

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

@xiedeyantu xiedeyantu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for taking the time to address the Jira issue I raised. I’ve left a few points open for discussion—perhaps we can continue the conversation there in the Jira ticket as well.

public class SqliteSqlDialect extends SqlDialect {
// Use plain function nodes here so the SQLite POSITION rewrite does not
// re-enter dialect-specific operator handling during unparsing.
private static final SqlFunction INSTR =
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to add additional function definitions in the dialect?

sql(query).withSQLite().ok(expected);

final String query1 = "select position('C' IN 'ABCABC' FROM 4) from \"product\"";
final String expected1 =
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m not sure if we need to make such complex rewrites. Honestly, I haven’t even figured out how to approach the rewrite in the Jira issue.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1. Additionally, there is another issue: for the query select * from db.table where d=position(a IN b FROM c), if this expression is placed within the WHERE clause, we still need to enclose it in parentheses.

@sonarqubecloud
Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants