/*
* #%L
* server
* %%
* Copyright (C) 2012 - 2015 valdasraps
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/lgpl-3.0.html>.
* #L%
*/
package lt.emasina.resthub.server.parser.check;
import com.google.inject.assistedinject.Assisted;
import lt.emasina.resthub.parser.AbstractSelectParser;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.annotation.Nullable;
import javax.inject.Inject;
import lombok.Getter;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.AllColumns;
import net.sf.jsqlparser.statement.select.AllTableColumns;
import net.sf.jsqlparser.statement.select.Join;
import net.sf.jsqlparser.statement.select.LateralSubSelect;
import net.sf.jsqlparser.statement.select.OrderByElement;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.SelectExpressionItem;
import net.sf.jsqlparser.statement.select.SetOperationList;
import net.sf.jsqlparser.statement.select.SubJoin;
import net.sf.jsqlparser.statement.select.SubSelect;
import net.sf.jsqlparser.statement.select.ValuesList;
import net.sf.jsqlparser.statement.select.WithItem;
import lt.emasina.resthub.server.factory.ResourceFactory;
import lt.emasina.resthub.exception.QueryException;
import lt.emasina.resthub.server.factory.MetadataFactoryIf;
import lt.emasina.resthub.server.table.TableId;
import lt.emasina.resthub.server.table.ServerTable;
/**
* SelectParser
* @author valdo
*/
public class CheckSelectParser extends AbstractSelectParser {
private final ResourceFactory rf;
@Inject
private MetadataFactoryIf mf;
@Getter
private final SubSelectDef selectDef;
private final CheckExpressionParser expParser;
@Inject
public CheckSelectParser(@Assisted @Nullable SubSelectDef parent, ResourceFactory rf) {
this.rf = rf;
this.selectDef = new SubSelectDef(parent);
this.expParser = rf.createExpressionParser(this.selectDef);
}
public List<ServerTable> getTables() {
List<ServerTable> tables = new ArrayList<>();
List<SelectDef> cache = new ArrayList<>();
cache.addAll(selectDef.getTables().values());
while (!cache.isEmpty()) {
SelectDef td = cache.remove(0);
if (td instanceof SubSelectDef) {
cache.addAll(((SubSelectDef) td).getTables().values());
} else
if (td instanceof TableDef) {
tables.add(((TableDef) td).getTableMd());
}
}
return tables;
}
/**
* SelectVisitor
* @param ps
*/
@Override
public void visit(PlainSelect ps) {
// First collect FROM and JOIN items
ps.getFromItem().accept(this);
if (ps.getJoins() != null) {
for (Join j : ps.getJoins()) {
j.getRightItem().accept(this);
if (j.getOnExpression() != null) {
j.getOnExpression().accept(expParser);
}
if (j.getUsingColumns() != null) {
for (Iterator<?> it1 = j.getUsingColumns().iterator(); it1.hasNext();) {
((Expression) it1.next()).accept(expParser);
}
}
}
}
// Next iterate over SELECT, WHERE and ORDER BY items
for (Object o: ps.getSelectItems()) {
if (o instanceof SelectExpressionItem) {
((SelectExpressionItem) o).accept(this);
}
if (o instanceof AllColumns) {
((AllColumns) o).accept(this);
}
if (o instanceof AllTableColumns) {
((AllTableColumns) o).accept(this);
}
}
if (ps.getWhere() != null) {
ps.getWhere().accept(expParser);
}
if (ps.getOrderByElements() != null) {
for (Iterator<?> it = ps.getOrderByElements().iterator(); it.hasNext();) {
((OrderByElement) it.next()).accept(this);
}
}
if (ps.getOracleHierarchical() != null) {
ps.getOracleHierarchical().accept(expParser);
}
}
/**
* FromItemVisitor
* @param table
*/
@Override
public void visit(Table table) {
if (table.getSchemaName() == null) {
throw new QueryException("Table namespace not defined: %s", table.getFullyQualifiedName());
}
if (table.getAlias() == null || table.getAlias().getName() == null) {
throw new QueryException("Table alias not defined: %s", table.getFullyQualifiedName());
}
String alias = table.getAlias().getName();
if (selectDef.getTop().hasAlias(alias)) {
throw new QueryException("Duplicate table alias: %s", alias);
}
TableId tid = new TableId(table.getSchemaName().toLowerCase(), table.getName().toLowerCase());
ServerTable tmd = mf.getTable(tid);
if (tmd == null) {
throw new QueryException("Table not found: %s", table.getFullyQualifiedName());
}
if (!selectDef.isSameConnectionName(tmd.getTable().getConnectionName())) {
throw new QueryException("Table connections do not match: %s", tmd.getId());
}
selectDef.getTables().put(alias, new TableDef(tmd, selectDef));
}
@Override
public void visit(SubSelect ss) {
if (ss.getAlias() == null || ss.getAlias().getName() == null) {
throw new QueryException("Subselect alias not defined: %s", ss.toString());
}
String alias = ss.getAlias().getName();
if (selectDef.getTop().hasAlias(alias)) {
throw new QueryException("Duplicate table alias: %s", alias);
}
CheckSelectParser sp = rf.createSelectParser(selectDef);
selectDef.getTables().put(alias, sp.getSelectDef());
ss.getSelectBody().accept(sp);
}
@Override
public void visit(SubJoin sj) {
throw new QueryException("SubJoins are not supported: %s", sj.toString());
}
// SelectItemVisitor
@Override
public void visit(AllTableColumns atc) {
String alias = atc.getTable().getName();
if (!selectDef.getTables().containsKey(alias)) {
throw new QueryException("Table alias %s not defined", alias);
}
SelectDef td = selectDef.getTables().get(alias);
selectDef.getColumns().addAll(td.getColumns());
}
@Override
public void visit(AllColumns ac) {
for (SelectDef td: selectDef.getTables().values()) {
selectDef.getColumns().addAll(td.getColumns());
}
}
@Override
public void visit(SelectExpressionItem sei) {
String alias = sei.getAlias() != null ? sei.getAlias().getName() : null;
if (alias != null) {
selectDef.getColumns().add(alias);
} else {
if (sei.getExpression() instanceof Column) {
Column col = (Column) sei.getExpression();
selectDef.getColumns().add(CheckExpressionParser.fixColumnName(col.getColumnName()));
} else {
throw new QueryException("Complex return expressions must have alias defined: %s", sei.getExpression());
}
}
sei.getExpression().accept(expParser);
}
@Override
public void visit(OrderByElement ob) {
ob.getExpression().accept(expParser);
}
@Override
public void visit(SetOperationList setOpList) {
throw new QueryException("Set operations are not supported: %s", setOpList.toString());
}
@Override
public void visit(WithItem withItem) {
throw new QueryException("With construct is not supported: %s", withItem.toString());
}
@Override
public void visit(LateralSubSelect lateralSubSelect) {
throw new QueryException("Lateral subselects are not supported: %s", lateralSubSelect.toString());
}
@Override
public void visit(ValuesList valuesList) {
valuesList.getMultiExpressionList().accept(expParser);
}
}