Skip to content

fix(sql): fix ADD COLUMN IF NOT EXISTS for DECIMAL, GEOHASH, and array types#6753

Merged
bluestreak01 merged 7 commits intomasterfrom
mt_fix-add-column-if-exists
Feb 9, 2026
Merged

fix(sql): fix ADD COLUMN IF NOT EXISTS for DECIMAL, GEOHASH, and array types#6753
bluestreak01 merged 7 commits intomasterfrom
mt_fix-add-column-if-exists

Conversation

@mtopolnik
Copy link
Copy Markdown
Contributor

@mtopolnik mtopolnik commented Feb 6, 2026

Fixes #6750

Summary

The ALTER TABLE t ADD COLUMN IF NOT EXISTS col <type> path did not
fully resolve parameterized or compound column types before comparing
with existing metadata, causing false type-mismatch errors.

Bug fixes

  • DECIMAL/GEOHASH: the IF NOT EXISTS path used ColumnType.typeOf() which
    returns the base type constant, but metadata stores the fully encoded
    type (with precision/scale/bits). Fixed by parsing DECIMAL and GEOHASH
    parameters before comparing.
  • Array types (e.g. DOUBLE[]): the IF NOT EXISTS path did not parse array
    dimensionality brackets, causing type mismatch errors and unconsumed tokens.
    Fixed by calling SqlUtil.parseArrayDimensionality() before comparing.
  • Unsupported array element types (e.g. INT[]) in the IF NOT EXISTS path
    now correctly report "unsupported array element type" instead of falling
    through to a misleading type-mismatch error.
  • Unmatched ] bracket after a type name (e.g. double]) is now detected
    in the IF NOT EXISTS path with a helpful error pointing at the bracket.

Consistency improvements

  • Moved parseGeoHashColumnType from SqlCompilerImpl (private) to
    SqlParser (public) to co-locate it with parseDecimalColumnType
  • Replaced ColumnType.typeOf() with SqlUtil.toPersistedType() in the
    IF NOT EXISTS path for consistency with addColumnWithType, giving
    more specific error messages for non-persisted types like CURSOR

Test plan

  • Verify ADD COLUMN IF NOT EXISTS col DECIMAL(48, 18) is a no-op when column already exists with same type
  • Verify ADD COLUMN IF NOT EXISTS col DECIMAL (bare, defaults to 18,3) is a no-op when column exists as DECIMAL(18,3)
  • Verify ADD COLUMN IF NOT EXISTS col DECIMAL(18, 3) correctly reports type mismatch when column exists as DECIMAL(48, 18)
  • Verify ADD COLUMN IF NOT EXISTS col GEOHASH(5c) is a no-op when column already exists with same type
  • Verify ADD COLUMN IF NOT EXISTS col GEOHASH(3c) correctly reports type mismatch when column exists as GEOHASH(5c)
  • Verify ADD COLUMN IF NOT EXISTS col DOUBLE[] is a no-op when column already exists with same type
  • Verify ADD COLUMN IF NOT EXISTS col DOUBLE[][] correctly reports type mismatch when column exists as DOUBLE[]
  • Verify ADD COLUMN IF NOT EXISTS col INT[] reports "unsupported array element type" in the IF NOT EXISTS path
  • Verify ADD COLUMN IF NOT EXISTS col double] detects unmatched bracket with correct error position
  • Verify existing ADD COLUMN and IF NOT EXISTS tests still pass

🤖 Generated with Claude Code

… EXISTS

In `alterTableAddColumn()`, the IF NOT EXISTS path called
`ColumnType.typeOf(tok)` which returns the base DECIMAL (34) or GEOHASH
(23) constant. But `tableMetadata.getColumnType()` returns the fully
encoded type (with precision/scale/storage-size for DECIMAL, or bit
precision for GEOHASH). The comparison always failed because the base
constant never equals an encoded type.

The fix resolves parameterized types before comparing:
- DECIMAL: call `SqlParser.parseDecimalColumnType(lexer)`
- GEOHASH: call new `parseGeoHashColumnType(lexer)` helper

Also extracted the inline GEOHASH parsing in `addColumnWithType` into the
reusable `parseGeoHashColumnType` static method, and captured the type
keyword position so error messages point at the type name rather than the
closing parenthesis.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 6, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • 🔍 Trigger a full review
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch mt_fix-add-column-if-exists

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@mtopolnik mtopolnik changed the title Fix DECIMAL/GEOHASH column type comparison in ADD COLUMN IF NOT EXISTS fix(sql): DECIMAL/GEOHASH column type comparison in ADD COLUMN IF NOT EXISTS Feb 6, 2026
@mtopolnik mtopolnik changed the title fix(sql): DECIMAL/GEOHASH column type comparison in ADD COLUMN IF NOT EXISTS fix(sql): fix DECIMAL/GEOHASH column type comparison in ADD COLUMN IF NOT EXISTS Feb 6, 2026
@mtopolnik mtopolnik added Bug Incorrect or unexpected behavior SQL Issues or changes relating to SQL execution labels Feb 6, 2026
@mtopolnik mtopolnik changed the title fix(sql): fix DECIMAL/GEOHASH column type comparison in ADD COLUMN IF NOT EXISTS fix(sql): fix ADD COLUMN IF NOT EXISTS for DECIMAL, GEOHASH, and array types Feb 6, 2026
…nsistency

- Move parseGeoHashColumnType from SqlCompilerImpl to SqlParser as a
  public static method, co-locating it with parseDecimalColumnType for
  consistency
- Replace ColumnType.typeOf() with SqlUtil.toPersistedType() in the
  IF NOT EXISTS path to match the addColumnWithType path and give
  more specific error messages for non-persisted types
- Add array dimensionality parsing to the IF NOT EXISTS path so that
  DOUBLE[] columns are correctly recognized and compared
- Add tests for array type handling in ADD COLUMN IF NOT EXISTS

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@mtopolnik mtopolnik force-pushed the mt_fix-add-column-if-exists branch from 7754ddc to 3e9a9c5 Compare February 6, 2026 11:13
mtopolnik and others added 2 commits February 6, 2026 12:24
Add two missing checks from the normal ADD COLUMN flow to the
IF NOT EXISTS path:

1. Unsupported array element type validation — e.g. `INT[]` now correctly
   reports "unsupported array element type" instead of falling through to
   a misleading "column already exists with different type" error.

2. Unmatched `]` bracket detection — a stray `]` after the type name
   (e.g. `double]`) is now caught with a helpful error message pointing
   at the bracket position.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
…STS path

Move GEOHASH type parsing before the unmatched `]` bracket check in the
normal addColumnWithType() path, making it consistent with the IF NOT
EXISTS path. Previously GEOHASH was parsed after the bracket check while
DECIMAL was parsed before it, which could produce different error messages
for malformed input between the two code paths.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Copy link
Copy Markdown
Contributor Author

@mtopolnik mtopolnik left a comment

Choose a reason for hiding this comment

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

PR Review by Claude

Overall: Looks good. The fix is well-scoped and the approach is sound — the IF NOT EXISTS path now mirrors the normal addColumnWithType path for type resolution, ensuring the comparison uses fully-encoded types rather than bare base types.

Positives

  1. Good deduplication of GEOHASH parsing — moving parseGeoHashColumnType to SqlParser (next to parseDecimalColumnType) and calling it from both paths eliminates the inline copy in addColumnWithType. The new method is a clean 1:1 extraction of the old inline code.

  2. Consistent use of SqlUtil.toPersistedType instead of raw ColumnType.typeOf in the IF NOT EXISTS path, giving better error messages for non-persisted types like CURSOR.

  3. Good test coverage — tests for DECIMAL (with and without explicit precision), GEOHASH, arrays, unsupported array element types, and the unmatched bracket case.

  4. Error positions are correcttypePosition is captured right after reading the type token and used for all error messages in the IF NOT EXISTS block.

Issues

1. IF NOT EXISTS path doesn't consume remaining tokens on success (medium)

When the column exists and types match, the code hits break at line 968. But if the statement had additional clauses after the type (e.g., ALTER TABLE x ADD COLUMN IF NOT EXISTS col SYMBOL CAPACITY 1024 CACHE, other_col INT), those tokens are left unconsumed. The normal addColumnWithType path parses SYMBOL capacity/cache/index options. The IF NOT EXISTS path ignores them, which means:

  • ALTER TABLE x ADD COLUMN IF NOT EXISTS col SYMBOL CAPACITY 1024 CACHE — the CAPACITY 1024 CACHE tokens are silently dropped when the column already exists
  • If there are more columns after a comma, they'd also be silently dropped

This was pre-existing behavior (not introduced by this PR), but it's worth noting as a follow-up item since you're already touching this code.

2. Minor: bare DECIMAL in the IF NOT EXISTS path (low)

The test testAddDuplicateColumnIfNotExistsDecimalDefault adds a column as bare DECIMAL then checks it with bare DECIMAL again. This works because SqlParser.parseDecimalColumnType defaults to (18,3) in both cases. Good — but there's no test for the cross-case: adding as bare DECIMAL and checking with explicit DECIMAL(18, 3) (or vice versa). Adding that would further validate the equivalence. Not blocking.

3. No test for GEOHASH missing precision in IF NOT EXISTS path (low)

There's no test for ALTER TABLE x ADD COLUMN IF NOT EXISTS geo_col GEOHASH (missing precision). The parsing would throw "missing GEOHASH precision" from the new parseGeoHashColumnType, but there's no explicit test asserting this error in the IF NOT EXISTS context. Not blocking.

Summary

The core logic is correct. The two code paths (normal add and IF NOT EXISTS) now produce the same fully-encoded type for DECIMAL, GEOHASH, and array types before comparison. Approve, with optional suggestions to add the cross-case DECIMAL test and to note the pre-existing SYMBOL options consumption gap as a follow-up.

mtopolnik and others added 2 commits February 6, 2026 12:53
Add a new "QuestDB's SQL dialect" section documenting key SQL dialect
differences: multidimensional array support with dimensionality encoded
in the column type, and the absence of DELETE support.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
…cation

The ADD COLUMN IF NOT EXISTS path duplicated ~30 lines of type-parsing
logic from addColumnWithType. This duplication was the root cause of the
bugs this branch already fixes (missing DECIMAL/GEOHASH/array handling).
The duplicated code also broke without consuming trailing column options
(SYMBOL CAPACITY, CACHE, INDEX, NOT NULL), leaving tokens in the lexer.

Changes:
- Make addColumnWithType accept nullable AlterOperationBuilder so the
  IF NOT EXISTS path can call it with null to parse and validate the
  full column definition without actually adding the column.
- Replace the duplicated type-parsing block in the IF NOT EXISTS path
  with a call to addColumnWithType(null, ...), which properly consumes
  trailing SYMBOL options and NOT NULL tokens.
- After addColumnWithType returns, handle delimiters (`,`, `;`, EOF)
  the same way the normal path does, including support for multi-column
  IF NOT EXISTS statements.
- Fix the unmatched bracket error position to use typePosition
  consistently (was using lexer.lastTokenPosition() for `]` in the
  IF NOT EXISTS path but typePosition in the normal path).
- Add tests for: IF NOT EXISTS with SYMBOL options, multi-column
  IF NOT EXISTS, and trailing garbage rejection.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@glasstiger
Copy link
Copy Markdown
Contributor

[PR Coverage check]

😍 pass : 36 / 37 (97.30%)

file detail

path covered line new line coverage
🔵 io/questdb/griffin/SqlCompilerImpl.java 19 20 95.00%
🔵 io/questdb/griffin/SqlParser.java 17 17 100.00%

@bluestreak01 bluestreak01 merged commit 452033a into master Feb 9, 2026
44 checks passed
@bluestreak01 bluestreak01 deleted the mt_fix-add-column-if-exists branch February 9, 2026 00:29
maciulis pushed a commit to maciulis/questdb that referenced this pull request Feb 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug Incorrect or unexpected behavior SQL Issues or changes relating to SQL execution

Projects

None yet

Development

Successfully merging this pull request may close these issues.

column already exists with a different column type when adding decimal(...) column

3 participants