### ----------------------------------------------------
### -- SQL::Abstract
### -- Licenses: Artistic-2.0
### -- Authors: Leon Timmermans
### -- File: lib/SQL/Abstract.rakumod
### ----------------------------------------------------
### -- Chunk 3 of 4
		@inner.push: self.render-order-by($placeholders, $definition.order-by);
		@inner.push: self.render-window-frame($placeholders, $definition.frame) with $definition.frame;
		parenthesize-list(@inner);
	}
	multi method render-window-definition(Placeholders $placeholders, Window::Definition $definition where .is-simple --> Str) {
		self.render-identifier($definition.existing);
	}

	multi method render-expression(Placeholders $placeholders, Window::Function $column, Precedence --> Str) {
		my $function = self.render-expression($placeholders, $column.function, Precedence::Comma);
		my $definition = self.render-window-definition($placeholders, $column.window);
		"$function OVER $definition";
	}

	multi method render-distinct(Placeholders $placeholders, Distinction::Columns:D $source --> List) {
		'DISTINCT ON', self.render-expression-list($placeholders, $source.columns.elements, True);
	}
	multi method render-distinct(Placeholders $placeholders, Distinction::Full:D $source, --> Str) {
		'DISTINCT';
	}

	multi method render-column-expressions(Placeholders $placeholders, Column::List:D $columns, Str $prefix? --> List) {
		my $results = self.render-expressions($placeholders, $columns.elements);
		$prefix ?? ($prefix, $results) !! ($results,);
	}
	multi method render-column-expressions(Placeholders $placeholders, Column::List:U $columns, Str $prefix? --> List) {
		Empty;
	}

	multi method render-names(Identifiers:D $columns --> Str) {
		$columns.elements.map({ self.render-identifier($^column) }).join(', ');
	}

	multi method render-identifiers(Identifiers:D $columns --> Str) {
		parenthesize-list($columns.elements.map({ self.render-identifier($^column) }), ', ');
	}
	multi method render-identifiers(Identifiers:U $columns --> List) {
		Empty;
	}

	method render-source-alias(Source::Aliased $source) {
		my $alias = self.render-identifier($source.alias);
		my @columns = self.render-identifiers($source.columns);
		'AS', $alias, |@columns;
	}

	multi method render-table(Table $table --> List) {
		my $name = self.render-identifier($table.name);
		$table.only ?? ('ONLY', $name) !! $name,;
	}
	multi method render-table(Table::Renamed $renamed--> List) {
		my @result;
		@result.push: 'ONLY' if $renamed.only;
		@result.push: self.render-identifier($renamed.name);
		@result.push: self.render-source-alias($renamed);
		@result;
	}

	multi method render-source-nested(Placeholders $placeholders, Source::Query $query --> Str) {
		self.render-expression($placeholders, $query.query, Precedence::Comma);
	}
	multi method render-source-nested(Placeholders $placeholders, Source::Function $function --> Str) {
		my $result = self.render-expression($placeholders, $function.function, Precedence::Rowlike);
		$function.ordinal ?? "($result WITH ORDINALITY)" !! "($result)";
	}

	multi method render-source(Placeholders $, Table $table --> List) {
		self.render-table($table);
	}
	multi method render-source(Placeholders $placeholders, Source::Nested $nested --> List) {
		my @lateral = $nested.lateral ?? 'LATERAL' !! Empty;
		my $rendered = self.render-source-nested($placeholders, $nested);
		|@lateral, $rendered, self.render-source-alias($nested);
	}

	multi method render-join-type(Placeholders $placeholders, Join::Natural $join) {
		'NATURAL', $join.type.uc, 'JOIN';
	}
	multi method render-join-type(Placeholders $placeholders, Join::Cross $join) {
		'CROSS JOIN';
	}
	multi method render-join-type(Placeholders $placeholders, Join $join) {
		$join.type.uc, 'JOIN';
	}

	multi method render-join-condition(Placeholders $placeholders, Join::On $join) {
		self.render-conditions($placeholders, $join.on, 'ON');
	}
	multi method render-join-condition(Placeholders $placeholders, Join::Using $join) {
		'USING', self.render-identifiers($join.using);
	}
	multi method render-join-condition(Placeholders $placeholders, Join $join) {
		Empty;
	}

	multi method render-source(Placeholders $placeholders, Join $join --> List) {
		my @result;
		@result.push: self.render-source($placeholders, $join.left);
		@result.push: self.render-join-type($placeholders, $join);
		@result.push: self.render-source($placeholders, $join.right);
		@result.push: self.render-join-condition($placeholders, $join);
		@result;
	}

	multi method render-from(Placeholders $placeholders, Source:D $source, Str $prefix = 'FROM') {
		$prefix, self.render-source($placeholders, $source);
	}
	multi method render-from(Placeholders $placeholders, Source:U $source, Str = Str) {
		Empty;
	}

	method render-common-table(Placeholders $placeholders, Common::Rename $rename --> Str) {
		my $alias        = self.render-identifier($rename.alias.name);
		my @columns      = self.render-identifiers($rename.alias.columns);
		my @materialized = $rename.materialized.defined ?? (($rename.materialized ?? Empty !! 'NOT'), 'MATERIALIZED') !! Empty;
		my $expression   = self.render-expression($placeholders, $rename.query, Precedence::Comma);
		($alias, |@columns, 'AS', |@materialized, $expression).join(' ');
	}

	multi method render-common-tables(Placeholders $placeholders, Common:D $common --> List) {
		my @recursive   = $common.recursive ?? 'RECURSIVE' !! Empty;
		my @expressions = $common.tables.map({ self.render-common-table($placeholders, $^table) });
		'WITH', |@recursive, @expressions.join(', ');
	}
	multi method render-common-tables(Placeholders $placeholders, Common:U --> List) {
		Empty;
	}

	multi method render-conditions(Placeholders $placeholders, Conditional:D $conditions, Str $type = 'WHERE' --> List) {
		$type, self.render-expression($placeholders, $conditions.expression, Precedence::And);
	}
	multi method render-conditions(Placeholders $placeholders, Conditional:U $conditions, Str $type = Str --> List) {
		Empty;
	}

	multi method render-expression(Placeholders $placeholders, Order::Modifier $column, Precedence --> Str) {
		my @result = self.render-expression($placeholders, $column.column, Precedence::Comma);
		@result.push: $column.order.uc if $column.order;
		@result.push: 'NULLS', $column.nulls.uc if $column.nulls;
		@result.join(' ');
	}

	method render-group-by(Placeholders $placeholders, GroupBy $group-by, Conditions $conditions --> List) {
		my @result = 'GROUP BY';
		@result.push: 'ALL' if $group-by.all;
		@result.push: self.render-expression-list($placeholders, $group-by.elements, False);
		@result.push: self.render-conditions($placeholders, $conditions, 'HAVING');
		@result;
	}

	method render-window(Placeholders $placeholders, Window::Clause $window --> Str) {
		my $name = self.render-identifier($window.name);
		my $definition = self.render-window-definition($placeholders, $window.definition);
		"$name AS $definition";
	}

	method render-windows(Placeholders $placeholders, Window::Clauses $windows --> List) {
		'WINDOW', $windows.windows.map({ self.render-window($placeholders, $^window) }).join(', ');
	}

	method render-compound(Placeholders $placeholders, Compound $compound --> List) {
		my @type = $compound.all ?? 'ALL' !! Empty;
		my $next = self.render-select-core($placeholders, $compound.next);
		$compound.type.uc, |@type, $next;
	}

	multi method render-order-by(Placeholders $placeholders, OrderBy:D $sorters --> List) {
		'ORDER BY', self.render-expressions($placeholders, $sorters.elements);
	}
	multi method render-order-by(Placeholders $placeholders, OrderBy:U $sorters --> List) {
		Empty;
	}

	multi method render-limit-offset(Placeholders $placeholders, Limit $limit, Offset $offset --> List) {
		my @result;
		@result.push: 'LIMIT',  self.render-expression($placeholders, $limit.value,  Precedence::Between) with $limit;
		@result.push: 'OFFSET', self.render-expression($placeholders, $offset.value, Precedence::Between) with $offset;
		@result;
	}

	method render-locking(Locking::Clause $locking --> List) {
		my @result = 'FOR', $locking.strength.Str.uc;
		@result.push('OF', self.render-names($locking.tables)) with $locking.tables;
		@result.push('NOWAIT') if $locking.no-wait;
		@result.push('SKIP LOCKED') if $locking.skip-locked;
		@result;
	}

	method render-lockings(Locking $lockings --> Str) {
		$lockings.lockings.map({ self.render-locking($^locking) }).join(', ');
	}


	method render-select-core(Placeholders $placeholders, Select $select --> Str) {
		my @parts = 'SELECT';
		@parts.append: self.render-distinct($placeholders, $select.distinct) with $select.distinct;
		@parts.append: self.render-column-expressions($placeholders, $select.columns);
		@parts.append: self.render-from($placeholders, $select.from);
		@parts.append: self.render-conditions($placeholders, $select.where);
		@parts.append: self.render-group-by($placeholders, $select.group-by, $select.having) with $select.group-by;
		@parts.append: self.render-compound($placeholders, $select.compound) with $select.compound;
		@parts.append: self.render-windows($placeholders, $select.windows) with $select.windows;
		@parts.join(' ');
	}

	multi method render-expression(Placeholders $placeholders, Select $select, Precedence --> Str) {
		my @parts;
		@parts.append: self.render-common-tables($placeholders, $select.common);
		@parts.append: self.render-select-core($placeholders, $select);
		@parts.append: self.render-order-by($placeholders, $select.order-by);
		@parts.append: self.render-limit-offset($placeholders, $select.limit, $select.offset);
		@parts.append: self.render-lockings($select.locking) with $select.locking;
		@parts.join(' ');
	}

	multi method render-expression(Placeholders $placeholders, Update $update, Precedence --> Str) {
		...
	}

	method render-overriding(Overriding $override) {
		...
	}

	method render-values(Placeholders $placeholders, Rows $rows) {
		my @values = $rows.elements.map({ self.render-expression-list($placeholders, $^row.elements, True) });
		'VALUES', @values.join(', ');
	}

	multi method render-insert-content(Placeholders $placeholders, Insert::Values $insert) {
		self.render-values($placeholders, $insert.rows);
	}
	multi method render-insert-content(Placeholders $placeholders, Insert::Select $insert) {
		self.render-expression($placeholders, $insert.select, Precedence::Rowlike);
	}
	multi method render-insert-content(Placeholders $placeholders, Insert::Defaults $insert) {
		'DEFAULT VALUES';
	}

	method render-conflicts(Placeholders $placeholders, Conflicts $conflicts) {
		...
	}

	multi method render-expression(Placeholders $placeholders, Insert $insert, Precedence --> Str) {
		my @common     = self.render-common-tables($placeholders, $insert.common);
		my @target     = self.render-table($insert.target);
		my @columns    = self.render-identifiers($insert.columns);
		my @overriding = $insert.overriding ?? self.render-overriding($insert.overriding) !! Empty;
		my @content    = self.render-insert-content($placeholders, $insert);
		my @conflicts  = $insert.conflicts ?? self.render-conflicts($placeholders, $insert.conflicts) !! Empty;
		my @returning  = self.render-column-expressions($placeholders, $insert.returning, 'RETURNING');

		(@common, 'INSERT INTO', @target, @columns, @overriding, @content, @conflicts, @returning).flat.join(' ');
	}

	multi method render-expression(Placeholders $placeholders, Delete $delete, Precedence) {
		...
	}

	multi method render-expression(Placeholders $placeholders, Values $values, Precedence --> Str) {
		my @rows     = self.render-values($placeholders, $values.rows);
		my @order-by = self.render-order-by($placeholders, $values.order-by);
		my @limits   = self.render-limit-offset($placeholders, $values.limit, $values.offset);

		(@rows, @order-by, @limits).flat.join(' ');
	}

	method render(Expression:D $expression, Precedence:D :$precedence = Precedence::Rowlike) {
		my $placeholders = self.placeholders.new;
		my $sql          = self.render-expression($placeholders, $expression, $precedence);
		SQL::Query.new($sql, $placeholders.values);
	}
}

class Exception::Input does Exception {
}
class Exception::Portability does Exception {
}

my %holder-for = (
	dbi      => SQL::Placeholders::DBI,
	postgres => SQL::Placeholders::Postgres,
);

role Renderer::Sensible does Renderer::SQL {
	multi method new(Str:D :placeholders($placeholder-str)!, *%arguments) {
		die Exception::Input.new("No such placeholder style $placeholder-str") unless %holder-for{$placeholder-str}:exists;
		my $placeholders = %holder-for{$placeholder-str};
		self.bless(:$placeholders, |%arguments);
	}

	multi method render-expression(Placeholders $placeholders, Update $update, Precedence --> Str) {
		my @common       = self.render-common-tables($placeholders, $update.common);
		my @target       = self.render-table($update.target);
		my @set          = self.render-expressions($placeholders, $update.assignments);
		my $from         = $update.from ?? $update.from.source !! Source;
		my $where        = $update.from ?? $update.where.merge($update.from.conditions) !! $update.where;
		my @from         = self.render-from($placeholders, $from);
		my @where        = self.render-conditions($placeholders, $where);
		my @returning    = self.render-column-expressions($placeholders, $update.returning, 'RETURNING');

		(@common, 'UPDATE', @target, 'SET', @set, @from, @where, @returning).flat.join(' ');
	}

	multi method render-conflict-target(Placeholders $placeholders, Conflict::Target::Columns $target) {
		my $columns = self.render-identifiers($target.columns);
		my @where = self.render-conditions($placeholders, $target.where);
		$columns, |@where;
	}
	multi method render-conflict-target(Placeholders $placeholders, Conflict::Target::Constraint $target) {
		'ON CONSTRAINT', self.render-identifier($target.constraint);
	}

	multi method render-conflict(Placeholders $placeholders, Conflict::Nothing $conflict) {
		my @target = $conflict.target ?? self.render-conflict-target($placeholders, $conflict.target) !! Empty;
		'ON CONFLICT', |@target, 'DO NOTHING';
	}
	multi method render-conflict(Placeholders $placeholders, Conflict::Update $conflict) {
		my @result = 'ON CONFLICT';
		@result.push: self.render-conflict-target($placeholders, $conflict.target) with $conflict.target;
		@result.push: 'DO UPDATE SET';
		@result.push: self.render-expressions($placeholders, $conflict.assignments);
		@result.push: self.render-conditions($placeholders, $conflict.where);
		@result;
	}

	method render-conflicts(Placeholders $placeholders, Conflicts $conflicts) {
		$conflicts.conflicts.flatmap: { self.render-conflict($placeholders, $^conflict) };
	}

	multi method render-expression(Placeholders $placeholders, Delete $delete, Precedence) {
		my @common    = self.render-common-tables($placeholders, $delete.common);
		my @target    = self.render-table($delete.target);
		my $using     = $delete.using ?? $delete.using.source !! Source;
		my $where     = $delete.using ?? $delete.where.merge($delete.using.conditions) !! $delete.where;
		my @using     = self.render-from($placeholders, $using, 'USING');
		my @where     = self.render-conditions($placeholders, $where);
		my @returning = self.render-column-expressions($placeholders, $delete.returning, 'RETURNING');

		(@common, 'DELETE FROM', @target, @using, @where, @returning).flat.join(' ');
	}
}

class Renderer::Postgres does Renderer::Sensible {
	has Placeholders $.placeholders is required where $_ === any(Placeholders::DBI, Placeholders::Postgres);

	method render-overriding(Overriding $override) {
		'OVERRIDING', $override.uc, 'VALUE';
	}
}

class Renderer::SQLite does Renderer::Sensible {
	has Placeholders $.placeholders where $_ === any(Placeholders::DBI, Placeholders::Postgres) = Placeholders::DBI;

	method render-overriding(Overriding $override) {
		Empty;
	}
}

class Renderer::MySQL does Renderer::SQL {
	method placeholders() { SQL::Placeholders::DBI }

	multi method render-expression(Placeholders $placeholders, Update $update, Precedence --> Str) {
		my @common       = self.render-common-tables($placeholders, $update.common);
		my $target       = $update.from ?? $update.target.join($update.from.source, :on($update.from.conditions)) !! $update.target;
		my @target       = self.render-source($placeholders, $target);
		my @set          = self.render-expressions($placeholders, $update.assignments);
		my @where        = self.render-conditions($placeholders, $update.where);
		die Exception::Portability.new("MySQL doesn't support RETURNING clause on UPDATE") if $update.returning;

		(@common, 'UPDATE', @target, 'SET', @set, @where).flat.join(' ');
	}

	method render-overriding(Overriding $override) {
		Empty;
	}

	multi method render-conflict(Placeholders $placeholders, Conflict::Nothing $conflict) {
		die Exception::Portability.new("MySQL can't do nothing on conflict");
	}
	multi method render-conflict(Placeholders $placeholders, Conflict::Update $conflict) {
		die Exception::Portability.new('No conflict conditions allowed for MySQL') if $conflict.where;
		my @result = 'ON DUPLICATE KEY UPDATE';
		@result.push: self.render-expressions($placeholders, $conflict.assignments);
		@result;
	}

	method render-conflicts(Placeholders $placeholders, Conflicts $conflicts) {
		die Exception::Portability.new('Only one conflict clause allowed in MySQL') if $conflicts.conflicts > 1;
		$conflicts.conflicts.flatmap: { self.render-conflict($placeholders, $^conflict) };
	}

	multi method render-expression(Placeholders $placeholders, Delete $delete, Precedence) {
		my @common    = self.render-common-tables($placeholders, $delete.common);
		my $target    = $delete.using ?? $delete.target.join($delete.using.source, :on($delete.using.conditions)) !! $delete.target;
		my @target    = self.render-source($placeholders, $target);
		my @where     = self.render-conditions($placeholders, $delete.where);
		my @returning = self.render-column-expressions($placeholders, $delete.returning, 'RETURNING');

		(@common, 'DELETE FROM', @target, @where, @returning).flat.join(' ');
	}
}

has Renderer:D $.renderer is required handles<render>;

my Renderer %renderer =(
	postgres => Renderer::Postgres,
	sqlite   => Renderer::SQLite,
	mysql    => Renderer::MySQL,
);

my sub renderer-for(Str $name) {
	my $result = %renderer{$name};
	die Exception::Input.new("No such renderer $name") if $result === Renderer;
	return $result;
}

multi submethod BUILD(Renderer:D :$!renderer!) {}
multi submethod BUILD(Renderer:U :$renderer!, *%arguments) {
	$!renderer = $renderer.new(|%arguments);
}
multi submethod BUILD(Str:D :renderer($renderer-name)!, *%arguments) {
	my $renderer = renderer-for($renderer-name);
	$!renderer = $renderer.new(|%arguments);
}

our sub select(Source(Any) $from, Column::List(Any) $columns = *, Conditions(Any) $where?, Common(Any) :$common, Distinction(Any) :$distinct, GroupBy(Any) :$group-by, Conditions(Any) :$having, Window::Clauses(Any) :$windows, Compound(Pair) :$compound, OrderBy(Any) :$order-by, Limit(Any) :$limit, Offset(Any) :$offset, Locking(Any) :$locking) is export(:functions) {
	Select.new(:$common, :$distinct, :$columns, :$from, :$where, :$group-by :$having, :$windows, :$compound, :$order-by, :$limit, :$offset, :$locking);
}

method select(|args) {
	$!renderer.render(select(|args));
}

our sub update(Table(Any) $target, Assigns(Any) $assigns, Conditions(Any) $where?, Common(Any) :$common, Update::From(Any) :$from, Column::List(Any) :$returning) is export(:functions) {
	my @assignments = $assigns.assignments;
	Update.new(:$common, :$target, :@assignments, :$where, :$from, :$returning);
}

method update(|args) {
	$!renderer.render(update(|args));
}

proto insert(|) is export(:functions) { * }
multi insert(Table(Any) $target, Identifiers(Any) $columns, Rows(List) $rows, Common(Any) :$common, Overriding(Str) :$overriding, Conflicts(Any) :$conflicts, Column::List(Any) :$returning) {
	Insert::Values.new(:$common, :$target, :$columns, :$rows, :$overriding, :$conflicts, :$returning);
}
multi insert(Table(Any) $target, Assigns(Any) $values, Common(Any) :$common, Overriding(Str) :$overriding, Conflicts(Any) :$conflicts, Column::List(Any) :$returning) {
	samewith($target, $values.keys, [$values.values,], :$common, :$overriding, :$conflicts, :$returning);
}
multi insert(Table(Any) $target, Identifiers(Any) $columns, Select(Map) $select, Common(Any) :$common, Overriding(Str) :$overriding, Conflicts(Any) :$conflicts, Column::List(Any) :$returning) {
	Insert::Select.new(:$common, :$target, :$columns, :$select, :$overriding, :$conflicts, :$returning);
}
multi insert(Table(Any) $target, Value::Default $, Common(Any) :$common, Overriding(Str) :$overriding, Conflicts(Any) :$conflicts, Column::List(Any) :$returning) {
	Insert::Defaults.new(:$common, :$target, :$overriding, :$conflicts, :$returning);
}

method insert(|args) {
	$!renderer.render(insert(|args));
}

our sub upsert(Table(Any) $target, Assigns(Any) $assigns, Conflict::Targets(Cool) $targets, Common(Any) :$common, Overriding(Str) :$overriding, Column::List(Any) :$returning) is export(:functions) {
	my Conflicts(Seq) $conflicts = $targets.targets.map: -> Conflict::Target::Columns $target {
		my $assignments = $assigns.without($target.columns);
		$assignments.empty ?? Conflict::Nothing.new(:$target) !! Conflict::Update.new(:$target, :$assignments);
	}
	my $columns = Identifiers.COERCE($assigns.keys);
	my $rows = Rows.COERCE([ $assigns.values, ]);
	Insert::Values.new(:$common, :$target, :$columns, :$rows, :$conflicts, :$overriding :$returning);
}

method upsert(|args) {
	$!renderer.render(upsert(|args));
}

our sub delete(Table(Any) $target, Conditions(Any) $where, Common(Any) :$common, Update::From(Any) :$using, Column::List(Any) :$returning) is export(:functions) {
	Delete.new(:$common, :$target, :$where, :$using, :$returning);
}

method delete(|args) {
	$!renderer.render(delete(|args));
}

our sub values(Rows(List) $rows, OrderBy(Any) :$order-by, Limit(Any) :$limit, Offset(Any) :$offset) {
	Values.new(:$rows, :$order-by, :$limit, :$offset);
}

method values(|args) {
	$!renderer.render(values(|args));
}

method begin() {
	SQL::Query.new('BEGIN');
}

method rollback() {
	SQL::Query.new('ROLLBACK');
}

method commit() {
	SQL::Query.new('COMMIT');
}


proto table(|) is export(:functions) { * }
multi table(Any $table, Identifier(Any) :$as!, Identifiers(Any) :$columns = Identifiers, Bool :$only) {
	Table.COERCE({ :$table, :alias($as), :$columns, :$only });
}
multi table(Identifier(Any) $name) {
	Table::Simple.new(:$name);
}

proto identifier(|) is export(:functions) {*}
multi identifier(Identifier(Str) $ident) {
	$ident;
}
multi identifier(@ident) {
	Identifier.new-from-list(@ident);
}

our sub identifiers(Identifiers(Any) $idents) is export(:functions) {
	$idents;
}

our sub binary(Str $operator, Expression $left, Expression $right, *%args) is export(:functions) {
	my $key = $operator.lc;
	my $class = %binary-op-for{$key}:exists ?? %binary-op-for{$key} !! Op::Comperative[$key];
	$class.new(:$left, :$right, |%args);
}

our sub logical(Str $operator, @elements) is export(:functions) {
	my $class = $operator.lc eq 'and' ?? Op::And !! $operator.lc eq 'or' ?? Op::Or !! die Exception::Input.new("No such operator '$operator'");
	$class.new(:@elements);
}

our sub not(Expression $value) is export(:functions) {
	Op::Not.new(:$value)
}

our sub function(Str $function, Column::List(Any) $arguments = (), Conditions(Any) :$filter, Quantifier(Str) :$quantifier, OrderBy(Any) :$order-by) is export(:functions) {
	Function.new(:$function, :$arguments, :$filter, :$quantifier, :$order-by);
}

our sub value(Any $value) is export(:functions) {
	expand-expression($value);
}

our sub null() is export(:functions) {
	Value::Null.new;
}
our sub default() is export(:functions) {
	Value::Default.new;
}
our sub true() is export(:functions) {
	Value::True.new;
}
our sub false() is export(:functions) {
	Value::False.new;
}

our sub current(Identifier(Cool) $cursor) is export(:functions) {
	Current.new(:$cursor);
}

my package EXPORT::functions {
	our &delegate       = &SQL::Query::delegate;
	our &delegate-pair  = &SQL::Query::delegate-pair;
	our &delegate-pairs = &SQL::Query::delegate-pairs;
}

role Builder::Query does Query::Proxy {
	has Renderer:D $.renderer is required;
	method query { ... }
	method expression { self.query }

	method build() {
		$!renderer.render(self.expression);
	}
}

class Builder::Update does Builder::Query {
	has Update:D $.query is required;

	method from(Update::From(Any) $from) {
		my $query = $!query.but({ :$from });
		self.bless(:$!renderer, :$query);
	}
	method where(Conditions(Any) $more-where) {
		my $where = $!query.where.merge($more-where);
		my $query = $!query.but({ :$where });
		self.bless(:$!renderer, :$query);
	}
	method returning(Column::List(Any) $returning) {
		my $query = $!query.but({ :$returning });
		self.bless(:$!renderer, :$query);
	}
}

class Builder::Insert does Builder::Query {
	has Insert:D $.query is required;

	method overriding(Overriding(Str) $overriding) {
		my $query = $!query.but({ :$overriding });
		self.bless(:$!renderer, :$query);
	}

	method conflicts(Conflicts(Any) $conflicts) {
		my $query = $!query.but({ :$conflicts });
		self.bless(:$!renderer, :$query);
	}

	method returning(Column::List(Any) $returning) {
		my $query = $!query.but({ :$returning });
		self.bless(:$!renderer, :$query);
	}
}

class Builder::Select does Builder::Query {
	has Select:D $.query is required;

	method !but(*%args) {
		my $query = $!query.but(%args);
		self.bless(:$!renderer, :$query);
	}

	method distinct(Distinction(Any) $distinct) {
		self!but(:$distinct)
	}
	method where(Conditions(Any) $more-where) {
		my $where = $!query.where.merge($more-where);
		self!but(:$where);
	}
	method group-by(GroupBy(Any) $group-by) {
		self!but(:$group-by);
	}
	method having(Conditions(Any) $more-having) {
		my $having = $!query.having.merge($more-having);
		self!but(:$having);
	}
	method windows(Window::Clauses(Any) $more-windows) {
		my $windows = $!query.windows.merge($more-windows);
		self!but(:$windows);
	}
	method compound(Compound(Pair) $compound) {
		self!but(:$compound);
	}
	method order-by(OrderBy(Any) $order-by) {
		self!but(:$order-by);
	}
	method limit(Limit(Any) $limit) {
		self!but(:$limit);
	}
	method offset(Offset(Any) $offset) {
		self!but(:$offset);
	}
	method locking(Locking(Any) $locking) {
		self!but(:$locking);
	}

	method insert(Table(Any) $target, Identifiers(Any) $columns = Identifiers) {
		my $common = $!query.common;
		my $select = $!query.but({:common(Common)});
		my $query = Insert::Select.new(:$common, :$target, :$columns, :$select);
		Builder::Insert.new(:$!renderer, :$query);
	}
}

class Builder::Delete does Builder::Query {
	has Delete:D $.query is required;

	method using(Update::From(Any) $using) {
		my $query = $!query.but({ :$using });
		self.bless(:$!renderer, :$query);
	}
	method where(Column::List(Any) $more-where) {
		my $where = $!query.where.merge($more-where);
		my $query = $!query.but({ :$where });
		self.bless(:$!renderer, :$query);
	}
	method returning(Column::List(Any) $returning) {
		my $query = $!query.but({ :$returning });
		self.bless(:$!renderer, :$query);
	}
}

class Builder::Source {
	has Renderer:D $.renderer is required;
	has Source:D $.source is required;
	has Conditions $.where;
	has Common $.common;

	method join(|args) {
		my $source = $!source.join(|args);
		Builder::Source.new(:$!renderer, :$source, :$!common, :$!where);
	}

	method where(Conditions(Any) $more-where) {
		my $where = $!where.merge($more-where);
		Builder::Source.new(:$!renderer, :$!source, :$!common, :$where);
	}

	multi method select(Column::List(Any) $columns) {
		my $query = Select.new(:$!common, :from($!source), :$columns, :$!where);
		Builder::Select.new(:$!renderer, :$query);
	}
	multi method select(*@columns) {
		my $columns = Column::List.COERCE(@columns);
		my $query = Select.new(:$!common, :from($!source), :$columns, :$!where);
		Builder::Select.new(:$!renderer, :$query);
	}

	my multi split-source(Table $source) {
		($source, Update::From);
	}
	my multi split-source(Join::On $source) {
		($source.left, Update::From.new(:source($source.right), :conditions($source.on)));
	}
	my multi split-source(Join::Using $source) {
		samewith($source.to-join-on);
	}
	my multi split-source(Join::Cross $source) {
		($source.left, Update::From.new(:source($source.right)));
	}

	method update(Assigns(Any) $assigns) {
		my @assignments = $assigns.assignments;
		my ($target, $from) = split-source($!source);
		my $query = Update.new(:$!common, :$target, :@assignments, :$from, :$!where);
		Builder::Update.new(:$!renderer, :$query);
	}

	my multi extract-where(Conditional:U $where) {
		Identifiers.new(:elements[]), Array[Expression].new;
	}
	my multi extract-where(Conditional:D $where) {
		my (@identifiers, Expression @columns);
		my @expressions = Op::And.unpack($where.expression);
		for @expressions -> $expression {
			if $expression ~~ Op::Equals and $expression.left ~~ Identifier {
				@identifiers.push($expression.left);
				@columns.push($expression.right);
			} else {
				die Exception::Input('Can\'t do insert with with non-trivial where clause');
			}
		}

		Identifiers.new(:elements(@identifiers)), @columns;
	}

	multi method insert(Identifiers(Any) $main-columns, Rows(List) $main-rows) {
		my ($extra-identifiers, Expression @extra-values) := extract-where($!where);
		my $columns = $main-columns.merge($extra-identifiers);
		my $rows = $main-rows.add-columns(@extra-values);
		my $query = Insert::Values.new(:$!common, :target($!source), :$columns, :$rows);
		Builder::Insert.new(:$!renderer, :$query);
	}
	multi method insert(Assigns(Any) $values) {
		samewith($values.keys, [ $values.values, ]);
	}
	multi method insert(Identifiers(Any) $columns, Value::Default $) {
		my $query = Insert::Defaults.new(:$!common, :target($!source), :$columns);
		Builder::Insert.new(:$!renderer, :$query);
	}

	method upsert(Assigns(