/* * 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); } } } } }