Skip to content

Commit

Permalink
Add experimental, initial, draft support for GPML-style graph query (#…
Browse files Browse the repository at this point in the history
…652)

This adds draft parser/AST support for a subset of GPML as outlined by "Graph Pattern Matching in GQL and SQL/PGQ"[1]. The use within the grammar is based on the assumption of a new graph data type being added to the specification of data types within PartiQL, and should be considered experimental until the semantics of the graph data type are specified.

This adds support for parsing:

    basic and abbreviated node and edge patterns (section 4.1 of the GPML paper)
    concatenated path patterns (section 4.2 of the GPML paper)
    graph patterns (i.e., comma separated path patterns) (section 4.3 of the GPML paper)
    AST support for path quantifiers (section 4.4 of the GPML paper)
    AST support for pre-filters (section 5.2 of the GPML paper)

[1]: https://arxiv.org/abs/2112.06217
  • Loading branch information
jpschorr authored Jun 28, 2022
1 parent 6ad876a commit 6ff2b6e
Show file tree
Hide file tree
Showing 9 changed files with 1,096 additions and 8 deletions.
60 changes: 58 additions & 2 deletions lang/resources/org/partiql/type-domains/partiql.ion
Original file line number Diff line number Diff line change
Expand Up @@ -210,12 +210,68 @@ may then be further optimized by selecting better implementations of each operat
// UNPIVOT <expr> [AS <id>] [AT <id>] [BY <id>]
(unpivot expr::expr as_alias::(? symbol) at_alias::(? symbol) by_alias::(? symbol))

// <from_source> JOIN [INNER | LEFT | RIGHT | FULL] <from_source> ON <expr>
(join type::join_type left::from_source right::from_source predicate::(? expr)))
// <from_source> JOIN [INNER | LEFT | RIGHT | FULL] <from_source> ON <expr>
(join type::join_type left::from_source right::from_source predicate::(? expr))

// <expr> MATCH <graph_pattern>
(graph_match expr::expr graph_expr::graph_match_expr))

// Indicates the logical type of join.
(sum join_type (inner) (left) (right) (full))

// The direction of an edge
// | Orientation | Edge pattern | Abbreviation |
// |---------------------------+--------------+--------------|
// | Pointing left | <−[ spec ]− | <− |
// | Undirected | ~[ spec ]~ | ~ |
// | Pointing right | −[ spec ]−> | −> |
// | Left or undirected | <~[ spec ]~ | <~ |
// | Undirected or right | ~[ spec ]~> | ~> |
// | Left or right | <−[ spec ]−> | <−> |
// | Left, undirected or right | −[ spec ]− | − |
//
// Fig. 5. Table of edge patterns:
// https://arxiv.org/abs/2112.06217
(sum graph_match_direction
(edge_left)
(edge_undirected)
(edge_right)
(edge_left_or_undirected)
(edge_undirected_or_right)
(edge_left_or_right)
(edge_left_or_undirected_or_right))

// A part of a graph pattern
(sum graph_match_pattern_part
// A single node in a graph pattern.
(node
predicate::(? expr) // an optional node pre-filter, e.g.: `WHERE c.name='Alarm'` in `MATCH (c WHERE c.name='Alarm')`
variable::(? symbol) // the optional element variable of the node match, e.g.: `x` in `MATCH (x)`
label::(* symbol 0)) // the optional label(s) to match for the node, e.g.: `Entity` in `MATCH (x:Entity)`

// A single edge in a graph pattern.
(edge
direction::graph_match_direction // edge direction
quantifier::(? graph_match_quantifier) // an optional quantifier for the edge match
predicate::(? expr) // an optional edge pre-filter, e.g.: `WHERE t.capacity>100` in `MATCH −[t:hasSupply WHERE t.capacity>100]−>`
variable::(? symbol) // the optional element variable of the edge match, e.g.: `t` in `MATCH −[t]−>`
label::(* symbol 0)) // the optional label(s) to match for the edge. e.g.: `Target` in `MATCH −[t:Target]−>`
// A sub-pattern.
(pattern pattern::graph_match_pattern))

// A quantifier for graph edges or patterns. (e.g., the `{2,5}` in `MATCH (x)->{2,5}(y)`)
(product graph_match_quantifier lower::int upper::(? int))

// A single graph match pattern.
(product graph_match_pattern
quantifier::(? graph_match_quantifier) // an optional quantifier for the entire pattern match
parts::(* graph_match_pattern_part 1)) // the ordered pattern parts

// A graph match clause as defined in GPML
// See https://arxiv.org/abs/2112.06217
(product graph_match_expr patterns::(*graph_match_pattern 1))


// A generic pair of expressions. Used in the `struct`, `searched_case` and `simple_case` expr variants above.
(product expr_pair first::expr second::expr)

Expand Down
1 change: 1 addition & 0 deletions lang/src/org/partiql/lang/ast/StatementToExprNode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ private class StatementTransformer(val ion: IonSystem) {
condition = predicate?.toExprNode() ?: Literal(ion.newBool(true), metaContainerOf(StaticTypeMeta(StaticType.BOOL))),
metas = metas
)
is PartiqlAst.FromSource.GraphMatch -> error("$this node has no representation in prior ASTs.")
}
}

Expand Down
36 changes: 36 additions & 0 deletions lang/src/org/partiql/lang/errors/ErrorCode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,42 @@ enum class ErrorCode(
"expected identifier for alias"
),

PARSE_EXPECTED_IDENT_FOR_MATCH(
ErrorCategory.PARSER,
LOC_TOKEN,
"expected identifier for match"
),

PARSE_EXPECTED_LEFT_PAREN_FOR_MATCH_NODE(
ErrorCategory.PARSER,
LOC_TOKEN,
"expected left parenthesis for match node"
),

PARSE_EXPECTED_RIGHT_PAREN_FOR_MATCH_NODE(
ErrorCategory.PARSER,
LOC_TOKEN,
"expected right parenthesis for match node"
),

PARSE_EXPECTED_LEFT_BRACKET_FOR_MATCH_EDGE(
ErrorCategory.PARSER,
LOC_TOKEN,
"expected left bracket for match edge"
),

PARSE_EXPECTED_RIGHT_BRACKET_FOR_MATCH_EDGE(
ErrorCategory.PARSER,
LOC_TOKEN,
"expected right bracket for match edge"
),

PARSE_EXPECTED_EDGE_PATTERN_MATCH_EDGE(
ErrorCategory.PARSER,
LOC_TOKEN,
"expected edge pattern for match edge"
),

PARSE_EXPECTED_AS_FOR_LET(
ErrorCategory.PARSER,
LOC_TOKEN,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ class GroupByPathExpressionVisitorTransform(

is PartiqlAst.FromSource.Unpivot ->
listOfNotNull(fromSource.asAlias?.text, fromSource.atAlias?.text)

is PartiqlAst.FromSource.GraphMatch ->
TODO("Handle MATCH for GROUP BY")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,8 @@ private object FromSourceToBexpr : PartiqlAst.FromSource.Converter<PartiqlLogica
node.metas
)
}

override fun convertGraphMatch(node: PartiqlAst.FromSource.GraphMatch): PartiqlLogical.Bexpr {
TODO("Support for MATCH")
}
}
11 changes: 8 additions & 3 deletions lang/src/org/partiql/lang/syntax/LexerConstants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -499,15 +499,20 @@ internal val DATE_TIME_PART_KEYWORDS: Set<String> = DateTimePart.values()
"+", "-", "not"
)

/** Operators specific to the `MATCH` clause. */
@JvmField internal val MATCH_OPERATORS = setOf(
"~"
)

/** All operators with special parsing rules. */
@JvmField internal val SPECIAL_OPERATORS = SPECIAL_INFIX_OPERATORS + setOf(
"@"
)

@JvmField internal val ALL_SINGLE_LEXEME_OPERATORS =
SINGLE_LEXEME_BINARY_OPERATORS + UNARY_OPERATORS + SPECIAL_OPERATORS
SINGLE_LEXEME_BINARY_OPERATORS + UNARY_OPERATORS + SPECIAL_OPERATORS + MATCH_OPERATORS
@JvmField internal val ALL_OPERATORS =
BINARY_OPERATORS + UNARY_OPERATORS + SPECIAL_OPERATORS
BINARY_OPERATORS + UNARY_OPERATORS + SPECIAL_OPERATORS + MATCH_OPERATORS

/**
* Operator precedence groups
Expand Down Expand Up @@ -585,7 +590,7 @@ internal const val DIGIT_CHARS = "0" + NON_ZERO_DIGIT_CHARS

@JvmField internal val E_NOTATION_CHARS = allCase("E")

internal const val NON_OVERLOADED_OPERATOR_CHARS = "^%=@+"
internal const val NON_OVERLOADED_OPERATOR_CHARS = "^%=@+~"
internal const val OPERATOR_CHARS = NON_OVERLOADED_OPERATOR_CHARS + "-*/<>|!"

@JvmField internal val ALPHA_CHARS = allCase("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
Expand Down
Loading

0 comments on commit 6ff2b6e

Please sign in to comment.