All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ru.curs.celesta.score.AbstractSelectStmt Maven / Gradle / Ivy

The newest version!
package ru.curs.celesta.score;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * A segment of a UNION chain in a SQL UNION ALL query.
 */
public abstract class AbstractSelectStmt {
    final AbstractView view;

    final Map columns = new LinkedHashMap<>();
    final Map groupByColumns = new LinkedHashMap<>();

    boolean distinct;

    final Map tables = new LinkedHashMap<>();

    public AbstractSelectStmt(AbstractView view) {
        this.view = view;
    }


    /**
     * Writes SELECT part to the stream.
     *
     * @param bw  output stream
     * @param gen SQL generator (visitor)
     * @param bww line break wrapper
     */
    void writeSelectPart(final PrintWriter bw, SQLGenerator gen, AbstractView.BWWrapper bww) {
        bww.append("  select ", bw);
        if (distinct) {
            bww.append("distinct ", bw);
        }

        boolean cont = false;
        for (Map.Entry e : columns.entrySet()) {
            if (cont) {
                bww.append(", ", bw);
            }
            String st = gen.generateSQL(e.getValue()) + " as ";
            if (gen.quoteNames()) {
                st = st + "\"" + e.getKey() + "\"";
            } else {
                st = st + e.getKey();
            }
            bww.append(st, bw);
            cont = true;
        }
        bw.println();
    }

    /**
     * Writes FROM part to the stream.
     *
     * @param bw  output stream
     * @param gen SQL generator (visitor)
     */
    void writeFromPart(final PrintWriter bw, SQLGenerator gen) {
        bw.write("  from ");
        boolean cont = false;
        for (TableRef tRef : tables.values()) {
            if (cont) {
                bw.println();
                bw.printf("    %s ", tRef.getJoinType().toString());
                bw.write("join ");
            }
            bw.write(gen.tableName(tRef));
            if (cont) {
                bw.write(" on ");
                bw.write(gen.generateSQL(tRef.getOnExpr()));
            }
            cont = true;
        }
    }

    /**
     * Writes WHERE part to the stream.
     *
     * @param bw  output stream
     * @param gen SQL generator (visitor)
     */
    void writeWherePart(final PrintWriter bw, SQLGenerator gen) {
    }

    /**
     * Writes GROUP BY part to the stream.
     *
     * @param bw  output stream
     * @param gen SQL generator (visitor)
     */
    void writeGroupByPart(final PrintWriter bw, SQLGenerator gen) {
        if (!groupByColumns.isEmpty()) {
            bw.println();
            bw.write(" group by ");

            int countOfProcessed = 0;
            for (Expr field : groupByColumns.values()) {
                bw.write(gen.generateSQL(field));

                if (++countOfProcessed != groupByColumns.size()) {
                    bw.write(", ");
                }
            }

        }
    }

    /**
     * Adds a column to the view.
     *
     * @param alias column alias.
     * @param expr  column expression.
     * @throws ParseException Non-unique alias name or some other semantic error
     */
    void addColumn(String alias, Expr expr) throws ParseException {
        if (expr == null) {
            throw new IllegalArgumentException();
        }

        if (alias == null || alias.isEmpty()) {
            throw new ParseException(String.format("%s '%s' contains a column with undefined alias.",
                    view.viewType(), view.getName()));
        }
        alias = view.getGrain().getScore().getIdentifierParser().parse(alias);
        if (columns.containsKey(alias)) {
            throw new ParseException(String.format(
                    "%s '%s' already contains column with name or alias '%s'. Use unique aliases for %s columns.",
                    view.viewType(), view.getName(), alias, view.viewType()));
        }

        columns.put(alias, expr);
    }

    /**
     * Adds a column to the "GROUP BY" clause of the view.
     *
     * @param fr Column expression.
     * @throws ParseException Non-unique alias name, missing column in selection or some other semantic error
     */
    void addGroupByColumn(FieldRef fr) throws ParseException {
        if (fr == null) {
            throw new IllegalArgumentException();
        }

        String alias = fr.getColumnName();

        if (groupByColumns.containsKey(alias)) {
            throw new ParseException(String.format(
                    "Duplicate column '%s' in GROUP BY expression for %s '%s.%s'.",
                    alias, view.viewType(), view.getGrain().getName(), view.getName()));
        }

        Expr existedColumn = columns.get(fr.getColumnName());

        if (existedColumn == null) {
            throw new ParseException("Couldn't resolve column ref " + fr.getColumnName());
        }

        if (existedColumn.getClass().equals(FieldRef.class)) {
            FieldRef existedColumnFr = (FieldRef) existedColumn;
            fr.setTableNameOrAlias(existedColumnFr.getTableNameOrAlias());
            fr.setColumnName(existedColumnFr.getColumnName());
            fr.setColumn(existedColumnFr.getColumn());
        }

        groupByColumns.put(alias, fr);
    }


    /**
     * Adds a table reference to the view.
     *
     * @param ref Table reference.
     * @throws ParseException Non-unique alias or some other semantic error.
     */
    void addFromTableRef(TableRef ref) throws ParseException {
        if (ref == null) {
            throw new IllegalArgumentException();
        }

        String alias = ref.getAlias();
        if (alias == null || alias.isEmpty()) {
            throw new ParseException(String.format("%s '%s' contains a table with undefined alias.",
                    view.viewType(), view.getName()));
        }
        if (tables.containsKey(alias)) {
            throw new ParseException(String.format(
                    "%s, '%s' already contains table with name or alias '%s'. Use unique aliases for %s tables.",
                    view.viewType(), view.getName(), alias, view.viewType()));
        }

        tables.put(alias, ref);

        Expr onCondition = ref.getOnExpr();
        if (onCondition != null) {
            onCondition.resolveFieldRefs(new ArrayList<>(tables.values()));
            onCondition.validateTypes();
        }
    }

    /**
     * Finalizes parsing of columns.
     * @throws ParseException if column not found
     */
    void finalizeColumnsParsing() throws ParseException {
        List t = new ArrayList<>(tables.values());
        for (Expr e : columns.values()) {
            e.resolveFieldRefs(t);
            e.validateTypes();
        }
    }

    /**
     * Sets where condition for SQL query.
     *
     * @param whereCondition where condition.
     * @throws ParseException if expression type is incorrect.
     */
    abstract void setWhereCondition(Expr whereCondition) throws ParseException;

    /**
     * Finalizes view parsing, resolving field references and checking expression types.
     *
     * @throws ParseException Error on types checking or reference resolving.
     */
    abstract void finalizeParsing() throws ParseException;

    /**
     * Finalizes parsing of GROUP BY clause.
     *
     * @throws ParseException if GROUP BY is invalid
     */
    void finalizeGroupByParsing() throws ParseException {
        //Check that columns which were not used for aggregation are mentioned in GROUP BY clause
        Set aggregateAliases = columns.entrySet().stream()
                .filter(e -> e.getValue() instanceof Aggregate)
                .map(Map.Entry::getKey)
                .collect(Collectors.toSet());

        if (!((aggregateAliases.isEmpty() || aggregateAliases.size() == columns.size())
                && groupByColumns.isEmpty())) {

            //Iterate by columns which are not aggregates and throw an exception
            // if at least one of them is absent from groupByColumns
            boolean hasErrorOpt = columns.entrySet().stream()
                    .anyMatch(e -> !(e.getValue() instanceof Aggregate) && !groupByColumns.containsKey(e.getKey()));
            if (hasErrorOpt) {
                throw new ParseException(String.format("%s '%s.%s' contains a column(s) "
                                + "which was not specified in aggregate function and GROUP BY expression.",
                        view.viewType(), view.getGrain().getName(), view.getName()));
            }
        }
    }


    /**
     * Whether DISTINCT keyword was used in the view query.
     *
     * @return
     */
    boolean isDistinct() {
        return distinct;
    }

    /**
     * Sets the use of keyword DISTINCT in the view query.
     *
     * @param distinct Whether the query has the form of SELECT DISTINCT.
     */
    void setDistinct(boolean distinct) {
        this.distinct = distinct;
    }

    final Grain getGrain() {
        return view.getGrain();
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy