/*
***************************************************************************************
* Copyright (C) 2006 EsperTech, Inc. All rights reserved. *
* http://www.espertech.com/esper *
* http://www.espertech.com *
* ---------------------------------------------------------------------------------- *
* The software in this package is published under the terms of the GPL license *
* a copy of which has been included with this distribution in the license.txt file. *
***************************************************************************************
*/
package com.espertech.esper.core.start;
import com.espertech.esper.client.EPException;
import com.espertech.esper.client.EventType;
import com.espertech.esper.core.context.factory.StatementAgentInstanceFactoryCreateTable;
import com.espertech.esper.core.context.factory.StatementAgentInstanceFactoryCreateTableResult;
import com.espertech.esper.core.context.mgr.ContextManagedStatementCreateAggregationVariableDesc;
import com.espertech.esper.core.context.util.AgentInstanceContext;
import com.espertech.esper.core.context.util.ContextMergeView;
import com.espertech.esper.core.service.EPServicesContext;
import com.espertech.esper.core.service.ExprEvaluatorContextStatement;
import com.espertech.esper.core.service.StatementContext;
import com.espertech.esper.core.service.resource.StatementResourceHolder;
import com.espertech.esper.epl.agg.access.AggregationAccessorSlotPair;
import com.espertech.esper.epl.agg.service.AggregationMethodFactory;
import com.espertech.esper.epl.agg.service.AggregationStateFactory;
import com.espertech.esper.epl.annotation.AnnotationUtil;
import com.espertech.esper.epl.core.EngineImportException;
import com.espertech.esper.epl.core.EngineImportService;
import com.espertech.esper.epl.core.StreamTypeServiceImpl;
import com.espertech.esper.epl.expression.baseagg.ExprAggregateNode;
import com.espertech.esper.epl.expression.core.*;
import com.espertech.esper.epl.rettype.EPType;
import com.espertech.esper.epl.rettype.EPTypeHelper;
import com.espertech.esper.epl.spec.*;
import com.espertech.esper.epl.table.mgmt.*;
import com.espertech.esper.epl.variable.VariableServiceUtil;
import com.espertech.esper.event.EventAdapterService;
import com.espertech.esper.event.EventTypeUtility;
import com.espertech.esper.event.arr.ObjectArrayEventType;
import com.espertech.esper.util.CollectionUtil;
import com.espertech.esper.util.JavaClassHelper;
import com.espertech.esper.view.ViewProcessingException;
import com.espertech.esper.view.Viewable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
/**
* Starts and provides the stop method for EPL statements.
*/
public class EPStatementStartMethodCreateTable extends EPStatementStartMethodBase {
private static final Logger log = LoggerFactory.getLogger(EPStatementStartMethodCreateTable.class);
public EPStatementStartMethodCreateTable(StatementSpecCompiled statementSpec) {
super(statementSpec);
}
public EPStatementStartResult startInternal(final EPServicesContext services, final StatementContext statementContext, boolean isNewStatement, boolean isRecoveringStatement, boolean isRecoveringResilient) throws ExprValidationException, ViewProcessingException {
final CreateTableDesc createDesc = statementSpec.getCreateTableDesc();
// determine whether already declared
VariableServiceUtil.checkAlreadyDeclaredVariable(createDesc.getTableName(), services.getVariableService());
if (isNewStatement) {
VariableServiceUtil.checkAlreadyDeclaredTable(createDesc.getTableName(), services.getTableService());
}
if (services.getEventAdapterService().getExistsTypeByName(createDesc.getTableName()) != null) {
throw new ExprValidationException("An event type or schema by name '" + createDesc.getTableName() + "' already exists");
}
// Determine event type names
String internalTypeName = "table_" + createDesc.getTableName() + "__internal";
String publicTypeName = "table_" + createDesc.getTableName() + "__public";
final TableMetadata metadata;
try {
// determine key types
Class[] keyTypes = getKeyTypes(createDesc.getColumns(), services.getEngineImportService());
// check column naming, interpret annotations
List<TableColumnDesc> columnDescs = validateExpressions(createDesc.getColumns(), services, statementContext);
// analyze and plan the state holders
TableAccessAnalysisResult plan = analyzePlanAggregations(createDesc.getTableName(), statementContext, columnDescs, services, internalTypeName, publicTypeName);
final TableStateRowFactory tableStateRowFactory = plan.getStateRowFactory();
// register new table
boolean queryPlanLogging = services.getConfigSnapshot().getEngineDefaults().getLogging().isEnableQueryPlan();
metadata = services.getTableService().addTable(createDesc.getTableName(), statementContext.getExpression(), statementContext.getStatementName(), keyTypes, plan.getTableColumns(), tableStateRowFactory, plan.getNumberMethodAggregations(), statementContext, plan.getInternalEventType(),
plan.getPublicEventType(), plan.getEventToPublic(), queryPlanLogging);
} catch (ExprValidationException ex) {
services.getEventAdapterService().removeType(internalTypeName);
services.getEventAdapterService().removeType(publicTypeName);
throw ex;
}
// allocate context factory
StatementAgentInstanceFactoryCreateTable contextFactory = new StatementAgentInstanceFactoryCreateTable(metadata);
statementContext.setStatementAgentInstanceFactory(contextFactory);
Viewable outputView;
EPStatementStopMethod stopStatementMethod;
EPStatementDestroyMethod destroyStatementMethod;
if (statementSpec.getOptionalContextName() != null) {
final String contextName = statementSpec.getOptionalContextName();
ContextMergeView mergeView = new ContextMergeView(metadata.getPublicEventType());
outputView = mergeView;
ContextManagedStatementCreateAggregationVariableDesc statement = new ContextManagedStatementCreateAggregationVariableDesc(statementSpec, statementContext, mergeView, contextFactory);
services.getContextManagementService().addStatement(statementSpec.getOptionalContextName(), statement, isRecoveringResilient);
stopStatementMethod = new EPStatementStopMethod() {
public void stop() {
services.getContextManagementService().stoppedStatement(contextName, statementContext.getStatementName(), statementContext.getStatementId(), statementContext.getExpression(), statementContext.getExceptionHandlingService());
}
};
destroyStatementMethod = new EPStatementDestroyMethod() {
public void destroy() {
services.getContextManagementService().destroyedStatement(contextName, statementContext.getStatementName(), statementContext.getStatementId());
services.getStatementVariableRefService().removeReferencesStatement(statementContext.getStatementName());
}
};
} else {
AgentInstanceContext defaultAgentInstanceContext = getDefaultAgentInstanceContext(statementContext);
StatementAgentInstanceFactoryCreateTableResult result = contextFactory.newContext(defaultAgentInstanceContext, false);
if (statementContext.getStatementExtensionServicesContext() != null && statementContext.getStatementExtensionServicesContext().getStmtResources() != null) {
StatementResourceHolder holder = statementContext.getStatementExtensionServicesContext().extractStatementResourceHolder(result);
statementContext.getStatementExtensionServicesContext().getStmtResources().setUnpartitioned(holder);
}
outputView = result.getFinalView();
stopStatementMethod = new EPStatementStopMethod() {
public void stop() {
}
};
destroyStatementMethod = new EPStatementDestroyMethod() {
public void destroy() {
services.getStatementVariableRefService().removeReferencesStatement(statementContext.getStatementName());
}
};
}
services.getStatementVariableRefService().addReferences(statementContext.getStatementName(), createDesc.getTableName());
return new EPStatementStartResult(outputView, stopStatementMethod, destroyStatementMethod);
}
private Class[] getKeyTypes(List<CreateTableColumn> columns, EngineImportService engineImportService)
throws ExprValidationException {
List<Class> keys = new ArrayList<Class>();
for (CreateTableColumn col : columns) {
if (col.getPrimaryKey() == null || !col.getPrimaryKey()) {
continue;
}
String msg = "Column '" + col.getColumnName() + "' may not be tagged as primary key";
if (col.getOptExpression() != null) {
throw new ExprValidationException(msg + ", an expression cannot become a primary key column");
}
if (col.getOptTypeIsArray() != null && col.getOptTypeIsArray()) {
throw new ExprValidationException(msg + ", an array-typed column cannot become a primary key column");
}
Object type = EventTypeUtility.buildType(new ColumnDesc(col.getColumnName(), col.getOptTypeName(), false, false), engineImportService);
if (!(type instanceof Class)) {
throw new ExprValidationException(msg + ", received unexpected event type '" + type + "'");
}
keys.add((Class) type);
}
return keys.toArray(new Class[keys.size()]);
}
private ExprAggregateNode validateAggregationExpr(ExprNode columnExpressionType, EventType optionalProvidedType, EPServicesContext services, StatementContext statementContext)
throws ExprValidationException {
// determine validation context types and istream/irstream
EventType[] types;
String[] streamNames;
boolean[] istreamOnly;
if (optionalProvidedType != null) {
types = new EventType[]{optionalProvidedType};
streamNames = new String[]{types[0].getName()};
istreamOnly = new boolean[]{false}; // always false (expected to be bound by data window), use "ever"-aggregation functions otherwise
} else {
types = new EventType[0];
streamNames = new String[0];
istreamOnly = new boolean[0];
}
StreamTypeServiceImpl streamTypeService = new StreamTypeServiceImpl(types, streamNames, istreamOnly, services.getEngineURI(), false);
ExprValidationContext validationContext = new ExprValidationContext(streamTypeService, statementContext.getEngineImportService(), statementContext.getStatementExtensionServicesContext(), null, statementContext.getSchedulingService(), statementContext.getVariableService(), statementContext.getTableService(), new ExprEvaluatorContextStatement(statementContext, false), statementContext.getEventAdapterService(), statementContext.getStatementName(), statementContext.getStatementId(), statementContext.getAnnotations(), statementContext.getContextDescriptor(), false, false, false, false, null, false);
// substitute parameter nodes
for (ExprNode childNode : columnExpressionType.getChildNodes()) {
if (childNode instanceof ExprIdentNode) {
ExprIdentNode identNode = (ExprIdentNode) childNode;
String propname = identNode.getFullUnresolvedName().trim();
Class clazz = JavaClassHelper.getClassForSimpleName(propname, services.getEngineImportService().getClassForNameProvider());
if (propname.toLowerCase(Locale.ENGLISH).trim().equals("object")) {
clazz = Object.class;
}
EngineImportException ex = null;
if (clazz == null) {
try {
clazz = services.getEngineImportService().resolveClass(propname, false);
} catch (EngineImportException e) {
ex = e;
}
}
if (clazz != null) {
ExprTypedNoEvalNode typeNode = new ExprTypedNoEvalNode(propname, clazz);
ExprNodeUtility.replaceChildNode(columnExpressionType, identNode, typeNode);
} else {
if (optionalProvidedType == null) {
if (ex != null) {
throw new ExprValidationException("Failed to resolve type '" + propname + "': " + ex.getMessage(), ex);
}
throw new ExprValidationException("Failed to resolve type '" + propname + "'");
}
}
}
}
// validate
ExprNode validated = ExprNodeUtility.getValidatedSubtree(ExprNodeOrigin.CREATETABLECOLUMN, columnExpressionType, validationContext);
if (!(validated instanceof ExprAggregateNode)) {
throw new ExprValidationException("Expression '" + ExprNodeUtility.toExpressionStringMinPrecedenceSafe(validated) + "' is not an aggregation");
}
return (ExprAggregateNode) validated;
}
private List<TableColumnDesc> validateExpressions(List<CreateTableColumn> columns, EPServicesContext services, StatementContext statementContext)
throws ExprValidationException {
Set<String> columnNames = new HashSet<String>();
List<TableColumnDesc> descriptors = new ArrayList<TableColumnDesc>();
int positionInDeclaration = 0;
for (CreateTableColumn column : columns) {
String msgprefix = "For column '" + column.getColumnName() + "'";
// check duplicate name
if (columnNames.contains(column.getColumnName())) {
throw new ExprValidationException("Column '" + column.getColumnName() + "' is listed more than once");
}
columnNames.add(column.getColumnName());
// determine presence of type annotation
EventType optionalEventType = validateExpressionGetEventType(msgprefix, column.getAnnotations(), services.getEventAdapterService());
// aggregation node
TableColumnDesc descriptor;
if (column.getOptExpression() != null) {
ExprAggregateNode validated = validateAggregationExpr(column.getOptExpression(), optionalEventType, services, statementContext);
descriptor = new TableColumnDescAgg(positionInDeclaration, column.getColumnName(), validated, optionalEventType);
} else {
Object unresolvedType = EventTypeUtility.buildType(new ColumnDesc(column.getColumnName(), column.getOptTypeName(), column.getOptTypeIsArray() == null ? false : column.getOptTypeIsArray(), column.getOptTypeIsPrimitiveArray() == null ? false : column.getOptTypeIsPrimitiveArray()),
services.getEngineImportService());
descriptor = new TableColumnDescTyped(positionInDeclaration, column.getColumnName(), unresolvedType, column.getPrimaryKey() == null ? false : column.getPrimaryKey());
}
descriptors.add(descriptor);
positionInDeclaration++;
}
return descriptors;
}
private static EventType validateExpressionGetEventType(String msgprefix, List<AnnotationDesc> annotations, EventAdapterService eventAdapterService)
throws ExprValidationException {
Map<String, List<AnnotationDesc>> annos = AnnotationUtil.mapByNameLowerCase(annotations);
// check annotations used
List<AnnotationDesc> typeAnnos = annos.remove("type");
if (!annos.isEmpty()) {
throw new ExprValidationException(msgprefix + " unrecognized annotation '" + annos.keySet().iterator().next() + "'");
}
// type determination
EventType optionalType = null;
if (typeAnnos != null) {
String typeName = AnnotationUtil.getExpectSingleStringValue(msgprefix, typeAnnos);
optionalType = eventAdapterService.getExistsTypeByName(typeName);
if (optionalType == null) {
throw new ExprValidationException(msgprefix + " failed to find event type '" + typeName + "'");
}
}
return optionalType;
}
private TableAccessAnalysisResult analyzePlanAggregations(String tableName, StatementContext statementContext, List<TableColumnDesc> columns, EPServicesContext services, String internalTypeName, String publicTypeName)
throws ExprValidationException {
// once upfront: obtains aggregation factories for each aggregation
// we do this once as a factory may be a heavier object
Map<TableColumnDesc, AggregationMethodFactory> aggregationFactories = new HashMap<TableColumnDesc, AggregationMethodFactory>();
for (TableColumnDesc column : columns) {
if (column instanceof TableColumnDescAgg) {
TableColumnDescAgg agg = (TableColumnDescAgg) column;
AggregationMethodFactory factory = agg.getAggregation().getFactory();
aggregationFactories.put(column, factory);
}
}
// sort into these categories:
// plain / method-agg / access-agg
// compile all-column public types
List<TableColumnDescTyped> plainColumns = new ArrayList<TableColumnDescTyped>();
List<TableColumnDescAgg> methodAggColumns = new ArrayList<TableColumnDescAgg>();
List<TableColumnDescAgg> accessAggColumns = new ArrayList<TableColumnDescAgg>();
Map<String, Object> allColumnsPublicTypes = new LinkedHashMap<String, Object>();
for (TableColumnDesc column : columns) {
// handle plain types
if (column instanceof TableColumnDescTyped) {
TableColumnDescTyped typed = (TableColumnDescTyped) column;
plainColumns.add(typed);
allColumnsPublicTypes.put(column.getColumnName(), typed.getUnresolvedType());
continue;
}
// handle aggs
TableColumnDescAgg agg = (TableColumnDescAgg) column;
AggregationMethodFactory aggFactory = aggregationFactories.get(agg);
if (aggFactory.isAccessAggregation()) {
accessAggColumns.add(agg);
} else {
methodAggColumns.add(agg);
}
allColumnsPublicTypes.put(column.getColumnName(), agg.getAggregation().getType());
}
// determine column metadata
//
Map<String, TableMetadataColumn> columnMetadata = new LinkedHashMap<String, TableMetadataColumn>();
// handle typed columns
Map<String, Object> allColumnsInternalTypes = new LinkedHashMap<String, Object>();
allColumnsInternalTypes.put(TableService.INTERNAL_RESERVED_PROPERTY, Object.class);
int indexPlain = 1;
List<Integer> groupKeyIndexes = new ArrayList<Integer>();
TableMetadataColumnPairPlainCol[] assignPairsPlain = new TableMetadataColumnPairPlainCol[plainColumns.size()];
for (TableColumnDescTyped typedColumn : plainColumns) {
allColumnsInternalTypes.put(typedColumn.getColumnName(), typedColumn.getUnresolvedType());
columnMetadata.put(typedColumn.getColumnName(), new TableMetadataColumnPlain(typedColumn.getColumnName(), typedColumn.isKey(), indexPlain));
if (typedColumn.isKey()) {
groupKeyIndexes.add(indexPlain);
}
assignPairsPlain[indexPlain - 1] = new TableMetadataColumnPairPlainCol(typedColumn.getPositionInDeclaration(), indexPlain);
indexPlain++;
}
// determine internally-used event type
// for use by indexes and lookups
ObjectArrayEventType internalEventType;
ObjectArrayEventType publicEventType;
try {
internalEventType = (ObjectArrayEventType) services.getEventAdapterService().addNestableObjectArrayType(internalTypeName, allColumnsInternalTypes, null, false, false, false, false, false, true, tableName);
publicEventType = (ObjectArrayEventType) services.getEventAdapterService().addNestableObjectArrayType(publicTypeName, allColumnsPublicTypes, null, false, false, false, false, false, false, null);
} catch (EPException ex) {
throw new ExprValidationException("Invalid type information: " + ex.getMessage(), ex);
}
services.getStatementEventTypeRefService().addReferences(statementContext.getStatementName(), new String[]{internalTypeName, publicTypeName});
// handle aggregation-methods single-func first.
AggregationMethodFactory[] methodFactories = new AggregationMethodFactory[methodAggColumns.size()];
int index = 0;
TableMetadataColumnPairAggMethod[] assignPairsMethod = new TableMetadataColumnPairAggMethod[methodAggColumns.size()];
for (TableColumnDescAgg column : methodAggColumns) {
AggregationMethodFactory factory = aggregationFactories.get(column);
EPType optionalEnumerationType = EPTypeHelper.optionalFromEnumerationExpr(statementContext.getStatementId(), statementContext.getEventAdapterService(), column.getAggregation());
methodFactories[index] = factory;
columnMetadata.put(column.getColumnName(), new TableMetadataColumnAggregation(column.getColumnName(), factory, index, null, optionalEnumerationType, column.getOptionalAssociatedType()));
assignPairsMethod[index] = new TableMetadataColumnPairAggMethod(column.getPositionInDeclaration());
index++;
}
// handle access-aggregation (sharable, multi-value) aggregations
AggregationStateFactory[] stateFactories = new AggregationStateFactory[accessAggColumns.size()];
TableMetadataColumnPairAggAccess[] assignPairsAccess = new TableMetadataColumnPairAggAccess[accessAggColumns.size()];
index = 0;
for (TableColumnDescAgg column : accessAggColumns) {
AggregationMethodFactory factory = aggregationFactories.get(column);
stateFactories[index] = factory.getAggregationStateFactory(false);
AggregationAccessorSlotPair pair = new AggregationAccessorSlotPair(index, factory.getAccessor());
EPType optionalEnumerationType = EPTypeHelper.optionalFromEnumerationExpr(statementContext.getStatementId(), statementContext.getEventAdapterService(), column.getAggregation());
columnMetadata.put(column.getColumnName(), new TableMetadataColumnAggregation(column.getColumnName(), factory, -1, pair, optionalEnumerationType, column.getOptionalAssociatedType()));
assignPairsAccess[index] = new TableMetadataColumnPairAggAccess(column.getPositionInDeclaration(), factory.getAccessor());
index++;
}
// create state factory
int[] groupKeyIndexesArr = CollectionUtil.intArray(groupKeyIndexes);
TableStateRowFactory stateRowFactory = new TableStateRowFactory(internalEventType, statementContext.getEngineImportService(), methodFactories, stateFactories, groupKeyIndexesArr, services.getEventAdapterService());
// create public event provision
TableMetadataInternalEventToPublic eventToPublic = new TableMetadataInternalEventToPublic(publicEventType,
assignPairsPlain, assignPairsMethod, assignPairsAccess, services.getEventAdapterService());
return new TableAccessAnalysisResult(stateRowFactory, columnMetadata, methodAggColumns.size(), internalEventType, publicEventType, eventToPublic);
}
private static class TableAccessAnalysisResult {
private final TableStateRowFactory stateRowFactory;
private final Map<String, TableMetadataColumn> tableColumns;
private final int numberMethodAggregations;
private final ObjectArrayEventType internalEventType;
private final ObjectArrayEventType publicEventType;
private final TableMetadataInternalEventToPublic eventToPublic;
private TableAccessAnalysisResult(TableStateRowFactory stateRowFactory, Map<String, TableMetadataColumn> tableColumns, int numberMethodAggregations, ObjectArrayEventType internalEventType, ObjectArrayEventType publicEventType, TableMetadataInternalEventToPublic eventToPublic) {
this.stateRowFactory = stateRowFactory;
this.tableColumns = tableColumns;
this.numberMethodAggregations = numberMethodAggregations;
this.internalEventType = internalEventType;
this.publicEventType = publicEventType;
this.eventToPublic = eventToPublic;
}
public TableStateRowFactory getStateRowFactory() {
return stateRowFactory;
}
public Map<String, TableMetadataColumn> getTableColumns() {
return tableColumns;
}
public int getNumberMethodAggregations() {
return numberMethodAggregations;
}
public ObjectArrayEventType getInternalEventType() {
return internalEventType;
}
public ObjectArrayEventType getPublicEventType() {
return publicEventType;
}
public TableMetadataInternalEventToPublic getEventToPublic() {
return eventToPublic;
}
}
}