QueryExpressionDSL.java

/*
 *    Copyright 2016-2024 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       https://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.mybatis.dynamic.sql.select;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;

import org.jetbrains.annotations.NotNull;
import org.mybatis.dynamic.sql.BasicColumn;
import org.mybatis.dynamic.sql.BindableColumn;
import org.mybatis.dynamic.sql.CriteriaGroup;
import org.mybatis.dynamic.sql.SortSpecification;
import org.mybatis.dynamic.sql.SqlTable;
import org.mybatis.dynamic.sql.TableExpression;
import org.mybatis.dynamic.sql.configuration.StatementConfiguration;
import org.mybatis.dynamic.sql.select.join.JoinCondition;
import org.mybatis.dynamic.sql.select.join.JoinCriterion;
import org.mybatis.dynamic.sql.select.join.JoinSpecification;
import org.mybatis.dynamic.sql.select.join.JoinType;
import org.mybatis.dynamic.sql.util.Buildable;
import org.mybatis.dynamic.sql.util.Utilities;
import org.mybatis.dynamic.sql.where.AbstractWhereFinisher;
import org.mybatis.dynamic.sql.where.AbstractWhereStarter;
import org.mybatis.dynamic.sql.where.EmbeddedWhereModel;

public class QueryExpressionDSL<R>
        extends AbstractQueryExpressionDSL<QueryExpressionDSL<R>.QueryExpressionWhereBuilder, QueryExpressionDSL<R>>
        implements Buildable<R> {

    private final String connector;
    private final SelectDSL<R> selectDSL;
    private final boolean isDistinct;
    private final List<BasicColumn> selectList;
    private QueryExpressionWhereBuilder whereBuilder;
    private GroupByModel groupByModel;
    private QueryExpressionHavingBuilder havingBuilder;

    protected QueryExpressionDSL(FromGatherer<R> fromGatherer, TableExpression table) {
        super(table);
        connector = fromGatherer.connector;
        selectList = fromGatherer.selectList;
        isDistinct = fromGatherer.isDistinct;
        selectDSL = Objects.requireNonNull(fromGatherer.selectDSL);
        selectDSL.registerQueryExpression(this);
    }

    protected QueryExpressionDSL(FromGatherer<R> fromGatherer, SqlTable table, String tableAlias) {
        this(fromGatherer, table);
        addTableAlias(table, tableAlias);
    }

    @Override
    public QueryExpressionWhereBuilder where() {
        whereBuilder = Utilities.buildIfNecessary(whereBuilder, QueryExpressionWhereBuilder::new);
        return whereBuilder;
    }

    @Override
    public QueryExpressionDSL<R> configureStatement(Consumer<StatementConfiguration> consumer) {
        selectDSL.configureStatement(consumer);
        return this;
    }

    /**
     * This method is protected here because it doesn't make sense at this point in the DSL.
     *
     * @return The having builder
     */
    protected QueryExpressionHavingBuilder having() {
        havingBuilder = Utilities.buildIfNecessary(havingBuilder, QueryExpressionHavingBuilder::new);
        return havingBuilder;
    }

    /**
     * This method is meant for use by the Kotlin DSL. We expect a full set of criteria.
     *
     * @param criteriaGroup the full criteria for a Kotlin Having clause
     */
    protected void applyHaving(CriteriaGroup criteriaGroup) {
        having().initialize(criteriaGroup);
    }

    @NotNull
    @Override
    public R build() {
        return selectDSL.build();
    }

    public JoinSpecificationStarter join(SqlTable joinTable) {
        return new JoinSpecificationStarter(joinTable, JoinType.INNER);
    }

    public JoinSpecificationStarter join(SqlTable joinTable, String tableAlias) {
        addTableAlias(joinTable, tableAlias);
        return join(joinTable);
    }

    public JoinSpecificationStarter join(Buildable<SelectModel> joinTable, String tableAlias) {
        return new JoinSpecificationStarter(buildSubQuery(joinTable, tableAlias), JoinType.INNER);
    }

    public JoinSpecificationStarter leftJoin(SqlTable joinTable) {
        return new JoinSpecificationStarter(joinTable, JoinType.LEFT);
    }

    public JoinSpecificationStarter leftJoin(SqlTable joinTable, String tableAlias) {
        addTableAlias(joinTable, tableAlias);
        return leftJoin(joinTable);
    }

    public JoinSpecificationStarter leftJoin(Buildable<SelectModel> joinTable, String tableAlias) {
        return new JoinSpecificationStarter(buildSubQuery(joinTable, tableAlias), JoinType.LEFT);
    }

    public JoinSpecificationStarter rightJoin(SqlTable joinTable) {
        return new JoinSpecificationStarter(joinTable, JoinType.RIGHT);
    }

    public JoinSpecificationStarter rightJoin(SqlTable joinTable, String tableAlias) {
        addTableAlias(joinTable, tableAlias);
        return rightJoin(joinTable);
    }

    public JoinSpecificationStarter rightJoin(Buildable<SelectModel> joinTable, String tableAlias) {
        return new JoinSpecificationStarter(buildSubQuery(joinTable, tableAlias), JoinType.RIGHT);
    }

    public JoinSpecificationStarter fullJoin(SqlTable joinTable) {
        return new JoinSpecificationStarter(joinTable, JoinType.FULL);
    }

    public JoinSpecificationStarter fullJoin(SqlTable joinTable, String tableAlias) {
        addTableAlias(joinTable, tableAlias);
        return fullJoin(joinTable);
    }

    public JoinSpecificationStarter fullJoin(Buildable<SelectModel> joinTable, String tableAlias) {
        return new JoinSpecificationStarter(buildSubQuery(joinTable, tableAlias), JoinType.FULL);
    }

    public GroupByFinisher groupBy(BasicColumn... columns) {
        return groupBy(Arrays.asList(columns));
    }

    public GroupByFinisher groupBy(Collection<? extends BasicColumn> columns) {
        groupByModel = GroupByModel.of(columns);
        return new GroupByFinisher();
    }

    public SelectDSL<R> orderBy(SortSpecification... columns) {
        return orderBy(Arrays.asList(columns));
    }

    public SelectDSL<R> orderBy(Collection<? extends SortSpecification> columns) {
        selectDSL.orderBy(columns);
        return selectDSL;
    }

    public UnionBuilder union() {
        return new UnionBuilder("union"); //$NON-NLS-1$
    }

    public UnionBuilder unionAll() {
        return new UnionBuilder("union all"); //$NON-NLS-1$
    }

    protected QueryExpressionModel buildModel() {
        return QueryExpressionModel.withSelectList(selectList)
                .withConnector(connector)
                .withTable(table())
                .isDistinct(isDistinct)
                .withTableAliases(tableAliases())
                .withJoinModel(buildJoinModel().orElse(null))
                .withGroupByModel(groupByModel)
                .withWhereModel(whereBuilder == null ? null : whereBuilder.buildWhereModel())
                .withHavingModel(havingBuilder == null ? null : havingBuilder.buildHavingModel())
                .build();
    }

    public SelectDSL<R>.LimitFinisher limit(long limit) {
        return selectDSL.limit(limit);
    }

    public SelectDSL<R>.OffsetFirstFinisher offset(long offset) {
        return selectDSL.offset(offset);
    }

    public SelectDSL<R>.FetchFirstFinisher fetchFirst(long fetchFirstRows) {
        return selectDSL.fetchFirst(fetchFirstRows);
    }

    @Override
    protected QueryExpressionDSL<R> getThis() {
        return this;
    }

    public static class FromGatherer<R> {
        private final String connector;
        private final List<BasicColumn> selectList;
        private final SelectDSL<R> selectDSL;
        private final boolean isDistinct;

        public FromGatherer(Builder<R> builder) {
            this.connector = builder.connector;
            this.selectList = builder.selectList;
            this.selectDSL = Objects.requireNonNull(builder.selectDSL);
            this.isDistinct = builder.isDistinct;
        }

        public QueryExpressionDSL<R> from(Buildable<SelectModel> select) {
            return new QueryExpressionDSL<>(this, buildSubQuery(select));
        }

        public QueryExpressionDSL<R> from(Buildable<SelectModel> select, String tableAlias) {
            return new QueryExpressionDSL<>(this, buildSubQuery(select, tableAlias));
        }

        public QueryExpressionDSL<R> from(SqlTable table) {
            return new QueryExpressionDSL<>(this, table);
        }

        public QueryExpressionDSL<R> from(SqlTable table, String tableAlias) {
            return new QueryExpressionDSL<>(this, table, tableAlias);
        }

        public static class Builder<R> {
            private String connector;
            private final List<BasicColumn> selectList = new ArrayList<>();
            private SelectDSL<R> selectDSL;
            private boolean isDistinct;

            public Builder<R> withConnector(String connector) {
                this.connector = connector;
                return this;
            }

            public Builder<R> withSelectList(Collection<? extends BasicColumn> selectList) {
                this.selectList.addAll(selectList);
                return this;
            }

            public Builder<R> withSelectDSL(SelectDSL<R> selectDSL) {
                this.selectDSL = selectDSL;
                return this;
            }

            public Builder<R> isDistinct() {
                this.isDistinct = true;
                return this;
            }

            public FromGatherer<R> build() {
                return new FromGatherer<>(this);
            }
        }
    }

    public class QueryExpressionWhereBuilder extends AbstractWhereFinisher<QueryExpressionWhereBuilder>
            implements Buildable<R> {
        private QueryExpressionWhereBuilder() {
            super(QueryExpressionDSL.this);
        }

        public UnionBuilder union() {
            return QueryExpressionDSL.this.union();
        }

        public UnionBuilder unionAll() {
            return QueryExpressionDSL.this.unionAll();
        }

        public SelectDSL<R> orderBy(SortSpecification... columns) {
            return orderBy(Arrays.asList(columns));
        }

        public SelectDSL<R> orderBy(Collection<? extends SortSpecification> columns) {
            return QueryExpressionDSL.this.orderBy(columns);
        }

        public GroupByFinisher groupBy(BasicColumn... columns) {
            return groupBy(Arrays.asList(columns));
        }

        public GroupByFinisher groupBy(Collection<? extends BasicColumn> columns) {
            return QueryExpressionDSL.this.groupBy(columns);
        }

        public SelectDSL<R>.LimitFinisher limit(long limit) {
            return QueryExpressionDSL.this.limit(limit);
        }

        public SelectDSL<R>.OffsetFirstFinisher offset(long offset) {
            return QueryExpressionDSL.this.offset(offset);
        }

        public SelectDSL<R>.FetchFirstFinisher fetchFirst(long fetchFirstRows) {
            return QueryExpressionDSL.this.fetchFirst(fetchFirstRows);
        }

        @NotNull
        @Override
        public R build() {
            return QueryExpressionDSL.this.build();
        }

        @Override
        protected QueryExpressionWhereBuilder getThis() {
            return this;
        }

        protected EmbeddedWhereModel buildWhereModel() {
            return super.buildModel();
        }
    }

    public class JoinSpecificationStarter {
        private final TableExpression joinTable;
        private final JoinType joinType;

        public JoinSpecificationStarter(TableExpression joinTable, JoinType joinType) {
            this.joinTable = joinTable;
            this.joinType = joinType;
        }

        public <T> JoinSpecificationFinisher on(BindableColumn<T> joinColumn, JoinCondition<T> joinCondition) {
            return new JoinSpecificationFinisher(joinTable, joinColumn, joinCondition, joinType);
        }

        public <T> JoinSpecificationFinisher on(BindableColumn<T> joinColumn, JoinCondition<T> onJoinCondition,
                JoinCriterion<?>... andJoinCriteria) {
            return new JoinSpecificationFinisher(joinTable, joinColumn, onJoinCondition, joinType, andJoinCriteria);
        }
    }

    public class JoinSpecificationFinisher
            extends AbstractWhereStarter<QueryExpressionWhereBuilder, JoinSpecificationFinisher>
            implements Buildable<R> {
        private final JoinSpecification.Builder joinSpecificationBuilder;

        public <T> JoinSpecificationFinisher(TableExpression table, BindableColumn<T> joinColumn,
                JoinCondition<T> joinCondition, JoinType joinType) {
            JoinCriterion<T> joinCriterion = new JoinCriterion.Builder<T>()
                    .withConnector("on") //$NON-NLS-1$
                    .withJoinColumn(joinColumn)
                    .withJoinCondition(joinCondition)
                    .build();

            joinSpecificationBuilder = JoinSpecification.withJoinTable(table)
                    .withJoinType(joinType)
                    .withJoinCriterion(joinCriterion);

            addJoinSpecificationBuilder(joinSpecificationBuilder);
        }

        public <T> JoinSpecificationFinisher(TableExpression table, BindableColumn<T> joinColumn,
                JoinCondition<T> joinCondition, JoinType joinType, JoinCriterion<?>... andJoinCriteria) {
            JoinCriterion<T> onJoinCriterion = new JoinCriterion.Builder<T>()
                    .withConnector("on") //$NON-NLS-1$
                    .withJoinColumn(joinColumn)
                    .withJoinCondition(joinCondition)
                    .build();

            joinSpecificationBuilder = JoinSpecification.withJoinTable(table)
                    .withJoinType(joinType)
                    .withJoinCriterion(onJoinCriterion)
                    .withJoinCriteria(Arrays.asList(andJoinCriteria));

            addJoinSpecificationBuilder(joinSpecificationBuilder);
        }

        @NotNull
        @Override
        public R build() {
            return QueryExpressionDSL.this.build();
        }

        @Override
        public JoinSpecificationFinisher configureStatement(Consumer<StatementConfiguration> consumer) {
            selectDSL.configureStatement(consumer);
            return this;
        }

        @Override
        public QueryExpressionWhereBuilder where() {
            return QueryExpressionDSL.this.where();
        }

        public <T> JoinSpecificationFinisher and(BindableColumn<T> joinColumn, JoinCondition<T> joinCondition) {
            JoinCriterion<T> joinCriterion = new JoinCriterion.Builder<T>()
                    .withConnector("and") //$NON-NLS-1$
                    .withJoinColumn(joinColumn)
                    .withJoinCondition(joinCondition)
                    .build();
            joinSpecificationBuilder.withJoinCriterion(joinCriterion);
            return this;
        }

        public JoinSpecificationStarter join(SqlTable joinTable) {
            return QueryExpressionDSL.this.join(joinTable);
        }

        public JoinSpecificationStarter join(SqlTable joinTable, String tableAlias) {
            return QueryExpressionDSL.this.join(joinTable, tableAlias);
        }

        public JoinSpecificationStarter join(Buildable<SelectModel> joinTable, String tableAlias) {
            return QueryExpressionDSL.this.join(joinTable, tableAlias);
        }

        public JoinSpecificationStarter leftJoin(SqlTable joinTable) {
            return QueryExpressionDSL.this.leftJoin(joinTable);
        }

        public JoinSpecificationStarter leftJoin(SqlTable joinTable, String tableAlias) {
            return QueryExpressionDSL.this.leftJoin(joinTable, tableAlias);
        }

        public JoinSpecificationStarter leftJoin(Buildable<SelectModel> joinTable, String tableAlias) {
            return QueryExpressionDSL.this.leftJoin(joinTable, tableAlias);
        }

        public JoinSpecificationStarter rightJoin(SqlTable joinTable) {
            return QueryExpressionDSL.this.rightJoin(joinTable);
        }

        public JoinSpecificationStarter rightJoin(SqlTable joinTable, String tableAlias) {
            return QueryExpressionDSL.this.rightJoin(joinTable, tableAlias);
        }

        public JoinSpecificationStarter rightJoin(Buildable<SelectModel> joinTable, String tableAlias) {
            return QueryExpressionDSL.this.rightJoin(joinTable, tableAlias);
        }

        public JoinSpecificationStarter fullJoin(SqlTable joinTable) {
            return QueryExpressionDSL.this.fullJoin(joinTable);
        }

        public JoinSpecificationStarter fullJoin(SqlTable joinTable, String tableAlias) {
            return QueryExpressionDSL.this.fullJoin(joinTable, tableAlias);
        }

        public JoinSpecificationStarter fullJoin(Buildable<SelectModel> joinTable, String tableAlias) {
            return QueryExpressionDSL.this.fullJoin(joinTable, tableAlias);
        }

        public GroupByFinisher groupBy(BasicColumn... columns) {
            return groupBy(Arrays.asList(columns));
        }

        public GroupByFinisher groupBy(Collection<? extends BasicColumn> columns) {
            return QueryExpressionDSL.this.groupBy(columns);
        }

        public UnionBuilder union() {
            return QueryExpressionDSL.this.union();
        }

        public UnionBuilder unionAll() {
            return QueryExpressionDSL.this.unionAll();
        }

        public SelectDSL<R> orderBy(SortSpecification... columns) {
            return orderBy(Arrays.asList(columns));
        }

        public SelectDSL<R> orderBy(Collection<? extends SortSpecification> columns) {
            return QueryExpressionDSL.this.orderBy(columns);
        }

        public SelectDSL<R>.LimitFinisher limit(long limit) {
            return QueryExpressionDSL.this.limit(limit);
        }

        public SelectDSL<R>.OffsetFirstFinisher offset(long offset) {
            return QueryExpressionDSL.this.offset(offset);
        }

        public SelectDSL<R>.FetchFirstFinisher fetchFirst(long fetchFirstRows) {
            return QueryExpressionDSL.this.fetchFirst(fetchFirstRows);
        }
    }

    public class GroupByFinisher extends AbstractHavingStarter<QueryExpressionHavingBuilder> implements Buildable<R> {
        public SelectDSL<R> orderBy(SortSpecification... columns) {
            return orderBy(Arrays.asList(columns));
        }

        public SelectDSL<R> orderBy(Collection<? extends SortSpecification> columns) {
            return QueryExpressionDSL.this.orderBy(columns);
        }

        @NotNull
        @Override
        public R build() {
            return QueryExpressionDSL.this.build();
        }

        public UnionBuilder union() {
            return QueryExpressionDSL.this.union();
        }

        public UnionBuilder unionAll() {
            return QueryExpressionDSL.this.unionAll();
        }

        public SelectDSL<R>.LimitFinisher limit(long limit) {
            return QueryExpressionDSL.this.limit(limit);
        }

        public SelectDSL<R>.OffsetFirstFinisher offset(long offset) {
            return QueryExpressionDSL.this.offset(offset);
        }

        public SelectDSL<R>.FetchFirstFinisher fetchFirst(long fetchFirstRows) {
            return QueryExpressionDSL.this.fetchFirst(fetchFirstRows);
        }

        @Override
        public QueryExpressionHavingBuilder having() {
            return QueryExpressionDSL.this.having();
        }
    }

    public class UnionBuilder {
        protected final String connector;

        public UnionBuilder(String connector) {
            this.connector = connector;
        }

        public FromGatherer<R> select(BasicColumn... selectList) {
            return select(Arrays.asList(selectList));
        }

        public FromGatherer<R> select(List<BasicColumn> selectList) {
            return new FromGatherer.Builder<R>()
                    .withConnector(connector)
                    .withSelectList(selectList)
                    .withSelectDSL(selectDSL)
                    .build();
        }

        public FromGatherer<R> selectDistinct(BasicColumn... selectList) {
            return selectDistinct(Arrays.asList(selectList));
        }

        public FromGatherer<R> selectDistinct(List<BasicColumn> selectList) {
            return new FromGatherer.Builder<R>()
                    .withConnector(connector)
                    .withSelectList(selectList)
                    .withSelectDSL(selectDSL)
                    .isDistinct()
                    .build();
        }
    }

    public class QueryExpressionHavingBuilder extends AbstractHavingFinisher<QueryExpressionHavingBuilder>
            implements Buildable<R> {

        public SelectDSL<R>.FetchFirstFinisher fetchFirst(long fetchFirstRows) {
            return QueryExpressionDSL.this.fetchFirst(fetchFirstRows);
        }

        public SelectDSL<R>.OffsetFirstFinisher offset(long offset) {
            return QueryExpressionDSL.this.offset(offset);
        }

        public SelectDSL<R>.LimitFinisher limit(long limit) {
            return QueryExpressionDSL.this.limit(limit);
        }

        public SelectDSL<R> orderBy(SortSpecification... columns) {
            return orderBy(Arrays.asList(columns));
        }

        public SelectDSL<R> orderBy(Collection<? extends SortSpecification> columns) {
            return QueryExpressionDSL.this.orderBy(columns);
        }

        public UnionBuilder union() {
            return QueryExpressionDSL.this.union();
        }

        public UnionBuilder unionAll() {
            return QueryExpressionDSL.this.unionAll();
        }

        @NotNull
        @Override
        public R build() {
            return QueryExpressionDSL.this.build();
        }

        @Override
        protected QueryExpressionHavingBuilder getThis() {
            return this;
        }

        protected HavingModel buildHavingModel() {
            return super.buildModel();
        }
    }
}