Skip to content

[BUG] SQLite: INSERT OR IGNORE / ABORT / FAIL / ROLLBACK and UPDATE OR ... do not parse #2852

Closed

Description

@ItsDeadlyProgrammer

SQLite accepts a conflict resolution algorithm after INSERT OR and UPDATE OR: ABORT, FAIL, IGNORE, REPLACE and ROLLBACK (lang_insert, lang_update, lang_conflict). Only INSERT OR REPLACE parses.

JSqlParser: current master (90ac3a7).

Statement Result
INSERT OR REPLACE INTO t VALUES (1) parses
INSERT OR IGNORE INTO t VALUES (1) Encountered: <K_INSERT> / "INSERT"
INSERT OR ABORT INTO t VALUES (1) same
INSERT OR FAIL INTO t VALUES (1) same
INSERT OR ROLLBACK INTO t VALUES (1) same
UPDATE OR IGNORE t SET a = 1 Encountered: <K_UPDATE> / "UPDATE"
UPDATE OR REPLACE t SET a = 1 same

INSERT OR IGNORE is the usual SQLite form of "insert if it does not exist yet", so it is common in application code. The grammar handles only <K_INSERT> <K_OR> <K_REPLACE> as an Upsert (UpsertType.INSERT_OR_REPLACE).

Related in the same dialect: CREATE TABLE t (a INT) WITHOUT ROWID fails with Expected OIDS but found ROWID, and ... STRICT fails with Encountered: <K_STRICT>.

Activity

  1. manticore-projects commented on Nov 13, 2026

    @manticore-projects
    Contributor

    I assume this has been completed and can be closed? Please verify and confirm. Thank you!

  2. ItsDeadlyProgrammer commented on Nov 13, 2026

    @ItsDeadlyProgrammer
    ContributorAuthor

    Verified on master bccb1c8 — built the grammar with jjtree + javacc 8.1.4.1944, compiled
    src/main/java against it, and parsed each statement with CCJSqlParser and
    withDialect(Dialect.SQLITE).

    Everything reported here parses and round-trips now:

    Statement Result
    INSERT OR IGNORE INTO t VALUES (1) OK, deparses to the same SQL
    INSERT OR ABORT INTO t VALUES (1) OK
    INSERT OR FAIL INTO t VALUES (1) OK
    INSERT OR ROLLBACK INTO t VALUES (1) OK
    INSERT OR REPLACE INTO t VALUES (1) OK
    UPDATE OR IGNORE t SET a = 1 OK, conflictAlgorithm=IGNORE
    UPDATE OR ABORT / FAIL / ROLLBACK / REPLACE t SET a = 1 OK
    CREATE TABLE t (a INT) WITHOUT ROWID OK, TableOption.Kind.WITHOUT_ROWID
    CREATE TABLE t (a INT) STRICT OK, TableOption.Kind.STRICT
    CREATE TABLE t (a INT) STRICT, WITHOUT ROWID and WITHOUT ROWID, STRICT OK in both orders

    Also checked green: lowercase forms, multi-row VALUES, INSERT OR IGNORE ... SELECT,
    UPDATE OR ... FROM/WHERE/ORDER BY/LIMIT ... RETURNING, and no collateral damage to other
    dialects (PostgreSQL WITHOUT OIDS and WITH (...), MySQL ENGINE=, SELECT * FROM t strict
    stays an alias).

    The dialect guard behaves as intended: without a dialect, INSERT OR IGNORE INTO t VALUES (1)
    fails with INSERT OR IGNORE needs Dialect.SQLITE instead of the old generic syntax error, while
    INSERT OR REPLACE still parses everywhere.

    So the reported cases are fixed — this can be closed for the conflict algorithms and the table
    options. Two things worth a line in the release notes or a follow-up:

    1. Dialect.SQLITE is a new constant on a public enum, so downstream code with an exhaustive
      switch (dialect) and no default branch stops compiling. It is also master-only:
      jsqlparser 5.4 has no SQLITE constant and none of the statements above parse there.

    2. The body of the OR-form is still parsed by Upsert(), not by Insert(), so it does not get
      the INSERT clauses the plain form supports. These all still fail with Dialect.SQLITE on
      master:

      OR-form Equivalent plain form
      INSERT OR IGNORE INTO t (a) VALUES (1) ON CONFLICT DO NOTHING fails INSERT INTO t (a) VALUES (1) ON CONFLICT DO NOTHING parses
      INSERT OR IGNORE INTO t (a) VALUES (1) RETURNING a fails INSERT INTO t (a) VALUES (1) RETURNING a parses
      INSERT OR IGNORE INTO t DEFAULT VALUES fails INSERT INTO t DEFAULT VALUES parses
      INSERT OR IGNORE INTO t AS alias VALUES (1) fails INSERT INTO t AS alias VALUES (1) parses

      REPLACE INTO t (a) VALUES (1) RETURNING a, ... ON CONFLICT ... and REPLACE ... DEFAULT VALUES fail the same way, so this is pre-existing on that path and not a regression of feat: parse INSERT OR ABORT, FAIL, IGNORE and ROLLBACK (SQLite) #2853,
      feat: parse UPDATE OR conflict algorithms in the SQLite dialect #2885 or feat: parse SQLite WITHOUT ROWID and STRICT table options #2903 — but INSERT OR IGNORE ... RETURNING and bare REPLACE ... RETURNING are
      common application code, and SQLiteTest only pins the three shapes from this issue, so a
      closure based on those tests would not show the gap.

      Suggested follow-up (separate issue): give Insert an INSERT OR <algorithm> prefix field,
      rendered in Insert.appendInsertPrefix and InsertDeParser, i.e. (INSERT | REPLACE) * [OR algorithm] INTO ..., and parse the SQLite keywords there. The OR-form then inherits
      ON CONFLICT, RETURNING, DEFAULT VALUES, table aliases and row aliases for free, while
      Upsert / UpsertDeParser keep serving REPLACE and UPSERT unchanged.

    Filed as a follow-up issue with the full reproduction matrix: #2923,

  3. ItsDeadlyProgrammer commented on Nov 13, 2026

    @ItsDeadlyProgrammer
    ContributorAuthor

    Verified on master bccb1c8: every statement in the report parses and round-trips — INSERT OR ABORT/FAIL/IGNORE/ROLLBACK, UPDATE OR , WITHOUT ROWID and STRICT (#2853, #2885, #2903). Closing as completed.

    The one thing this issue surfaced but does not cover — the OR-form not inheriting ON CONFLICT, RETURNING, DEFAULT VALUES and table aliases — is tracked separately in #2923, with #2924 open for it. Thanks for the nudge.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions