Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -802,14 +802,23 @@ class HelpCorpusSpec extends AnyFlatSpec with Matchers {
// and no user can type one as a statement of its own, so the package walk's coverage is
// unchanged. Verified by the superset assertion below and by this file's parser -> doc gate
// staying green with no new help document.
// `app$softnetwork$elastic$sql$parser$WhereParser$$subqueryBody` is the TRAIT-PRIVATE
// `subqueryBody` of `WhereParser`. It appears here - and only here - because the packrat
// memoisation fix declares every production as a `lazy val`: a trait-private `def` compiles to
// a private method, while a trait-private `lazy val` needs a mangled but PUBLIC accessor in
// the mixing class, which `getMethods` can see. It names no new statement leaf: its body is
// `start ~> derivedTableBodyInner <~ end`, i.e. the already-enumerated `derivedTableBodyInner`
// in parentheses, so the package walk's coverage is unchanged (the superset assertion below
// stays green and no new help document is required).
val expectedAbstract =
Set(
"statement",
"dqlStatement",
"ddlStatement",
"dmlStatement",
"searchStatement",
"derivedTableBodyInner"
"derivedTableBodyInner",
"app$softnetwork$elastic$sql$parser$WhereParser$$subqueryBody"
)
withClue(
"the set of productions returning a SEALED TRAIT has changed. Every one of them hides its " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,20 @@ import app.softnetwork.elastic.sql.query.{
trait FromParser {
self: Parser with WhereParser with LimitParser =>

def unnest: PackratParser[Join] =
lazy val unnest: PackratParser[Join] =
Unnest.regex ~ start ~ identifier ~ end ~ alias.? ^^ { case _ ~ i ~ _ ~ a =>
Unnest(i, None, a)
}

def inner_join: PackratParser[JoinType] = InnerJoin.regex ^^ { _ => InnerJoin }
def left_join: PackratParser[JoinType] = LeftJoin.regex ^^ { _ => LeftJoin }
def right_join: PackratParser[JoinType] = RightJoin.regex ^^ { _ => RightJoin }
def full_join: PackratParser[JoinType] = FullJoin.regex ^^ { _ => FullJoin }
def cross_join: PackratParser[JoinType] = CrossJoin.regex ^^ { _ => CrossJoin }
def join_type: PackratParser[JoinType] =
lazy val inner_join: PackratParser[JoinType] = InnerJoin.regex ^^ { _ => InnerJoin }
lazy val left_join: PackratParser[JoinType] = LeftJoin.regex ^^ { _ => LeftJoin }
lazy val right_join: PackratParser[JoinType] = RightJoin.regex ^^ { _ => RightJoin }
lazy val full_join: PackratParser[JoinType] = FullJoin.regex ^^ { _ => FullJoin }
lazy val cross_join: PackratParser[JoinType] = CrossJoin.regex ^^ { _ => CrossJoin }
lazy val join_type: PackratParser[JoinType] =
inner_join | left_join | right_join | full_join | cross_join

def on: PackratParser[On] = On.regex ~> whereCriteria >> { rawTokens =>
lazy val on: PackratParser[On] = On.regex ~> whereCriteria >> { rawTokens =>
// `On(criteria: Criteria)` is not optional (query/From.scala), which is why an ON whose
// criteria resolve to nothing used to `throw new Exception`. #250: `err`, like every other
// rejection in this package - see `WhereParser.where` for the full reasoning.
Expand Down Expand Up @@ -79,7 +79,7 @@ trait FromParser {
* would render `"logs-2025"."03"` and re-parse as qualifier `logs-2025` + index `03`.
* `StandardJoin.sql` renders each `NamePart` as ONE lexeme instead (21.2 AD-5).
*/
def source: PackratParser[StandardJoin] =
lazy val source: PackratParser[StandardJoin] =
derivedTable ^^ { dt =>
// A derived JOIN leg owns its alias and its parts are empty — the AD-1 invariant that
// `StandardJoin.validate()` ENFORCES.
Expand All @@ -95,12 +95,13 @@ trait FromParser {
)
}

def join: PackratParser[Join] = opt(join_type) ~ Join.regex ~ (unnest | source) ~ opt(on) ^^ {
case _ ~ _ ~ (u: Unnest) ~ _ =>
u // Unnest cannot have a join type or an ON clause
case jt ~ _ ~ (sj: StandardJoin) ~ o =>
sj.copy(joinType = jt, on = o)
}
lazy val join: PackratParser[Join] =
opt(join_type) ~ Join.regex ~ (unnest | source) ~ opt(on) ^^ {
case _ ~ _ ~ (u: Unnest) ~ _ =>
u // Unnest cannot have a join type or an ON clause
case jt ~ _ ~ (sj: StandardJoin) ~ o =>
sj.copy(joinType = jt, on = o)
}

/** The FROM (and DELETE, and CTAS/MV/WATCHER body) table reference.
*
Expand All @@ -109,7 +110,7 @@ trait FromParser {
* discarded, which is what the `quotedSchemaPrefix` this replaces used to do (#85): the render
* no longer deletes a clause the statement carried.
*/
def table: PackratParser[Table] =
lazy val table: PackratParser[Table] =
(derivedTable ^^ { dt => Table(dt.name, None, Nil, Nil, derived = Some(dt)) } |
tableParts ~ alias.? ^^ { case ps ~ a => Table(ps.last.value, a, Nil, parts = ps) }) ~
rep(join) ^^ { case t ~ js => t.copy(joins = js) }
Expand Down Expand Up @@ -142,7 +143,7 @@ trait FromParser {
* Recursion (`table -> derivedTable -> searchStatement -> single -> from -> table`) runs through
* a CONSUMED `(`, so it is not left recursion; Packrat memoises it and nesting is unbounded.
*/
override def derivedTable: PackratParser[DerivedTable] =
override lazy val derivedTable: PackratParser[DerivedTable] =
(start ~> (derivedTableBodyInner | err(
"A derived table body must be a SELECT: write FROM (SELECT ...) AS <name>"
)) <~ end) ~ alias.? >> {
Expand All @@ -154,8 +155,9 @@ trait FromParser {
)
}

def from: PackratParser[From] = From.regex ~ rep1sep(table, separator) ^^ { case _ ~ tables =>
From(tables)
lazy val from: PackratParser[From] = From.regex ~ rep1sep(table, separator) ^^ {
case _ ~ tables =>
From(tables)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import app.softnetwork.elastic.sql.query.{Bucket, GroupBy}
trait GroupByParser {
self: Parser with WhereParser =>

def bucketWithFunction: PackratParser[Identifier] =
lazy val bucketWithFunction: PackratParser[Identifier] =
// #284 - see quotedIdentifierUnlessArithmetic.
quotedIdentifierUnlessArithmetic |
identifierWithArithmeticExpression |
Expand All @@ -33,11 +33,11 @@ trait GroupByParser {
identifierWithFunction |
identifier

def bucket: PackratParser[Bucket] = (long | bucketWithFunction) ^^ { i =>
lazy val bucket: PackratParser[Bucket] = (long | bucketWithFunction) ^^ { i =>
Bucket(i)
}

def groupBy: PackratParser[GroupBy] =
lazy val groupBy: PackratParser[GroupBy] =
GroupBy.regex ~ rep1sep(bucket, separator) ^^ { case _ ~ buckets =>
GroupBy(buckets)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import app.softnetwork.elastic.sql.query.Having
trait HavingParser {
self: Parser with WhereParser =>

def having: PackratParser[Having] = Having.regex ~> whereCriteria >> { rawTokens =>
lazy val having: PackratParser[Having] = Having.regex ~> whereCriteria >> { rawTokens =>
// `err`, not `throw` and not `failure` (#250) - same treatment as `WhereParser.where`, whose
// comment carries the full reasoning. `~>` binds tighter than `>>`.
processTokens(rawTokens) match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ import app.softnetwork.elastic.sql.query.{Limit, Offset}
trait LimitParser {
self: Parser =>

def offset: PackratParser[Offset] = Offset.regex ~ long ^^ { case _ ~ i =>
lazy val offset: PackratParser[Offset] = Offset.regex ~ long ^^ { case _ ~ i =>
Offset(i.value.toInt)
}

def limit: PackratParser[Limit] = Limit.regex ~ long ~ offset.? ^^ { case _ ~ i ~ o =>
lazy val limit: PackratParser[Limit] = Limit.regex ~ long ~ offset.? ^^ { case _ ~ i ~ o =>
Limit(i.value.toInt, o)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,20 @@ import app.softnetwork.elastic.sql.query.{
trait OrderByParser {
self: Parser =>

def asc: PackratParser[Asc.type] = Asc.regex ^^ (_ => Asc)
lazy val asc: PackratParser[Asc.type] = Asc.regex ^^ (_ => Asc)

def desc: PackratParser[Desc.type] = Desc.regex ^^ (_ => Desc)
lazy val desc: PackratParser[Desc.type] = Desc.regex ^^ (_ => Desc)

def nullsFirst: PackratParser[NullsFirst.type] = NullsFirst.regex ^^ (_ => NullsFirst)
lazy val nullsFirst: PackratParser[NullsFirst.type] = NullsFirst.regex ^^ (_ => NullsFirst)

def nullsLast: PackratParser[NullsLast.type] = NullsLast.regex ^^ (_ => NullsLast)
lazy val nullsLast: PackratParser[NullsLast.type] = NullsLast.regex ^^ (_ => NullsLast)

def nullOrdering: PackratParser[NullOrdering] = nullsFirst | nullsLast
lazy val nullOrdering: PackratParser[NullOrdering] = nullsFirst | nullsLast

private def fieldName: PackratParser[String] =
private lazy val fieldName: PackratParser[String] =
"""\b(?!(?i)limit\b)[a-zA-Z_][a-zA-Z0-9_]*""".r ^^ (f => f)

def fieldWithFunction: PackratParser[Identifier] =
lazy val fieldWithFunction: PackratParser[Identifier] =
// #284 - see quotedIdentifierUnlessArithmetic.
quotedIdentifierUnlessArithmetic |
identifierWithArithmeticExpression |
Expand All @@ -55,13 +55,14 @@ trait OrderByParser {
identifierWithFunction |
identifier

def sort: PackratParser[FieldSort] =
lazy val sort: PackratParser[FieldSort] =
fieldWithFunction ~ (asc | desc).? ~ nullOrdering.? ^^ { case f ~ o ~ n =>
FieldSort(f, o, n)
}

def orderBy: PackratParser[OrderBy] = OrderBy.regex ~ rep1sep(sort, separator) ^^ { case _ ~ s =>
OrderBy(s)
lazy val orderBy: PackratParser[OrderBy] = OrderBy.regex ~ rep1sep(sort, separator) ^^ {
case _ ~ s =>
OrderBy(s)
}

}
Loading
Loading