Skip to content

Parser: drop the mandatory alias requirement on joined tables #95

@fupelaqu

Description

@fupelaqu

Summary

Join.validate() in sql/src/main/scala/app/softnetwork/elastic/sql/query/From.scala
unconditionally rejects any join whose joined table has no explicit alias,
even when the join condition correctly qualifies columns by the table name
itself. Standard SQL does not require an alias on joined tables — the bare
table name is a valid qualifier.

Repro

-- Fails: "JOIN  JOIN customers ON orders.customer_id = customers.id requires an alias"
SELECT *
FROM orders
LEFT JOIN customers ON orders.customer_id = customers.id;

-- Works: alias `c` is purely decorative
SELECT *
FROM orders
LEFT JOIN customers c ON orders.customer_id = c.id;

The first form is accepted by PostgreSQL, MySQL, DuckDB, ClickHouse, SQL
Server, and the SQL-2003 standard. The bare table name customers is a
valid qualifier in ON / WHERE / SELECT.

Root cause

override def validate(): Either[String, Unit] =
  for {
    _ <- source.validate()
    _ <- alias match {
      case Some(a) if a.alias.nonEmpty => Right(())
      case _                           => Left(s"JOIN $this requires an alias")
    }
    _ <- this match {
      case j if joinType.isDefined && on.isEmpty && joinType.get != CrossJoin =>
        Left(s"JOIN $j requires an ON clause")
      case j if alias.isEmpty =>
        Left(s"JOIN $j requires an alias")
      case _ => Right(())
    }
  } yield ()

Two separate code paths reject the missing alias (lines 170–173 and
lines 177–178). Neither path is conditional on whether the ON/WHERE
clause actually depends on an alias.

Impact

  • Direct queries hand-written by users fail with a confusing "requires an
    alias" error that the SQL standard does not impose.
  • Round-trip SQL from any external generator (BI tools, ORMs, DuckDB
    rewrites, JDBC drivers) that emits alias-less joins breaks at parse
    time.
  • Discovered while writing the ParserSpec coverage for Parser: accept the OUTER keyword in LEFT/RIGHT/FULL OUTER JOIN #94 (optional
    OUTER keyword): the natural test SQL LEFT JOIN customers ON ...
    failed not because of the OUTER work, but because the alias was
    missing.

Proposed fix

Two options, ranked by safety:

  1. Drop the alias requirement entirely. Alias was always optional in
    SQL; the table name itself is a valid qualifier. Internally, anywhere
    tableAlias is used as a key, fall back to the table name when no
    alias was given (some codepaths already do this — e.g. the MV graph
    builder's tableAliases.getOrElse(tableName, tableName)).

  2. Reject only when the ON / WHERE / SELECT actually references
    an undefined alias.
    Keeps the safety net, but only fires when a
    qualifier in the query cannot be resolved against either an alias or
    a table name. More conservative but more complex.

Option 1 mirrors how every mainstream SQL engine behaves and aligns with
how the MV graph builder already treats missing aliases.

Acceptance criteria

  • SELECT * FROM orders LEFT JOIN customers ON orders.customer_id = customers.id
    parses and validates.
  • Existing tests using explicit aliases continue to pass — the alias
    stays optional.
  • Where downstream code keys off tableAlias, the table name is
    used as a fallback.
  • New ParserSpec cases for alias-less INNER / LEFT / RIGHT / FULL JOIN.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions