package org.jooq.impl;
import static org.jooq.Clause.CONDITION;
import static org.jooq.Clause.CONDITION_COMPARISON;
import static org.jooq.Comparator.EQUALS;
import static org.jooq.Comparator.GREATER;
import static org.jooq.Comparator.GREATER_OR_EQUAL;
import static org.jooq.Comparator.IN;
import static org.jooq.Comparator.LESS;
import static org.jooq.Comparator.LESS_OR_EQUAL;
import static org.jooq.Comparator.NOT_EQUALS;
import static org.jooq.Comparator.NOT_IN;
import static org.jooq.SQLDialect.DERBY;
import static org.jooq.SQLDialect.FIREBIRD;
import static org.jooq.SQLDialect.H2;
import static org.jooq.SQLDialect.HSQLDB;
import static org.jooq.SQLDialect.MARIADB;
import static org.jooq.SQLDialect.MYSQL;
import static org.jooq.SQLDialect.POSTGRES;
import static org.jooq.SQLDialect.SQLITE;
import static org.jooq.impl.DSL.exists;
import static org.jooq.impl.DSL.name;
import static org.jooq.impl.DSL.noCondition;
import static org.jooq.impl.DSL.notExists;
import static org.jooq.impl.DSL.row;
import static org.jooq.impl.DSL.select;
import static org.jooq.impl.Quantifier.ALL;
import static org.jooq.impl.Tools.embeddedFieldsRow;
import static org.jooq.impl.Tools.fieldNames;
import static org.jooq.impl.Tools.fieldsByName;
import static org.jooq.impl.Tools.visitSubquery;
import java.util.Set;
import org.jooq.Clause;
import org.jooq.Comparator;
import org.jooq.Context;
import org.jooq.Name;
import org.jooq.QuantifiedSelect;
import org.jooq.QueryPartInternal;
import org.jooq.Record;
import org.jooq.RenderContext;
import org.jooq.Row;
import org.jooq.SQLDialect;
import org.jooq.Select;
import org.jooq.SelectOrderByStep;
import org.jooq.impl.Tools.BooleanDataKey;
final class RowSubqueryCondition extends AbstractCondition {
private static final long serialVersionUID = -1806139685201770706L;
private static final Clause[] CLAUSES = { CONDITION, CONDITION_COMPARISON };
private static final Set<SQLDialect> SUPPORT_NATIVE = SQLDialect.supportedBy(H2, HSQLDB, MARIADB, MYSQL, POSTGRES, SQLITE);
private static final Set<SQLDialect> NO_SUPPORT_NATIVE_QUANTIFIED = SQLDialect.supportedBy(DERBY, FIREBIRD, MARIADB, MYSQL, SQLITE);
private final Row left;
private final Select<?> right;
private final QuantifiedSelect<?> rightQuantified;
private final Comparator comparator;
RowSubqueryCondition(Row left, Select<?> right, Comparator comparator) {
this.left = left;
this.right = right;
this.rightQuantified = null;
this.comparator = comparator;
}
RowSubqueryCondition(Row left, QuantifiedSelect<?> right, Comparator comparator) {
this.left = left;
this.right = null;
this.rightQuantified = right;
this.comparator = comparator;
}
@Override
public final void accept(Context<?> ctx) {
ctx.visit(delegate(ctx));
}
@Override
public final Clause[] clauses(Context<?> ctx) {
return null;
}
private final QueryPartInternal delegate(Context<?> ctx) {
if (rightQuantified != null) {
if (NO_SUPPORT_NATIVE_QUANTIFIED.contains(ctx.dialect())) {
QuantifiedSelectImpl<?> q = (QuantifiedSelectImpl<?>) rightQuantified;
switch (comparator) {
case EQUALS:
case NOT_EQUALS: {
if (comparator == NOT_EQUALS ^ q.quantifier == ALL)
return emulationUsingExists(ctx, left, q.query, comparator == EQUALS ? NOT_EQUALS : EQUALS, comparator == EQUALS);
else
return new RowSubqueryCondition(left, q.query, comparator == EQUALS ? IN : NOT_IN);
}
case GREATER:
case GREATER_OR_EQUAL:
case LESS:
case LESS_OR_EQUAL:
default:
return emulationUsingExists(ctx, left, q.query, q.quantifier == ALL ? comparator.inverse() : comparator, q.quantifier == ALL);
}
}
else
return new Native();
}
else if (SUPPORT_NATIVE.contains(ctx.dialect()))
return new Native();
else
return emulationUsingExists(ctx, left, right,
comparator == GREATER
|| comparator == GREATER_OR_EQUAL
|| comparator == LESS
|| comparator == LESS_OR_EQUAL ? comparator : EQUALS,
comparator == NOT_IN || comparator == NOT_EQUALS
);
}
private static final QueryPartInternal emulationUsingExists(Context<?> ctx, Row row, Select<?> select, Comparator comparator, boolean notExists) {
Select<Record> subselect = emulatedSubselect(ctx, row, select, comparator);
return (QueryPartInternal) (notExists ? notExists(subselect) : exists(subselect));
}
private static final SelectOrderByStep<Record> emulatedSubselect(Context<?> ctx, Row row, Select<?> s, Comparator c) {
RenderContext render = ctx instanceof RenderContext ? (RenderContext) ctx : null;
Row l = embeddedFieldsRow(row);
Name table = name(render == null ? "t" : render.nextAlias());
Name[] names = fieldNames(l.size());
return select()
.from(new AliasedSelect<>(s, names).as(table))
.where(c == null
? noCondition()
: new RowCondition(l, row(fieldsByName(table, names)), c));
}
private class Native extends AbstractCondition {
private static final long serialVersionUID = -1552476981094856727L;
@Override
public final void accept(Context<?> ctx) {
ctx.visit(left)
.sql(' ')
.visit(comparator.toKeyword())
.sql(' ');
if (rightQuantified == null) {
boolean extraParentheses = false ;
ctx.sql(extraParentheses ? "((" : "(");
visitSubquery(ctx, right);
ctx.sql(extraParentheses ? "))" : ")");
}
else {
ctx.subquery(true)
.visit(rightQuantified)
.subquery(false);
}
}
@Override
public final Clause[] clauses(Context<?> ctx) {
return CLAUSES;
}
}
}