/*
* Copyright 2014-2015 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
*
* http://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 com.wplatform.ddal.dbobject.table;
import java.util.ArrayList;
import java.util.HashSet;
import com.wplatform.ddal.command.Prepared;
import com.wplatform.ddal.command.dml.Query;
import com.wplatform.ddal.command.expression.*;
import com.wplatform.ddal.dbobject.DbObject;
import com.wplatform.ddal.dbobject.User;
import com.wplatform.ddal.dbobject.index.Index;
import com.wplatform.ddal.dbobject.index.IndexMate;
import com.wplatform.ddal.dbobject.index.IndexType;
import com.wplatform.ddal.dbobject.schema.Schema;
import com.wplatform.ddal.engine.Constants;
import com.wplatform.ddal.engine.Session;
import com.wplatform.ddal.message.DbException;
import com.wplatform.ddal.message.ErrorCode;
import com.wplatform.ddal.result.LocalResult;
import com.wplatform.ddal.util.New;
import com.wplatform.ddal.util.StatementBuilder;
import com.wplatform.ddal.util.StringUtils;
import com.wplatform.ddal.value.Value;
/**
* A view is a virtual table that is defined by a query.
*
* @author Thomas Mueller
* @author Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888
*/
public class TableView extends Table {
private static final long ROW_COUNT_APPROXIMATION = 100;
private String querySQL;
private ArrayList<Table> tables;
private String[] columnNames;
private Query viewQuery;
private boolean recursive;
private DbException createException;
private User owner;
private Query topQuery;
private LocalResult recursiveResult;
private boolean tableExpression;
public TableView(Schema schema, int id, String name, String querySQL,
ArrayList<Parameter> params, String[] columnNames, Session session,
boolean recursive) {
super(schema, id, name);
init(querySQL, params, columnNames, session, recursive);
}
private static Query compileViewQuery(Session session, String sql) {
Prepared p = session.prepare(sql);
if (!(p instanceof Query)) {
throw DbException.getSyntaxError(sql, 0);
}
return (Query) p;
}
/**
* Create a temporary view out of the given query.
*
* @param session the session
* @param owner the owner of the query
* @param name the view name
* @param query the query
* @param topQuery the top level query
* @return the view table
*/
public static TableView createTempView(Session session, User owner,
String name, Query query, Query topQuery) {
Schema mainSchema = session.getDatabase().getSchema(Constants.SCHEMA_MAIN);
String querySQL = query.getPlanSQL();
TableView v = new TableView(mainSchema, 0, name,
querySQL, query.getParameters(), null, session,
false);
if (v.createException != null) {
throw v.createException;
}
v.setTopQuery(topQuery);
v.setOwner(owner);
v.setTemporary(true);
return v;
}
private synchronized void init(String querySQL, ArrayList<Parameter> params,
String[] columnNames, Session session, boolean recursive) {
this.querySQL = querySQL;
this.columnNames = columnNames;
this.recursive = recursive;
initColumnsAndTables(session);
}
private void initColumnsAndTables(Session session) {
Column[] cols;
//removeViewFromTables();
try {
Query query = compileViewQuery(session, querySQL);
this.querySQL = query.getPlanSQL();
tables = New.arrayList(query.getTables());
ArrayList<Expression> expressions = query.getExpressions();
ArrayList<Column> list = New.arrayList();
for (int i = 0, count = query.getColumnCount(); i < count; i++) {
Expression expr = expressions.get(i);
String name = null;
if (columnNames != null && columnNames.length > i) {
name = columnNames[i];
}
if (name == null) {
name = expr.getAlias();
}
int type = expr.getType();
long precision = expr.getPrecision();
int scale = expr.getScale();
int displaySize = expr.getDisplaySize();
Column col = new Column(name, type, precision, scale, displaySize);
col.setTable(this, i);
// Fetch check constraint from view column source
ExpressionColumn fromColumn = null;
if (expr instanceof ExpressionColumn) {
fromColumn = (ExpressionColumn) expr;
} else if (expr instanceof Alias) {
Expression aliasExpr = expr.getNonAliasExpression();
if (aliasExpr instanceof ExpressionColumn) {
fromColumn = (ExpressionColumn) aliasExpr;
}
}
if (fromColumn != null) {
Expression checkExpression = fromColumn.getColumn()
.getCheckConstraint(session, name);
if (checkExpression != null) {
col.addCheckConstraint(session, checkExpression);
}
}
list.add(col);
}
cols = new Column[list.size()];
list.toArray(cols);
createException = null;
viewQuery = query;
} catch (DbException e) {
e.addSQL(querySQL);
createException = e;
// if it can't be compiled, then it's a 'zero column table'
// this avoids problems when creating the view when opening the
// database
tables = New.arrayList();
cols = new Column[0];
if (recursive && columnNames != null) {
cols = new Column[columnNames.length];
for (int i = 0; i < columnNames.length; i++) {
cols[i] = new Column(columnNames[i], Value.STRING);
}
//index.setRecursive(true);
createException = null;
}
}
setColumns(cols);
if (getId() != 0) {
//addViewToTables();
}
}
/**
* Check if this view is currently invalid.
*
* @return true if it is
*/
public boolean isInvalid() {
return createException != null;
}
@Override
public boolean isQueryComparable() {
if (!super.isQueryComparable()) {
return false;
}
for (Table t : tables) {
if (!t.isQueryComparable()) {
return false;
}
}
return !(topQuery != null &&
!topQuery.isEverything(ExpressionVisitor.QUERY_COMPARABLE_VISITOR));
}
/**
* Generate "CREATE" SQL statement for the view.
*
* @param orReplace if true, then include the OR REPLACE clause
* @param force if true, then include the FORCE clause
* @return the SQL statement
*/
public String getCreateSQL(boolean orReplace, boolean force) {
return getCreateSQL(orReplace, force, getSQL());
}
private String getCreateSQL(boolean orReplace, boolean force,
String quotedName) {
StatementBuilder buff = new StatementBuilder("CREATE ");
if (orReplace) {
buff.append("OR REPLACE ");
}
if (force) {
buff.append("FORCE ");
}
buff.append("VIEW ");
buff.append(quotedName);
if (comment != null) {
buff.append(" COMMENT ").append(StringUtils.quoteStringSQL(comment));
}
if (columns != null && columns.length > 0) {
buff.append('(');
for (Column c : columns) {
buff.appendExceptFirst(", ");
buff.append(c.getSQL());
}
buff.append(')');
} else if (columnNames != null) {
buff.append('(');
for (String n : columnNames) {
buff.appendExceptFirst(", ");
buff.append(n);
}
buff.append(')');
}
return buff.append(" AS\n").append(querySQL).toString();
}
@Override
public void checkRename() {
// ok
}
@Override
public long getRowCount(Session session) {
throw DbException.throwInternalError();
}
@Override
public boolean canGetRowCount() {
return false;
}
@Override
public String getTableType() {
return Table.VIEW;
}
@Override
public void removeChildrenAndResources(Session session) {
super.removeChildrenAndResources(session);
querySQL = null;
invalidate();
}
@Override
public String getSQL() {
if (isTemporary()) {
return "(\n" + StringUtils.indent(querySQL) + ")";
}
return super.getSQL();
}
public String getQuery() {
return querySQL;
}
@Override
public Index getScanIndex(Session session) {
if (createException != null) {
String msg = createException.getMessage();
throw DbException.get(ErrorCode.VIEW_IS_INVALID_2,
createException, getSQL(), msg);
}
return new IndexMate(this, 0, null, IndexColumn.wrap(columns), IndexType.createScan());
}
@Override
public ArrayList<Index> getIndexes() {
return null;
}
@Override
public Index getUniqueIndex() {
return null;
}
public User getOwner() {
return owner;
}
private void setOwner(User owner) {
this.owner = owner;
}
private void setTopQuery(Query topQuery) {
this.topQuery = topQuery;
}
@Override
public long getRowCountApproximation() {
return ROW_COUNT_APPROXIMATION;
}
public int getParameterOffset() {
return topQuery == null ? 0 : topQuery.getParameters().size();
}
@Override
public boolean isDeterministic() {
if (recursive || viewQuery == null) {
return false;
}
return viewQuery.isEverything(ExpressionVisitor.DETERMINISTIC_VISITOR);
}
public LocalResult getRecursiveResult() {
return recursiveResult;
}
public void setRecursiveResult(LocalResult value) {
if (recursiveResult != null) {
recursiveResult.close();
}
this.recursiveResult = value;
}
public boolean isTableExpression() {
return tableExpression;
}
public void setTableExpression(boolean tableExpression) {
this.tableExpression = tableExpression;
}
@Override
public void addDependencies(HashSet<DbObject> dependencies) {
super.addDependencies(dependencies);
if (tables != null) {
for (Table t : tables) {
if (!Table.VIEW.equals(t.getTableType())) {
t.addDependencies(dependencies);
}
}
}
}
}