Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright (c) 2009 Leonardo Alves da Costa
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
* License for more details. You should have received a copy of the GNU General Public License along with this
* program. If not, see .
*/
package com.googlecode.paradox.planner.plan;
import com.googlecode.paradox.ConnectionInfo;
import com.googlecode.paradox.exceptions.*;
import com.googlecode.paradox.metadata.Field;
import com.googlecode.paradox.metadata.Table;
import com.googlecode.paradox.parser.nodes.*;
import com.googlecode.paradox.planner.FieldValueUtils;
import com.googlecode.paradox.planner.context.SelectContext;
import com.googlecode.paradox.planner.nodes.*;
import com.googlecode.paradox.planner.nodes.join.ANDNode;
import com.googlecode.paradox.planner.nodes.join.ORNode;
import com.googlecode.paradox.results.Column;
import com.googlecode.paradox.results.ParadoxType;
import com.googlecode.paradox.utils.FunctionalUtils;
import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.googlecode.paradox.utils.FunctionalUtils.functionWrapper;
import static com.googlecode.paradox.utils.FunctionalUtils.predicateWrapper;
/**
* Creates a SELECT plan for execution.
*
* @since 1.1
*/
public final class SelectPlan implements Plan, SelectContext> {
/**
* The columns in this plan to show in result set.
*/
private final List columns;
/**
* The columns to load in this plan, not only in result set.
*/
private final List columnsFromFunctions = new ArrayList<>();
/**
* The tables in this plan.
*/
private final List tables;
/**
* If this result needs to be distinct.
*/
private final boolean distinct;
/**
* Order by fields.
*/
private final OrderByNode orderBy;
/**
* The group by node.
*/
private final GroupByNode groupBy;
/**
* The conditions to filter values
*/
private AbstractConditionalNode condition;
/**
* The statement parameters count.
*/
private final int parameterCount;
/**
* Result set limit.
*/
private final Integer limit;
/**
* Result set offset.
*/
private final Integer offset;
/**
* Creates a SELECT plan.
*
* @param connectionInfo the connection info.
* @param statement the statement node.
* @throws ParadoxSyntaxErrorException in case of failures.
*/
public SelectPlan(final ConnectionInfo connectionInfo, final SelectNode statement) throws SQLException {
this.condition = statement.getCondition();
this.distinct = statement.isDistinct();
this.parameterCount = statement.getParameterCount();
this.limit = statement.getLimit();
this.offset = statement.getOffset();
// Load the table information.
this.tables = statement.getTables().stream()
.map(functionWrapper(table -> new PlanTableNode(connectionInfo, table)))
.collect(Collectors.toList());
this.columns = parseColumns(statement);
this.groupBy = new GroupByNode(statement, this.tables, this.columns);
this.orderBy = new OrderByNode(statement, this.tables, columns, connectionInfo, this.groupBy.isGroupBy());
if (this.columns.isEmpty()) {
throw new ParadoxSyntaxErrorException(SyntaxError.EMPTY_COLUMN_LIST);
}
// Check for columns to load.
for (final PlanTableNode table : this.tables) {
// Columns in SELECT clause.
table.addColumns(this.columns);
// Columns in SELECT functions.
table.addColumns(this.columnsFromFunctions);
// Columns in GROUP BY clause.
table.addColumns(this.groupBy.getColumns());
// Columns in ORDER BY clause.
table.addColumns(this.orderBy.getColumns());
// Fields from WHERE clause.
table.addColumns(SelectUtils.getConditionalFields(table, this.condition));
// Get fields from other tables join.
for (final PlanTableNode tableToField : this.tables) {
table.addColumns(SelectUtils.getConditionalFields(table, tableToField.getConditionalJoin()));
}
}
}
@Override
public void optimize() {
if (optimizeConditions(condition)) {
condition = null;
}
// Optimize default conditions.
condition = SelectUtils.joinClauses(condition);
// Optimize table conditions.
for (final PlanTableNode table : this.tables) {
table.setConditionalJoin(SelectUtils.joinClauses(table.getConditionalJoin()));
}
}
@Override
public SelectContext createContext(final ConnectionInfo connectionInfo, final Object[] parameters,
final ParadoxType[] parameterTypes) {
return new SelectContext(connectionInfo, parameters, parameterTypes);
}
/**
* Parses the table columns.
*
* @param statement the SELECT statement.
* @return the statement columns.
* @throws SQLException in case of parse errors.
*/
private List parseColumns(final SelectNode statement) throws SQLException {
final List ret = new ArrayList<>();
for (final SQLNode field : statement.getFields()) {
if (field instanceof AsteriskNode) {
if (this.tables.isEmpty()) {
throw new ParadoxSyntaxErrorException(SyntaxError.ASTERISK_WITHOUT_TABLE,
field.getPosition());
}
ret.addAll(parseAsterisk((AsteriskNode) field));
} else {
ret.addAll(processColumn((FieldNode) field));
}
}
// Sets the column indexes.
for (int i = 0; i < ret.size(); i++) {
ret.get(i).setIndex(i);
}
return ret;
}
/**
* Parses the asterisk fields in SELECT.
*
* @param field the asterisk field.
* @return the columns to add.
* @throws SQLException in case of parse errors.
*/
private List parseAsterisk(final AsteriskNode field) throws SQLException {
if (field.getTableName() != null) {
// Add all columns from one table.
final List
tablesFound = this.tables.stream()
.filter(t -> t.isThis(field.getTableName()))
.map(PlanTableNode::getTable).collect(Collectors.toList());
if (tablesFound.isEmpty()) {
throw new ParadoxDataException(DataError.TABLE_NOT_FOUND, field.getPosition(),
field.getTableName());
} else if (tablesFound.size() > 1) {
throw new ParadoxException(ParadoxException.Error.TABLE_AMBIGUOUS_DEFINED, field.getPosition(),
field.getTableName());
}
return Arrays.stream(tablesFound.get(0).getFields()).map(Column::new).collect(Collectors.toList());
} else {
// Add all fields from all tables.
return this.tables.stream()
.map(PlanTableNode::getTable)
.map(Table::getFields)
.flatMap(Arrays::stream)
.map(Column::new)
.collect(Collectors.toList());
}
}
private int getTableIndex(final Table table) {
int index = -1;
for (int i = 0; i < this.tables.size(); i++) {
if (this.tables.get(i).getTable().equals(table)) {
index = i;
break;
}
}
return index;
}
private PlanTableNode getPlanTable(final Table table) {
return tables.stream().filter(t -> table.equals(t.getTable())).findFirst().orElse(null);
}
/**
* Add column from select list.
*
* @param node SQL node with column attributes.
* @throws SQLException search column exception.
*/
private List processColumn(final FieldNode node) throws SQLException {
List ret;
if (node instanceof ValueNode) {
ret = Collections.singletonList(new Column((ValueNode) node));
} else if (node instanceof ParameterNode) {
ret = Collections.singletonList(new Column((ParameterNode) node));
} else if (node instanceof FunctionNode) {
final List columnsToProcess = SelectUtils.getParadoxFields(node, this.tables);
// The fist column is always the function column.
final Column column = columnsToProcess.get(0);
column.setName(node.getAlias());
ret = Collections.singletonList(column);
this.columnsFromFunctions.addAll(columnsToProcess);
} else {
ret = SelectUtils.getParadoxFields(node, this.tables);
ret.forEach((Column column) -> column.setName(node.getAlias()));
}
return ret;
}
/**
* Process the node and change it to it's table (if it is possible).
*
* @param node the node to process.
* @return true if the node is processed and needed to be removed.
*/
private boolean optimizeConditions(final SQLNode node) {
boolean ret = false;
if (node instanceof ANDNode) {
ANDNode andNode = (ANDNode) node;
andNode.getChildren().removeIf(this::optimizeConditions);
ret = node.getClauseFields().isEmpty();
} else if (node != null && !(node instanceof ORNode)) {
// Don't process 'OR' nodes.
final List conditionalFields = new ArrayList<>();
final Set fields = node.getClauseFields();
fields.forEach((FieldNode fn) -> {
for (final PlanTableNode table : this.tables) {
if (table.isThis(fn.getTableName())) {
conditionalFields.addAll(Arrays.stream(table.getTable().getFields())
.filter(f -> f.getName().equalsIgnoreCase(fn.getName()))
.collect(Collectors.toSet()));
}
}
});
if (conditionalFields.size() == 1) {
// FIELD = VALUE
final Table paradoxTable = conditionalFields.get(0).getTable();
final PlanTableNode planTableNode = getPlanTable(paradoxTable);
if (planTableNode != null && (planTableNode.getJoinType() == JoinType.CROSS
|| planTableNode.getJoinType() == JoinType.INNER)) {
// Do not change OUTER joins.
SelectUtils.addAndClause(planTableNode, node);
ret = true;
}
} else if (conditionalFields.size() > 1) {
// FIELD = FIELD
final Table paradoxTable1 = conditionalFields.get(0).getTable();
final Table paradoxTable2 = conditionalFields.get(1).getTable();
final int index1 = getTableIndex(paradoxTable1);
final int index2 = getTableIndex(paradoxTable2);
// Both tables exist?
if (index1 != -1 && index2 != -1) {
// Use the last table to
int lastIndex = Math.max(index1, index2);
SelectUtils.addAndClause(this.tables.get(lastIndex), node);
ret = true;
}
}
}
// Unprocessed.
return ret;
}
@Override
public List