Skip to content

DELETE FROM ... AS ... in trigger causes parse error #1339

Open
@coreybutler

Description

@coreybutler

I'm running into a parsing error when executing a CREATE TRIGGER statement including a DELETE FROM ... AS ... clause inside the trigger body.

I'm using v1.14.28 of this module.

_, err := db.Exec(`
  CREATE TABLE host (host TEXT);
  CREATE TABLE dsn (id INTEGER, host TEXT);

  CREATE TRIGGER test_trigger AFTER DELETE ON dsn
  BEGIN
    DELETE FROM host AS h
    WHERE h.host = OLD.host
      AND h.host NOT IN (
        SELECT DISTINCT d2.host
        FROM dsn AS d2
        WHERE d2.id != OLD.id
          AND d2.host = OLD.host
      );
  END;
`)

returns

near "AS": syntax error

This SQL is valid as of SQLite 3.33.0, which introduced support for DELETE FROM ... AS alias. This is using SQLite v3.49.1. The same SQL executes without issue using tools like DBeaver.

I can work around this by removing the alias and referencing the table name directly, i.e.

DELETE FROM host
WHERE host.host = OLD.host
  AND host.host NOT IN (...)

I'm also able to successfully parse this using rqlite/sql.

Could this be a bug or parser limitation in the handling of Exec() for DDL/trigger bodies?


For testing/reproduction, here is the code that works in rqlite/sql (and whose output runs in DBeaver) but fails in this module:

package main

import (
  "fmt"
  "strings"

  "github.com/rqlite/sql"
)

func main() {
  query := `
    CREATE TRIGGER IF NOT EXISTS tr_log_dsn_removal AFTER DELETE
	ON dsn
	FOR EACH ROW
    BEGIN
	INSERT INTO audit (id, nm, data, source_id, source_table)
	VALUES (
	  lower(hex(randomblob(16))),
	  'DSN_REMOVED',
	  json_object(
	    'id', OLD.id,
	    'name', OLD.nm
	  ),
	  OLD.id,
	  'dsn'
	);

	DELETE FROM host h
	WHERE h.host = OLD.host
	  AND h.host NOT IN (
	    SELECT distinct d2.host
	    FROM dsn d2
	    WHERE d2.id != OLD.id AND d2.host = OLD.host
	);
    END
  ;`

  parser := sql.NewParser(strings.NewReader(query))

  for {
    stmt, err := parser.ParseStatement()
    if err != nil {
      if err.Error() == "EOF" {
	break
      }
      panic(err)
    }

    fmt.Printf("-- Reconstructed statement as: \n%s\n", stmt.String())
  }
}

Output:

-- Reconstructed statement as: 
CREATE TRIGGER IF NOT EXISTS "tr_log_dsn_removal" AFTER DELETE ON "dsn" FOR EACH ROW BEGIN INSERT INTO "audit" ("id", "nm", "data", "source_id", "source_table") VALUES (lower(hex(randomblob(16))), 'DSN_REMOVED', json_object('id', "OLD"."id", 'name', "OLD"."nm"), "OLD"."id", 'dsn'); DELETE FROM "host" AS "h" WHERE "h"."host" = "OLD"."host" AND "h"."host" NOT IN (SELECT DISTINCT "d2"."host" FROM "dsn" AS "d2" WHERE "d2"."id" != "OLD"."id" AND "d2"."host" = "OLD"."host"); END

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions