Skip to content

Commit

Permalink
Merge pull request #1644 from partiql/v1-udop
Browse files Browse the repository at this point in the history
 [V1] Adds basic strategies to compiler (see #1625)
  • Loading branch information
johnedquinn authored Nov 5, 2024
2 parents 9d6d0e2 + 0182d30 commit e2e7735
Show file tree
Hide file tree
Showing 18 changed files with 523 additions and 39 deletions.
86 changes: 86 additions & 0 deletions docs/wiki/v1/compiler.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# PartiQL Compiler Guide

This document is a guide to using the PartiQL compiler.

## Overview

The compiler is responsible for converting logical plans (an _operator_ tree)
to physical plans (an _expr_ tree) by applying _strategies_. A _strategy_ is a class
that has a _pattern_ and an _apply_ method. The pattern determines when to
invoke the _apply_ method which converts the matched operators (logical) to expressions (physical).

## Pattern Matching

A pattern is a tree composed of nodes with one of the types,

```
TYPE: T – match if class T, check children.
ANY: ? - match any node, check children.
ALL: * - match all nodes in the subtree.
```

For backwards compatibility, a pattern will ignore unmatched children.
If this is not the desired behavior, a pattern can be marked as "strict"
which will match children exactly and error on extraneous children.

### Example

Let's combine a limit and offset into a single relational expression; the logical tree looks like this.

```
RelLimit Pattern.match(..)
\
RelOffset Pattern.match(..)
```

We use the builders to create a pattern.

```
Pattern.match(RelLimit::class)
.child(Pattern.match(RelOffset::class))
.build()
```

In practice, the compiler will be walking the tree so we must deal with the inputs which have been recursively compiled.
Because these nodes have been compiled, they are part of the "physical" or "Expr" domain. To illustrate, I've enclosed
the compiled children nodes with `< >` so `ExprValue -> <Value>` and `ExprRelation -> <Rel>`.

Recall the definition of a `RelLimit` and a `RelOffset`

```
* RelLimit(input: Rel, limit: Rex)
* RelOffset(input: Rel, offset: Rex)
```

I have labelled these children in the illustration so that you can see where the end up in the match.

```
...
\
RelLimit
/ \
x:<Value> RelOffset
/ \
y:<Value> z:<Rel>
```

The compiler will look for this pattern in the operator tree, and produce a match like so,

```
Match {
matched: [
RelLimit,
RelOffset,
],
children: [
[x:<Value>],
[y:<Value>, z:<Rel> ]
],
}
```

The matched items are the flattened (in-order traversal) matched nodes from the pattern, while the children
is a nested list of corresponding compiled children.

This match structure is sent to the Strategy which gives the implementor all the information they need to know
to continue folding the tree.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ internal class Pipeline private constructor(
private val planner: PartiQLPlanner,
private val compiler: PartiQLCompiler,
private val ctx: Context,
private val mode: Mode
private val mode: Mode,
) {

/**
Expand Down
19 changes: 19 additions & 0 deletions partiql-eval/api/partiql-eval.api
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ public abstract interface class org/partiql/eval/Statement {
public abstract fun execute ()Lorg/partiql/spi/value/Datum;
}

public class org/partiql/eval/compiler/Match {
public fun <init> (Lorg/partiql/plan/Operator;Ljava/util/List;)V
public fun children (I)Ljava/util/List;
public fun operator (I)Lorg/partiql/plan/Operator;
}

public abstract interface class org/partiql/eval/compiler/PartiQLCompiler {
public static fun builder ()Lorg/partiql/eval/compiler/PartiQLCompiler$Builder;
public fun prepare (Lorg/partiql/plan/Plan;Lorg/partiql/eval/Mode;)Lorg/partiql/eval/Statement;
Expand All @@ -52,6 +58,19 @@ public abstract interface class org/partiql/eval/compiler/PartiQLCompiler {
}

public class org/partiql/eval/compiler/PartiQLCompiler$Builder {
public fun addStrategy (Lorg/partiql/eval/compiler/Strategy;)Lorg/partiql/eval/compiler/PartiQLCompiler$Builder;
public fun build ()Lorg/partiql/eval/compiler/PartiQLCompiler;
}

public class org/partiql/eval/compiler/Pattern {
public fun <init> (Ljava/lang/Class;)V
protected fun <init> (Ljava/lang/Class;Ljava/util/function/Predicate;)V
public fun matches (Lorg/partiql/plan/Operator;)Z
}

public abstract class org/partiql/eval/compiler/Strategy {
public fun <init> (Lorg/partiql/eval/compiler/Pattern;)V
public abstract fun apply (Lorg/partiql/eval/compiler/Match;)Lorg/partiql/eval/Expr;
public fun getPattern ()Lorg/partiql/eval/compiler/Pattern;
}

10 changes: 5 additions & 5 deletions partiql-eval/src/main/java/org/partiql/eval/Environment.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ public String toString() {
sb.append("$i: $row");
sb.append("---------------------");
}
// if (scope.isEmpty()) {
// appendLine("empty")
// appendLine("---------------------")
// }
return "stack";
if (stack.length == 0) {
sb.append("empty\n");
sb.append("---------------------\n");
}
return sb.toString();
}
}
50 changes: 50 additions & 0 deletions partiql-eval/src/main/java/org/partiql/eval/compiler/Match.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.partiql.eval.compiler;

import org.jetbrains.annotations.NotNull;
import org.partiql.eval.Expr;
import org.partiql.plan.Operator;

import java.util.Collections;
import java.util.List;

/**
* Match represents a subtree match to be sent to the
*/
public class Match {

private final Operator[] operators;
private final List<List<Expr>> children;

/**
* Single operator match with zero-or-more inputs.
*
* @param operator matched logical operator.
* @param children compiled child operators.
*/
public Match(@NotNull Operator operator, @NotNull List<Expr> children) {
this.operators = new Operator[]{operator};
this.children = Collections.singletonList(children);
}

/**
* Get the i-th operator (pre-order) matched by the pattern.
*
* @param i 0-indexed
* @return Operator
*/
@NotNull
public Operator operator(int i) {
return operators[i];
}

/**
* Get the i-th input to this pattern.
*
* @param i 0-indexed
* @return Expr
*/
@NotNull
public List<Expr> children(int i) {
return children.get(i);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@
import org.partiql.plan.Plan;
import org.partiql.spi.Context;

import java.util.ArrayList;
import java.util.List;

/**
* TODO JAVADOC
* PartiQLCompiler is responsible for transforming PartiQL plans to an executable statement (think physical planner).
*/
public interface PartiQLCompiler {

Expand All @@ -26,7 +29,6 @@ default Statement prepare(@NotNull Plan plan, @NotNull Mode mode) {
@NotNull
public Statement prepare(@NotNull Plan plan, @NotNull Mode mode, @NotNull Context ctx);


/**
* @return A new [PartiQLCompilerBuilder].
*/
Expand All @@ -47,15 +49,29 @@ public static PartiQLCompiler standard() {
*/
public static class Builder {

//
private final List<Strategy> strategies = new ArrayList<>();

private Builder() {
// empty
}

/**
* Adds a strategy to the compiler.
*
* @param strategy The strategy to add.
* @return this.
*/
public Builder addStrategy(Strategy strategy) {
strategies.add(strategy);
return this;
}

/**
* @return A new [PartiQLCompiler].
*/
public PartiQLCompiler build() {
return new StandardCompiler();
return new StandardCompiler(strategies);
}
}
}
48 changes: 48 additions & 0 deletions partiql-eval/src/main/java/org/partiql/eval/compiler/Pattern.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.partiql.eval.compiler;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.partiql.plan.Operator;

import java.util.function.Predicate;

/**
* Pattern defines a tree pattern.
*/
public class Pattern {

@NotNull
private final Class<? extends Operator> clazz;

@Nullable
private final Predicate<Operator> predicate;

/**
* The only public method to create a pattern for now is this single-node match.
*
* @param clazz Operator class.
*/
public Pattern(@NotNull Class<? extends Operator> clazz) {
this.clazz = clazz;
this.predicate = null;
}

/**
* Internal constructor for simple patterns.
*
* @param clazz root type.
* @param predicate optional predicate.
*/
protected Pattern(@NotNull Class<? extends Operator> clazz, @Nullable Predicate<Operator> predicate) {
this.clazz = clazz;
this.predicate = predicate;
}

public boolean matches(Operator operator) {
if (!clazz.isInstance(operator)) {
return false;
}
return predicate == null || predicate.test(operator);
}

}
41 changes: 41 additions & 0 deletions partiql-eval/src/main/java/org/partiql/eval/compiler/Strategy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.partiql.eval.compiler;

import org.jetbrains.annotations.NotNull;
import org.partiql.eval.Expr;
import org.partiql.plan.Operator;

/**
* Strategy converts a logical operator into a physical operator. The compiler uses the list of operands
* to determine a subtree match, then invokes `apply` to produce an {@link Expr}.
*/
public abstract class Strategy {

@NotNull
private final Pattern pattern;

/**
* Create a strategy for a given pattern.
*
* @param pattern strategy pattern.
*/
public Strategy(@NotNull Pattern pattern) {
this.pattern = pattern;
}

/**
* @return the pattern associated with this strategy
*/
@NotNull
public Pattern getPattern() {
return pattern;
}

/**
* Applies the strategy to a logical plan operator and returns the physical operation (expr).
*
* @param match holds the matched operators
* @return the physical operation
*/
@NotNull
public abstract Expr apply(@NotNull Match match);
}
Loading

0 comments on commit e2e7735

Please sign in to comment.