/*
* JBoss, Home of Professional Open Source.
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
package org.teiid.olingo.service;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.sql.SQLXML;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import org.apache.olingo.commons.api.data.Entity;
import org.apache.olingo.commons.api.data.Property;
import org.apache.olingo.commons.api.edm.EdmEntitySet;
import org.apache.olingo.commons.api.edm.EdmEntityType;
import org.apache.olingo.commons.api.edm.EdmNavigationProperty;
import org.apache.olingo.commons.api.edm.EdmOperation;
import org.apache.olingo.commons.api.edm.EdmProperty;
import org.apache.olingo.commons.api.ex.ODataRuntimeException;
import org.apache.olingo.commons.core.edm.primitivetype.EdmInt32;
import org.apache.olingo.commons.core.edm.primitivetype.SingletonPrimitiveType;
import org.apache.olingo.server.api.OData;
import org.apache.olingo.server.api.ServiceMetadata;
import org.apache.olingo.server.api.uri.*;
import org.apache.olingo.server.api.uri.queryoption.*;
import org.apache.olingo.server.api.uri.queryoption.OrderByItem;
import org.apache.olingo.server.core.RequestURLHierarchyVisitor;
import org.apache.olingo.server.core.uri.UriResourceEntitySetImpl;
import org.apache.olingo.server.core.uri.parser.Parser;
import org.apache.olingo.server.core.uri.parser.UriParserException;
import org.apache.olingo.server.core.uri.queryoption.ExpandOptionImpl;
import org.apache.olingo.server.core.uri.validator.UriValidationException;
import org.teiid.core.TeiidException;
import org.teiid.core.TeiidProcessingException;
import org.teiid.core.types.BlobImpl;
import org.teiid.core.types.BlobType;
import org.teiid.core.types.ClobImpl;
import org.teiid.core.types.ClobType;
import org.teiid.core.types.DataTypeManager;
import org.teiid.core.types.InputStreamFactory;
import org.teiid.core.types.JDBCSQLTypeInfo;
import org.teiid.core.types.SQLXMLImpl;
import org.teiid.metadata.Column;
import org.teiid.metadata.KeyRecord;
import org.teiid.metadata.MetadataStore;
import org.teiid.metadata.Schema;
import org.teiid.metadata.Table;
import org.teiid.odata.api.SQLParameter;
import org.teiid.olingo.ODataPlugin;
import org.teiid.olingo.common.ODataTypeManager;
import org.teiid.olingo.service.ProcedureSQLBuilder.ProcedureReturn;
import org.teiid.olingo.service.TeiidServiceHandler.ExpandNode;
import org.teiid.olingo.service.TeiidServiceHandler.OperationParameterValueProvider;
import org.teiid.olingo.service.TeiidServiceHandler.UniqueNameGenerator;
import org.teiid.query.sql.lang.*;
import org.teiid.query.sql.lang.ExistsCriteria.SubqueryHint;
import org.teiid.query.sql.symbol.AggregateSymbol;
import org.teiid.query.sql.symbol.AliasSymbol;
import org.teiid.query.sql.symbol.Array;
import org.teiid.query.sql.symbol.Constant;
import org.teiid.query.sql.symbol.ElementSymbol;
import org.teiid.query.sql.symbol.Expression;
import org.teiid.query.sql.symbol.GroupSymbol;
import org.teiid.query.sql.symbol.Reference;
import org.teiid.query.sql.symbol.ScalarSubquery;
public class ODataSQLBuilder extends RequestURLHierarchyVisitor {
final static int MAX_EXPAND_LEVEL = 3;
private final MetadataStore metadata;
private boolean prepared = true;
private final ArrayList<SQLParameter> params = new ArrayList<SQLParameter>();
private final ArrayList<TeiidException> exceptions = new ArrayList<TeiidException>();
private DocumentNode context;
private SkipOption skipOption;
private TopOption topOption;
private boolean countOption;
private OrderBy orderBy;
private boolean selectionComplete;
private String nextToken;
private boolean aliasedGroups;
private boolean countQuery = false;
private boolean reference = false;
private String baseURI;
private ServiceMetadata serviceMetadata;
private UniqueNameGenerator nameGenerator;
private ExpandOption expandOption;
private URLParseService parseService;
private OData odata;
private boolean navigation = false;
private OperationParameterValueProvider parameters;
class URLParseService {
public Query parse(String rawPath) throws TeiidException {
try {
rawPath = rawPath.replace("$root", "");
UriInfo uriInfo = new Parser(serviceMetadata.getEdm(), odata).parseUri(rawPath, null, null);
ODataSQLBuilder visitor = new ODataSQLBuilder(odata, metadata,
prepared, aliasedGroups, baseURI, serviceMetadata,
nameGenerator) {
public void visit(OrderByOption option) {
//no implicit ordering now.
}
};
visitor.visit(uriInfo);
return visitor.selectQuery();
} catch (UriParserException e) {
throw new TeiidException(e);
} catch (UriValidationException e) {
throw new TeiidException(e);
}
}
}
public ODataSQLBuilder(OData odata, MetadataStore metadata, boolean prepared,
boolean aliasedGroups, String baseURI,
ServiceMetadata serviceMetadata, UniqueNameGenerator nameGenerator) {
this.odata = odata;
this.metadata = metadata;
this.prepared = prepared;
this.aliasedGroups = aliasedGroups;
this.baseURI = baseURI;
this.serviceMetadata = serviceMetadata;
this.nameGenerator = nameGenerator;
this.parseService = new URLParseService();
}
public DocumentNode getContext() {
return this.context;
}
public boolean includeTotalSize() {
return countOption;
}
public Integer getSkip() {
if (skipOption == null) {
return null;
}
return skipOption.getValue();
}
public Integer getTop() {
if (topOption == null) {
return null;
}
return topOption.getValue();
}
public boolean hasNavigation() {
return this.navigation;
}
public Query selectQuery() throws TeiidException {
if (!this.exceptions.isEmpty()) {
throw this.exceptions.get(0);
}
Query query = this.context.buildQuery();
if (this.countQuery) {
AggregateSymbol aggregateSymbol = new AggregateSymbol(AggregateSymbol.Type.COUNT.name(), false, null);
Select select = new Select(Arrays.asList(aggregateSymbol));
query.setSelect(select);
} else if (this.orderBy != null) {
if (this.context.getIterator() != null) {
//currently this doesn't matter as the ordering can only be based upon the parent entity
((AggregateSymbol)((AliasSymbol)query.getSelect().getSymbol(query.getSelect().getProjectedSymbols().size() - 1)).getSymbol()).setOrderBy(this.orderBy);
} else {
query.setOrderBy(this.orderBy);
}
}
if (this.expandOption != null) {
processExpandOption(this.expandOption, this.context, query, 1, null);
}
return query;
}
private void processExpandOption(ExpandOption option, DocumentNode node, Query outerQuery, int expandLevel, Integer cyclicLevel) throws TeiidException {
checkExpandLevel(expandLevel);
int starLevels = 0;
HashSet<String> seen = new HashSet<String>();
for (ExpandItem ei : option.getExpandItems()) {
if (ei.getSearchOption() != null) {
throw new TeiidNotImplementedException(
ODataPlugin.Event.TEIID16035, ODataPlugin.Util.gs(ODataPlugin.Event.TEIID16035));
}
Integer levels = null;
if (cyclicLevel != null) {
levels = cyclicLevel - 1;
} else if (ei.getLevelsOption() != null) {
if (ei.getLevelsOption().isMax()) {
levels = MAX_EXPAND_LEVEL - expandLevel + 1;
} else {
levels = ei.getLevelsOption().getValue();
checkExpandLevel(expandLevel + levels - 1);
}
}
ExpandSQLBuilder esb = new ExpandSQLBuilder(ei);
EdmNavigationProperty property = esb.getNavigationProperty();
if (property == null) {
if (ei.isStar()) {
if (starLevels > 0) {
throw new TeiidProcessingException(
ODataPlugin.Event.TEIID16058, ODataPlugin.Util.gs(ODataPlugin.Event.TEIID16058, "*")); //$NON-NLS-1$
}
if (levels != null) {
starLevels = levels;
} else {
starLevels = 1;
}
continue;
}
throw new TeiidNotImplementedException(
ODataPlugin.Event.TEIID16057, ODataPlugin.Util.gs(ODataPlugin.Event.TEIID16057));
}
if (!seen.add(property.getName())) {
throw new TeiidProcessingException(
ODataPlugin.Event.TEIID16058, ODataPlugin.Util.gs(ODataPlugin.Event.TEIID16058, property.getName()));
}
//always pass in the root as the parent as that seems to be the definition of the current context
//if instead it should refer to the parent expands, then we would pass in the node instead
ExpandDocumentNode expandResource = ExpandDocumentNode.buildExpand(
property, this.metadata, this.odata, this.nameGenerator, true,
getUriInfo(), this.parseService, this.context);
node.addExpand(expandResource);
// process $filter
if (ei.getFilterOption() != null) {
Expression expandCriteria = processFilterOption(ei.getFilterOption(), expandResource);
expandResource.addCriteria(expandCriteria);
}
OrderBy expandOrder = null;
if (ei.getOrderByOption() != null) {
expandOrder = new OrderBy();
processOrderBy(expandOrder, ei.getOrderByOption().getOrders(), expandResource);
} else {
expandOrder = expandResource.addDefaultOrderBy();
}
// process $select
processSelectOption(ei.getSelectOption(), expandResource, this.reference);
//TODO: if not the count option, then we can process the skip/top inline
//but it's messier - select array_agg(cols) from (select ... where ... order by .. limit) x
if (ei.getSkipOption() != null) {
expandResource.setSkip(ei.getSkipOption().getValue());
}
if (ei.getTopOption() != null) {
expandResource.setTop(ei.getTopOption().getValue());
}
Query query = expandResource.buildQuery();
if (ei.getExpandOption() != null) {
processExpandOption(ei.getExpandOption(), expandResource, query, expandLevel + 1, null);
} else if (levels != null) {
//self reference check
if (!property.getType().getFullQualifiedName().equals(node.getEdmEntityType().getFullQualifiedName())) {
throw new TeiidProcessingException(ODataPlugin.Event.TEIID16060, ODataPlugin.Util.gs(ODataPlugin.Event.TEIID16060, node.getEdmEntityType().getFullQualifiedName(), property.getType().getFullQualifiedName()));
}
if (levels > 1) {
ExpandOptionImpl eoi = new ExpandOptionImpl();
eoi.addExpandItem(ei);
processExpandOption(eoi, expandResource, query, expandLevel + 1, levels);
}
}
buildAggregateQuery(node, outerQuery, expandResource,
expandOrder, query);
}
if (starLevels > 0) {
List<ExpandNode> starExpand = new ArrayList<TeiidServiceHandler.ExpandNode>();
EdmEntityType edmEntityType = node.getEdmEntityType();
buildExpandGraph(seen, starExpand, edmEntityType, starLevels - 1);
if (!starExpand.isEmpty()) {
processExpand(starExpand, node, outerQuery, expandLevel);
}
}
}
private void buildExpandGraph(HashSet<String> seen,
List<ExpandNode> starExpand, EdmEntityType edmEntityType, int remainingLevels) {
for (String name : edmEntityType.getNavigationPropertyNames()) {
if (seen != null && seen.contains(name)) {
continue; //explicit expand supersedes
}
EdmNavigationProperty property = edmEntityType.getNavigationProperty(name);
ExpandNode en = new ExpandNode();
en.navigationProperty = property;
starExpand.add(en);
if (remainingLevels > 0) {
buildExpandGraph(null, en.children, property.getType(), remainingLevels - 1);
}
}
}
public static void checkExpandLevel(int expandLevel)
throws TeiidProcessingException {
if (expandLevel > MAX_EXPAND_LEVEL) {
throw new TeiidProcessingException(
ODataPlugin.Event.TEIID16059, ODataPlugin.Util.gs(ODataPlugin.Event.TEIID16059, MAX_EXPAND_LEVEL));
}
}
private void buildAggregateQuery(DocumentNode node, Query outerQuery,
ExpandDocumentNode expandResource, OrderBy expandOrder, Query query) {
Select select = query.getSelect();
Array array = new Array(Object.class, new ArrayList<Expression>(select.getSymbols()));
select.getSymbols().clear();
AggregateSymbol symbol = new AggregateSymbol(AggregateSymbol.Type.ARRAY_AGG.name(), false, array);
select.addSymbol(symbol);
symbol.setOrderBy(expandOrder);
Criteria crit = DocumentNode.buildJoinCriteria(expandResource, node);
if (crit == null) {
crit = DocumentNode.buildJoinCriteria(node, expandResource);
}
if (crit != null) {
query.setCriteria(Criteria.combineCriteria(crit, query.getCriteria()));
} // else assertion error?
expandResource.setColumnIndex(outerQuery.getSelect().getCount() + 1);
ScalarSubquery agg = new ScalarSubquery(query);
SubqueryHint subqueryHint = new SubqueryHint();
subqueryHint.setMergeJoin(true);
agg.setSubqueryHint(subqueryHint);
outerQuery.getSelect().addSymbol(agg);
}
private Expression processFilterOption(FilterOption option, DocumentNode resource) throws TeiidException {
ODataExpressionToSQLVisitor visitor = new ODataExpressionToSQLVisitor(
resource, this.prepared, getUriInfo(), this.metadata, this.odata,
this.nameGenerator, this.params, this.parseService);
return visitor.getExpression(option.getExpression());
}
public List<SQLParameter> getParameters(){
return this.params;
}
@Override
public void visit(UriResourceEntitySet info) {
try {
this.context = DocumentNode.build(info.getEntitySet().getEntityType(),
info.getKeyPredicates(), this.metadata,
this.odata, this.nameGenerator, this.aliasedGroups,
getUriInfo(), this.parseService);
} catch (TeiidException e) {
this.exceptions.add(e);
}
}
@Override
public void visit(SkipOption option) {
this.skipOption = option;
}
@Override
public void visit(TopOption option) {
this.topOption = option;
}
@Override
public void visit(CountOption info) {
this.countOption = info.getValue();
}
@Override
public void visit(SelectOption option) {
if (this.selectionComplete) {
return;
}
try {
processSelectOption(option, this.context, this.reference);
} catch (TeiidException e) {
this.exceptions.add(e);
}
}
private void processSelectOption(SelectOption option, DocumentNode resource, boolean onlyReference) throws TeiidException {
if (option == null) {
// default select columns
resource.addAllColumns(onlyReference);
}
else {
boolean addkeys = true;
ArrayList<String> keys = new ArrayList<String>(resource.getKeyColumnNames());
for (SelectItem si:option.getSelectItems()) {
if (si.isStar()) {
resource.addAllColumns(onlyReference);
addkeys = false;
continue;
}
ODataExpressionToSQLVisitor visitor = new ODataExpressionToSQLVisitor(
resource, false,
getUriInfo(), this.metadata, this.odata, this.nameGenerator, this.params,
this.parseService);
ElementSymbol expr = (ElementSymbol)visitor.getExpression(si.getResourcePath());
resource.addProjectedColumn(expr.getShortName(), expr);
keys.remove(expr.getShortName());
}
if (!keys.isEmpty() && addkeys) {
for (String key:keys) {
ElementSymbol es = new ElementSymbol(key, resource.getGroupSymbol());
resource.addProjectedColumn(key, es);
}
}
}
}
@Override
public void visit(OrderByOption option) {
if (option == null || option.getOrders().isEmpty()) {
this.orderBy = this.context.addDefaultOrderBy();
}
else {
List<OrderByItem> orderBys = option.getOrders();
try {
this.orderBy = processOrderBy(new OrderBy(), orderBys, this.context);
} catch (TeiidException e) {
this.exceptions.add(e);
}
}
}
private OrderBy processOrderBy(OrderBy orderBy, List<OrderByItem> orderByItems, DocumentNode resource) throws TeiidException {
for (OrderByItem obitem:orderByItems) {
ODataExpressionToSQLVisitor visitor = new ODataExpressionToSQLVisitor(
resource, false,
getUriInfo(), this.metadata, this.odata, this.nameGenerator, this.params,
this.parseService);
Expression expr = visitor.getExpression(obitem.getExpression());
if (expr instanceof ElementSymbol) {
orderBy.addVariable(expr, !obitem.isDescending());
}
else {
AliasSymbol alias = new AliasSymbol("_orderByAlias", expr);
orderBy.addVariable(alias, !obitem.isDescending());
visitor.getEntityResource().addProjectedColumn(alias, EdmInt32.getInstance(), null, false);
}
}
return orderBy;
}
@Override
public void visit(FilterOption info) {
ODataExpressionToSQLVisitor visitor = new ODataExpressionToSQLVisitor(
this.context, this.prepared, getUriInfo(), this.metadata, this.odata,
this.nameGenerator, this.params, this.parseService);
Expression filter = null;
try {
filter = visitor.getExpression(info.getExpression());
} catch (TeiidException e) {
this.exceptions.add(e);
}
// Here Lambda operation may have joined a table and changed the context.
this.context = visitor.getEntityResource();
this.context.addCriteria(filter);
}
@Override
public void visit(UriResourceNavigation info) {
EdmNavigationProperty property = info.getProperty();
try {
DocumentNode joinResource = DocumentNode.build(property.getType(),
info.getKeyPredicates(), this.metadata, this.odata, this.nameGenerator,
true, getUriInfo(), parseService);
this.context.joinTable(joinResource, property, JoinType.JOIN_INNER);
// In the context of canonical queries if key predicates are available then do not set the criteria
if (joinResource.getCriteria() == null) {
joinResource.addCriteria(this.context.getCriteria());
}
this.context = joinResource;
this.navigation = true;
} catch (TeiidException e) {
this.exceptions.add(e);
}
}
@Override
public void visit(UriResourcePrimitiveProperty info) {
String propertyName = info.getProperty().getName();
ElementSymbol es = new ElementSymbol(propertyName, this.context.getGroupSymbol());
this.context.addProjectedColumn(propertyName, es);
this.selectionComplete = true;
}
public String getNextToken() {
return nextToken;
}
@Override
public void visit(SkipTokenOption option) {
if (option != null) {
this.nextToken = option.getValue();
}
}
@Override
public void visit(SearchOption option) {
this.exceptions.add(new TeiidNotImplementedException(
ODataPlugin.Event.TEIID16035, ODataPlugin.Util.gs(ODataPlugin.Event.TEIID16035)));
}
public Insert insert(EdmEntityType entityType, Entity entity,
List<UriParameter> keys, boolean prepared) throws TeiidException {
Table entityTable = findTable(entityType.getName(), this.metadata);
DocumentNode resource = new DocumentNode(entityTable, new GroupSymbol(entityTable.getFullName()), entityType);
List<Reference> referenceValues = new ArrayList<Reference>();
List<Constant> constantValues = new ArrayList<Constant>();
Insert insert = new Insert();
insert.setGroup(resource.getGroupSymbol());
if (keys != null) {
for (UriParameter key : keys) {
EdmProperty edmProperty = (EdmProperty)entityType.getProperty(key.getName());
Column column = entityTable.getColumnByName(edmProperty.getName());
Object propertyValue = ODataTypeManager.parseLiteral(edmProperty, column.getJavaType(), key.getText());
Property existing = entity.getProperty(edmProperty.getName());
if (existing == null ||
(existing.getValue() == null && propertyValue != null) ||
(existing.getValue() != null && propertyValue == null) ||
(existing.getValue() != null && !existing.getValue().equals(propertyValue))) {
throw new TeiidProcessingException(ODataPlugin.Util.gs(ODataPlugin.Event.TEIID16048,
edmProperty.getName()));
}
}
}
int i = 0;
for (Property prop : entity.getProperties()) {
EdmProperty edmProp = (EdmProperty)entityType.getProperty(prop.getName());
Column column = entityTable.getColumnByName(edmProp.getName());
insert.addVariable(new ElementSymbol(column.getName(), resource.getGroupSymbol()));
if (prepared) {
referenceValues.add(new Reference(i++));
this.params.add(asParam(edmProp, prop.getValue()));
}
else {
constantValues.add(new Constant(asParam(edmProp, prop.getValue()).getValue()));
}
}
if (prepared) {
insert.setValues(referenceValues);
}
else {
insert.setValues(constantValues);
}
return insert;
}
static SQLParameter asParam(EdmProperty edmProp, Object value) throws TeiidException {
return asParam(edmProp, value, false);
}
static SQLParameter asParam(EdmProperty edmProp, Object value, boolean rawValue) throws TeiidException {
String teiidType = ODataTypeManager.teiidType((SingletonPrimitiveType)edmProp.getType(), edmProp.isCollection());
int sqlType = JDBCSQLTypeInfo.getSQLType(teiidType);
if (value == null) {
return new SQLParameter(null, sqlType);
}
if (rawValue) {
return new SQLParameter(ODataTypeManager.convertByteArrayToTeiidRuntimeType(
DataTypeManager.getDataTypeClass(teiidType), (byte[])value,
((SingletonPrimitiveType)edmProp.getType()).getFullQualifiedName().getFullQualifiedNameAsString()),
sqlType);
}
return new SQLParameter(ODataTypeManager.convertToTeiidRuntimeType(
DataTypeManager.getDataTypeClass(teiidType), value,
((SingletonPrimitiveType)edmProp.getType()).getFullQualifiedName().getFullQualifiedNameAsString()),
sqlType);
}
private Table findTable(String tableName, MetadataStore store) {
int idx = tableName.indexOf('.');
if (idx > 0) {
Schema s = store.getSchema(tableName.substring(0, idx));
if (s != null) {
Table t = s.getTable(tableName.substring(idx+1));
if (t != null) {
return t;
}
}
}
for (Schema s : store.getSchemaList()) {
Table t = s.getTables().get(tableName);
if (t != null) {
return t;
}
}
return null;
}
//TODO: allow the generated key building.
public Query selectWithEntityKey(EdmEntityType entityType, Entity entity,
Map<String, Object> generatedKeys, List<ExpandNode> expand) throws TeiidException {
Table table = findTable(entityType.getName(), this.metadata);
DocumentNode resource = new DocumentNode(table, new GroupSymbol(table.getFullName()), entityType);
resource.setFromClause(new UnaryFromClause(new GroupSymbol(table.getFullName())));
resource.addAllColumns(false);
this.context = resource;
Query query = this.context.buildQuery();
processExpand(expand, resource, query, 1);
Criteria criteria = null;
KeyRecord pk = ODataSchemaBuilder.getIdentifier(table);
for (Column c:pk.getColumns()) {
Property prop = entity.getProperty(c.getName());
Constant right = null;
if (prop != null) {
right = new Constant(ODataTypeManager.convertToTeiidRuntimeType(c.getJavaType(), prop.getValue(), null));
} else {
Object value = generatedKeys.get(c.getName());
if (value == null) {
throw new TeiidProcessingException(ODataPlugin.Util.gs(ODataPlugin.Event.TEIID16016, entityType.getName()));
}
right = new Constant(value);
}
ElementSymbol left = new ElementSymbol(c.getName(), this.context.getGroupSymbol());
if (criteria == null) {
criteria = new CompareCriteria(left,AbstractCompareCriteria.EQ, right);
}
else {
CompareCriteria rightCC = new CompareCriteria(left,AbstractCompareCriteria.EQ, right);
criteria = new CompoundCriteria(CompoundCriteria.AND, criteria, rightCC);
}
}
query.setCriteria(criteria);
return query;
}
private void processExpand(List<ExpandNode> expand, DocumentNode resource, Query outerQuery, int expandLevel)
throws TeiidException {
if (expand.isEmpty()) {
return;
}
checkExpandLevel(expandLevel);
for (ExpandNode expandNode: expand) {
ExpandDocumentNode expandResource = ExpandDocumentNode.buildExpand(
expandNode.navigationProperty, this.metadata, this.odata, this.nameGenerator,
this.aliasedGroups, getUriInfo(), this.parseService, this.context);
OrderBy expandOrder = expandResource.addDefaultOrderBy();
expandResource.addAllColumns(false);
resource.addExpand(expandResource);
Query query = expandResource.buildQuery();
processExpand(expandNode.children, expandResource, query, expandLevel + 1);
buildAggregateQuery(resource, outerQuery, expandResource, expandOrder, query);
}
}
public Update update(EdmEntityType entityType, Entity entity, boolean prepared) throws TeiidException {
Update update = new Update();
update.setGroup(this.context.getGroupSymbol());
int i = 0;
for (Property property : entity.getProperties()) {
EdmProperty edmProperty = (EdmProperty)entityType.getProperty(property.getName());
Column column = this.context.getColumnByName(edmProperty.getName());
ElementSymbol symbol = new ElementSymbol(column.getName(), this.context.getGroupSymbol());
boolean add = true;
for (String c : this.context.getKeyColumnNames()) {
if (c.equals(column.getName())) {
add = false;
break;
}
}
if (add) {
if (prepared) {
update.addChange(symbol, new Reference(i++));
this.params.add(asParam(edmProperty, property.getValue()));
}
else {
update.addChange(symbol, new Constant(asParam(edmProperty, property.getValue()).getValue()));
}
}
}
update.setCriteria(this.context.getCriteria());
return update;
}
public Update updateProperty(EdmProperty edmProperty, Property property,
boolean prepared, boolean rawValue) throws TeiidException {
Update update = new Update();
update.setGroup(this.context.getGroupSymbol());
Column column = this.context.getColumnByName(edmProperty.getName());
ElementSymbol symbol = new ElementSymbol(column.getName(), this.context.getGroupSymbol());
if (prepared) {
update.addChange(symbol, new Reference(0));
this.params.add(asParam(edmProperty, property.getValue(), rawValue));
}
else {
update.addChange(symbol, new Constant(asParam(edmProperty, property.getValue()).getValue()));
}
update.setCriteria(this.context.getCriteria());
return update;
}
public Update updateStreamProperty(EdmProperty edmProperty, final InputStream content) throws TeiidException {
Update update = new Update();
update.setGroup(this.context.getGroupSymbol());
Column column = this.context.getColumnByName(edmProperty.getName());
ElementSymbol symbol = new ElementSymbol(column.getName(), this.context.getGroupSymbol());
update.addChange(symbol, new Reference(0));
Class<?> lobType = DataTypeManager.getDataTypeClass(column.getRuntimeType());
int sqlType = JDBCSQLTypeInfo.getSQLType(column.getRuntimeType());
if (content == null) {
this.params.add(new SQLParameter(null, sqlType));
} else {
Object value = null;
InputStreamFactory isf = new InputStreamFactory() {
@Override
public InputStream getInputStream() throws IOException {
return content;
}
};
if (lobType.isAssignableFrom(SQLXML.class)) {
value = new SQLXMLImpl(isf);
} else if (lobType.isAssignableFrom(ClobType.class)) {
value = new ClobImpl(isf, -1);
} else if (lobType.isAssignableFrom(BlobType.class)) {
value = new BlobImpl(isf);
} else {
throw new TeiidException(ODataPlugin.Util.gs(ODataPlugin.Event.TEIID16031, column.getName()));
}
this.params.add(new SQLParameter(value, sqlType));
}
update.setCriteria(this.context.getCriteria());
return update;
}
public Delete delete() {
Delete delete = new Delete();
delete.setGroup(this.context.getGroupSymbol());
delete.setCriteria(this.context.getCriteria());
return delete;
}
@Override
public void visit(UriInfoService info) {
this.exceptions.add(new TeiidException("UriInfoService NotSupported"));
}
@Override
public void visit(UriInfoAll info) {
this.exceptions.add(new TeiidException("UriInfoAll NotSupported"));
}
@Override
public void visit(UriInfoBatch info) {
this.exceptions.add(new TeiidException("UriInfoBatch NotSupported"));
}
@Override
public void visit(UriInfoCrossjoin info) {
for (String name:info.getEntitySetNames()) {
EdmEntitySet entitySet = this.serviceMetadata.getEdm().getEntityContainer().getEntitySet(name);
EdmEntityType entityType = entitySet.getEntityType();
CrossJoinNode resource = null;
try {
boolean hasExpand = hasExpand(entitySet.getName(), info.getExpandOption());
resource = CrossJoinNode.buildCrossJoin(entityType, null,
this.metadata, this.odata, this.nameGenerator, this.aliasedGroups,
getUriInfo(), this.parseService, hasExpand);
resource.addAllColumns(!hasExpand);
if (this.context == null) {
this.context = resource;
this.orderBy = this.context.addDefaultOrderBy();
}
else {
this.context.addSibiling(resource);
OrderBy orderby = resource.addDefaultOrderBy();
int index = orderby.getVariableCount();
for (int i = 0; i < index; i++) {
this.orderBy.addVariable(orderby.getVariable(i));
}
}
} catch (TeiidException e) {
this.exceptions.add(e);
}
}
super.visit(info);
// the expand behavior is handled above with selection of the columns
this.expandOption = null;
}
private boolean hasExpand(String name, ExpandOption expandOption) {
if (expandOption == null) {
return false;
}
for (ExpandItem ei:expandOption.getExpandItems()) {
String expand = ((UriResourceEntitySetImpl)ei.getResourcePath().getUriResourceParts().get(0)).getEntitySet().getName();
if (expand.equalsIgnoreCase(name)) {
return true;
}
}
return false;
}
@Override
public void visit(UriInfoMetadata info) {
this.exceptions.add(new TeiidException("UriInfoMetadata NotSupported"));
}
@Override
public void visit(ExpandOption option) {
this.expandOption = option;
}
@Override
public void visit(FormatOption info) {
}
@Override
public void visit(UriInfoEntityId info) {
try {
visit(buildUriInfo(new URI(info.getIdOption().getValue()), this.baseURI, this.serviceMetadata, this.odata));
} catch (UriParserException e) {
this.exceptions.add(new TeiidException(e));
} catch (URISyntaxException e) {
this.exceptions.add(new TeiidException(e));
} catch (UriValidationException e) {
this.exceptions.add(new TeiidException(e));
}
visit(info.getSelectOption());
if (info.getExpandOption() != null) {
visit(info.getExpandOption());
}
if (info.getFormatOption() != null) {
visit(info.getFormatOption());
}
}
static UriInfo buildUriInfo(URI uri, String baseUri,
ServiceMetadata serviceMetadata, OData odata) throws URISyntaxException,
UriParserException, UriValidationException {
URI servicePath = new URI(baseUri);
String path = servicePath.getPath();
String rawPath = uri.getPath();
int e = rawPath.indexOf(path);
if (-1 == e) {
rawPath = uri.getPath();
} else {
rawPath = rawPath.substring(e+path.length());
}
return new Parser(serviceMetadata.getEdm(), odata).parseUri(rawPath, uri.getQuery(), null);
}
@Override
public void visit(UriResourceCount option) {
if (option!= null) {
this.countQuery = true;
}
}
@Override
public void visit(UriResourceRef info) {
this.reference = true;
}
@Override
public void visit(UriResourceRoot info) {
this.exceptions.add(new TeiidException("UriResourceRoot NotSupported"));
}
@Override
public void visit(UriResourceValue info) {
}
@Override
public void visit(UriResourceAction info) {
visitOperation(info.getAction());
}
@Override
public void visit(UriResourceFunction info) {
visitOperation(info.getFunction());
}
private void visitOperation(EdmOperation operation) {
try {
ProcedureSQLBuilder builder = new ProcedureSQLBuilder(
this.metadata, operation, this.parameters,
this.params);
ProcedureReturn pp = builder.getReturn();
if (!pp.hasResultSet()) {
NoDocumentNode ndn = new NoDocumentNode();
ndn.setProcedureReturn(pp);
ndn.setQuery(builder.buildProcedureSQL());
this.context = ndn;
} else {
ComplexDocumentNode cdn = ComplexDocumentNode.buildComplexDocumentNode(
operation, this.metadata,
this.odata, this.nameGenerator, this.aliasedGroups,
getUriInfo(), this.parseService);
cdn.setProcedureReturn(pp);
this.context = cdn;
}
} catch (TeiidProcessingException e) {
throw new ODataRuntimeException(e);
}
}
@Override
public void visit(UriResourceIt info) {
this.exceptions.add(new TeiidException("UriResourceIt NotSupported"));
}
@Override
public void visit(UriResourceLambdaAll info) {
this.exceptions.add(new TeiidException("UriResourceLambdaAll NotSupported"));
}
@Override
public void visit(UriResourceLambdaAny info) {
this.exceptions.add(new TeiidException("UriResourceLambdaAll NotSupported"));
}
@Override
public void visit(UriResourceLambdaVariable info) {
this.exceptions.add(new TeiidException("UriResourceLambdaVariable NotSupported"));
}
@Override
public void visit(UriResourceSingleton info) {
this.exceptions.add(new TeiidException("UriResourceSingleton NotSupported"));
}
@Override
public void visit(UriResourceComplexProperty info) {
this.exceptions.add(new TeiidException("UriResourceComplexProperty NotSupported"));
}
public void setOperationParameterValueProvider(
OperationParameterValueProvider parameters) {
this.parameters = parameters;
}
}