### ----------------------------------------------------
### -- SQL::Abstract
### -- Licenses: Artistic-2.0
### -- Authors: Leon Timmermans
### -- File: lib/SQL/Abstract.rakumod
### ----------------------------------------------------
### -- Chunk 1 of 4
use v6.d;

unit class SQL::Abstract:ver<0.0.9>:auth<zef:leont>;

use fatal;

use SQL::Placeholders;
use SQL::Query;

enum Precedence <Rowlike Comma Assignment And Or Not Between In Postfix Comparative Bitwise Additive Multiplicative Concatlike Prefix Termlike>;

class Op::Not { ... }
class Identifier { ... }
class Expression::Renamed { ... }

our proto expand-capture(|) { * }
role Expression {
	method precedence(--> Precedence) { ... }

	method negate(--> Expression) {
		Op::Not.new(:value(self));
	}
	method as(Identifier(Any) $alias) {
		Expression::Renamed.new(:source(self), :$alias);
	}

	multi method COERCE(Capture $capture) {
		expand-capture(|$capture);
	}
}

our proto expand-expression(|) { * }
multi expand-expression(Expression:D $expression) {
	$expression;
}
multi expand-expression(Capture $capture) {
	expand-capture(|$capture);
}

role Term does Expression {
	method precedence(--> Precedence::Termlike) {}
}

class Literal does Expression {
	has Precedence $.precedence = Precedence::Termlike;
	has Str:D $.payload is required;
	has Any:D @.arguments;

	multi method COERCE(Capture $capture) {
		my ($payload, @arguments, *%args) = |$capture;
		self.new(:$payload, :@arguments, |%args);
	}
}

class Integer does Term {
	has Int:D $.value is required;

	multi method COERCE(Int:D $value) {
		self.new(:$value);
	}
}

multi expand-capture(Integer(Int:D) :$int) {
	$int;
}

class String does Term {
	has Str:D $.value is required;

	multi method COERCE(Str:D $value) {
		self.new(:$value);
	}
}

multi expand-capture(String(Str:D) :$str) {
	$str;
}

class Placeholder does Term {
	has Any $.value;

	multi method COERCE(Any $value) {
		self.bless(:$value);
	}
}

multi expand-expression(Any:D $value) {
	Placeholder.new(:$value);
}
multi expand-capture(Placeholder(Any) :$bind!) {
	$bind;
}

role Constant[Str $keyword] does Term {
	method Str() {
		$keyword;
	}
	method WHICH() {
		ValueObjAt.new("SQL::Abstract::Constant|$keyword");
	}
}

class Value::Default does Constant['DEFAULT'] {}
multi expand-capture(Bool :$default!) { Value::Default.new }
class Value::True    does Constant['TRUE']    {}
multi expand-capture(Bool :$true!) { $true ?? Value::True.new !! Value::False.new }
class Value::False   does Constant['FALSE']   {}
multi expand-capture(Bool :$false!) { $false ?? Value::False.new !! Value::True.new }
class Value::Null    does Constant['NULL']    {}
multi expand-capture(Bool :$null!) { Value::Null.new }

multi expand-expression(Any:U) {
	Value::Null.new;
}
multi expand-capture(Str $payload, *@arguments, *%args) {
	Literal.new(:$payload, :@arguments, |%args);
}
multi expand-capture(:literal($) ($payload, *@arguments), *%args) {
	Literal.new(:$payload, :@arguments, |%args);
}

my role Prefixable does Term {
	method parts(--> List) { ... }
}

class Identifier does Prefixable {
	my role Element {
		has Str:D $.payload is required;
		method quoted(Bool $quoted) { ... }
	}
	class Quotable does Element {
		method quoted(Bool $quoted) {
			$quoted || $!payload ~~ /\W/ ?? '"' ~ $!payload.subst('"', '""', :g) ~ '"' !! $!payload;
		}
	}
	class Literal does Element {
		method quoted(Bool) {
			return $!payload;
		}
	}
	has Element @.parts;

	multi quote(Str $payload) {
		Quotable.new(:$payload);
	}
	multi quote(Whatever) {
		Literal.new(:payload<*>);
	}
	method new(Cool $name) {
		my @parts = $name.split('.').map(&quote);
		self.bless(:@parts);
	}
	multi method new-from-list(@list) {
		my @parts = @list.map(&quote);
		self.bless(:@parts);
	}

	method concat(Prefixable $other) {
		my @parts = |@!parts, |$other.parts;
		self.bless(:@parts);
	}

	method quoted(Bool $quoted) {
		@!parts.map(*.quoted($quoted)).join('.');
	}
	method Str() {
		self.quoted(True);
	}
	method WHICH() {
		ValueObjAt.new("{ self.WHAT.^name }|{ self }");
	}
}

multi expand-capture(Identifier(Str) :$ident!) {
	$ident;
}
multi expand-capture(:@ident!) {
	Identifier.new-from-list(@ident);
}

class Expression::Renamed does Expression {
	method precedence(--> Precedence::Comma) {}

	has Identifier:D $.alias is required;
	has Expression:D $.source is required;

	multi method COERCE(Pair (Identifier(Any) :key($alias), Expression :value($source))) {
		self.new(:$alias, :$source);
	}

	method as(Identifier(Any) $alias) {
		Expression::Renamed.new(:$!source, :$alias);
	}
}

role Value::List does Expression {
	method precedence(--> Precedence::Rowlike) {}

	method elements() { ... }

	multi method merge(::?CLASS:D: ::?CLASS:D $other) {
		my @elements = flat self.elements, $other.elements;
		self.bless(:@elements);
	}
	multi method merge(::?CLASS:U: ::?CLASS:D $other) {
		self.bless(:elements($other.elements));
	}
	multi method merge(::?CLASS: ::?CLASS:U $other) {
		self;
	}
}

class Identifiers does Value::List {
	has Identifier:D @.elements is required;

	multi method COERCE(Identifier(Any) $column) {
		self.new(:elements[$column]);
	}
	multi method COERCE(@list) {
		my Identifier(Any) @elements = @list;
		self.new(:@elements);
	}
	multi method COERCE(List:U $list) {
		Identifiers;
	}
}

multi expand-capture(Identifiers(Any) :$idents!) {
	$idents;
}

class Function { ... }

class Star does Prefixable does Constant['*'] {
	method parts() {
		Identifier::Literal.new(:payload<*>);
	}
}

multi expand-capture(Bool :$star!) {
	Star.new;
}

class Column::List does Value::List {
	has Expression:D @.elements is required;

	our proto to-column(|) { * }
	multi to-column(Expression $column) {
		$column;
	}
	multi to-column(Identifier(Str) $column) {
		$column;
	}
	multi to-column(Pair $ (:$key, Pair :$value)) {
		my $function = Function.COERCE({ :name($value.key), :arguments($value.value) });
		Expression::Renamed.COERCE($key => $function);
	}
	multi to-column(Pair $ (:$key, :$value)) {
		Expression::Renamed.COERCE($key => to-column($value));
	}
	multi to-column(Whatever) {
		Star.new;
	}
	multi to-column(Map $map (Str :$function!, :%over!, *%args)) {
		Function.COERCE({ :$function, |%args }).over(|%over);
	}
	multi to-column(Map $map (Str :$function!, Str :$over!, *%args)) {
		Function.COERCE({ :$function, |%args }).over($over);
	}
	multi to-column(Function(Map) $function) {
		$function;
	}
	multi to-column(Capture $capture) {
		expand-capture(|$capture);
	}

	multi method COERCE(Any $column) {
		self.new(:elements[to-column($column)]);
	}
	multi method COERCE(@list) {
		my @elements = @list.map(&to-column);
		self.new(:@elements);
	}
	multi method COERCE(Identifiers $elements) {
		self.new(:elements($elements.elements));
	}

	multi method COERCE(Capture $capture) {
		my $expression = expand-capture(|$capture);
		$expression ~~ Column::List ?? $expression !! self.new(:elements($expression));
	}
}

multi expand-capture(Column::List(Any) :$columns!) {
	$columns;
}

class Row does Value::List {
	has Expression:D @.elements is required;

	multi method COERCE(@values) {
		my Expression @elements = @values.map(&expand-expression);
		self.new(:@elements);
	}

	method add-columns(Expression @columns) {
		my @elements = flat @!elements, @columns;
		self.new(:@elements);
	}
}

multi expand-capture(Row(List) :$row!) {
	$row;
}

class Rows {
	has Row @.elements is required;

	multi method COERCE(@input) {
		my Row(Any) @elements = @input;
		self.new(:@elements);
	}

	method add-columns(Expression @columns) {
		my @elements = @!elements.map(*.add-columns(@columns));
		self.new(:@elements);
	}
}

role Op::HasLeft does Expression {
	has Expression:D $.left is required;
}

role Op::Postfix does Op::HasLeft {
	method precedence(--> Precedence::Postfix) {}

	method postfix(--> Str) { ... }
}

class Op::IsNull does Op::Postfix {
	has Bool $.negated;
	method postfix() {
		$!negated ?? 'IS NOT NULL' !! 'IS NULL';
	}
	method negate {
		Op::IsNull.new(:$!left, :negated(!$!negated));
	}
}

role Op::Prefix[Str $operator] does Expression {
	has Expression:D $.value is required;
	method precedence(--> Precedence::Prefix) {}
	method operator(--> Str:D) { $operator }
}

class Op::Positive does Op::Prefix['+'] {}
class Op::Negative does Op::Prefix['-'] {}
class Op::Complement does Op::Prefix['~'] {}

class Op::Not does Op::Prefix['NOT'] {
	method precedence(--> Precedence::Not) {}

	method negate(--> Bool) {
		$!value;
	}
}

my %prefix-op-for = (
	'+' => Op::Positive,
	'-' => Op::Negative,
	'~' => Op::Complement,
	'not' => Op::Not,
);

multi expand-capture(:op(@) (Str $op where { %prefix-op-for{$op}:exists }, $expr)) {
	my $value = expand-expression($expr);
	%prefix-op-for{$op}.new(:$value);
}

role Op::Binary does Expression {
	method operator() { ... }

	has Expression:D $.left is required;
	has Expression:D $.right is required;
}

role Op::Comperative[Str $operator] does Op::Binary {
	method precedence(--> Precedence::Comparative) {}
	method operator(--> Str) { $operator }
}

class Op::Unequals does Op::Comperative['<>'] {}
class Op::Equals does Op::Comperative['='] {
	method negate() {
		Op::Unequals.new($!left, $!right);
	}
}

class Op::LessThan does Op::Comperative['<'] {}
class Op::GreaterOrEqual does Op::Comperative['>='] {}

class Op::LessOrEqual does Op::Comperative['<='] {}
class Op::GreaterThan does Op::Comperative['>'] {}

class Op::Distinct does Op::Comperative['IS DISTINCT FROM'] {}
class Op::NotDistinct does Op::Comperative['IS NOT DISTINCT FROM'] {}

role Op::Liking[Bool $positive] does Expression {
	method precedence(--> Precedence::Comparative) {}
	method negated() { !$positive }

	has Expression:D $.left is required;
	has Expression:D $.right is required;
	has Expression $.escape;
}
class Op::Like does Op::Liking[True] {}
class Op::NotLike does Op::Liking[False] {}

class Op::Assign does Op::Binary {
	method precedence(--> Precedence::Assignment) {}

	method operator(--> '=') {}
}

role Op::Concatenative[Str $operator] does Op::Binary {
	method precedence(--> Precedence::Concatlike) {}
	method operator() { $operator }
}

class Op::Concat does Op::Concatenative['||'] {}

class Op::JSON::GetElem does Op::Concatenative['->'] {}
class Op::JSON::GetElemText does Op::Concatenative['->>'] {}
class Op::JSON::GetPath does Op::Concatenative['#>'] {}
class Op::JSON::GetPathText does Op::Concatenative['#>>'] {}

role Op::Additive[Str $operator] does Op::Binary {
	method precedence(--> Precedence::Additive) {}
	method operator() { $operator }
}
class Op::Plus does Op::Additive['+'] {}
class Op::Minus does Op::Additive['-'] {}

role Op::Multiplicative[Str $operator] does Op::Binary {
	method precedence(--> Precedence::Multiplicative) {}
	method operator() { $operator }
}
class Op::Times does Op::Multiplicative['*'] {}
class Op::Divide does Op::Multiplicative['/'] {}
class Op::Rest does Op::Multiplicative['%'] {}

role Op::Bitwise[Str $operator] does Op::Binary {
	method precedence(--> Precedence::Bitwise) {}
	method operator() { $operator }
}
class Op::Bitwise::And does Op::Bitwise['&'] {}
class Op::Bitwise::Or does Op::Bitwise['&'] {}
class Op::Bitwise::Left does Op::Bitwise['<<'] {}
class Op::Bitwise::Right does Op::Bitwise['>>'] {}

my %binary-op-for =
	'||'           => Op::Concat,
	'->'           => Op::JSON::GetElem,
	'->>'          => Op::JSON::GetElemText,
	'#>'           => Op::JSON::GetPath,
	'#>>'          => Op::JSON::GetPathText,
	'*'            => Op::Times,
	'/'            => Op::Divide,
	'%'            => Op::Rest,
	'+'            => Op::Plus,
	'-'            => Op::Minus,
	'&'            => Op::Bitwise::And,
	'|'            => Op::Bitwise::Or,
	'<<'           => Op::Bitwise::Left,
	'>>'           => Op::Bitwise::Right,
	'='            => Op::Equals,
	'!='           => Op::Unequals,
	'<>'           => Op::Unequals,
	'<'            => Op::LessThan,
	'<='           => Op::LessOrEqual,
	'>'            => Op::GreaterThan,
	'>='           => Op::GreaterOrEqual,
	'like'         => Op::Like,
	'not-like'     => Op::NotLike,
	'distinct'     => Op::Distinct,
	'not-distinct' => Op::NotDistinct;

multi expand-capture(:op(@) (Str $op where 'like'|'not-like', $left-expr, $right-expr, $escape-expr)) {
	my $left = expand-expression($left-expr);
	my $right = expand-expression($right-expr);
	my $escape = expand-expression($escape-expr);
	my $class = $op eq 'like' ?? Op::Like !! Op::NotLike;
	$class.new(:$left, :$right, :$escape);
}

role Op::BetweenLike[Bool $positive] does Expression {
	method precedence(--> Precedence::Between) {}

	method negated(--> Bool) { !$positive }

	has Expression:D $.left is required;
	has Expression:D $.min is required;
	has Expression:D $.max is required;
}

class Op::NotBetween does Op::BetweenLike[False] {}
class Op::Between does Op::BetweenLike[True] {
	method negate() {
		Op::NotBetween.new(:$!left, :$!min, :$!max);
	}
}

multi expand-capture(:op(@) (Str $op where 'between'|'not-between', $column-expr, $left-expr, $right-expr)) {
	my $column = expand-expression($column-expr);
	my $left = expand-expression($left-expr);
	my $right = expand-expression($right-expr);
	Op::Between.new(:$left, :$right);
}

role Op::Logical[Str $operator, Precedence $precedence, Constant $empty] does Expression {
	has Expression:D @.elements is required;

	method precedence(--> Precedence) { $precedence }
	method operator(--> Str) { $operator }

	method pack(*@elements) {
		given @elements.elems {
			when 1  { @elements[0] }
			when 0  { $empty.new }
			default { self.new(:@elements) }
		}
	}

	method unpack(Expression:D $expression) {
		given $expression {
			when self   { |$expression.elements }
			when $empty { Empty }
			default     { $expression }
		}
	}

	method merge(*@expressions) {
		my @unpacked = @expressions.map: { self.unpack($^expression) };
		self.pack(@unpacked);
	}
}
class Op::And does Op::Logical['AND', Precedence::And, Value::True] {}
class Op::Or  does Op::Logical['OR' , Precedence::Or, Value::False] {}

multi expand-capture(:@and) {
	my @elements = @and.map: { expand-expression($^item) };
	Op::And.pack(@elements);
}
multi expand-capture(:@or) {
	my @elements = @or.map: { expand-expression($^item) };
	Op::Or.pack(@elements);
}


role Op::InLike does Expression {
	method precedence(--> Precedence::In) {}
}

role Op::In does Op::InLike does Op::HasLeft {
	has Bool $.negated;
}

class Op::In::List does Op::In {
	has Row:D $.values is required handles<elements>;

	method create(Expression :$left, Row(List) :$values, Bool :$negated = False) {
		self.new(:$left, :$values, :$negated);
	}

	method negate() {
		self.bless(:$!left, :$!values, :negated(!$!negated));
	}
}

role Query { ... }
class Select { ... }

role Op::Subquery does Op::InLike {
	has Query:D $.query is required;
	method operator() { ... }
}

class Op::In::Query does Op::In does Op::Subquery {
	method operator { $!negated ?? 'NOT IN' !! 'IN' }

	method negate() {
		self.new($!left, $!query, :negated(!$!negated));
	}
}

multi expand-capture(:@op ('in', $left-expr, Capture (Select(Map) :$select!))) {
	my $left = expand-expression($left-expr);
	Op::In::Query.new(:$left, :query($select));
}
multi expand-capture(:@op ('in', $left-expr, *@values)) {
	my $left = expand-expression($left-expr);
	Op::In::List.create(:$left, :@values);
}
multi expand-capture(:@op ('not-in', $left-expr, Capture (Select(Map) :$select!))) {
	my $left = expand-expression($left-expr);
	Op::In::Query.new(:$left, :query($select), :negated);
}
multi expand-capture(:@op ('not-in', $left-expr, *@values)) {
	my $left = expand-expression($left-expr);
	Op::In::List.create(:$left, :@values, :negated);
}

class Op::Exists does Op::Subquery {
	has Bool $.negated;
	method operator() { $!negated ?? 'NOT EXISTS' !! 'EXISTS' }

	method negate() {
		self.new(:$!query, :negated(!$!negated));
	}
}

multi expand-capture(Select(Map) :$exists) {
	Op::Exists.new(:query($exists));
}
multi expand-capture(Select(Map) :$not-exists) {
	Op::Exists.new(:query($not-exists), :negated);
}

class Op::Cast does Term {
	has Expression:D $.primary is required;
	has Str:D $.typename is required;
}

multi expand-capture(:@op ('cast', Any $expression, Str $typename)) {
	my $primary = expand-expression($expression);
	Op::Cast.new(:$primary, $typename);
}

class Op::Case does Expression {
	method precedence(--> Precedence::Between) {}

	has Expression $.left;
	class When {
		has Expression:D $.condition is required;
		has Expression:D $.then is required;

		method COERCE(Pair $pair) {
			my $condition = expand-expression($pair.key);
			my $then = expand-expression($pair.value);
			Op::Case::When.new(:$condition, :$then);
		}
	}
	has When:D @.whens;
	has Expression $.else;
}

multi expand-capture(:@case is copy) {
	my $left = @case[0] !~~ Pair ?? expand-expression(@case.shift) !! Expression;
	my $else = @case[*-1] !~~ Pair ?? expand-expression(@case.pop) !! Expression;
	my Op::Case::When(Pair) @whens = @case;
	Op::Case.new(:$left, :@whens, :$else);
}
multi expand-capture(:op(@case) where @case[0] eq 'case') {
	my ($case, $left-expr, @args) = |@case;
	my $left = expand-expression($left-expr);
	my $else = @args[*-1] !~~ Pair ?? expand-expression(@args.pop) !! Expression;
	my Op::Case::When(Pair) @whens = @args;
	Op::Case.new(:$left, :@whens, :$else);
}

multi expand-capture(:op(@) (Str $key, $left-expr, $right-expr)) {
	my Op::Binary:U $class = %binary-op-for{$key}:exists ?? %binary-op-for{$key} !! Op::Comperative[$key];
	my $left = expand-expression($left-expr);
	my $right = expand-expression($right-expr);
	$class.new(:$left, :$right);
}

class Current does Expression {
	method precedence(--> Precedence::Between) {}
	has Identifier:D $.cursor is required;
}

multi expand-capture(Identifier(Any) :$current!) {
	Current.new(:cursor($current));
}

role Conditional {
	has Expression:D $.expression is required;

	method new(:@expressions) {
		my $expression = Op::And.pack(@expressions);
		self.bless(:$expression);
	}

	multi method merge(Conditional:D: Conditional:D $other) {
		my $expression = Op::And.merge($!expression, $other.expression);
		self.bless(:$expression);
	}
	multi method merge(Conditional:U: Conditional:D $other) {
		self.bless(:expression($other.expression));
	}
	multi method merge(Conditional: Conditional:U $other) {
		self;
	}
}

class Conditions does Conditional {
	multi expand-partial(Expression:D $left, Capture:D (:@op) ) {
		my ($op, *@args) = @op;
		my $capture = \(:op[ $op, $left, |@args ]);
		expand-capture(|$capture);
	}
	multi expand-partial(Expression:D $left, Capture:D $capture (Bool :$null)) {
		Op::IsNull.new(:$left, :negated(!$null));
	}
	multi expand-partial(Expression:D $left, Capture:D $capture (Bool :$true)) {
		my $op = $true ?? $left !! Op::Not.new(:value($left));
	}
	multi expand-partial(Expression:D $left, Capture:D $capture (Bool :$false)) {
		my $op = $false ?? Op::Not.new(:value($left)) !! $left;
	}
	multi expand-partial(Expression:D $left, Capture:D (:@and) ) {
		my @elements = @and.map: { expand-partial($left, $^item) };
		Op::And.pack(@elements);
	}
	multi expand-partial(Expression:D $left, Capture:D (:@or) ) {
		my @elements = @or.map: { expand-partial($left, $^item) };
		Op::Or.pack(@elements);
	}
	multi expand-partial(Expression:D $left, Capture:D $capture) {
		Op::Equals.new(:$left, :right(expand-capture(|$capture)));
	}
	multi expand-partial(Expression:D $left, Any:D $expression) {
		Op::Equals.new(:$left, :right(expand-expression($expression)));
	}
	multi expand-partial(Expression:D $left, Any:U $) {
		Op::IsNull.new(:$left);
	}
	multi expand-partial(Expression:D $left, Constant:U $constant) {
		Op::Equals.new(:$left, :right($constant.new));
	}
	multi expand-partial(Expression:D $left, Constant $constant where Value::Null) {
		Op::IsNull.new(:$left);
	}

	multi expand-pair(Expression:D $left, 'null', Bool $positive) {
		Op::IsNull.new(:$left, :negated(!$positive));
	}
	multi expand-pair(Expression:D $left, 'in', @values) {
		Op::In::List.create(:$left, :@values);
	}
	multi expand-pair(Expression:D $left, 'not-in', @values) {
		Op::In::List.create(:$left, :@values, :negated);
	}
	multi expand-pair(Expression:D $left, 'and', @and) {
		my @elements = @and.map: { expand-partial($left, $^item) };
		Op::And.pack(@elements);
	}
	multi expand-pair(Expression:D $left, 'or', @or) {
		my @elements = @or.map: { expand-partial($left, $^item) };
		Op::Or.pack(@elements);
	}
	multi expand-pair(Expression:D $left, Str $key, Any:D $value) {
		my $right = expand-expression($value);
		my $class = %binary-op-for{$key}:exists ?? %binary-op-for{$key} !! Op::Comperative[$key];
		$class.new(:$left, :$right);
	}
	multi expand-partial(Expression:D $left, Pair (:$key, :$value)) {
		expand-pair($left, $key, $value);
	}

	multi expand-partial(Expression:D $left, Range $range) {
		my $min = expand-expression($range.min);
		my $max = expand-expression($range.max);
		Op::Between.new(:$left, :$min, :$max);
	}

	multi expand-junction(Expression:D $left, 'any', @partials) {
		Op::Or.pack(@partials);
	}
	multi expand-junction(Expression:D $left, 'any', @partials where @partials > 0 && all(@partials) ~~ Op::Equals && all(@partials).left === $left) {
		Op::In::List.create(:$left, :values(@partials».right));
	}
	multi expand-junction(Expression:D $left, 'all', @partials) {
		Op::And.pack(@partials);
	}
	multi expand-junction(Expression:D $left, 'none', @partials) {
		Op::Or.pack(@partials).negate;
	}
	multi expand-junction(Expression:D $left, 'none', @partials where @partials > 0 && all(@partials) ~~ Op::Equals && all(@partials).left === $left) {
		Op::In::List.create(:$left, :values(@partials».right), :negated);
	}
	multi expand-junction(Expression:D $left, 'one', @partials) {
		my @comparisons = @partials.map: { Op::Cast.new(:$^primary, :typename<INTEGER>) };
		my $addition = @comparisons.reduce: { Op::Plus.new(:$^left, :$^right) };
		Op::Equals.new(:left($addition), :right(Integer.new(:1value)));
	}
	multi expand-partial(Expression:D $left, Junction $junction) {
		use nqp;
		my $type = nqp::box_s(nqp::getattr($junction, Junction, '$!type'), Str);
		my @eigenstates = nqp::getattr($junction, Junction, '$!eigenstates').List;
		expand-junction($left, $type, @eigenstates.map({ expand-partial($left, $^value) }));
	}

	multi expand-condition(Pair (Identifier(Any) :$key, Mu :$value)) {
		expand-partial($key, $value);
	}
	multi expand-condition(Pair (Expression :$key, Mu :$value)) {
		expand-partial($key, $value);
	}

	multi method COERCE(Expression:D $expression) {
		self.bless(:$expression);
	}
	multi method COERCE(Pair $pair) {
		my @expressions = expand-condition($pair);
		self.new(:@expressions);
	}
	multi method COERCE(@list) {
		my @expressions = @list.flatmap(&expand-condition);
		self.new(:@expressions);
	}
	multi method COERCE(%hash) {
		samewith(%hash.sort);
	}
	multi method COERCE(Capture $capture) {
		my $expression = expand-capture(|$capture);
		self.bless(:$expression);
	}
}

class Order::Modifier does Expression {
	method precedence(--> Precedence::Comma) {}

	enum Sorting (:Asc<asc> :Desc<desc>);
	enum Nulls (:First<first>, :Last<last>);
	has Expression:D $.column is required;
	has Sorting $.order is required;
	has Nulls $.nulls;

	multi method COERCE(Pair (Expression :key($column), Sorting(Str) :value($order))) {
		self.new(:$column, :$order);
	}
	multi method COERCE(Pair (Identifier(Any:D) :key($column), Sorting(Str) :value($order))) {
		self.new(:$column, :$order);
	}
	multi method COERCE(Map (Expression :$column!, Sorting(Str) :$order, Nulls(Str) :$nulls)) {
		self.new(:$column, :$order, :$nulls);
	}
	multi method COERCE(Map (Identifier(Any) :$column!, Sorting(Str) :$order, Nulls(Str) :$nulls)) {
		self.new(:$column, :$order, :$nulls);
	}
}

class OrderBy {
	has Expression:D @.elements is required;

	multi to-sorter(Expression $expression) {
		$expression;
	}
	multi to-sorter(Identifier(Cool) $ident) {
		$ident;
	}
	multi to-sorter(Identifier(Capture) $ident) {
		$ident;
	}
	multi to-sorter(Order::Modifier(Pair) $modifier) {
		$modifier;
	}
	multi to-sorter(Order::Modifier(Map) $modifier) is default {
		$modifier;
	}

	multi method COERCE(Any $sorter) {
		self.new(:elements[to-sorter($sorter)]);
	}
	multi method COERCE(@list) {
		my @elements = @list.map(&to-sorter);
		self.new(:@elements);
	}
}

class Assigns {
	has Pair @.pairs;

	multi transform-pair(Pair (Expression :$key, :$value)) {
		my $expression = expand-expression($value);
		$key => $expression;
	}
	multi transform-pair(Pair (Capture :$key, :$value)) {
		my $ident = expand-expression($key);
		my $expression = expand-expression($value);
		$ident => $expression;
	}
	multi transform-pair(Pair (Identifier(Any) :$key, :$value)) {
		my $expression = expand-expression($value);
		$key => $expression;
	}

	multi method COERCE(@list) {
		my @pairs = @list.unique(:as(*.key)).map(&transform-pair);
		self.new(:@pairs);
	}
	multi method COERCE(%set) {
		my @pairs = %set.sort(*.key).map(&transform-pair);
		self.new(:@pairs);
	}
	multi method COERCE(Pair $pair) {
		my @pairs = transform-pair($pair);
		self.new(:@pairs);
	}

	method assignments() {
		@!pairs.map: { Op::Assign.new(:left($^pair.key), :right($^pair.value)) };
	}

	method keys() {
		@!pairs.map(*.key);
	}
	method values() {
		@!pairs.map(*.value);
	}
	method empty() {
		not @!pairs;
	}

	method merge(Assigns $other) {
		my @pairs = |@!pairs, |$other.pairs;
		self.new(:@pairs);
	}

	method without(Identifiers $exclude) {
		my $none = none($exclude.elements);
		my @pairs = @!pairs.grep: -> (:$key, :$value) { $key === $none };
		self.new(:@pairs);
	}
}

enum Quantifier (:All<all> :Distinct<distinct>);

package Window {
	class Function { ... }
	class Definition { ... }
}
class Source::Function { ... }

class Function does Expression {
	has Str:D $.function is required;
	has Quantifier $.quantifier;
	has Value::List:D $.arguments is required;
	has OrderBy $.order-by;
	has Conditions $.filter;

	multi method COERCE(Map (Str :$function!, Column::List(Any) :$arguments = (), Conditions(Any) :$filter, Quantifier(Str) :$quantifier, OrderBy(Any) :$order-by)) {
		Function.new(:$function, :$arguments, :$filter, :$quantifier, :$order-by);
	}
	multi method COERCE(Pair (Str :$key, Column::List(Any) :$value)) {
		Function.new(:function($key), :arguments($value));
	}

	method precedence() {
		$!filter ?? Precedence::Between !! Precedence::Termlike;
	}

	multi method over(Str $existing) {
		my $window = Window::Definition.COERCE(:$existing);
		Window::Function.new(:function(self), :$window);
	}
	multi method over(*%arguments) {
		my $window = Window::Definition.COERCE(%arguments);
		Window::Function.new(:function(self), :$window);
	}

	method as-source(Identifier(Any) $alias, Identifiers(Cool) $columns?, Bool :$lateral, Bool :$ordinal) {
		Source::Function.new(:function(self), :$alias, :$columns, :$lateral, :$ordinal);
	}
}

multi expand-capture(Function(Map) :$function!) {
	$function;
}
multi expand-capture(Map :function($)! (Str :$over, *%args)) {
	Function.COERCE(%args).over($over);
}
multi expand-capture(Map :function($)! (:%over, *%args)) {
	Function.COERCE(%args).over(|%over);
}

package Window {
	enum Exclusion (:CurrentRow('current row'), :Group<group>, :Ties<ties>, :NoOthers('no others'));
	enum Mode (:Rows<rows> :Range<range> :Groups<groups>);

	class Boundary::Preceding::Unbounded { ... }
	class Boundary::Preceding::Bounded { ... }
	class Boundary::CurrentRow { ... }
	class Boundary::Following::Bounded { ... }
	class Boundary::Following::Unbounded { ... }

	role Boundary {
#		method tag(--> Str) { ... }
		multi method COERCE('preceding') { Boundary::Preceding::Unbounded.new }
		multi method COERCE('current') { Boundary::CurrentRow.new }
		multi method COERCE('following') { Boundary::Following::Unbounded.new }
		multi method COERCE(Pair (Str :$key where 'preceding', Any :$value)) {
			my $offset = expand