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.
/* This file is part of VoltDB.
* Copyright (C) 2008-2018 VoltDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with VoltDB. If not, see .
*/
package org.voltdb.compiler;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.TreeSet;
import org.hsqldb_voltpatches.HSQLInterface;
import org.hsqldb_voltpatches.HSQLInterface.HSQLParseException;
import org.hsqldb_voltpatches.VoltXMLElement;
import org.json_voltpatches.JSONException;
import org.json_voltpatches.JSONObject;
import org.voltdb.VoltType;
import org.voltdb.catalog.CatalogMap;
import org.voltdb.catalog.Column;
import org.voltdb.catalog.ColumnRef;
import org.voltdb.catalog.Constraint;
import org.voltdb.catalog.Database;
import org.voltdb.catalog.Index;
import org.voltdb.catalog.IndexRef;
import org.voltdb.catalog.MaterializedViewHandlerInfo;
import org.voltdb.catalog.MaterializedViewInfo;
import org.voltdb.catalog.Statement;
import org.voltdb.catalog.Table;
import org.voltdb.catalog.TableRef;
import org.voltdb.compiler.VoltCompiler.VoltCompilerException;
import org.voltdb.expressions.AbstractExpression;
import org.voltdb.expressions.AbstractExpression.UnsafeOperatorsForDDL;
import org.voltdb.expressions.ExpressionUtil;
import org.voltdb.expressions.TupleValueExpression;
import org.voltdb.planner.AbstractParsedStmt;
import org.voltdb.planner.ParsedColInfo;
import org.voltdb.planner.ParsedSelectStmt;
import org.voltdb.planner.StatementPartitioning;
import org.voltdb.planner.SubPlanAssembler;
import org.voltdb.planner.parseinfo.StmtTableScan;
import org.voltdb.planner.parseinfo.StmtTargetTableScan;
import org.voltdb.plannodes.AbstractPlanNode;
import org.voltdb.plannodes.IndexScanPlanNode;
import org.voltdb.plannodes.NestLoopPlanNode;
import org.voltdb.plannodes.PlanNodeTree;
import org.voltdb.types.ConstraintType;
import org.voltdb.types.ExpressionType;
import org.voltdb.types.IndexType;
import org.voltdb.utils.CatalogUtil;
import org.voltdb.utils.Encoder;
public class MaterializedViewProcessor {
private final VoltCompiler m_compiler;
private final HSQLInterface m_hsql;
public MaterializedViewProcessor(VoltCompiler compiler,
HSQLInterface hsql) {
assert(compiler != null);
assert(hsql != null);
m_compiler = compiler;
m_hsql = hsql;
}
/**
* Add materialized view info to the catalog for the tables that are
* materialized views.
* @throws VoltCompilerException
*/
public void startProcessing(Database db, HashMap
matViewMap, TreeSet exportTableNames)
throws VoltCompilerException {
HashSet viewTableNames = new HashSet<>();
for (Entry
entry : matViewMap.entrySet()) {
viewTableNames.add(entry.getKey().getTypeName());
}
for (Entry
entry : matViewMap.entrySet()) {
Table destTable = entry.getKey();
String query = entry.getValue();
// get the xml for the query
VoltXMLElement xmlquery = null;
try {
xmlquery = m_hsql.getXMLCompiledStatement(query);
}
catch (HSQLParseException e) {
e.printStackTrace();
}
assert(xmlquery != null);
// parse the xml like any other sql statement
ParsedSelectStmt stmt = null;
try {
stmt = (ParsedSelectStmt) AbstractParsedStmt.parse(null, query, xmlquery, null, db, null);
}
catch (Exception e) {
throw m_compiler.new VoltCompilerException(e.getMessage());
}
assert(stmt != null);
String viewName = destTable.getTypeName();
// throw an error if the view isn't within voltdb's limited world view
checkViewMeetsSpec(viewName, stmt);
// Allow only non-unique indexes other than the primary key index.
// The primary key index is yet to be defined (below).
for (Index destIndex : destTable.getIndexes()) {
if (destIndex.getUnique() || destIndex.getAssumeunique()) {
String msg = "A UNIQUE or ASSUMEUNIQUE index is not allowed on a materialized view. " +
"Remove the qualifier from the index " + destIndex.getTypeName() +
"defined on the materialized view \"" + viewName + "\".";
throw m_compiler.new VoltCompilerException(msg);
}
}
// A Materialized view cannot depend on another view.
for (Table srcTable : stmt.m_tableList) {
if (viewTableNames.contains(srcTable.getTypeName())) {
String msg = String.format("A materialized view (%s) can not be defined on another view (%s).",
viewName, srcTable.getTypeName());
throw m_compiler.new VoltCompilerException(msg);
}
}
// The existing code base still need this materializer field to tell if a table
// is a materialized view table. Leaving this for future refactoring.
destTable.setMaterializer(stmt.m_tableList.get(0));
List destColumnArray = CatalogUtil.getSortedCatalogItems(destTable.getColumns(), "index");
List groupbyExprs = null;
if (stmt.hasComplexGroupby()) {
groupbyExprs = new ArrayList<>();
for (ParsedColInfo col: stmt.groupByColumns()) {
groupbyExprs.add(col.m_expression);
}
}
// Generate query XMLs for min/max recalculation (ENG-8641)
boolean isMultiTableView = stmt.m_tableList.size() > 1;
MatViewFallbackQueryXMLGenerator xmlGen = new MatViewFallbackQueryXMLGenerator(xmlquery, stmt.groupByColumns(), stmt.m_displayColumns, isMultiTableView);
List fallbackQueryXMLs = xmlGen.getFallbackQueryXMLs();
// create an index and constraint for the table
// After ENG-7872 is fixed if there is no group by column then we will not create any
// index or constraint in order to avoid error and crash.
if (stmt.groupByColumns().size() != 0) {
Index pkIndex = destTable.getIndexes().add(HSQLInterface.AUTO_GEN_MATVIEW_IDX);
pkIndex.setType(IndexType.BALANCED_TREE.getValue());
pkIndex.setUnique(true);
// add the group by columns from the src table
// assume index 1 throuh #grpByCols + 1 are the cols
for (int i = 0; i < stmt.groupByColumns().size(); i++) {
ColumnRef c = pkIndex.getColumns().add(String.valueOf(i));
c.setColumn(destColumnArray.get(i));
c.setIndex(i);
}
Constraint pkConstraint = destTable.getConstraints().add(HSQLInterface.AUTO_GEN_MATVIEW_CONST);
pkConstraint.setType(ConstraintType.PRIMARY_KEY.getValue());
pkConstraint.setIndex(pkIndex);
}
// If we have an unsafe MV message, then
// remember it here. We don't really know how
// to transfer the message through the catalog, but
// we can transmit the existence of the message.
boolean isSafeForDDL = (stmt.getUnsafeMVMessage() == null);
// Here we will calculate the maximum allowed size for variable-length columns in the view.
// The maximum row size is 2MB. We will first subtract the sum of
// all fixed-size data type column sizes from this 2MB allowance then
// divide it by the number of variable-length columns.
int maximumColumnSize = DDLCompiler.MAX_ROW_SIZE;
int varLengthColumnCount = stmt.m_displayColumns.size();
for (int i = 0; i < stmt.m_displayColumns.size(); i++) {
ParsedColInfo col = stmt.m_displayColumns.get(i);
if ( ! col.m_expression.getValueType().isVariableLength()) {
varLengthColumnCount--;
maximumColumnSize -= col.m_expression.getValueSize();
}
}
if (varLengthColumnCount > 0) {
maximumColumnSize /= varLengthColumnCount;
}
// Note that the size of a single column cannot be larger than 1MB.
if (maximumColumnSize > DDLCompiler.MAX_VALUE_LENGTH) {
maximumColumnSize = DDLCompiler.MAX_VALUE_LENGTH;
}
// Here the code path diverges for different kinds of views (single table view and joined table view)
if (isMultiTableView) {
// Materialized view on joined tables
// Add mvHandlerInfo to the destTable:
MaterializedViewHandlerInfo mvHandlerInfo = destTable.getMvhandlerinfo().add("mvHandlerInfo");
mvHandlerInfo.setDesttable(destTable);
for (Table srcTable : stmt.m_tableList) {
// Now we do not support having a view on persistent tables joining streamed tables.
if (exportTableNames.contains(srcTable.getTypeName())) {
String msg = String.format("A materialized view (%s) on joined tables cannot have streamed table (%s) as its source.",
viewName, srcTable.getTypeName());
throw m_compiler.new VoltCompilerException(msg);
}
// The view table will need to keep a list of its source tables.
// The list is used to install / uninstall the view reference on the source tables when the
// view handler is constructed / destroyed.
TableRef tableRef = mvHandlerInfo.getSourcetables().add(srcTable.getTypeName());
tableRef.setTable(srcTable);
// Try to find a partition column for the view table.
// There could be more than one partition column candidate, but we will only use the first one we found.
if (destTable.getPartitioncolumn() == null && srcTable.getPartitioncolumn() != null) {
Column partitionColumn = srcTable.getPartitioncolumn();
String partitionColName = partitionColumn.getTypeName();
String srcTableName = srcTable.getTypeName();
destTable.setIsreplicated(false);
if (stmt.hasComplexGroupby()) {
for (int i = 0; i < groupbyExprs.size(); i++) {
AbstractExpression groupbyExpr = groupbyExprs.get(i);
if (groupbyExpr instanceof TupleValueExpression) {
TupleValueExpression tve = (TupleValueExpression) groupbyExpr;
if (tve.getTableName().equals(srcTableName) && tve.getColumnName().equals(partitionColName)) {
// The partition column is set to destColumnArray.get(i), because we have the restriction
// that the non-aggregate columns must come at the very begining, and must exactly match
// the group-by columns.
// If we are going to remove this restriction in the future, then we need to do more work
// in order to find a proper partition column.
destTable.setPartitioncolumn(destColumnArray.get(i));
break;
}
}
}
}
else {
for (int i = 0; i < stmt.groupByColumns().size(); i++) {
ParsedColInfo gbcol = stmt.groupByColumns().get(i);
if (gbcol.m_tableName.equals(srcTableName) && gbcol.m_columnName.equals(partitionColName)) {
destTable.setPartitioncolumn(destColumnArray.get(i));
break;
}
}
}
} // end find partition column
} // end for each source table
compileFallbackQueriesAndUpdateCatalog(db, query, fallbackQueryXMLs, mvHandlerInfo);
compileCreateQueryAndUpdateCatalog(db, query, xmlquery, mvHandlerInfo);
mvHandlerInfo.setGroupbycolumncount(stmt.groupByColumns().size());
for (int i=0; i srcColumnArray = CatalogUtil.getSortedCatalogItems(srcTable.getColumns(), "index");
if (stmt.hasComplexGroupby()) {
// Parse group by expressions to json string
String groupbyExprsJson = null;
try {
groupbyExprsJson = DDLCompiler.convertToJSONArray(groupbyExprs);
} catch (JSONException e) {
throw m_compiler.new VoltCompilerException ("Unexpected error serializing non-column " +
"expressions for group by expressions: " + e.toString());
}
matviewinfo.setGroupbyexpressionsjson(groupbyExprsJson);
}
else {
// add the group by columns from the src table
for (int i = 0; i < stmt.groupByColumns().size(); i++) {
ParsedColInfo gbcol = stmt.groupByColumns().get(i);
Column srcCol = srcColumnArray.get(gbcol.m_index);
ColumnRef cref = matviewinfo.getGroupbycols().add(srcCol.getTypeName());
// groupByColumns is iterating in order of groups. Store that grouping order
// in the column ref index. When the catalog is serialized, it will, naturally,
// scramble this order like a two year playing dominos, presenting the data
// in a meaningless sequence.
cref.setIndex(i); // the column offset in the view's grouping order
cref.setColumn(srcCol); // the source column from the base (non-view) table
// parse out the group by columns into the dest table
ParsedColInfo col = stmt.m_displayColumns.get(i);
Column destColumn = destColumnArray.get(i);
processMaterializedViewColumn(srcTable, destColumn,
ExpressionType.VALUE_TUPLE, (TupleValueExpression)col.m_expression);
}
}
// prepare info for aggregation columns and COUNT(*) column(s)
List aggregationExprs = new ArrayList<>();
boolean hasAggregationExprs = false;
ArrayList minMaxAggs = new ArrayList<>();
for (int i = stmt.groupByColumns().size(); i < stmt.m_displayColumns.size(); i++) {
ParsedColInfo col = stmt.m_displayColumns.get(i);
// skip COUNT(*)
if ( col.m_expression.getExpressionType() == ExpressionType.AGGREGATE_COUNT_STAR ) {
continue;
}
AbstractExpression aggExpr = col.m_expression.getLeft();
if (aggExpr.getExpressionType() != ExpressionType.VALUE_TUPLE) {
hasAggregationExprs = true;
}
aggregationExprs.add(aggExpr);
if (col.m_expression.getExpressionType() == ExpressionType.AGGREGATE_MIN ||
col.m_expression.getExpressionType() == ExpressionType.AGGREGATE_MAX) {
minMaxAggs.add(aggExpr);
}
}
compileFallbackQueriesAndUpdateCatalog(db, query, fallbackQueryXMLs, matviewinfo);
// set Aggregation Expressions.
if (hasAggregationExprs) {
String aggregationExprsJson = null;
try {
aggregationExprsJson = DDLCompiler.convertToJSONArray(aggregationExprs);
} catch (JSONException e) {
throw m_compiler.new VoltCompilerException ("Unexpected error serializing non-column " +
"expressions for aggregation expressions: " + e.toString());
}
matviewinfo.setAggregationexpressionsjson(aggregationExprsJson);
}
// Find index for each min/max aggCol/aggExpr (ENG-6511 and ENG-8512)
for (Integer i = 0; i < minMaxAggs.size(); ++i) {
Index found = findBestMatchIndexForMatviewMinOrMax(matviewinfo, srcTable, groupbyExprs, minMaxAggs.get(i));
IndexRef refFound = matviewinfo.getIndexforminmax().add(i.toString());
if (found != null) {
refFound.setName(found.getTypeName());
} else {
refFound.setName("");
}
}
// This is to fix the data type mismatch of the group by columns (and potentially other columns).
// The COUNT(*) should return a BIGINT column, whereas we found here the COUNT(*) was assigned a INTEGER column is fixed below loop.
for (int i = 0; i < stmt.groupByColumns().size(); i++) {
ParsedColInfo col = stmt.m_displayColumns.get(i);
Column destColumn = destColumnArray.get(i);
setTypeAttributesForColumn(destColumn, col.m_expression, maximumColumnSize);
}
// parse out the aggregation columns into the dest table
for (int i = stmt.groupByColumns().size(); i < stmt.m_displayColumns.size(); i++) {
ParsedColInfo col = stmt.m_displayColumns.get(i);
Column destColumn = destColumnArray.get(i);
AbstractExpression colExpr = col.m_expression.getLeft();
TupleValueExpression tve = null;
if ( col.m_expression.getExpressionType() != ExpressionType.AGGREGATE_COUNT_STAR
&& colExpr.getExpressionType() == ExpressionType.VALUE_TUPLE) {
tve = (TupleValueExpression)colExpr;
}
processMaterializedViewColumn(srcTable, destColumn,
col.m_expression.getExpressionType(), tve);
setTypeAttributesForColumn(destColumn, col.m_expression, maximumColumnSize);
}
if (srcTable.getPartitioncolumn() != null) {
// Set the partitioning of destination tables of associated views.
// If a view's source table is replicated, then a full scan of the
// associated view is single-sited. If the source is partitioned,
// a full scan of the view must be distributed, unless it is filtered
// by the original table's partitioning key, which, to be filtered,
// must also be a GROUP BY key.
destTable.setIsreplicated(false);
setGroupedTablePartitionColumn(matviewinfo, srcTable.getPartitioncolumn());
}
matviewinfo.setIssafewithnonemptysources(isSafeForDDL);
} // end if single table view materialized view.
}
}
private void setGroupedTablePartitionColumn(MaterializedViewInfo mvi, Column partitionColumn)
throws VoltCompilerException {
// A view of a replicated table is replicated.
// A view of a partitioned table is partitioned -- regardless of whether it has a partition key
// -- it certainly isn't replicated!
// If the partitioning column is grouped, its counterpart is the partitioning column of the view table.
// Otherwise, the view table just doesn't have a partitioning column
// -- it is seemingly randomly distributed,
// and its grouped columns are only locally unique but not globally unique.
Table destTable = mvi.getDest();
// Get the grouped columns in "index" order.
// This order corresponds to the iteration order of the MaterializedViewInfo's group by columns.
List destColumnArray = CatalogUtil.getSortedCatalogItems(destTable.getColumns(), "index");
String partitionColName = partitionColumn.getTypeName(); // Note getTypeName gets the column name -- go figure.
if (mvi.getGroupbycols().size() > 0) {
int index = 0;
for (ColumnRef cref : CatalogUtil.getSortedCatalogItems(mvi.getGroupbycols(), "index")) {
Column srcCol = cref.getColumn();
if (srcCol.getName().equals(partitionColName)) {
Column destCol = destColumnArray.get(index);
destTable.setPartitioncolumn(destCol);
return;
}
++index;
}
} else {
String complexGroupbyJson = mvi.getGroupbyexpressionsjson();
if (complexGroupbyJson.length() > 0) {
int partitionColIndex = partitionColumn.getIndex();
List mvComplexGroupbyCols = null;
try {
mvComplexGroupbyCols = AbstractExpression.fromJSONArrayString(complexGroupbyJson, null);
} catch (JSONException e) {
e.printStackTrace();
}
int index = 0;
for (AbstractExpression expr: mvComplexGroupbyCols) {
if (expr instanceof TupleValueExpression) {
TupleValueExpression tve = (TupleValueExpression) expr;
if (tve.getColumnIndex() == partitionColIndex) {
Column destCol = destColumnArray.get(index);
destTable.setPartitioncolumn(destCol);
return;
}
}
++index;
}
}
}
}
/**
* If the view is defined on joined tables (>1 source table),
* check if there are self-joins.
*
* @param tableList The list of view source tables.
* @param compiler The VoltCompiler
* @throws VoltCompilerException
*/
private void checkViewSources(ArrayList
tableList) throws VoltCompilerException {
HashSet tableSet = new HashSet<>();
for (Table tbl : tableList) {
if (! tableSet.add(tbl.getTypeName())) {
String errMsg = "Table " + tbl.getTypeName() + " appeared in the table list more than once: " +
"materialized view does not support self-join.";
throw m_compiler.new VoltCompilerException(errMsg);
}
}
}
/**
* Verify the materialized view meets our arcane rules about what can and can't
* go in a materialized view. Throw hopefully helpful error messages when these
* rules are inevitably borked.
*
* @param viewName The name of the view being checked.
* @param stmt The output from the parser describing the select statement that creates the view.
* @throws VoltCompilerException
*/
private void checkViewMeetsSpec(String viewName, ParsedSelectStmt stmt) throws VoltCompilerException {
int groupColCount = stmt.groupByColumns().size();
int displayColCount = stmt.m_displayColumns.size();
StringBuffer msg = new StringBuffer();
msg.append("Materialized view \"" + viewName + "\" ");
if (stmt.getParameters().length > 0) {
msg.append("contains placeholders (?), which are not allowed in the SELECT query for a view.");
throw m_compiler.new VoltCompilerException(msg.toString());
}
List checkExpressions = new ArrayList<>();
int i;
// First, check the group by columns. They are at
// the beginning of the display list.
for (i = 0; i < groupColCount; i++) {
ParsedColInfo gbcol = stmt.groupByColumns().get(i);
ParsedColInfo outcol = stmt.m_displayColumns.get(i);
// The columns must be equal.
if (!outcol.m_expression.equals(gbcol.m_expression)) {
msg.append("must exactly match the GROUP BY clause at index " + String.valueOf(i) + " of SELECT list.");
throw m_compiler.new VoltCompilerException(msg.toString());
}
// check if the expression return type is not unique indexable
StringBuffer exprMsg = new StringBuffer();
if (!outcol.m_expression.isValueTypeUniqueIndexable(exprMsg)) {
msg.append("with " + exprMsg + " in GROUP BY clause not supported.");
throw m_compiler.new VoltCompilerException(msg.toString());
}
// collect all the expressions and we will check
// for other guards on all of them together
checkExpressions.add(outcol.m_expression);
}
// check for count star in the display list
boolean countStarFound = false;
UnsafeOperatorsForDDL unsafeOps = new UnsafeOperatorsForDDL();
// Finally, the display columns must have aggregate
// calls. But these are not any aggregate calls. They
// must be count(), min(), max() or sum().
for (; i < displayColCount; i++) {
ParsedColInfo outcol = stmt.m_displayColumns.get(i);
// Note that this expression does not catch all aggregates.
// An instance of avg() would cause the exception.
// ENG-10945 - We can have count(*) anywhere after the group by columns and multiple count(*)(s)
if ( outcol.m_expression.getExpressionType() == ExpressionType.AGGREGATE_COUNT_STAR) {
if ( countStarFound == false )
countStarFound = true;
continue;
}
if ((outcol.m_expression.getExpressionType() != ExpressionType.AGGREGATE_COUNT) &&
(outcol.m_expression.getExpressionType() != ExpressionType.AGGREGATE_SUM) &&
(outcol.m_expression.getExpressionType() != ExpressionType.AGGREGATE_MIN) &&
(outcol.m_expression.getExpressionType() != ExpressionType.AGGREGATE_MAX)) {
msg.append("must have non-group by columns aggregated by sum, count, min or max.");
throw m_compiler.new VoltCompilerException(msg.toString());
}
// Don't push the expression, though. Push the argument.
// We will check for aggregate calls and fail, and we don't
// want to fail on legal aggregate expressions.
if (outcol.m_expression.getLeft() != null) {
checkExpressions.add(outcol.m_expression.getLeft());
}
// Check if the aggregation is safe for non-empty view source table.
outcol.m_expression.findUnsafeOperatorsForDDL(unsafeOps);
assert(outcol.m_expression.getRight() == null);
assert(outcol.m_expression.getArgs() == null || outcol.m_expression.getArgs().size() == 0);
}
// Users can create SINGLE TABLE VIEWS without declaring count(*) in the stmt.
// Multiple table views still need this restriction.
if (stmt.m_tableList.size() > 1 && countStarFound == false) {
msg.append("joins multiple tables, therefore must include COUNT(*) after any GROUP BY columns.");
throw m_compiler.new VoltCompilerException(msg.toString());
}
AbstractExpression where = stmt.getSingleTableFilterExpression();
if (where != null) {
checkExpressions.add(where);
}
/*
* Gather up all the join expressions. The ParsedSelectStatement
* has not been analyzed yet, so it's not clear where these are. But
* the stmt knows.
*/
stmt.gatherJoinExpressions(checkExpressions);
if (stmt.getHavingPredicate() != null) {
checkExpressions.add(stmt.getHavingPredicate());
}
// Check all the subexpressions we gathered up.
if (!AbstractExpression.validateExprsForIndexesAndMVs(checkExpressions, msg, true)) {
// The error message will be in the StringBuffer msg.
throw m_compiler.new VoltCompilerException(msg.toString());
}
// Check some other materialized view specific things.
//
// Check to see if the expression is safe for creating
// views on nonempty tables.
for (AbstractExpression expr : checkExpressions) {
expr.findUnsafeOperatorsForDDL(unsafeOps);
}
if (unsafeOps.isUnsafe()) {
stmt.setUnsafeDDLMessage(unsafeOps.toString());
}
if (stmt.hasSubquery()) {
msg.append("cannot contain subquery sources.");
throw m_compiler.new VoltCompilerException(msg.toString());
}
if (! stmt.m_joinTree.allInnerJoins()) {
throw m_compiler.new VoltCompilerException("Materialized view only supports INNER JOIN.");
}
if (stmt.orderByColumns().size() != 0) {
msg.append("with an ORDER BY clause is not supported.");
throw m_compiler.new VoltCompilerException(msg.toString());
}
if (stmt.hasLimitOrOffset()) {
msg.append("with a LIMIT or OFFSET clause is not supported.");
throw m_compiler.new VoltCompilerException(msg.toString());
}
if (stmt.getHavingPredicate() != null) {
msg.append("with a HAVING clause is not supported.");
throw m_compiler.new VoltCompilerException(msg.toString());
}
// ENG-10892, since count(*) can be removed from SV table
if ((stmt.m_tableList.size() > 1 && displayColCount <= groupColCount) ||
displayColCount < groupColCount) {
msg.append("has too few columns.");
throw m_compiler.new VoltCompilerException(msg.toString());
}
checkViewSources(stmt.m_tableList);
}
private static void processMaterializedViewColumn(Table srcTable, Column destColumn,
ExpressionType type, TupleValueExpression colExpr) {
if (colExpr != null) {
assert(colExpr.getTableName().equalsIgnoreCase(srcTable.getTypeName()));
String srcColName = colExpr.getColumnName();
Column srcColumn = srcTable.getColumns().getIgnoreCase(srcColName);
destColumn.setMatviewsource(srcColumn);
}
destColumn.setAggregatetype(type.getValue());
}
// Compile the fallback query XMLs, add the plans into the catalog statement (ENG-8641).
private void compileFallbackQueriesAndUpdateCatalog(Database db,
String query,
List fallbackQueryXMLs,
MaterializedViewInfo matviewinfo) throws VoltCompilerException {
DatabaseEstimates estimates = new DatabaseEstimates();
for (int i=0; i fallbackQueryXMLs,
MaterializedViewHandlerInfo mvHandlerInfo)
throws VoltCompilerException {
DatabaseEstimates estimates = new DatabaseEstimates();
for (int i=0; i matViewMap) throws VoltCompilerException {
for (Table table : db.getTables()) {
for (MaterializedViewInfo mvInfo : table.getViews()) {
for (Statement stmt : mvInfo.getFallbackquerystmts()) {
// If there is any statement in the fallBackQueryStmts map, then
// there must be some min/max columns.
// Only check if the plan uses index scan.
if (needsWarningForSingleTableView( getPlanNodeTreeFromCatalogStatement(db, stmt))) {
// If we are using IS NOT DISTINCT FROM as our equality operator (which is necessary
// to get correct answers), then there will often be no index scans in the plan,
// since we cannot optimize IS NOT DISTINCT FROM.
m_compiler.addWarn(
"No index found to support UPDATE and DELETE on some of the min() / max() columns " +
"in the materialized view " + mvInfo.getTypeName() +
", and a sequential scan might be issued when current min / max value is updated / deleted.");
break;
}
}
}
// If it's a view on join query case, we check if the join can utilize indices.
// We throw out warning only if no index scan is used in the plan (ENG-10864).
MaterializedViewHandlerInfo mvHandlerInfo = table.getMvhandlerinfo().get("mvHandlerInfo");
if (mvHandlerInfo != null) {
Statement createQueryStatement = mvHandlerInfo.getCreatequery().get("createQuery");
if (needsWarningForJoinQueryView( getPlanNodeTreeFromCatalogStatement(db, createQueryStatement))) {
m_compiler.addWarn(
"No index found to support some of the join operations required to refresh the materialized view " +
table.getTypeName() +
". The refreshing may be slow.");
}
}
}
}
private enum MatViewIndexMatchingGroupby {GB_COL_IDX_COL, GB_COL_IDX_EXP, GB_EXP_IDX_EXP}
// if the materialized view has MIN / MAX, try to find an index defined on the source table
// covering all group by cols / exprs to avoid expensive tablescan.
// For now, the only acceptable index is defined exactly on the group by columns IN ORDER.
// This allows the same key to be used to do lookups on the grouped table index and the
// base table index.
// TODO: More flexible (but usually less optimal*) indexes may be allowed here and supported
// in the EE in the future including:
// -- *indexes on the group keys listed out of order
// -- *indexes on the group keys as a prefix before other indexed values.
// -- (ENG-6511) indexes on the group keys PLUS the MIN/MAX argument value (to eliminate post-filtering)
// This function is mostly re-written for the fix of ENG-6511. --yzhang
private static Index findBestMatchIndexForMatviewMinOrMax(MaterializedViewInfo matviewinfo,
Table srcTable, List groupbyExprs, AbstractExpression minMaxAggExpr) {
CatalogMap allIndexes = srcTable.getIndexes();
StmtTableScan tableScan = new StmtTargetTableScan(srcTable);
// Candidate index. If we can find an index covering both group-by columns and aggExpr (optimal) then we will
// return immediately.
// If the index found covers only group-by columns (sub-optimal), we will first cache it here.
Index candidate = null;
for (Index index : allIndexes) {
// indexOptimalForMinMax == true if the index covered both the group-by columns and the min/max aggExpr.
boolean indexOptimalForMinMax = false;
// If minMaxAggExpr is not null, the diff can be zero or one.
// Otherwise, for a usable index, its number of columns must agree with that of the group-by columns.
final int diffAllowance = minMaxAggExpr == null ? 0 : 1;
// Get all indexed exprs if there is any.
String expressionjson = index.getExpressionsjson();
List indexedExprs = null;
if ( ! expressionjson.isEmpty() ) {
try {
indexedExprs = AbstractExpression.fromJSONArrayString(expressionjson, tableScan);
} catch (JSONException e) {
e.printStackTrace();
assert(false);
return null;
}
}
// Get source table columns.
List srcColumnArray = CatalogUtil.getSortedCatalogItems(srcTable.getColumns(), "index");
MatViewIndexMatchingGroupby matchingCase = null;
if (groupbyExprs == null) {
// This means group-by columns are all simple columns.
// It also means we can only access the group-by columns by colref.
List groupbyColRefs =
CatalogUtil.getSortedCatalogItems(matviewinfo.getGroupbycols(), "index");
if (indexedExprs == null) {
matchingCase = MatViewIndexMatchingGroupby.GB_COL_IDX_COL;
// All the columns in the index are also simple columns, EASY! colref vs. colref
List indexedColRefs =
CatalogUtil.getSortedCatalogItems(index.getColumns(), "index");
// The number of columns in index can never be less than that in the group-by column list.
// If minMaxAggExpr == null, they must be equal (diffAllowance == 0)
// Otherwise they may be equal (sub-optimal) or
// indexedColRefs.size() == groupbyColRefs.size() + 1 (optimal, diffAllowance == 1)
if (isInvalidIndexCandidate(indexedColRefs.size(), groupbyColRefs.size(), diffAllowance)) {
continue;
}
if (! isGroupbyMatchingIndex(matchingCase, groupbyColRefs, null, indexedColRefs, null, null)) {
continue;
}
if (isValidIndexCandidateForMinMax(indexedColRefs.size(), groupbyColRefs.size(), diffAllowance)) {
if(! isIndexOptimalForMinMax(matchingCase, minMaxAggExpr, indexedColRefs, null, srcColumnArray)) {
continue;
}
indexOptimalForMinMax = true;
}
}
else {
matchingCase = MatViewIndexMatchingGroupby.GB_COL_IDX_EXP;
// In this branch, group-by columns are simple columns, but the index contains complex columns.
// So it's only safe to access the index columns from indexedExprs.
// You can still get something from indexedColRefs, but they will be inaccurate.
// e.g.: ONE index column (a+b) will get you TWO separate entries {a, b} in indexedColRefs.
// In order to compare columns: for group-by columns: convert colref => col
// for index columns: convert tve => col
if (isInvalidIndexCandidate(indexedExprs.size(), groupbyColRefs.size(), diffAllowance)) {
continue;
}
if (! isGroupbyMatchingIndex(matchingCase, groupbyColRefs, null, null, indexedExprs, srcColumnArray)) {
continue;
}
if (isValidIndexCandidateForMinMax(indexedExprs.size(), groupbyColRefs.size(), diffAllowance)) {
if(! isIndexOptimalForMinMax(matchingCase, minMaxAggExpr, null, indexedExprs, null)) {
continue;
}
indexOptimalForMinMax = true;
}
}
}
else {
matchingCase = MatViewIndexMatchingGroupby.GB_EXP_IDX_EXP;
// This means group-by columns have complex columns.
// It's only safe to access the group-by columns from groupbyExprs.
// AND, indexedExprs must not be null in this case. (yeah!)
if ( indexedExprs == null ) {
continue;
}
if (isInvalidIndexCandidate(indexedExprs.size(), groupbyExprs.size(), diffAllowance)) {
continue;
}
if (! isGroupbyMatchingIndex(matchingCase, null, groupbyExprs, null, indexedExprs, null)) {
continue;
}
if (isValidIndexCandidateForMinMax(indexedExprs.size(), groupbyExprs.size(), diffAllowance)) {
if (! isIndexOptimalForMinMax(matchingCase, minMaxAggExpr, null, indexedExprs, null)) {
continue;
}
indexOptimalForMinMax = true;
}
}
// NOW index at least covered all group-by columns (sub-optimal candidate)
if (!index.getPredicatejson().isEmpty()) {
// Additional check for partial indexes to make sure matview WHERE clause
// covers the partial index predicate
List coveringExprs = new ArrayList<>();
List exactMatchCoveringExprs = new ArrayList<>();
try {
String encodedPredicate = matviewinfo.getPredicate();
if (!encodedPredicate.isEmpty()) {
String predicate = Encoder.hexDecodeToString(encodedPredicate);
AbstractExpression matViewPredicate = AbstractExpression.fromJSONString(predicate, tableScan);
coveringExprs.addAll(ExpressionUtil.uncombineAny(matViewPredicate));
}
} catch (JSONException e) {
e.printStackTrace();
assert(false);
return null;
}
String predicatejson = index.getPredicatejson();
if ( ! predicatejson.isEmpty() &&
! SubPlanAssembler.isPartialIndexPredicateCovered(
tableScan, coveringExprs,
predicatejson, exactMatchCoveringExprs)) {
// the partial index predicate does not match the MatView's
// where clause -- give up on this index
continue;
}
}
// if the index already covered group by columns and the aggCol/aggExpr,
// it is already the best index we can get, return immediately.
if (indexOptimalForMinMax) {
return index;
}
// otherwise wait to see if we can find something better!
candidate = index;
}
return candidate;
}
private static void setTypeAttributesForColumn(Column column, AbstractExpression expr,
int maximumDefaultColumnSize) {
VoltType voltTy = expr.getValueType();
column.setType(voltTy.getValue());
if (expr.getValueType().isVariableLength()) {
int viewColumnLength = expr.getValueSize();
int lengthInBytes = expr.getValueSize();
lengthInBytes = expr.getInBytes() ? lengthInBytes : lengthInBytes * 4;
// We don't create a view column that is wider than the default.
if (lengthInBytes < maximumDefaultColumnSize) {
column.setSize(viewColumnLength);
column.setInbytes(expr.getInBytes());
}
else {
// Declining to create a view column that is wider than the default.
// This ensures that if there are a large number of aggregates on a string
// column that we have a reasonable chance of not exceeding the static max row size limit.
column.setSize(maximumDefaultColumnSize);
column.setInbytes(true);
}
}
else {
column.setSize(voltTy.getMaxLengthInBytes());
}
}
private static boolean isInvalidIndexCandidate(int idxSize, int gbSize, int diffAllowance) {
if ( idxSize < gbSize || idxSize > gbSize + diffAllowance ) {
return true;
}
return false;
}
private static boolean isGroupbyMatchingIndex(
MatViewIndexMatchingGroupby matchingCase,
List groupbyColRefs, List groupbyExprs,
List indexedColRefs, List indexedExprs,
List srcColumnArray) {
// Compare group-by columns/expressions for different cases
switch(matchingCase) {
case GB_COL_IDX_COL:
for (int i = 0; i < groupbyColRefs.size(); ++i) {
int groupbyColIndex = groupbyColRefs.get(i).getColumn().getIndex();
int indexedColIndex = indexedColRefs.get(i).getColumn().getIndex();
if (groupbyColIndex != indexedColIndex) {
return false;
}
}
break;
case GB_COL_IDX_EXP:
for (int i = 0; i < groupbyColRefs.size(); ++i) {
AbstractExpression indexedExpr = indexedExprs.get(i);
if (! (indexedExpr instanceof TupleValueExpression)) {
// Group-by columns are all simple columns, so indexedExpr must be tve.
return false;
}
int indexedColIdx = ((TupleValueExpression)indexedExpr).getColumnIndex();
Column indexedColumn = srcColumnArray.get(indexedColIdx);
Column groupbyColumn = groupbyColRefs.get(i).getColumn();
if ( ! indexedColumn.equals(groupbyColumn) ) {
return false;
}
}
break;
case GB_EXP_IDX_EXP:
for (int i = 0; i < groupbyExprs.size(); ++i) {
if (! indexedExprs.get(i).equals(groupbyExprs.get(i))) {
return false;
}
}
break;
default:
assert(false);
// invalid option
return false;
}
// group-by columns/expressions are matched with the corresponding index
return true;
}
private static boolean isValidIndexCandidateForMinMax(int idxSize, int gbSize, int diffAllowance) {
return diffAllowance == 1 && idxSize == gbSize + 1;
}
private static boolean isIndexOptimalForMinMax(
MatViewIndexMatchingGroupby matchingCase, AbstractExpression minMaxAggExpr,
List indexedColRefs, List indexedExprs,
List srcColumnArray) {
// We have minMaxAggExpr and the index also has one extra column
switch(matchingCase) {
case GB_COL_IDX_COL:
if ( ! (minMaxAggExpr instanceof TupleValueExpression) ) {
// Here because the index columns are all simple columns (indexedExprs == null)
// so the minMaxAggExpr must be TupleValueExpression.
return false;
}
int aggSrcColIdx = ((TupleValueExpression)minMaxAggExpr).getColumnIndex();
Column aggSrcCol = srcColumnArray.get(aggSrcColIdx);
Column lastIndexCol = indexedColRefs.get(indexedColRefs.size() - 1).getColumn();
// Compare the two columns, if they are equal as well, then this is the optimal index! Congrats!
if (aggSrcCol.equals(lastIndexCol)) {
return true;
}
break;
case GB_COL_IDX_EXP:
case GB_EXP_IDX_EXP:
if (indexedExprs.get(indexedExprs.size()-1).equals(minMaxAggExpr)) {
return true;
}
break;
default:
assert(false);
}
// If the last part of the index does not match the MIN/MAX expression
// this is not the optimal index candidate for now
return false;
}
/**
* If the argument table is a single-table materialized view,
* then return the attendant MaterializedViewInfo object. Otherwise
* return null.
*/
public static MaterializedViewInfo getMaterializedViewInfo(Table tbl) {
MaterializedViewInfo mvInfo = null;
Table source = tbl.getMaterializer();
if (source != null) {
mvInfo = source.getViews().get(tbl.getTypeName());
}
return mvInfo;
}
}